feat: add payment module
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { join } from "node:path";
|
||||
|
||||
import { load } from "@std/dotenv";
|
||||
|
||||
const env = await load();
|
||||
const env = await load({ envPath: getDotEnvPath(), export: true });
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
@@ -8,3 +10,11 @@ const env = await load();
|
||||
export function getDotEnvVariable(key: string): string {
|
||||
return env[key] ?? Deno.env.get(key);
|
||||
}
|
||||
|
||||
function getDotEnvPath(): string {
|
||||
const dirname = import.meta.dirname;
|
||||
if (dirname === undefined) {
|
||||
throw new Error("Unable to determine dirname in config");
|
||||
}
|
||||
return join(dirname, "..", "..", ".env");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { load } from "@std/dotenv";
|
||||
import type { ZodType, z } from "zod";
|
||||
|
||||
import { getDotEnvVariable } from "./dotenv.ts";
|
||||
import { InvalidEnvironmentKeyError } from "./errors.ts";
|
||||
import { getServiceEnvironment, type ServiceEnvironment } from "./service.ts";
|
||||
|
||||
const env = await load();
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
@@ -21,7 +19,7 @@ export function getEnvironmentVariable<TType extends ZodType>({
|
||||
fallback?: string;
|
||||
}): z.infer<TType> {
|
||||
const serviceEnv = getServiceEnvironment();
|
||||
const providedValue = env[key] ?? Deno.env.get(key);
|
||||
const providedValue = getDotEnvVariable(key);
|
||||
const fallbackValue = typeof envFallback === "object" ? (envFallback[serviceEnv] ?? fallback) : fallback;
|
||||
const toBeUsed = providedValue ?? fallbackValue;
|
||||
try {
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@std/dotenv": "npm:@jsr/std__dotenv@0.225.5",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { serialize } from "node:v8";
|
||||
|
||||
import { takeAll, takeOne } from "@platform/parse";
|
||||
import postgres, { type Options, type Sql, type TransactionSql } from "postgres";
|
||||
import transit from "transit-js";
|
||||
import type { ZodType } from "zod";
|
||||
|
||||
import { takeAll, takeOne } from "./parser.ts";
|
||||
|
||||
const storage = new AsyncLocalStorage<TransactionSql>();
|
||||
|
||||
const transitReader = transit.reader("json");
|
||||
const transitWriter = transit.writer("json");
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Database
|
||||
@@ -24,7 +28,16 @@ export class Client {
|
||||
*
|
||||
* @param db - Dependency container token to retrieve.
|
||||
*/
|
||||
constructor(readonly config: Options<{}>) {}
|
||||
constructor(
|
||||
readonly config: Options<{
|
||||
transit: {
|
||||
to: 16384;
|
||||
from: [16384];
|
||||
serialize: (value: unknown) => string;
|
||||
parse: (value: unknown) => any;
|
||||
};
|
||||
}>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* SQL instance to perform queries against.
|
||||
@@ -51,7 +64,34 @@ export class Client {
|
||||
*/
|
||||
#getResolvedInstance(): Sql {
|
||||
if (this.#db === undefined) {
|
||||
this.#db = postgres(this.config);
|
||||
this.#db = postgres({
|
||||
...this.config,
|
||||
connection: {
|
||||
fallback_output_format: "transit",
|
||||
},
|
||||
types: {
|
||||
transit: {
|
||||
to: 16384,
|
||||
from: [16384],
|
||||
serialize: (value: unknown) => {
|
||||
return transitWriter.write(value);
|
||||
},
|
||||
parse: (value: string) => {
|
||||
return transitReader.read(value);
|
||||
},
|
||||
},
|
||||
int64: {
|
||||
from: [20],
|
||||
parse: (value: string) => {
|
||||
const res = parseInt(value, 10);
|
||||
if (Number.isSafeInteger(res) === false) {
|
||||
throw Error(`Could not convert to integer reliably: ${value}`);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
} as unknown as postgres.PostgresType,
|
||||
},
|
||||
});
|
||||
}
|
||||
return this.#db;
|
||||
}
|
||||
@@ -106,6 +146,30 @@ export class Client {
|
||||
many: (strings: TemplateStringsArray, ...values: any[]) => this.sql(strings, ...values).then(takeAll(schema)),
|
||||
};
|
||||
}
|
||||
|
||||
boolean(value: boolean) {
|
||||
return this.sql.typed(value, 16);
|
||||
}
|
||||
|
||||
int64(value: number) {
|
||||
return this.sql.typed(value, 20);
|
||||
}
|
||||
|
||||
int32(value: number) {
|
||||
return this.sql.typed(value, 23);
|
||||
}
|
||||
|
||||
text(value: string) {
|
||||
return this.sql.typed(value, 25);
|
||||
}
|
||||
|
||||
float64(value: number) {
|
||||
return this.sql.typed(value, 701);
|
||||
}
|
||||
|
||||
transit(value: object) {
|
||||
return this.sql.typed(value, 16384);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { getEnvironmentVariable } from "@platform/config/environment.ts";
|
||||
import { getEnvironmentVariable } from "@platform/config";
|
||||
import z from "zod";
|
||||
|
||||
export const config = {
|
||||
xtdb: {
|
||||
host: getEnvironmentVariable({
|
||||
key: "DB_XTDB_HOST",
|
||||
type: z.string(),
|
||||
fallback: "localhost",
|
||||
}),
|
||||
port: getEnvironmentVariable({
|
||||
key: "DB_XTDB_PORT",
|
||||
type: z.coerce.number(),
|
||||
fallback: "5432",
|
||||
}),
|
||||
user: getEnvironmentVariable({
|
||||
key: "DB_XTDB_USER",
|
||||
type: z.string(),
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
pass: getEnvironmentVariable({
|
||||
key: "DB_XTDB_PASSWORD",
|
||||
type: z.string(),
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
},
|
||||
host: getEnvironmentVariable({
|
||||
key: "DB_XTDB_HOST",
|
||||
type: z.string(),
|
||||
fallback: "localhost",
|
||||
}),
|
||||
port: getEnvironmentVariable({
|
||||
key: "DB_XTDB_PORT",
|
||||
type: z.coerce.number(),
|
||||
fallback: "5432",
|
||||
}),
|
||||
user: getEnvironmentVariable({
|
||||
key: "DB_XTDB_USER",
|
||||
type: z.string(),
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
pass: getEnvironmentVariable({
|
||||
key: "DB_XTDB_PASSWORD",
|
||||
type: z.string(),
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
};
|
||||
|
||||
6
platform/database/mod.ts
Normal file
6
platform/database/mod.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Client } from "./client.ts";
|
||||
import { config } from "./config.ts";
|
||||
|
||||
export * from "./client.ts";
|
||||
|
||||
export const db = new Client(config);
|
||||
@@ -3,9 +3,16 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./mod.ts",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@platform/config": "workspace:*",
|
||||
"@platform/parse": "workspace:*",
|
||||
"@types/transit-js": "0.8.3",
|
||||
"postgres": "3.4.7",
|
||||
"zod": "4.1.12"
|
||||
"transit-js": "0.8.874",
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import type z from "zod";
|
||||
import type { ZodType } from "zod";
|
||||
|
||||
/**
|
||||
* Takes a single record from a list of database rows.
|
||||
*
|
||||
* @param rows - List of rows to retrieve record from.
|
||||
*/
|
||||
export function takeOne<TSchema extends ZodType>(
|
||||
schema: TSchema,
|
||||
): (records: unknown[]) => z.output<TSchema> | undefined {
|
||||
return (records: unknown[]) => {
|
||||
if (records[0] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return schema.parse(records[0]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all records from a list of database rows and validates each one.
|
||||
*
|
||||
* @param schema - Zod schema to validate each record against.
|
||||
*/
|
||||
export function takeAll<TSchema extends ZodType>(schema: TSchema): (records: unknown[]) => z.output<TSchema>[] {
|
||||
return (records: unknown[]) => {
|
||||
return records.map((record) => schema.parse(record));
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,6 @@
|
||||
"dependencies": {
|
||||
"@platform/config": "workspace:*",
|
||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.1",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
1
platform/parse/mod.ts
Normal file
1
platform/parse/mod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./zod.ts";
|
||||
13
platform/parse/package.json
Normal file
13
platform/parse/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@platform/parse",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./mod.ts",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
13
platform/parse/time.ts
Normal file
13
platform/parse/time.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import z from "zod";
|
||||
|
||||
export const ZonedDateTimeSchema = z.object({
|
||||
tag: z.literal("time/zoned-date-time"),
|
||||
rep: z.string(),
|
||||
hashCode: z.number(),
|
||||
});
|
||||
|
||||
export function assertZonedDateTime(value: object | null): value is ZonedDateTime {
|
||||
return ZonedDateTimeSchema.parse(value) !== undefined;
|
||||
}
|
||||
|
||||
export type ZonedDateTime = z.output<typeof ZonedDateTimeSchema>;
|
||||
66
platform/parse/zod.ts
Normal file
66
platform/parse/zod.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import z, { type ZodType } from "zod";
|
||||
|
||||
import { assertZonedDateTime } from "./time.ts";
|
||||
|
||||
/**
|
||||
* Takes a single record from a list of database rows.
|
||||
*
|
||||
* @param rows - List of rows to retrieve record from.
|
||||
*/
|
||||
export function takeOne<TSchema extends ZodType>(
|
||||
schema: TSchema,
|
||||
): (records: unknown[]) => z.output<TSchema> | undefined {
|
||||
return (records: unknown[]) => {
|
||||
if (records[0] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return schema.parse(records[0]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all records from a list of database rows and validates each one.
|
||||
*
|
||||
* @param schema - Zod schema to validate each record against.
|
||||
*/
|
||||
export function takeAll<TSchema extends ZodType>(schema: TSchema): (records: unknown[]) => z.output<TSchema>[] {
|
||||
return (records: unknown[]) => {
|
||||
return records.map((record) => schema.parse(record));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a XTDB NEST_MANY sub query to the provided schema.
|
||||
*
|
||||
* @param schema - Schema to parse the NEST_MANY result against.
|
||||
*/
|
||||
export function nestMany<TSchema extends ZodType>(schema: TSchema) {
|
||||
return z.array(z.preprocess(xtdbEntriesToObject, schema)).default([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a _entries key on the provided record and re-map them to
|
||||
* a new object if _entries exists.
|
||||
*
|
||||
* @param record - Record to re-map.
|
||||
*/
|
||||
function xtdbEntriesToObject(record: any) {
|
||||
if ("_entries" in record) {
|
||||
const obj: any = {};
|
||||
for (let i = 0; i < record._entries.length; i += 2) {
|
||||
obj[record._entries[i]] = xtdbEntryValue(record._entries[i + 1]);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
function xtdbEntryValue(value: unknown) {
|
||||
if (Array.isArray(value) === true) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "object" && assertZonedDateTime(value) === true) {
|
||||
return value.rep.replace("[UTC]", "");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -12,6 +12,6 @@
|
||||
"@platform/socket": "workspace:*",
|
||||
"@platform/supertokens": "workspace:*",
|
||||
"path-to-regexp": "8",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@platform/relay": "workspace:*",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"@platform/socket": "workspace:*",
|
||||
"@platform/storage": "workspace:*",
|
||||
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"dependencies": {
|
||||
"@platform/models": "workspace:*",
|
||||
"@platform/relay": "workspace:*",
|
||||
"zod": "4.1.12"
|
||||
"zod": "4.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user