Template
1
0

feat: react zitadel

This commit is contained in:
2025-11-23 22:56:58 +01:00
parent 2b462993cc
commit fe4220ede0
139 changed files with 3389 additions and 2771 deletions

View File

@@ -1,48 +0,0 @@
import type { Collection, CollectionOptions, Db, Document, MongoClient } from "mongodb";
import { mongo } from "./client.ts";
export function getDatabaseAccessor<TSchemas extends Record<string, Document>>(
database: string,
): DatabaseAccessor<TSchemas> {
let instance: Db | undefined;
return {
get db(): Db {
if (instance === undefined) {
instance = this.client.db(database);
}
return instance;
},
get client(): MongoClient {
return mongo;
},
collection<TSchema extends keyof TSchemas>(
name: TSchema,
options?: CollectionOptions,
): Collection<TSchemas[TSchema]> {
return this.db.collection<TSchemas[TSchema]>(name.toString(), options);
},
};
}
export type DatabaseAccessor<TSchemas extends Record<string, Document>> = {
/**
* Database for given accessor.
*/
db: Db;
/**
* Lazy loaded mongo client.
*/
client: MongoClient;
/**
* Returns a reference to a MongoDB Collection. If it does not exist it will be created implicitly.
*
* Collection namespace validation is performed server-side.
*
* @param name - Collection name we wish to access.
* @param options - Optional settings for the command.
*/
collection<TSchema extends keyof TSchemas>(name: TSchema, options?: CollectionOptions): Collection<TSchemas[TSchema]>;
};

View File

@@ -1,4 +1,121 @@
import { config } from "./config.ts";
import { getMongoClient } from "./connection.ts";
import { AsyncLocalStorage } from "node:async_hooks";
export const mongo = getMongoClient(config.mongo);
import postgres, { type Options, type Sql, type TransactionSql } from "postgres";
import type { ZodType } from "zod";
import { takeAll, takeOne } from "./parser.ts";
const storage = new AsyncLocalStorage<TransactionSql>();
/*
|--------------------------------------------------------------------------------
| Database
|--------------------------------------------------------------------------------
*/
export class Client {
/**
* Cached SQL instance.
*/
#db?: Sql;
/**
* Instantiate a new Database accessor wrapper.
*
* @param db - Dependency container token to retrieve.
*/
constructor(readonly config: Options<{}>) {}
/**
* SQL instance to perform queries against.
*/
get sql(): Sql {
const tx = storage.getStore();
if (tx !== undefined) {
return tx;
}
return this.#getResolvedInstance();
}
/**
* SQL instance which ignores any potential transaction established
* in instance scope.
*/
get direct(): Sql {
return this.#getResolvedInstance();
}
/**
* Retrieves cached SQL instance or attempts to create and return
* a new instance.
*/
#getResolvedInstance(): Sql {
if (this.#db === undefined) {
this.#db = postgres(this.config);
}
return this.#db;
}
/**
* Initiates a SQL transaction by wrapping a new db instance with a
* new transaction instance.
*
* @example
* ```ts
* import { db } from "@optio/database/client.ts";
*
* db.begin(async (tx) => {
* tx`SELECT ...`
* });
* ```
*/
begin<TResponse>(cb: (tx: TransactionSql) => TResponse | Promise<TResponse>): Promise<UnwrapPromiseArray<TResponse>> {
return this.direct.begin((tx) => storage.run(tx, () => cb(tx)));
}
/**
* Closes SQL connection if it has been instantiated.
*/
async close(): Promise<void> {
if (this.#db !== undefined) {
await this.#db.end();
this.#db = undefined;
}
}
/**
* Returns a schema pepared querying object allowing for a one or many
* response based on the query used.
*
* @param schema - Zod schema to parse.
*/
schema<TSchema extends ZodType>(schema: TSchema) {
return {
/**
* Executes a sql query and parses the result with the given schema.
*
* @param sql - Template string SQL value.
*/
one: (strings: TemplateStringsArray, ...values: any[]) => this.sql(strings, ...values).then(takeOne(schema)),
/**
* Executes a sql query and parses the resulting list with the given schema.
*
* @param sql - Template string SQL value.
*/
many: (strings: TemplateStringsArray, ...values: any[]) => this.sql(strings, ...values).then(takeAll(schema)),
};
}
}
/*
|--------------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------------
*/
type UnwrapPromiseArray<T> = T extends any[]
? {
[k in keyof T]: T[k] extends Promise<infer R> ? R : T[k];
}
: T;

View File

@@ -2,26 +2,26 @@ import { getEnvironmentVariable } from "@platform/config/environment.ts";
import z from "zod";
export const config = {
mongo: {
xtdb: {
host: getEnvironmentVariable({
key: "DB_MONGO_HOST",
key: "DB_XTDB_HOST",
type: z.string(),
fallback: "localhost",
}),
port: getEnvironmentVariable({
key: "DB_MONGO_PORT",
key: "DB_XTDB_PORT",
type: z.coerce.number(),
fallback: "67017",
fallback: "5432",
}),
user: getEnvironmentVariable({
key: "DB_MONGO_USER",
key: "DB_XTDB_USER",
type: z.string(),
fallback: "root",
fallback: "xtdb",
}),
pass: getEnvironmentVariable({
key: "DB_MONGO_PASSWORD",
key: "DB_XTDB_PASSWORD",
type: z.string(),
fallback: "password",
fallback: "xtdb",
}),
},
};

View File

@@ -1,24 +0,0 @@
import { MongoClient } from "mongodb";
export function getMongoClient(config: MongoConnectionInfo) {
return new MongoClient(getConnectionUrl(config));
}
export function getConnectionUrl({ host, port, user, pass }: MongoConnectionInfo): MongoConnectionUrl {
return `mongodb://${user}:${pass}@${host}:${port}`;
}
/*
|--------------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------------
*/
export type MongoConnectionUrl = `mongodb://${string}:${string}@${string}:${number}`;
export type MongoConnectionInfo = {
host: string;
port: number;
user: string;
pass: string;
};

View File

@@ -1,3 +0,0 @@
import type { CreateIndexesOptions, IndexSpecification } from "mongodb";
export const idIndex: [IndexSpecification, CreateIndexesOptions] = [{ id: 1 }, { unique: true }];

View File

@@ -5,8 +5,7 @@
"type": "module",
"dependencies": {
"@platform/config": "workspace:*",
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1.0.1",
"mongodb": "6.20.0",
"zod": "4.1.11"
"postgres": "3.4.7",
"zod": "4.1.12"
}
}

View File

@@ -0,0 +1,29 @@
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));
};
}

View File

@@ -1,30 +0,0 @@
import type { CreateIndexesOptions, Db, IndexSpecification } from "mongodb";
import { getCollectionsSet } from "./utilities.ts";
/**
* Takes a mongo database and registers the event store collections and
* indexes defined internally.
*
* @param db - Mongo database to register event store collections against.
* @param registrars - List of registrars to register with the database.
* @param logger - Logger method to print internal logs.
*/
export async function register(db: Db, registrars: Registrar[], logger?: (...args: any[]) => any) {
const list = await getCollectionsSet(db);
for (const { name, indexes } of registrars) {
if (list.has(name) === false) {
await db.createCollection(name);
}
for (const [indexSpec, options] of indexes) {
await db.collection(name).createIndex(indexSpec, options);
logger?.("Mongo Event Store > Collection '%s' is indexed [%O] with options %O", name, indexSpec, options ?? {});
}
logger?.("Mongo Event Store > Collection '%s' is registered", name);
}
}
export type Registrar = {
name: string;
indexes: [IndexSpecification, CreateIndexesOptions?][];
};

View File

@@ -1,70 +0,0 @@
import type { Db } from "mongodb";
import type { ZodObject, ZodType, z } from "zod";
/**
* TODO ...
*/
export function takeOne<TDocument>(documents: TDocument[]): TDocument | undefined {
return documents[0];
}
/**
* TODO ...
*/
export function makeDocumentParser<TSchema extends ZodObject>(schema: TSchema): ModelParserFn<TSchema> {
return ((value: unknown | unknown[]) => {
if (Array.isArray(value)) {
return value.map((value: unknown) => schema.parse(value));
}
if (value === undefined || value === null) {
return undefined;
}
return schema.parse(value);
}) as ModelParserFn<TSchema>;
}
/**
* TODO ...
*/
export function toParsedDocuments<TSchema extends ZodType>(
schema: TSchema,
): (documents: unknown[]) => Promise<z.infer<TSchema>[]> {
return async (documents: unknown[]) => {
const parsed = [];
for (const document of documents) {
parsed.push(await schema.parseAsync(document));
}
return parsed;
};
}
/**
* TODO ...
*/
export function toParsedDocument<TSchema extends ZodType>(
schema: TSchema,
): (document?: unknown) => Promise<z.infer<TSchema> | undefined> {
return async (document: unknown) => {
if (document === undefined || document === null) {
return undefined;
}
return schema.parseAsync(document);
};
}
/**
* Get a Set of collections that exists on a given mongo database instance.
*
* @param db - Mongo database to fetch collection list for.
*/
export async function getCollectionsSet(db: Db) {
return db
.listCollections()
.toArray()
.then((collections) => new Set(collections.map((c) => c.name)));
}
type ModelParserFn<TSchema extends ZodObject> = {
(value: unknown): z.infer<TSchema> | undefined;
(value: unknown[]): z.infer<TSchema>[];
};