orange-orm
Version:
Object Relational Mapper
1,650 lines (1,485 loc) • 556 kB
JavaScript
void !function() {
typeof self === 'undefined' && typeof global === 'object'
? global.self = global : null;
}();import * as fastJsonPatch from 'fast-json-patch';
import * as axios from 'axios';
import * as _default from 'rfdc/default';
import * as ajv from 'ajv';
import * as onChange from '@lroal/on-change';
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 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 sync;
var hasRequiredSync;
function requireSync () {
if (hasRequiredSync) return sync;
hasRequiredSync = 1;
const stringify = requireStringify();
function newSyncHandler(client, options = {}) {
const syncOptions = normalizeSyncOptions(options.sync);
if (!syncOptions || syncOptions.enabled === false)
return null;
const tableMeta = createTableMeta(client, syncOptions);
const queue = createQueue(syncOptions.queue);
return async function handleSync(request, response) {
try {
const result = await queue.run(() => execute(request.body || {}));
response.json(result);
}
catch (e) {
if (e.status === undefined)
response.status(500).send(e.message || e);
else
response.status(e.status).send(e.message);
}
};
async function execute(body) {
const phase = body.phase || body.action;
if (phase === 'push')
return pushMutations(body);
if (phase === 'keys')
return pullKeys(body);
if (phase === 'rows')
return pullRows(body);
const error = new Error('Invalid sync phase. Use { phase: "keys" }, { phase: "rows" }, or { phase: "push" }.');
error.status = 400;
throw error;
}
async function pushMutations(body) {
const clientId = normalizeClientId(body.clientId ?? body.client_id);
const mutations = normalizeMutations(body.mutations, syncOptions.limits.maxMutationsPerBatch);
if (!clientId) {
const error = new Error('Sync push requires "clientId".');
error.status = 400;
throw error;
}
if (mutations.length === 0) {
return {
phase: 'push',
applied: 0,
duplicates: 0,
results: []
};
}
const results = [];
let applied = 0;
let duplicates = 0;
for (let i = 0; i < mutations.length; i++) {
const mutation = mutations[i];
await client.transaction(async (tx) => {
const claim = await claimAppliedMutation(tx, clientId, mutation.id);
if (!claim.claimed) {
duplicates += 1;
results.push({ id: mutation.id, table: mutation.table, ...(claim.result || {}), duplicate: true });
return;
}
const patchResult = await applyMutationPatches(tx, mutation);
const result = {
id: mutation.id,
table: mutation.table,
applied: true,
changed: patchResult.changed,
result: patchResult
};
await updateAppliedMutation(tx, clientId, mutation.id, result);
results.push(result);
applied += 1;
});
}
return {
phase: 'push',
applied,
duplicates,
results
};
}
async function applyMutationPatches(tx, mutation) {
const entries = Array.isArray(mutation.patches)
? mutation.patches
: [{ table: mutation.table, patch: mutation.patch, options: mutation.options }];
let changed = 0;
const results = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const table = tx[entry.table];
if (!table || typeof table.patch !== 'function') {
const error = new Error(`Table "${entry.table}" is not exposed or does not exist`);
error.status = 400;
throw error;
}
const result = await table.patch(entry.patch, entry.options || mutation.options || {});
changed += Array.isArray(result && result.changed) ? result.changed.length : 0;
results.push({ table: entry.table, result });
}
return { changed, results };
}
async function pullKeys(body) {
const requestedTables = normalizeRequestedTables(body.tables, tableMeta, syncOptions.limits.maxTablesPerRequest);
const limit = normalizeLimit(body.limit, syncOptions.limits.maxKeysPerBatch);
const token = normalizeToken(body.token, requestedTables);
if (token && token.mode === 'changes')
return pullKeysFromChanges(token, limit);
if (token && token.mode === 'snapshot')
return pullKeysFromSnapshot(token, limit);
const startCursor = normalizeCursor(body.cursor ?? body.since);
const bounds = await getChangeBounds(syncOptions.changeTable);
const fallback = shouldUseSnapshot(startCursor, bounds, syncOptions.limits.maxChangeWindow);
if (fallback.useSnapshot) {
const snapshotToken = {
v: 1,
mode: 'snapshot',
tables: requestedTables,
tableIndex: 0,
offset: 0,
watermark: bounds.max
};
const result = await pullKeysFromSnapshot(snapshotToken, limit);
result.reason = fallback.reason;
return result;
}
const changeToken = {
v: 1,
mode: 'changes',
tables: requestedTables,
cursor: startCursor,
watermark: bounds.max
};
return pullKeysFromChanges(changeToken, limit);
}
async function pullKeysFromSnapshot(token, limit) {
const items = [];
let tableIndex = normalizeInteger(token.tableIndex, 0);
let offset = normalizeInteger(token.offset, 0);
while (items.length < limit && tableIndex < token.tables.length) {
const tableName = token.tables[tableIndex];
const meta = tableMeta.byName.get(tableName);
if (!meta) {
tableIndex += 1;
offset = 0;
continue;
}
const remaining = limit - items.length;
const keys = await fetchSnapshotKeys(meta, remaining, offset);
for (let i = 0; i < keys.length; i++) {
const pk = keys[i];
items.push({ table: tableName, pk, key: toKeyObject(meta, pk), op: 'U' });
}
if (keys.length < remaining) {
tableIndex += 1;
offset = 0;
}
else {
offset += keys.length;
}
}
const done = tableIndex >= token.tables.length;
return {
phase: 'keys',
mode: 'snapshot',
done,
cursor: token.watermark,
token: done ? null : {
v: 1,
mode: 'snapshot',
tables: token.tables,
tableIndex,
offset,
watermark: token.watermark
},
items
};
}
async function pullKeysFromChanges(token, limit) {
const fromCursor = normalizeInteger(token.cursor, 0);
const watermark = normalizeInteger(token.watermark, 0);
if (fromCursor >= watermark) {
return {
phase: 'keys',
mode: 'changes',
done: true,
cursor: watermark,
token: null,
items: []
};
}
const whereTables = token.tables.length === 0
? ''
: ` AND table_name IN (${token.tables.map((name) => sqlStringLiteral(tableMeta.byName.get(name).dbName)).join(',')})`;
const sql = [
'SELECT id, table_name, op, pk_json',
`FROM ${quoteQualified(syncOptions.changeTable)}`,
`WHERE id > ${fromCursor} AND id <= ${watermark}${whereTables}`,
'ORDER BY id ASC',
`LIMIT ${limit}`
].join(' ');
const rows = await safeQuery(sql, []);
const dedup = new Map();
let nextCursor = fromCursor;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const id = normalizeInteger(row.id ?? row.ID, nextCursor);
nextCursor = id > nextCursor ? id : nextCursor;
const rawTableName = row.table_name ?? row.TABLE_NAME;
const meta = tableMeta.byDbName.get(rawTableName);
if (!meta)
continue;
let keyObject;
try {
keyObject = typeof row.pk_json === 'string'
? JSON.parse(row.pk_json)
: JSON.parse(row.PK_JSON);
}
catch (_e) {
continue;
}
const pk = toPkArray(meta, keyObject);
if (!pk)
continue;
const mapKey = `${meta.name}|${stringify(pk)}`;
const op = normalizeOp(row.op ?? row.OP);
if (dedup.has(mapKey))
dedup.delete(mapKey);
dedup.set(mapKey, { table: meta.name, pk, key: toKeyObject(meta, pk), op });
}
const items = Array.from(dedup.values());
const done = rows.length === 0 || nextCursor >= watermark;
return {
phase: 'keys',
mode: 'changes',
done,
cursor: watermark,
token: done ? null : {
v: 1,
mode: 'changes',
tables: token.tables,
cursor: nextCursor,
watermark
},
items
};
}
async function pullRows(body) {
const rawItems = Array.isArray(body.items) ? body.items : [];
const limit = normalizeLimit(rawItems.length, syncOptions.limits.maxRowsPerBatch);
const items = rawItems.slice(0, limit);
const tableKeys = new Map();
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (!item || typeof item.table !== 'string')
continue;
const meta = tableMeta.byName.get(item.table);
if (!meta)
continue;
if (normalizeOp(item.op) === 'D')
continue;
const pk = Array.isArray(item.pk) ? item.pk : toPkArray(meta, item.key);
if (!pk || pk.length !== meta.pkColumns.length)
continue;
if (!tableKeys.has(meta.name))
tableKeys.set(meta.name, []);
tableKeys.get(meta.name).push(pk);
}
const rowMap = new Map();
const tableNames = Array.from(tableKeys.keys());
for (let i = 0; i < tableNames.length; i++) {
const tableName = tableNames[i];
const meta = tableMeta.byName.get(tableName);
const keys = tableKeys.get(tableName);
const rows = await fetchRowsByPrimaryKeys(meta, keys);
const perTable = new Map();
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex];
const pk = toPkArray(meta, row);
perTable.set(stringify(pk), row);
}
rowMap.set(tableName, perTable);
}
const resolved = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (!item || typeof item.table !== 'string')
continue;
const meta = tableMeta.byName.get(item.table);
if (!meta)
continue;
const pk = Array.isArray(item.pk) ? item.pk : toPkArray(meta, item.key);
if (!pk)
continue;
const row = rowMap.get(meta.name)?.get(stringify(pk));
if (row !== undefined)
resolved.push({ table: meta.name, pk, key: toKeyObject(meta, pk), row, op: normalizeOp(item.op) });
}
return {
phase: 'rows',
items: resolved
};
}
async function fetchSnapshotKeys(meta, limit, offset) {
const strategy = {};
for (let i = 0; i < meta.pkColumns.length; i++) {
strategy[meta.pkColumns[i].alias] = true;
}
strategy.orderBy = meta.pkColumns.map(x => x.alias);
strategy.limit = limit;
strategy.offset = offset;
const rows = await client.transaction(async (tx) => {
return tx[meta.name].getMany(undefined, strategy);
}, { readonly: true });
const result = [];
for (let i = 0; i < rows.length; i++) {
const pk = toPkArray(meta, rows[i]);
if (pk)
result.push(pk);
}
return result;
}
async function fetchRowsByPrimaryKeys(meta, keys) {
if (!Array.isArray(keys) || keys.length === 0)
return [];
const where = [];
const parameters = [];
for (let i = 0; i < keys.length; i++) {
const pk = keys[i];
if (!Array.isArray(pk) || pk.length !== meta.pkColumns.length)
continue;
const parts = [];
for (let colIndex = 0; colIndex < meta.pkColumns.length; colIndex++) {
const col = meta.pkColumns[colIndex];
parts.push(`${quoteIdent(col.dbName)} = ?`);
parameters.push(pk[colIndex]);
}
where.push(`(${parts.join(' AND ')})`);
}
if (where.length === 0)
return [];
const filter = {
sql: where.join(' OR '),
parameters
};
return client.transaction(async (tx) => {
return tx[meta.name].getMany(filter);
}, { readonly: true });
}
async function getChangeBounds(changeTable) {
try {
const sql = [
'SELECT',
'COALESCE(MIN(id), 0) AS min_id,',
'COALESCE(MAX(id), 0) AS max_id',
`FROM ${quoteQualified(changeTable)}`
].join(' ');
const rows = await safeQuery(sql, []);
const row = rows[0] || {};
return {
exists: true,
min: normalizeInteger(row.min_id ?? row.MIN_ID, 0),
max: normalizeInteger(row.max_id ?? row.MAX_ID, 0)
};
}
catch (_error) {
return { exists: false, min: 0, max: 0 };
}
}
async function safeQuery(sql, fallback) {
const result = await client.query(sql);
if (Array.isArray(result))
return result;
if (Array.isArray(result?.rows))
return result.rows;
return fallback;
}
async function claimAppliedMutation(tx, clientId, mutationId) {
const rows = await safeTxQuery(tx, [
`INSERT INTO ${quoteQualified(syncOptions.appliedMutationsTable)} (client_id, mutation_id, result_json)`,
`VALUES (${sqlStringLiteral(clientId)}, ${sqlStringLiteral(mutationId)}, ${sqlJsonLiteral({ pending: true })})`,
'ON CONFLICT (client_id, mutation_id) DO NOTHING',
'RETURNING result_json'
].join(' '), []);
if (rows.length > 0)
return { claimed: true };
const existingRows = await safeTxQuery(tx, [
'SELECT result_json',
`FROM ${quoteQualified(syncOptions.appliedMutationsTable)}`,
`WHERE client_id = ${sqlStringLiteral(clientId)} AND mutation_id = ${sqlStringLiteral(mutationId)}`,
'LIMIT 1'
].join(' '), []);
const result = parseResultJson(existingRows[0]);
return { claimed: false, result };
}
function parseResultJson(row) {
if (!row)
return null;
const raw = row.result_json ?? row.RESULT_JSON;
if (typeof raw === 'string') {
try {
return JSON.parse(raw);
}
catch (_e) {
return null;
}
}
return raw && raw === Object(raw) ? raw : null;
}
async function updateAppliedMutation(tx, clientId, mutationId, result) {
await tx.query([
`UPDATE ${quoteQualified(syncOptions.appliedMutationsTable)}`,
`SET result_json = ${sqlJsonLiteral(result)}, applied_at = NOW()`,
`WHERE client_id = ${sqlStringLiteral(clientId)} AND mutation_id = ${sqlStringLiteral(mutationId)}`
].join(' '));
}
async function safeTxQuery(tx, sql, fallback) {
const result = await tx.query(sql);
if (Array.isArray(result))
return result;
if (Array.isArray(result?.rows))
return result.rows;
return fallback;
}
}
function normalizeSyncOptions(sync) {
if (!sync)
return null;
const queueOptions = sync.queue || {};
const limits = sync.limits || {};
return {
enabled: sync.enabled !== false,
changeTable: sync.changeTable || 'orange_changes',
appliedMutationsTable: sync.appliedMutationsTable || 'orange_sync_applied_mutations',
queue: {
concurrency: clamp(normalizeInteger(queueOptions.concurrency, 4), 1, 100),
maxPending: clamp(normalizeInteger(queueOptions.maxPending, 1000), 0, 100000)
},
limits: {
maxTablesPerRequest: clamp(normalizeInteger(limits.maxTablesPerRequest, 50), 1, 1000),
maxKeysPerBatch: clamp(normalizeInteger(limits.maxKeysPerBatch, 200), 1, 10000),
maxRowsPerBatch: clamp(normalizeInteger(limits.maxRowsPerBatch, 200), 1, 10000),
maxMutationsPerBatch: clamp(normalizeInteger(limits.maxMutationsPerBatch, 200), 1, 10000),
maxChangeWindow: clamp(normalizeInteger(limits.maxChangeWindow, 50000), 1, 100000000)
}
};
}
function createTableMeta(client, syncOptions) {
const byName = new Map();
const byDbName = new Map();
for (let tableName in client.tables) {
const table = client.tables[tableName];
const pkColumns = Array.isArray(table?._primaryColumns) ? table._primaryColumns : [];
if (pkColumns.length === 0)
continue;
const dbName = table._dbName;
if (!dbName || dbName === syncOptions.changeTable)
continue;
const meta = {
name: tableName,
dbName,
pkColumns: pkColumns.map((col) => ({ alias: col.alias, dbName: col._dbName || col.alias }))
};
byName.set(tableName, meta);
byDbName.set(dbName, meta);
const split = dbName.split('.');
byDbName.set(split[split.length - 1], meta);
}
return { byName, byDbName };
}
function createQueue({ concurrency, maxPending }) {
let running = 0;
const pending = [];
return { run };
function run(job) {
return new Promise((resolve, reject) => {
if (running >= concurrency && pending.length >= maxPending) {
const error = new Error('Sync queue is full. Try again later.');
error.status = 429;
reject(error);
return;
}
pending.push({ job, resolve, reject });
drain();
});
}
function drain() {
while (running < concurrency && pending.length > 0) {
const next = pending.shift();
running += 1;
Promise.resolve()
.then(next.job)
.then(next.resolve, next.reject)
.finally(() => {
running -= 1;
drain();
});
}
}
}
function shouldUseSnapshot(cursor, bounds, maxChangeWindow) {
if (!Number.isFinite(cursor))
return { useSnapshot: true, reason: 'first_sync' };
if (!bounds.exists)
return { useSnapshot: true, reason: 'change_table_unavailable' };
if (cursor < bounds.min - 1)
return { useSnapshot: true, reason: 'cursor_too_old' };
if (bounds.max - cursor > maxChangeWindow)
return { useSnapshot: true, reason: 'cursor_too_far_behind' };
return { useSnapshot: false };
}
function normalizeRequestedTables(rawTables, tableMeta, maxTablesPerRequest) {
if (!Array.isArray(rawTables) || rawTables.length === 0)
return Array.from(tableMeta.byName.keys());
const normalized = [];
for (let i = 0; i < rawTables.length; i++) {
const raw = rawTables[i];
if (typeof raw !== 'string')
continue;
const byName = tableMeta.byName.get(raw);
if (byName) {
normalized.push(byName.name);
continue;
}
const byDbName = tableMeta.byDbName.get(raw);
if (byDbName)
normalized.push(byDbName.name);
}
const deduped = Array.from(new Set(normalized));
return deduped.slice(0, maxTablesPerRequest);
}
function normalizeToken(token, requestedTables) {
if (!token || token !== Object(token))
return null;
if (token.v !== 1)
return null;
if (token.mode === 'changes') {
return {
v: 1,
mode: 'changes',
tables: requestedTables,
cursor: normalizeInteger(token.cursor, 0),
watermark: normalizeInteger(token.watermark, 0)
};
}
if (token.mode === 'snapshot') {
return {
v: 1,
mode: 'snapshot',
tables: requestedTables,
tableIndex: normalizeInteger(token.tableIndex, 0),
offset: normalizeInteger(token.offset, 0),
watermark: normalizeInteger(token.watermark, 0)
};
}
return null;
}
function normalizeCursor(cursor) {
if (cursor === null || cursor === undefined || cursor === '')
return NaN;
if (typeof cursor === 'number' && Number.isFinite(cursor))
return cursor;
if (typeof cursor === 'string') {
const parsed = Number.parseInt(cursor, 10);
return Number.isFinite(parsed) ? parsed : NaN;
}
return NaN;
}
function normalizeLimit(limit, max) {
return clamp(normalizeInteger(limit, max), 1, max);
}
function normalizeInteger(value, fallback) {
if (typeof value === 'number' && Number.isFinite(value))
return Math.floor(value);
if (typeof value === 'string') {
const parsed = Number.parseInt(value, 10);
if (Number.isFinite(parsed))
return parsed;
}
return fallback;
}
function normalizeOp(value) {
if (typeof value !== 'string')
return 'U';
const op = value.toUpperCase();
if (op === 'I' || op === 'U' || op === 'D')
return op;
return 'U';
}
function normalizeClientId(value) {
if (typeof value !== 'string')
return '';
return value.trim();
}
function normalizeMutations(value, limit) {
if (!Array.isArray(value))
return [];
const result = [];
for (let i = 0; i < value.length && result.length < limit; i++) {
const mutation = normalizeMutation(value[i]);
if (mutation)
result.push(mutation);
}
return result;
}
function normalizeMutation(value) {
if (!value || value !== Object(value))
return null;
const id = value.id ?? value.mutationId ?? value.mutation_id;
if (typeof id !== 'string' || id.length === 0)
return null;
if (Array.isArray(value.patches)) {
const patches = value.patches.map(normalizeMutationPatch).filter(Boolean);
if (patches.length === 0)
return null;
return {
id,
patches,
options: value.options && value.options === Object(value.options) ? value.options : undefined
};
}
const entry = normalizeMutationPatch(value);
if (!entry)
return null;
return {
id,
...entry,
options: value.options && value.options === Object(value.options) ? value.options : undefined
};
}
function normalizeMutationPatch(value) {
if (!value || value !== Object(value))
return null;
if (typeof value.table !== 'string' || value.table.length === 0)
return null;
if (!Array.isArray(value.patch))
return null;
return {
table: value.table,
patch: value.patch,
options: value.options && value.options === Object(value.options) ? value.options : undefined
};
}
function toPkArray(meta, row) {
if (!row || row !== Object(row))
return null;
const result = [];
for (let i = 0; i < meta.pkColumns.length; i++) {
const key = meta.pkColumns[i].alias;
if (!(key in row))
return null;
result.push(row[key]);
}
return result;
}
function toKeyObject(meta, pk) {
const key = {};
for (let i = 0; i < meta.pkColumns.length && i < pk.length; i++) {
key[meta.pkColumns[i].alias] = pk[i];
}
return key;
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function quoteQualified(name) {
return String(name).split('.').map(quoteIdent).join('.');
}
function quoteIdent(name) {
return `"${String(name).replace(/"/g, '""')}"`;
}
function sqlStringLiteral(value) {
return `'${String(value).replace(/'/g, '\'\'')}'`;
}
function sqlJsonLiteral(value) {
return `${sqlStringLiteral(stringify(value))}::jsonb`;
}
sync = newSyncHandler;
return sync;
}
var hostExpress_1;
var hasRequiredHostExpress;
function requireHostExpress () {
if (hasRequiredHostExpress) return hostExpress_1;
hasRequiredHostExpress = 1;
const getTSDefinition = requireGetTSDefinition();
// let hostLocal = _hostLocal;
const getMeta = requireGetMeta();
const newSyncHandler = requireSync();
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
});
}
const syncHandler = newSyncHandler(client, options);
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.sync) {
if (!syncHandler) {
const e = new Error('Sync is not enabled for this endpoint');
// @ts-ignore
e.status = 404;
throw e;
}
return syncHandler(request, response);
}
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')