@everystack/api 0.2.12 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handler/index.ts +78 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "PostgREST-compatible HTTP handler + typed fetch client for Drizzle ORM",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
package/src/handler/index.ts
CHANGED
|
@@ -522,8 +522,81 @@ function transformBodyKeys(body: Record<string, unknown>, table: DrizzleTable):
|
|
|
522
522
|
return result;
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
|
|
525
|
+
/** The name a drizzle pgTable knows itself by in SQL, or null for a non-table export. */
|
|
526
|
+
function sqlNameOf(value: unknown): string | null {
|
|
527
|
+
if (!value || typeof value !== 'object') return null;
|
|
528
|
+
const name = (value as Record<symbol, unknown>)[Symbol.for('drizzle:Name')];
|
|
529
|
+
return typeof name === 'string' ? name : null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Export-key ↔ SQL-name pairs for every table whose two spellings differ. */
|
|
533
|
+
function tableNamePairs(schema: Schema): Array<{ exportKey: string; sqlName: string }> {
|
|
534
|
+
const pairs: Array<{ exportKey: string; sqlName: string }> = [];
|
|
535
|
+
for (const [exportKey, value] of Object.entries(schema)) {
|
|
536
|
+
const sqlName = sqlNameOf(value);
|
|
537
|
+
if (sqlName && sqlName !== exportKey) pairs.push({ exportKey, sqlName });
|
|
538
|
+
}
|
|
539
|
+
return pairs;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** The schema with each table reachable under BOTH spellings (existing keys win). */
|
|
543
|
+
function expandSchema(schema: Schema, pairs: Array<{ exportKey: string; sqlName: string }>): Schema {
|
|
544
|
+
if (pairs.length === 0) return schema;
|
|
545
|
+
const expanded: Schema = { ...schema };
|
|
546
|
+
for (const { exportKey, sqlName } of pairs) {
|
|
547
|
+
if (!(sqlName in expanded)) expanded[sqlName] = schema[exportKey];
|
|
548
|
+
}
|
|
549
|
+
return expanded;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Table-keyed options written in EITHER naming domain apply to both spellings.
|
|
554
|
+
* `deriveHandlerConfig` emits SQL names; hand-written configs use export keys —
|
|
555
|
+
* each key gains its twin (same value reference), so neither authoring style
|
|
556
|
+
* can strand a table behind the other domain's gate.
|
|
557
|
+
*/
|
|
558
|
+
function expandTableKeyedOptions(
|
|
559
|
+
options: HandlerOptions | undefined,
|
|
560
|
+
pairs: Array<{ exportKey: string; sqlName: string }>,
|
|
561
|
+
): HandlerOptions | undefined {
|
|
562
|
+
if (!options || pairs.length === 0) return options;
|
|
563
|
+
const twin = (name: string): string | undefined =>
|
|
564
|
+
pairs.find((p) => p.exportKey === name)?.sqlName ?? pairs.find((p) => p.sqlName === name)?.exportKey;
|
|
565
|
+
const expandList = (list: string[]): string[] =>
|
|
566
|
+
[...new Set(list.flatMap((name) => { const t = twin(name); return t ? [name, t] : [name]; }))];
|
|
567
|
+
const expandMap = <T,>(map: Record<string, T>): Record<string, T> => {
|
|
568
|
+
const out: Record<string, T> = { ...map };
|
|
569
|
+
for (const key of Object.keys(map)) {
|
|
570
|
+
const t = twin(key);
|
|
571
|
+
if (t && !(t in out)) out[t] = map[key];
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
};
|
|
575
|
+
return {
|
|
576
|
+
...options,
|
|
577
|
+
...(options.exposedTables ? { exposedTables: expandList(options.exposedTables) } : {}),
|
|
578
|
+
...(options.hiddenColumns ? { hiddenColumns: expandMap(options.hiddenColumns) } : {}),
|
|
579
|
+
...(options.protectedFields ? { protectedFields: expandMap(options.protectedFields) } : {}),
|
|
580
|
+
...(options.rowOwnership ? { rowOwnership: expandMap(options.rowOwnership) } : {}),
|
|
581
|
+
...(options.relations ? { relations: expandMap(options.relations) } : {}),
|
|
582
|
+
...(options.hooks ? { hooks: expandMap(options.hooks) } : {}),
|
|
583
|
+
...(options.softDelete?.tables
|
|
584
|
+
? { softDelete: { ...options.softDelete, tables: expandList(options.softDelete.tables) } }
|
|
585
|
+
: {}),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function createHandler(db: DrizzleDb, rawSchema: Schema, rawOptions?: HandlerOptions): Handler {
|
|
590
|
+
// The naming seam: URLs, exposedTables, and relation targets historically mixed
|
|
591
|
+
// drizzle export keys (`texturePacks`) with SQL names (`texture_packs`), and any
|
|
592
|
+
// multi-word table failed one gate or the other — both spellings 404'd. Collapse
|
|
593
|
+
// the two domains here, once: both spellings route, and options in either domain
|
|
594
|
+
// apply. The root listing below stays one entry per table (the SQL name).
|
|
595
|
+
const namePairs = tableNamePairs(rawSchema);
|
|
596
|
+
const schema = expandSchema(rawSchema, namePairs);
|
|
597
|
+
const options = expandTableKeyedOptions(rawOptions, namePairs);
|
|
526
598
|
const tableNames = new Set(Object.keys(schema));
|
|
599
|
+
const canonicalTables = [...new Set(Object.entries(rawSchema).map(([key, value]) => sqlNameOf(value) ?? key))];
|
|
527
600
|
|
|
528
601
|
return async (request: Request): Promise<Response> => {
|
|
529
602
|
const url = new URL(request.url);
|
|
@@ -583,11 +656,12 @@ export function createHandler(db: DrizzleDb, schema: Schema, options?: HandlerOp
|
|
|
583
656
|
}
|
|
584
657
|
}
|
|
585
658
|
|
|
586
|
-
// Root path — table listing, always public
|
|
659
|
+
// Root path — table listing, always public. One entry per table (SQL name),
|
|
660
|
+
// regardless of how many spellings route to it.
|
|
587
661
|
if (pathSegments.length === 0) {
|
|
588
662
|
const tables = options?.exposedTables
|
|
589
|
-
?
|
|
590
|
-
:
|
|
663
|
+
? canonicalTables.filter(t => options.exposedTables!.includes(t))
|
|
664
|
+
: canonicalTables;
|
|
591
665
|
return json({ tables }, 200);
|
|
592
666
|
}
|
|
593
667
|
|