UNPKG

orange-orm

Version:

Object Relational Mapper

1,706 lines (1,499 loc) 619 kB
void !function() { typeof self === 'undefined' && typeof global === 'object' ? global.self = global : null; }();import * as fastJsonPatch from 'fast-json-patch'; import * as uuid from 'uuid'; import * as axios from 'axios'; import * as _default from 'rfdc/default'; import * as ajv from 'ajv'; import * as onChange from '@lroal/on-change'; import * as connectionString from '@tediousjs/connection-string'; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function getDefaultExportFromNamespaceIfPresent (n) { return n && Object.prototype.hasOwnProperty.call(n, 'default') ? n['default'] : n; } var getTSDefinition_1; var hasRequiredGetTSDefinition; function requireGetTSDefinition () { if (hasRequiredGetTSDefinition) return getTSDefinition_1; hasRequiredGetTSDefinition = 1; const typeMap = { StringColumn: 'string', BooleanColumn: 'boolean', UUIDColumn: 'string', BinaryColumn: 'string', JSONColumn: 'object', DateColumn: 'Date | string', NumberColumn: 'number', }; function getTSDefinition(tableConfigs, {isNamespace = false, isHttp = false} = {}) { const rootTablesAdded = new Map(); const tableNames = new Set(); const tablesAdded = new Map(); let src = ''; const defs = tableConfigs.map(getTSDefinitionTable).join(''); const tables = tableConfigs.reduce((tables, x) => { tables[x.name] = x.table; return tables; }, {}); src += getPrefixTs(isNamespace); if (isNamespace) src += startNamespace(tables, isHttp); src += defs; src += getRdbClientTs(tables, isHttp); if (isNamespace) src += '}'; return src; function getTSDefinitionTable({table, customFilters, name}) { let Name = name.substr(0, 1).toUpperCase() + name.substr(1); name = name.substr(0, 1).toLowerCase() + name.substr(1); let result = '' + getTable(table, Name, name, customFilters); return result; } function getTable(table, Name, name, customFilters) { const _columns = columns(table); const _tableRelations = tableRelations(table); return ` export interface ${Name}Table { count(filter?: RawFilter): Promise<number>; getAll(): Promise<${Name}Array>; getAll(fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getMany(filter?: RawFilter): Promise<${Name}Array>; getMany(filter: RawFilter, fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getMany(${name}s: Array<${Name}>): Promise<${Name}Array>; getMany(${name}s: Array<${Name}>, fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getOne(filter?: RawFilter): Promise<${Name}Row>; getOne(filter?: RawFilter, fetchingStrategy?: ${Name}Strategy): Promise<${Name}Row>; getOne(${name}: ${Name}): Promise<${Name}Row>; getOne(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; getById(${getIdArgs(table)}): Promise<${Name}Row>; getById(${getIdArgs(table)}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; replace(${name}s: ${Name}[] | ${Name}): Promise<void>; replace(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; replace(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; update(${name}: ${Name}): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row[]>; updateChanges(${name}s: ${Name}[], old${name}s: ${Name}[]): Promise<${Name}Array>; updateChanges(${name}s: ${Name}[],old${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; updateChanges(${name}: ${Name}, old${name}: ${Name}): Promise<${Name}Row>; updateChanges(${name}: ${Name},old${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; insert(${name}s: ${Name}[]): Promise<${Name}Array>; insert(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; insert(${name}: ${Name}): Promise<${Name}Row>; insert(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; insertAndForget(${name}s: ${Name}[]): Promise<void>; insertAndForget(${name}: ${Name}): Promise<void>; delete(filter?: RawFilter): Promise<void>; delete(${name}s: Array<${Name}>): Promise<void>; deleteCascade(filter?: RawFilter): Promise<void>; deleteCascade(${name}s: Array<${Name}>): Promise<void>; proxify(${name}s: ${Name}[]): ${Name}Array; proxify(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): ${Name}Array; proxify(${name}: ${Name}): ${Name}Row; proxify(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): ${Name}Row; patch(patch: JsonPatch): Promise<void>; patch(patch: JsonPatch, concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; customFilters: ${Name}CustomFilters; ${_columns} ${_tableRelations} } export interface ${Name}ExpressConfig { baseFilter?: RawFilter | ((context: ExpressContext) => RawFilter | Promise<RawFilter>); customFilters?: Record<string, (context: ExpressContext,...args: any[]) => RawFilter | Promise<RawFilter>>; concurrency?: ${Name}Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; } export interface ${Name}HonoConfig { baseFilter?: RawFilter | ((context: HonoContext) => RawFilter | Promise<RawFilter>); customFilters?: Record<string, (context: HonoContext,...args: any[]) => RawFilter | Promise<RawFilter>>; concurrency?: ${Name}Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; } export interface ${Name}CustomFilters { ${getCustomFilters(customFilters)} } export interface ${Name}Array extends Array<${Name}> { saveChanges(): Promise<void>; saveChanges(concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; acceptChanges(): void; clearChanges(): void; refresh(): Promise<void>; refresh(fetchingStrategy: ${Name}Strategy): Promise<void>; delete(): Promise<void>; delete(options: ${Name}Concurrency): Promise<void>; } export interface ${Name}Row extends ${Name} { saveChanges(): Promise<void>; saveChanges(concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; acceptChanges(): void; clearChanges(): void; refresh(): Promise<void>; refresh(fetchingStrategy: ${Name}Strategy): Promise<void>; delete(): Promise<void>; delete(options: ${Name}Concurrency): Promise<void>; } ${Concurrency(table, Name, true)} `; } function getIdArgs(table) { let result = []; for (let i = 0; i < table._primaryColumns.length; i++) { let column = table._primaryColumns[i]; result.push(`${column.alias}: ${typeMap[column.tsType]}`); } return result.join(', '); } function tableRelations(table) { let relations = table._relations; let result = ''; for (let relationName in relations) { const tableName = getTableName(relations[relationName], relationName); result += `${relationName}: ${tableName}RelatedTable;`; } return result; } function columns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}${column.alias} : ${column.tsType};`; separator = ` `; } return result; } function Concurrency(table, name, isRoot) { name = pascalCase(name); if (!isRoot) { if (tablesAdded.has(table)) return ''; else { tablesAdded.set(table, name); } } let otherConcurrency = ''; let concurrencyRelations = ''; let strategyRelations = ''; let regularRelations = ''; let relations = table._relations; let relationName; let separator = ` `; let visitor = {}; visitor.visitJoin = function(relation) { const tableTypeName = getTableName(relation, relationName); otherConcurrency += `${Concurrency(relation.childTable, tableTypeName)}`; concurrencyRelations += `${relationName}?: ${tableTypeName}Concurrency;${separator}`; strategyRelations += `${relationName}?: ${tableTypeName}Strategy | boolean;${separator}`; regularRelations += `${relationName}?: ${tableTypeName} | null;${separator}`; }; visitor.visitOne = visitor.visitJoin; visitor.visitMany = function(relation) { const tableTypeName = getTableName(relation, relationName); otherConcurrency += `${Concurrency(relation.childTable, tableTypeName)}`; concurrencyRelations += `${relationName}?: ${tableTypeName}Concurrency;${separator}`; strategyRelations += `${relationName}?: ${tableTypeName}Strategy | boolean;${separator}`; regularRelations += `${relationName}?: ${tableTypeName}[] | null;${separator}`; }; for (relationName in relations) { var relation = relations[relationName]; relation.accept(visitor); } let row = ''; if (!isRoot) { row = `export interface ${name}RelatedTable { ${columns(table)} ${tableRelations(table)} all: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; any: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; none: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; exists: () => Filter; }`; } return ` export interface ${name}Concurrency { readonly?: boolean; concurrency?: Concurrency; ${concurrencyColumns(table)} ${concurrencyRelations} } export interface ${name} { ${regularColumns(table)} ${regularRelations} } export interface ${name}TableBase { ${columns(table)} ${tableRelations(table)} } export interface ${name}Strategy { ${strategyColumns(table)} ${strategyRelations} limit?: number; offset?: number; orderBy?: Array<${orderByColumns(table)}> | ${orderByColumns(table)}; where?: (table: ${name}TableBase) => RawFilter; } ${otherConcurrency} ${row}`; } function getTableName(relation, relationName) { let name = rootTablesAdded.get(relation.childTable); if (name) return name; else { let name = pascalCase(relationName); let count = 2; while (tableNames.has(name)) { name = name + 'x' + count; count++; } rootTablesAdded.set(relation.childTable, name); tableNames.add(name); return name; } } } function regularColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; if (column._notNull) result += `${separator}${column.alias} : ${typeMap[column.tsType]};`; else result += `${separator}${column.alias}? : ${typeMap[column.tsType]} | null;`; separator = ` `; } return result; } function orderByColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}'${column.alias}' | '${column.alias} desc'`; separator = '| '; } return result; } function pascalCase(name) { return name[0].toUpperCase() + name.substr(1); } function concurrencyColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}${column.alias}? : ColumnConcurrency;`; separator = ` `; } return result; } function strategyColumns(table) { let primarySet = new Set(table._primaryColumns); let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; if (primarySet.has(column)) continue; result += `${separator}${column.alias}? : boolean;`; separator = ` `; } return result; } function getCustomFilters(filters) { return getLeafNames(filters); function getLeafNames(obj, tabs = '\t\t\t\t\t') { let result = ''; for (let p in obj) { if (typeof obj[p] === 'object' && obj[p] !== null) { result += '\n' + tabs + p + ': {' + tabs + getLeafNames(obj[p], tabs + '\t'); result += '\n' + tabs + '}'; } else if (typeof obj[p] === 'function') result += '\n' + tabs + p + ': (' + getParamNames(obj[p]) + ') => import(\'orange-orm\').Filter;'; } return result; } } function getParamNames(func) { let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; let ARGUMENT_NAMES = /([^\s,]+)/g; let fnStr = func.toString().replace(STRIP_COMMENTS, ''); let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if (result === null) return ''; return result.slice(1).join(': unknown, ') + ': unknown'; } function getPrefixTs(isNamespace) { if (isNamespace) return ` /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { AxiosInterceptorManager, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import type { BooleanColumn, JSONColumn, UUIDColumn, DateColumn, NumberColumn, BinaryColumn, StringColumn, Concurrency, Filter, RawFilter, TransactionOptions, Pool, Express, Hono, Url, ColumnConcurrency, JsonPatch } from 'orange-orm'; export { RequestHandler } from 'express'; export { Concurrency, Filter, RawFilter, Config, TransactionOptions, Pool } from 'orange-orm'; export = r; declare function r(config: Config): r.RdbClient; `; return ` /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import schema from './schema'; import type { AxiosInterceptorManager, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import type { BooleanColumn, JSONColumn, UUIDColumn, DateColumn, NumberColumn, BinaryColumn, StringColumn, Concurrency, Filter, RawFilter, TransactionOptions, Pool, Express, Hono, Url, ColumnConcurrency, JsonPatch } from 'orange-orm'; export default schema as RdbClient;`; } function startNamespace(tables, isHttp) { return ` declare namespace r {${getTables(isHttp)} `; function getTables(isHttp) { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` const ${name}: ${Name}Table;`; } if (!isHttp) result += ` function and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function not(): Filter; function transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; function query(filter: RawFilter | string): Promise<unknown[]>; function query<T>(filter: RawFilter | string): Promise<T[]>; function transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; const filter: Filter; function express(): Express; function express(config: ExpressConfig): Express; function hono(): Hono; function hono(config: HonoConfig): Hono; `; else result += ` const interceptors: { request: AxiosInterceptorManager<InternalAxiosRequestConfig>; response: AxiosInterceptorManager<AxiosResponse>; }; function reactive(proxyMethod: (obj: unknown) => unknown): void; function and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function not(): Filter; const filter: Filter; `; return result; } } function getRdbClientTs(tables, isHttp) { return ` export interface RdbClient {${getTables(isHttp)} } export interface RdbConfig { db?: Pool | (() => Pool); readonly?: boolean; concurrency?: Concurrency;${getConcurrencyTables()} } export interface MetaData { readonly?: boolean; concurrency?: Concurrency;${getConcurrencyTables()} } export interface ExpressConfig { db?: Pool | (() => Pool); tables?: ExpressTables; concurrency?: Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; hooks?: ExpressHooks; } export interface HonoConfig { db?: Pool | (() => Pool); tables?: HonoTables; concurrency?: Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; hooks?: HonoHooks; } export interface ExpressContext { request: import('express').Request; response: import('express').Response; client: RdbClient; } export interface HonoRequest { method: string; query: Record<string, string>; headers: Record<string, string>; json(): Promise<unknown>; } export interface HonoResponse { status(code: number): HonoResponse; setHeader(name: string, value: string): HonoResponse; json(value: unknown): unknown; send(value: unknown): unknown; } export interface HonoContext { request: HonoRequest; response: HonoResponse; client: RdbClient; } export interface ExpressTransactionHooks { beforeBegin?: (db: Pool, request: import('express').Request, response: import('express').Response) => void | Promise<void>; afterBegin?: (db: Pool, request: import('express').Request, response: import('express').Response) => void | Promise<void>; beforeCommit?: (db: Pool, request: import('express').Request, response: import('express').Response) => void | Promise<void>; afterCommit?: (db: Pool, request: import('express').Request, response: import('express').Response) => void | Promise<void>; afterRollback?: (db: Pool, request: import('express').Request, response: import('express').Response, error?: unknown) => void | Promise<void>; } export interface ExpressHooks extends ExpressTransactionHooks { transaction?: ExpressTransactionHooks; } export interface HonoTransactionHooks { beforeBegin?: (db: Pool, request: HonoRequest, response: HonoResponse) => void | Promise<void>; afterBegin?: (db: Pool, request: HonoRequest, response: HonoResponse) => void | Promise<void>; beforeCommit?: (db: Pool, request: HonoRequest, response: HonoResponse) => void | Promise<void>; afterCommit?: (db: Pool, request: HonoRequest, response: HonoResponse) => void | Promise<void>; afterRollback?: (db: Pool, request: HonoRequest, response: HonoResponse, error?: unknown) => void | Promise<void>; } export interface HonoHooks extends HonoTransactionHooks { transaction?: HonoTransactionHooks; } export interface ExpressTables {${getExpressTables()} } export interface HonoTables {${getHonoTables()} } `; function getConcurrencyTables() { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}?: ${Name}Concurrency;`; } return result; } function getTables(isHttp) { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}: ${Name}Table;`; } if (isHttp) result += ` (config: {db: Url}): RdbClient; interceptors: { request: AxiosInterceptorManager<InternalAxiosRequestConfig>; response: AxiosInterceptorManager<AxiosResponse>; }; reactive(proxyMethod: (obj: unknown) => unknown): void; and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; not(): Filter; transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; filter: Filter; createPatch(original: any[], modified: any[]): JsonPatch; createPatch(original: any, modified: any): JsonPatch;`; else result += ` (config: RdbConfig): RdbClient; and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; not(): Filter; query(filter: RawFilter | string): Promise<unknown[]>; query<T>(filter: RawFilter | string): Promise<T[]>; transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; filter: Filter; createPatch(original: any[], modified: any[]): JsonPatch; createPatch(original: any, modified: any): JsonPatch; express(): Express; express(config: ExpressConfig): Express; hono(): Hono; hono(config: HonoConfig): Hono; readonly metaData: MetaData;`; return result; } function getExpressTables() { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}?: boolean | ${Name}ExpressConfig;`; } return result; } function getHonoTables() { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}?: boolean | ${Name}HonoConfig;`; } return result; } } getTSDefinition_1 = getTSDefinition; return getTSDefinition_1; } var getMeta_1; var hasRequiredGetMeta; function requireGetMeta () { if (hasRequiredGetMeta) return getMeta_1; hasRequiredGetMeta = 1; function getMeta(table, map = new Map()) { if (map.has(table)) return map.get(table).id; let strategy = { keys: table._primaryColumns.map(x => ({name: x.alias, type: x.tsType})), columns: {}, relations: {}, id: map.size }; map.set(table, strategy); for (let i = 0; i < table._columns.length; i++) { const column = table._columns[i]; strategy.columns[column.alias] = {}; if ('serializable' in column && !column.serializable) strategy.columns[column.alias].serializable = false; else strategy.columns[column.alias].serializable = true; } let relations = table._relations; let relationName; let visitor = {}; visitor.visitJoin = function(relation) { strategy.relations[relationName] = getMeta(relation.childTable, map); }; visitor.visitMany = function(relation) { strategy.relations[relationName] = getMeta(relation.childTable, map); }; visitor.visitOne = visitor.visitMany; for (relationName in relations) { let relation = relations[relationName]; relation.accept(visitor); } return strategy; } getMeta_1 = getMeta; return getMeta_1; } var hostExpress_1; var hasRequiredHostExpress; function requireHostExpress () { if (hasRequiredHostExpress) return hostExpress_1; hasRequiredHostExpress = 1; const getTSDefinition = requireGetTSDefinition(); // let hostLocal = _hostLocal; const getMeta = requireGetMeta(); function hostExpress(hostLocal, client, options = {}) { if ('db' in options && (options.db ?? undefined) === undefined || !client.db) throw new Error('No db specified'); const dbOptions = { db: options.db || client.db }; let c = {}; const readonly = { readonly: options.readonly}; const sharedHooks = options.hooks; for (let tableName in client.tables) { const tableOptions = options[tableName] || {}; const hooks = tableOptions.hooks || sharedHooks; c[tableName] = hostLocal({ ...dbOptions, ...readonly, ...tableOptions, table: client.tables[tableName], isHttp: true, client, hooks }); } async function handler(req, res) { if (req.method === 'POST') return post.apply(null, arguments); if (req.method === 'PATCH') return patch.apply(null, arguments); if (req.method === 'GET') return get.apply(null, arguments); if (req.method === 'OPTIONS') return handleOptions(req, res); // assuming the second argument is `response` else res.status(405).set('Allow', 'GET, POST, PATCH, OPTIONS').send('Method Not Allowed'); } handler.db = handler; handler.dts = get; function get(request, response) { try { if (request.query.table) { if (!(request.query.table in c)) { let e = new Error('Table is not exposed or does not exist'); // @ts-ignore e.status = 400; throw e; } const result = getMeta(client.tables[request.query.table]); response.setHeader('content-type', 'text/plain'); response.status(200).send(result); } else { const isNamespace = request.query.isNamespace === 'true'; let tsArg = Object.keys(c).map(x => { return { table: client.tables[x], customFilters: options?.tables?.[x].customFilters, name: x }; }); response.setHeader('content-type', 'text/plain'); response.status(200).send(getTSDefinition(tsArg, { isNamespace, isHttp: true })); } } catch (e) { if (e.status === undefined) response.status(500).send(e.message || e); else response.status(e.status).send(e.message); } } async function patch(request, response) { try { response.json(await c[request.query.table].patch(request.body, request, response)); } catch (e) { if (e.status === undefined) response.status(500).send(e.message || e); else response.status(e.status).send(e.message); } } async function post(request, response) { try { if (!request.query.table) { let e = new Error('Table not defined'); // @ts-ignore e.status = 400; throw e; } else if (!(request.query.table in c)) { let e = new Error('Table is not exposed or does not exist'); // @ts-ignore e.status = 400; throw e; } response.json(await c[request.query.table].post(request.body, request, response)); } catch (e) { if (e.status === undefined) response.status(500).send(e.message || e); else response.status(e.status).send(e.message); } } function handleOptions(req, response) { response.setHeader('Access-Control-Allow-Origin', '*'); // Adjust this as per your CORS needs response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS'); // And any other methods you support response.setHeader('Access-Control-Allow-Headers', 'Content-Type'); // And any other headers you expect in requests response.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight request for a day. Adjust as you see fit response.status(204).send(); // 204 No Content response for successful OPTIONS requests } return handler; } hostExpress_1 = hostExpress; return hostExpress_1; } var hostHono_1; var hasRequiredHostHono; function requireHostHono () { if (hasRequiredHostHono) return hostHono_1; hasRequiredHostHono = 1; const getTSDefinition = requireGetTSDefinition(); const getMeta = requireGetMeta(); function hostHono(hostLocal, client, options = {}) { if ('db' in options && (options.db ?? undefined) === undefined || !client.db) throw new Error('No db specified'); const dbOptions = { db: options.db || client.db }; let c = {}; const readonly = { readonly: options.readonly }; const sharedHooks = options.hooks; for (let tableName in client.tables) { const tableOptions = options[tableName] || {}; const hooks = tableOptions.hooks || sharedHooks; c[tableName] = hostLocal({ ...dbOptions, ...readonly, ...tableOptions, table: client.tables[tableName], isHttp: true, client, hooks }); } async function handler(ctx) { const request = createRequest(ctx); const response = createResponse(); try { if (request.method === 'POST') return await post(request, response); if (request.method === 'PATCH') return await patch(request, response); if (request.method === 'GET') return get(request, response); if (request.method === 'OPTIONS') return handleOptions(response); return response .status(405) .setHeader('Allow', 'GET, POST, PATCH, OPTIONS') .send('Method Not Allowed'); } catch (e) { if (e.status === undefined) return response.status(500).send(e.message || e); return response.status(e.status).send(e.message); } } handler.db = handler; handler.dts = get; function get(request, response) { if (request.query.table) { if (!(request.query.table in c)) { let e = new Error('Table is not exposed or does not exist'); // @ts-ignore e.status = 400; throw e; } const result = getMeta(client.tables[request.query.table]); response.setHeader('content-type', 'text/plain'); return response.status(200).send(result); } const isNamespace = request.query.isNamespace === 'true'; let tsArg = Object.keys(c).map(x => { return { table: client.tables[x], customFilters: options?.tables?.[x].customFilters, name: x }; }); response.setHeader('content-type', 'text/plain'); return response.status(200).send(getTSDefinition(tsArg, { isNamespace, isHttp: true })); } async function patch(request, response) { const table = request.query.table; const body = await request.json(); return response.json(await c[table].patch(body, request, response)); } async function post(request, response) { if (!request.query.table) { let e = new Error('Table not defined'); // @ts-ignore e.status = 400; throw e; } if (!(request.query.table in c)) { let e = new Error('Table is not exposed or does not exist'); // @ts-ignore e.status = 400; throw e; } const body = await request.json(); return response.json(await c[request.query.table].post(body, request, response)); } function handleOptions(response) { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS'); response.setHeader('Access-Control-Allow-Headers', 'Content-Type'); response.setHeader('Access-Control-Max-Age', '86400'); return response.status(204).send(''); } function createRequest(ctx) { let bodyPromise; const query = Object.fromEntries(new URL(ctx.req.url).searchParams.entries()); const headers = {}; for (const [name, value] of ctx.req.raw.headers.entries()) headers[name] = value; return { method: ctx.req.method, query, headers, json: async () => { if (!bodyPromise) bodyPromise = ctx.req.json(); return bodyPromise; } }; } function createResponse() { let statusCode = 200; const headers = new Headers(); return { status(code) { statusCode = code; return this; }, setHeader(name, value) { headers.set(name, value); return this; }, json(value) { if (!headers.has('content-type')) headers.set('content-type', 'application/json'); return new Response(JSON.stringify(value), { status: statusCode, headers }); }, send(value) { if (typeof value === 'string') { if (!headers.has('content-type')) headers.set('content-type', 'text/plain'); return new Response(value, { status: statusCode, headers }); } if (!headers.has('content-type')) headers.set('content-type', 'application/json'); return new Response(JSON.stringify(value), { status: statusCode, headers }); } }; } return handler; } hostHono_1 = hostHono; return hostHono_1; } var require$$0$4 = /*@__PURE__*/getDefaultExportFromNamespaceIfPresent(fastJsonPatch); var dateToISOString_1; var hasRequiredDateToISOString; function requireDateToISOString () { if (hasRequiredDateToISOString) return dateToISOString_1; hasRequiredDateToISOString = 1; function dateToISOString(date) { let tzo = -date.getTimezoneOffset(); let dif = tzo >= 0 ? '+' : '-'; function pad(num) { let norm = Math.floor(Math.abs(num)); return (norm < 10 ? '0' : '') + norm; } function padMilli(d) { return (d.getMilliseconds() + '').padStart(3, '0'); } return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) + '.' + padMilli(date) + dif + pad(tzo / 60) + ':' + pad(tzo % 60); } dateToISOString_1 = dateToISOString; return dateToISOString_1; } var stringify_1; var hasRequiredStringify; function requireStringify () { if (hasRequiredStringify) return stringify_1; hasRequiredStringify = 1; let dateToISOString = requireDateToISOString(); function stringify(value) { return JSON.stringify(value, replacer); } function replacer(key, value) { // @ts-ignore if (typeof value === 'bigint') return value.toString(); else if (value instanceof Date && !isNaN(value)) return dateToISOString(value); else return value; } stringify_1 = stringify; return stringify_1; } var require$$0$3 = /*@__PURE__*/getDefaultExportFromNamespaceIfPresent(uuid); var createPatch; var hasRequiredCreatePatch; function requireCreatePatch () { if (hasRequiredCreatePatch) return createPatch; hasRequiredCreatePatch = 1; const jsonpatch = require$$0$4; let dateToIsoString = requireDateToISOString(); let stringify = requireStringify(); let { v4: uuid } = require$$0$3; createPatch = function createPatch(original, dto, options) { let subject = toCompareObject({ d: original }, options, true); let clonedDto = toCompareObject({ d: dto }, options, true); let keyPositionMap = toKeyPositionMap(dto); let observer = jsonpatch.observe(subject); subject.d = clonedDto.d; let changes = jsonpatch.generate(observer); let clonedOriginal = toCompareObject(original, options); let {inserted, deleted, updated} = splitChanges(changes); updated.sort(comparePatch); return [...inserted, ...updated, ...deleted]; function splitChanges(changes) { let inserted = []; let deleted = []; let updated = []; for (let change of changes) { change.path = change.path.substring(2); if (change.op === 'add' && change.path.split('/').length === 2) { inserted.push(change); } else if (change.op === 'remove' && change.path.split('/').length === 2) { addOldValue(change); deleted.push(change); } else { addOldValue(change); updated.push(change); } } return { inserted, updated, deleted}; } function addOldValue(change) { if (change.op === 'remove' || change.op === 'replace') { let splitPath = change.path.split('/'); splitPath.shift(); change.oldValue = splitPath.reduce(extract, clonedOriginal); } else return change; function extract(obj, element) { return obj[element]; } return change; } function toKeyPositionMap(rows) { return rows.reduce((map, element, i) => { if (options && options.keys && element === Object(element)) { let key = []; for (let i = 0; i < options.keys.length; i++) { let keyName = options.keys[i].name; key.push(negotiateTempKey(element[keyName])); } map[stringify(key)] = i; } else if ('id' in element) map[stringify([element.id])] = i; else map[i] = i; return map; }, {}); } function toCompareObject(object, options, isRoot) { if (Array.isArray(object)) { let copy = { __patchType: 'Array' }; for (let i = 0; i < object.length; i++) { let element = toCompareObject(object[i], options); if (options && options.keys && element === Object(element)) { let key = []; for (let i = 0; i < options.keys.length; i++) { let keyName = options.keys[i].name; key.push(negotiateTempKey(element[keyName])); } copy[stringify(key)] = element; } else if (element === Object(element) && 'id' in element) copy[stringify([element.id])] = element; else copy[i] = element; } return copy; } else if (typeof object === 'bigint') return object.toString(); else if (isValidDate(object)) return dateToIsoString(object); else if (object === Object(object)) { let copy = {}; for (let name in object) { copy[name] = toCompareObject(object[name], isRoot ? options : options && options.relations && options.relations[name]); } return copy; } return object; } function isValidDate(d) { return d instanceof Date && !isNaN(d); } function negotiateTempKey(value) { if (value === undefined) return `~${uuid()}`; else return value; } function comparePatch(a, b) { const aPathArray = a.path.split('/'); const bPathArray = b.path.split('/'); return (aPathArray.length - bPathArray.length) || (keyPositionMap[aPathArray[1]] ?? Infinity - keyPositionMap[bPathArray[1]] ?? Infinity) || a.path.localeCompare(b.path); } }; return createPatch; } var extractSql; var hasRequiredExtractSql; function requireExtractSql () { if (hasRequiredExtractSql) return extractSql; hasRequiredExtractSql = 1; function extract(sql) { if (sql && typeof(sql) === 'function') return sql(); else if (sql === undefined) return ''; else return sql; } extractSql = extract; return extractSql; } var extractParameters; var hasRequiredExtractParameters; function requireExtractParameters () { if (hasRequiredExtractParameters) return extractParameters; hasRequiredExtractParameters = 1; function extract(parameters) { if (parameters) { return parameters.slice(0); } return []; } extractParameters = extract; return extractParameters; } var newParameterized_1; var hasRequiredNewParameterized; function requireNewParameterized () { if (hasRequiredNewParameterized) return newParameterized_1; hasRequiredNewParameterized = 1; var extractSql = requireExtractSql(); var extractParameters = requireExtractParameters(); function Parameterized(text, parameters) { this._text = text; this.parameters = parameters; } Parameterized.prototype.sql = function() { return this._text; }; Parameterized.prototype.prepend = function(other) { if (other.sql) { var params = other.parameters.concat(this.parameters); return newParameterized(other.sql() + this._text, params); } else return newParameterized(other + this._text, this.parameters); }; Parameterized.prototype.append = function(other) { if (other.sql) { var params = this.parameters.concat(other.parameters); return newParameterized(this._text + other.sql(), params); } else return newParameterized(this._text + other, this.parameters); }; function newParameterized(text, parameters) { text = extractSql(text); parameters = extractParameters(parameters); return new Parameterized(text, parameters); } newParameterized_1 = newParameterized; return newParameterized_1; } var negotiateNextAndFilter_1; var hasRequiredNegotiateNextAndFilter; function requireNegotiateNextAndFilter () { if (hasRequiredNegotiateNextAndFilter) return negotiateNextAndFilter_1; hasRequiredNegotiateNextAndFilter = 1; function negotiateNextAndFilter(filter, other) { if (!other.sql()) return filter; return filter.append(' AND ').append(other); } negotiateNextAndFilter_1 = negotiateNextAndFilter; return negotiateNextAndFilter_1; } var negotiateNextOrFilter_1; var hasRequiredNegotiateNextOrFilter; function requireNegotiateNextOrFilter () { if (hasRequiredNegotiateNextOrFilter) return negotiateNextOrFilter_1; hasRequiredNegotiateNextOrFilter = 1; function negotiateNextOrFilter(filter, other) { if (!other.sql()) return filter; return filter.prepend('(').append(' OR ').append(other).append(')'); } negotiateNextOrFilter_1 = negotiateNextOrFilter; return negotiateNextOrFilter_1; } var utils; var hasRequiredUtils; function requireUtils () { if (hasRequiredUtils) return utils; hasRequiredUtils = 1; const newParameterized = requireNewParameterized(); const negotiateNextAndFilter = requireNegotiateNextAndFilter(); const negotiateNextOrFilter = requireNegotiateNextOrFilter(); function newBoolean(filter) { var c = {}; c.sql = filter.sql.bind(filter); c.parameters = filter.parameters; c.append = function(other) { var nextFilter = filter.append(other); return newBoolean(nextFilter); }; c.prepend = function(other) { var nextFilter = filter.prepend(other); return newBoolean(nextFilter); }; c.and = function(context, other) { if (other === undefined) { other = context; context = null; } other = negotiateRawSqlFilter(context, other); var nextFilter = negotiateNextAndFilter(filter, other); var next = newBoolean(nextFilter); for (var i = 2; i < arguments.length; i++) { next = next.and(context, arguments[i]); } return next; }; c.or = function(context, other) { if (other === undefined) { other = context; context = null; } other = negotiateRawSqlFilter(context, other); var nextFilter = negotiateNextOrFilter(filter, other); var next = newBoolean(nextFilter); for (var i = 2; i < arguments.length; i++) { next = next.or(context, arguments[i]); } return next; }; c.not = function(_context) { var nextFilter = filter.prepend('NOT (').append(')'); return newBoolean(nextFilter); }; return c; } function negotiateRawSqlFilter(context, filter, optionalTable, emptyArrayMeansFalse) { if (Array.isArray(filter) && filter.length === 0) { const sql = emptyArrayMeansFalse ? '1 = 2' : '1 = 1'; return newBoolean(newParameterized(sql)); } else if (Array.isArray(filter)) { let curFilter; let curObjectFilter; for (let i = 0; i < filter.length; i++) { let nextFilter = negotiateRawSqlFilter(context,filter[i], optionalTable); if (nextFilter.isObjectFilter) curObjectFilter = curObjectFilter ? curObjectFilter.or(context, nextFilter) : nextFilter; else curFilter = curFilter ? curFilter.and(context, nextFilter) : nextFilter; } if (curFilter && curObjectFilter) return curFilter.and(context, curObjectFilter); else if (curFilter) return curFilter; else return curObjectFilter; } else { let params = []; if (filter) { if (filter.and) return filter; if (filter.sql) { let sql = filter.sql; if (typeof filter.sql === 'function') { sql = filter.sql(); } params.push(sql, filter.parameters); } else if (isObjectFilter(filter, optionalTable)) { return newObjectFilter(context, filter, optionalTable); } else params = [filter]; } else { params = [filter]; } let parameterized = newParameterized.apply(null, params); return newBoolean(parameterized); } } function isObjectFilter(object, optionalTable) { return optionalTable && object; } function newObjectFilter(context, object, table) { let primaryColumns = table._primaryColumns; let filter; for (let i = 0; i < primaryColumns.length; i++) { let column = primaryColumns[i]; let colFilter = column.equal(context, object[column.alias]); filter = filter ? filter.and(context, colFilter) : colFilter ; } filter.isObjectFilter = true; return filter; } utils = { negotiateRawSqlFilter, newBoolean}; return utils; } var negotiateRawSqlFilter_1; var hasRequiredNegotiateRawSqlFilter; function requireNegotiateRawSqlFilter () { if (hasRequiredNegotiateRawSqlFilter) return negotiateRawSqlFilter_1; hasRequiredNegotiateRawSqlFilter = 1; const { negotiateRawSqlFilter } = requireUtils(); negotiateRawSqlFilter_1 = negotiateRawSqlFilter; return negotiateRawSqlFilter_1; } var emptyFilter_1; var hasRequiredEmptyFilter; function requireEmptyFilter () { if (hasRequiredEmptyFilter) return emptyFilter_1; hasRequiredEmptyFilter = 1; var negotiateRawSqlFilter = requireNegotiateRawSqlFilter(); var parameterized = requireNewParameterized()(''); function emptyFilter() { return emptyFilter.and.apply(null, arguments); } emptyFilter.sql = parameterized.sql.bind(parameterized); emptyFilter.parameters = parameterized.parameters; emptyFilter.and = function(context, other) { if (other === undefined) { other = context; context = null; } other = negotiateRawSqlFilter(context, other); for (var i = 2; i < arguments.length; i++) { other = other.and(context, arguments[i]); } return other; }; emptyFilter.or = function(context, other) { if (other === undefined) { other = context; context = null; } other = negotiateRawSqlFilter(context, other); for (var i = 2; i < arguments.length; i++) { other = other.or(context, arguments[i]); } return other; }; emptyFilter.not = function(context, other) { if (other === undefined) { other = context; context = null; } other = negotiateRawSqlFilter(context, other).not(context); for (var i = 2; i < arguments.length; i++) { other = other.and(context, arguments[i]); } return other; }; emptyFilter_1 = emptyFilter; return emptyFilter_1; } var executePath; var hasRequiredExecutePath; function requireExecutePath () { if (hasRequiredExecutePath) return executePath; hasRequiredExecutePath = 1; const createPatch = requireCreatePatch(); const emptyFilter = requireEmptyFilter(); const negotiateRawSqlFilter = requireNegotiateRawSqlFilter(); let getMeta = requireGetMeta(); let isSafe = Symbol(); let _allowedOps = { and: true, or: true, not: true, AND: true, OR: true, NOT: true, equal: true, eq: true, EQ: true, notEqual: true, ne: true, NE: true, lessThan: true, lt: true, LT: true, lessThanOrEqual: true, le: true, LE: true, greaterThan: true, gt: true, GT: true, greaterThanOrEqual: true, ge: true, GE: true, between: true, in: true, IN: true, startsWith: true, iStartsWith: true, endsWith: true, iEndsWith: true, contains: true, iContains: true, iEqual: true, iEq: true, ieq: true, IEQ: true, exists: true, all: true, any: true, none: true, where: true, sum: true, avg: true, max: true, min: true, count: true, groupSum: true, groupAvg: true, groupMax: true, groupMin: true, groupCount: true, _aggregate: true, self: true, }; function _executePath(context, ...rest) { const _ops = { and: emptyFilter.and.bind(null, context), or: emptyFilter.or.bind(null, context), not: emptyFilter.not.bind(null, context), AND: emptyFilter.and.bind(null, context), OR: emptyFilter.or.bind(null, context), NOT: emptyFilter.not.bind(null, context), }; return executePath(...rest); async function executePath({ table, JSONFilter, baseFilter, customFilters = {}, request, response, readonly, disableBulkDeletes, isHttp, client }) { let allowedOps = { ..._allowedOps, insert: !readonly, ...extractRelations(getMeta(table)) }; let ops = { ..._ops, ...getCustomFilterPaths(customFilters), getManyDto, getMany, aggregate, distinct, count, delete: _delete, cascadeDelete, update, replace }; let res = await parseFilter(JSONFilter, table); if (res === undefined) return {}; else return res; function parseFilter(json, table) { if (isFilter(json)) { let subFilters = []; let anyAllNone = tryGetAnyAllNone(json.path, table); if (anyAllNone) { const arg0 = json.args[0]; if (isHttp && arg0 !== undefined) validateArgs(arg0); const f = arg0 === undefined ? anyAllNone(context) : anyAllNone(context, x => parseFilter(arg0, x)); if(!('isSafe' in f)) f.isSafe = isSafe; return f; } else { for (let i = 0; i < json.args.length; i++) { subFilters.push(parseFilter(json.args[i], nextTable(json.path, table))); } } return executePath(json.path, subFilters); } else if (Array.isArray(json)) { const result = []; for (let i = 0; i < json.length; i++) { result.push(parseFilter(json[i], table)); } return result; } else if (isColumnRef(json)) { return resolveColumnRef(table, json.__columnRef); } return json; function tryGetAnyAllNone(path, table) { const parts = path.split('.'); for (let i = 0; i < parts.length; i++) { table = table[parts[i]]; } let ops = new Set(['all', 'any', 'none', 'where', '_aggregate']); // let ops = new Set(['all', 'any', 'none', 'where']); let last = parts[parts.length - 1]; if (last === 'count' && parts.length > 1) ops.add('count'); if (ops.has(last) || (table && (table._primaryColumns || (table.any && table.all)))) return table; } function executePath(path, args) { if (path in ops) { if (isHttp) validateArgs(args); let op = ops[path].apply(null, args); if (op.then) return op.then((o) => { setSafe(o); return o; }); setSafe(op); return op; } let pathArray = path.split('.'); let target = table; let op = pathArray[pathArray.length - 1]; if (!allowedOps[op] && isHttp) { let e = new Error('Disallowed operator ' + op); // @ts-ignore e.status = 403; throw e; } for (let i = 0; i < pathArray.length; i++) { target = target[pathArray[i]]; } if (!target) { const left = args && args[0]; if (left) { target = left; for (let i = 0; i < pathArray.length; i++) { target = target[pathArray[i]]; } if (target) { let res = target.apply(null, [context].concat(args.slice(1))); setSafe(res); return res; } } throw new Error(`Method '${path}' does not exist`); } let res = target.apply(null, [context, ...args]); setSafe(res); return res; } } async function invokeBaseFilter() { if (typeof baseFilter === 'function') { const res = await baseFilter.apply(null, [bindDb(client), request, response]); if (!res) return; const JSONFilter = JSON.parse(JSON.stringify(res)); //@ts-ignore return executePath({ table, JSONFilter, request, response }); } else return; } function getCustomFilterPaths(customFilters) { return getLeafNames(customFilters); function getLeafNames(obj, result = {}, current = 'customFilters.') { for (let p in obj) { if (typeof obj[p] === 'object' && obj[p] !== null) getLeafNames(obj[p], result, current + p + '.'); else result[current + p] = resolveFilter.bind(null, obj[p]); } return result; } async function resolveFilter(fn, ...args) { const context = { db: bindDb(client), request, response }; let res = fn.apply(null, [context, ...args]); if (res.then)