@palbase/backend 23.0.0 → 24.0.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/dist/bin/palbase-backend.cjs +695 -61
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -5
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-FSGSB42K.js → chunk-7Z6MGMXQ.js} +64 -4
- package/dist/chunk-7Z6MGMXQ.js.map +1 -0
- package/dist/{chunk-OMRTHM4X.js → chunk-H3JAISUY.js} +136 -1
- package/dist/chunk-H3JAISUY.js.map +1 -0
- package/dist/{chunk-REZU6UKT.js → chunk-NXDH6VQJ.js} +549 -42
- package/dist/chunk-NXDH6VQJ.js.map +1 -0
- package/dist/{chunk-W5ODXPY3.js → chunk-P2Q27SGP.js} +32 -3
- package/dist/chunk-P2Q27SGP.js.map +1 -0
- package/dist/{chunk-ZC6Q2BRD.js → chunk-T5IOSOE5.js} +7 -2
- package/dist/chunk-T5IOSOE5.js.map +1 -0
- package/dist/{chunk-HAF67F2H.js → chunk-ZUGY7RGS.js} +86 -3
- package/dist/chunk-ZUGY7RGS.js.map +1 -0
- package/dist/db/index.cjs +115 -3
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/{endpoint-BavvbW4P.d.ts → endpoint-0_DGBajf.d.ts} +168 -9
- package/dist/{endpoint-i8TTCohk.d.cts → endpoint-CcQ1a36a.d.cts} +168 -9
- package/dist/engine/index.cjs +684 -48
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -4
- package/dist/engine/index.d.ts +4 -4
- package/dist/engine/index.js +4 -4
- package/dist/{index-B3jmmItD.d.ts → index-CJiJU9ux.d.ts} +209 -36
- package/dist/{index-Bmvx1EvJ.d.cts → index-D-4-PNuQ.d.cts} +209 -36
- package/dist/{index-B7YBEG5w.d.ts → index-D17r-MKb.d.ts} +177 -7
- package/dist/{index-E7OscPJT.d.cts → index-DRFxf07H.d.cts} +177 -7
- package/dist/index.cjs +269 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +45 -5
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +9 -4
- package/dist/openapi/index.d.ts +9 -4
- package/dist/openapi/index.js +35 -11
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-C3H2uPeZ.d.cts → registry-1X-skBNu.d.cts} +101 -7
- package/dist/{registry-DY3d9l1k.d.ts → registry-CEod_5sz.d.ts} +101 -7
- package/dist/test/index.cjs +509 -9
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +35 -3
- package/dist/test/index.d.ts +35 -3
- package/dist/test/index.js +507 -8
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +4 -4
- package/docs/database.md +115 -11
- package/docs/getting-started.md +5 -4
- package/docs/llms-full.txt +385 -89
- package/docs/migrations.md +81 -59
- package/docs/schema.md +82 -2
- package/docs/services.md +98 -9
- package/package.json +3 -2
- package/stager/return_types.js +23 -0
- package/template/AGENTS.md +121 -41
- package/template/controllers/notes.controller.ts +64 -0
- package/template/package.json +1 -1
- package/template/services/note.service.ts +74 -0
- package/template/tsconfig.json +11 -1
- package/dist/chunk-FSGSB42K.js.map +0 -1
- package/dist/chunk-HAF67F2H.js.map +0 -1
- package/dist/chunk-OMRTHM4X.js.map +0 -1
- package/dist/chunk-REZU6UKT.js.map +0 -1
- package/dist/chunk-W5ODXPY3.js.map +0 -1
- package/dist/chunk-Y5HXVUMP.js +0 -90
- package/dist/chunk-Y5HXVUMP.js.map +0 -1
- package/dist/chunk-ZC6Q2BRD.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -57,26 +57,35 @@ __export(src_exports, {
|
|
|
57
57
|
Req: () => Req,
|
|
58
58
|
RequestId: () => RequestId,
|
|
59
59
|
Secrets: () => Secrets,
|
|
60
|
+
Signal: () => Signal,
|
|
61
|
+
Sse: () => Sse,
|
|
62
|
+
SseOut: () => SseOut,
|
|
60
63
|
Storage: () => Storage,
|
|
61
64
|
TooManyRequests: () => TooManyRequests,
|
|
62
65
|
TraceId: () => TraceId,
|
|
63
66
|
TxPlanError: () => TxPlanError,
|
|
64
67
|
TxRefError: () => TxRefError,
|
|
65
68
|
Unauthorized: () => Unauthorized,
|
|
69
|
+
UniqueViolation: () => UniqueViolation,
|
|
66
70
|
Upload: () => Upload,
|
|
67
71
|
UploadedObject: () => UploadedObject,
|
|
68
72
|
User: () => User,
|
|
69
73
|
Webhook: () => Webhook,
|
|
70
74
|
__getRuntime: () => __getRuntime,
|
|
71
75
|
__requestALS: () => __requestALS,
|
|
76
|
+
__resetDefaultAuth: () => __resetDefaultAuth,
|
|
77
|
+
__resetLifecycleHooks: () => __resetLifecycleHooks,
|
|
72
78
|
__resetRegisteredControllers: () => __resetRegisteredControllers,
|
|
79
|
+
__runStartHooks: () => __runStartHooks,
|
|
73
80
|
__runWithRuntime: () => __runWithRuntime,
|
|
74
81
|
__setRuntime: () => __setRuntime,
|
|
82
|
+
assertZeroArgConstructor: () => assertZeroArgConstructor,
|
|
75
83
|
bigint: () => bigint,
|
|
76
84
|
boolean: () => boolean,
|
|
77
85
|
buildModuleClients: () => buildModuleClients,
|
|
78
86
|
dec: () => dec,
|
|
79
87
|
defineChannels: () => defineChannels,
|
|
88
|
+
defineDefaultAuth: () => defineDefaultAuth,
|
|
80
89
|
defineError: () => defineError,
|
|
81
90
|
defineMiddleware: () => defineMiddleware,
|
|
82
91
|
defineSchema: () => defineSchema,
|
|
@@ -97,6 +106,8 @@ __export(src_exports, {
|
|
|
97
106
|
makeTypedDB: () => makeTypedDB,
|
|
98
107
|
now: () => now,
|
|
99
108
|
numeric: () => numeric,
|
|
109
|
+
onShutdown: () => onShutdown,
|
|
110
|
+
onStart: () => onStart,
|
|
100
111
|
openai: () => openai,
|
|
101
112
|
ownerOnly: () => ownerOnly,
|
|
102
113
|
policy: () => policy,
|
|
@@ -354,7 +365,20 @@ var TxPlanBuilder = class {
|
|
|
354
365
|
}
|
|
355
366
|
return this.push({ op: "insert", table: name, values: encoded }, `${name}.insert()`);
|
|
356
367
|
},
|
|
357
|
-
|
|
368
|
+
upsert: (values, options) => {
|
|
369
|
+
const encoded = encodeMap(values, false);
|
|
370
|
+
if (Object.keys(encoded).length === 0) {
|
|
371
|
+
throw new TxPlanError(`${name}.upsert() needs at least one column`);
|
|
372
|
+
}
|
|
373
|
+
if (options.onConflict.length === 0) {
|
|
374
|
+
throw new TxPlanError(`${name}.upsert() needs at least one onConflict column`);
|
|
375
|
+
}
|
|
376
|
+
return this.push(
|
|
377
|
+
{ op: "upsert", table: name, values: encoded, onConflict: options.onConflict },
|
|
378
|
+
`${name}.upsert()`
|
|
379
|
+
);
|
|
380
|
+
},
|
|
381
|
+
insertMany: (rows, opts) => {
|
|
358
382
|
if (rows.length === 0) {
|
|
359
383
|
return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
|
|
360
384
|
}
|
|
@@ -365,7 +389,23 @@ var TxPlanBuilder = class {
|
|
|
365
389
|
}
|
|
366
390
|
const encoded = rows.map((row) => encodeMap(row, false));
|
|
367
391
|
assertUniformRows(encoded, name);
|
|
368
|
-
|
|
392
|
+
if (opts !== void 0 && opts.onConflict.length === 0) {
|
|
393
|
+
throw new TxPlanError(
|
|
394
|
+
`${name}.insertMany() was given a conflict action with no onConflict columns. Postgres matches a collision on columns, so name them.`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return this.push(
|
|
398
|
+
{
|
|
399
|
+
op: "insertMany",
|
|
400
|
+
table: name,
|
|
401
|
+
rows: encoded,
|
|
402
|
+
// Omitted entirely when no options were given, so the op a plain
|
|
403
|
+
// insertMany produces is byte-identical to the one it produced
|
|
404
|
+
// before this option existed.
|
|
405
|
+
...opts !== void 0 ? { onConflict: opts.onConflict, action: opts.action ?? "ignore" } : {}
|
|
406
|
+
},
|
|
407
|
+
`${name}.insertMany()`
|
|
408
|
+
);
|
|
369
409
|
},
|
|
370
410
|
updateWhere: (where, set) => {
|
|
371
411
|
const encodedWhere = encodeMap(where, false);
|
|
@@ -542,6 +582,52 @@ function __getRuntime() {
|
|
|
542
582
|
}
|
|
543
583
|
return runtime;
|
|
544
584
|
}
|
|
585
|
+
var LIFECYCLE = /* @__PURE__ */ Symbol.for("palbase.backend.lifecycleHooks");
|
|
586
|
+
function declaredLifecycle() {
|
|
587
|
+
const g = globalThis;
|
|
588
|
+
return g[LIFECYCLE] ??= { start: [], shutdown: [] };
|
|
589
|
+
}
|
|
590
|
+
function onStart(name, hook) {
|
|
591
|
+
declaredLifecycle().start.push({ name, run: hook });
|
|
592
|
+
}
|
|
593
|
+
function onShutdown(name, hook) {
|
|
594
|
+
declaredLifecycle().shutdown.push({ name, run: hook });
|
|
595
|
+
}
|
|
596
|
+
function reason(err) {
|
|
597
|
+
return err instanceof Error ? err.message : String(err);
|
|
598
|
+
}
|
|
599
|
+
async function drain(hooks) {
|
|
600
|
+
for (const h of [...hooks].reverse()) {
|
|
601
|
+
try {
|
|
602
|
+
await h.run();
|
|
603
|
+
} catch (err) {
|
|
604
|
+
console.error(`[palbase] shutdown hook "${h.name}" failed: ${reason(err)}`, err);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
async function __runStartHooks() {
|
|
609
|
+
const slot = declaredLifecycle();
|
|
610
|
+
const start = slot.start.splice(0);
|
|
611
|
+
const shutdown = slot.shutdown.splice(0);
|
|
612
|
+
for (const h of start) {
|
|
613
|
+
try {
|
|
614
|
+
await h.run();
|
|
615
|
+
} catch (err) {
|
|
616
|
+
await drain(shutdown);
|
|
617
|
+
throw new Error(`[palbase] start hook "${h.name}" failed: ${reason(err)}`, { cause: err });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
let drained = false;
|
|
621
|
+
return async () => {
|
|
622
|
+
if (drained) return;
|
|
623
|
+
drained = true;
|
|
624
|
+
await drain(shutdown);
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function __resetLifecycleHooks() {
|
|
628
|
+
const g = globalThis;
|
|
629
|
+
delete g[LIFECYCLE];
|
|
630
|
+
}
|
|
545
631
|
function makeServiceProxy(key) {
|
|
546
632
|
const handler = {
|
|
547
633
|
get(_target, prop, receiver) {
|
|
@@ -564,10 +650,12 @@ function makeTablesAccessor(ops) {
|
|
|
564
650
|
update: (id, data) => ops().update(name, id, data),
|
|
565
651
|
delete: (id) => ops().delete(name, id),
|
|
566
652
|
findById: (id) => ops().findById(name, id),
|
|
567
|
-
findMany: (query) => ops().findMany(name, query),
|
|
653
|
+
findMany: (query, opts) => ops().findMany(name, query, opts),
|
|
654
|
+
upsert: (data, opts) => ops().upsert(name, data, opts),
|
|
568
655
|
search: (params) => ops().search(name, params),
|
|
569
656
|
similar: (id, params) => ops().similar(name, id, params),
|
|
570
657
|
recommend: (params) => ops().recommend(name, params),
|
|
658
|
+
facets: (params) => ops().facets(name, params),
|
|
571
659
|
supersede: (id, row) => ops().supersede(name, id, row)
|
|
572
660
|
};
|
|
573
661
|
}
|
|
@@ -584,13 +672,21 @@ function makeTypedSurface(raw2) {
|
|
|
584
672
|
update: (table, id, data) => raw2.update(table, id, data),
|
|
585
673
|
delete: (table, id) => raw2.delete(table, id),
|
|
586
674
|
findById: (table, id) => raw2.findById(table, id),
|
|
587
|
-
findMany: (table, query) => raw2.findMany(table, query),
|
|
675
|
+
findMany: (table, query, opts) => raw2.findMany(table, query, opts),
|
|
676
|
+
upsert: (table, data, opts) => raw2.upsert(table, data, opts),
|
|
677
|
+
updateMany: (table, where, set) => raw2.updateMany(table, where, set),
|
|
678
|
+
deleteMany: (table, where) => raw2.deleteMany(table, where),
|
|
679
|
+
count: (table, where) => raw2.count(table, where),
|
|
588
680
|
search: (table, params) => raw2.search(table, params),
|
|
589
681
|
similar: (table, id, params) => reco.similar(table, id, params),
|
|
590
682
|
recommend: (table, params) => reco.recommend(table, params),
|
|
683
|
+
facets: (table, params) => reco.facets(table, params),
|
|
591
684
|
supersede: (table, id, row) => raw2.supersede(table, id, row)
|
|
592
685
|
};
|
|
593
686
|
return Object.assign(ops, {
|
|
687
|
+
// Both surfaces get it: a savepoint on the service transaction is as useful
|
|
688
|
+
// as one on the request's, and each is bound to its own connection.
|
|
689
|
+
attempt: (fn) => raw2.attempt(fn),
|
|
594
690
|
tables: makeTablesAccessor(() => reco),
|
|
595
691
|
transaction(fn) {
|
|
596
692
|
const builder = new TxPlanBuilder();
|
|
@@ -1573,6 +1669,9 @@ function buildModuleClients(cfg) {
|
|
|
1573
1669
|
}
|
|
1574
1670
|
|
|
1575
1671
|
// src/db/policy.ts
|
|
1672
|
+
function quote(name) {
|
|
1673
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
1674
|
+
}
|
|
1576
1675
|
var PolicyBuilder = class {
|
|
1577
1676
|
_def;
|
|
1578
1677
|
constructor(name) {
|
|
@@ -1608,6 +1707,41 @@ var PolicyBuilder = class {
|
|
|
1608
1707
|
this._def.using = sqlExpr;
|
|
1609
1708
|
return this;
|
|
1610
1709
|
}
|
|
1710
|
+
/**
|
|
1711
|
+
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
1712
|
+
* pattern, written so it cannot recurse.
|
|
1713
|
+
*
|
|
1714
|
+
* THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
|
|
1715
|
+
* other: `channels` is visible to members, so its policy reads
|
|
1716
|
+
* `channel_members`; `channel_members` is visible to members, so its policy
|
|
1717
|
+
* reads `channels`. Postgres refuses the pair at query time with `infinite
|
|
1718
|
+
* recursion detected in policy for relation ...`, and the error names the
|
|
1719
|
+
* relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
|
|
1720
|
+
* is protected by `user_id = auth.uid()` and nothing else, and every other
|
|
1721
|
+
* table subqueries INTO it. That shape was in the platform's own schema and
|
|
1722
|
+
* written down nowhere; a customer recovered it by reading that schema.
|
|
1723
|
+
*
|
|
1724
|
+
* `(select auth.uid())` rather than a bare call: the scalar subquery is
|
|
1725
|
+
* evaluated ONCE per statement instead of per row.
|
|
1726
|
+
*
|
|
1727
|
+
* @example
|
|
1728
|
+
* // channels: visible to members. The membership table gets the simple one.
|
|
1729
|
+
* policy("member_read").for("select").to("authenticated")
|
|
1730
|
+
* .memberOf("channel_members", "channel_id")
|
|
1731
|
+
* // → id IN (SELECT "channel_id" FROM "channel_members"
|
|
1732
|
+
* // WHERE "user_id" = (select auth.uid()))
|
|
1733
|
+
*/
|
|
1734
|
+
memberOf(membershipTable, foreignKey, options = {}) {
|
|
1735
|
+
if (this._def.using !== null) {
|
|
1736
|
+
throw new Error(
|
|
1737
|
+
`policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
const column = options.column ?? "id";
|
|
1741
|
+
const userColumn = options.userColumn ?? "user_id";
|
|
1742
|
+
this._def.using = `${quote(column)} IN (SELECT ${quote(foreignKey)} FROM ${quote(membershipTable)} WHERE ${quote(userColumn)} = (select auth.uid()))`;
|
|
1743
|
+
return this;
|
|
1744
|
+
}
|
|
1611
1745
|
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
1612
1746
|
withCheck(sqlExpr) {
|
|
1613
1747
|
this._def.withCheck = sqlExpr;
|
|
@@ -2033,6 +2167,21 @@ var ColumnBuilder = class _ColumnBuilder {
|
|
|
2033
2167
|
this._def.defaultNow = true;
|
|
2034
2168
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2035
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* The DATABASE assigns this column's value — a trigger, a rule, an identity.
|
|
2172
|
+
*
|
|
2173
|
+
* The column becomes optional on INSERT (the author has nothing to send) while
|
|
2174
|
+
* the DDL stays free of a DEFAULT this schema would not honour. It is NOT
|
|
2175
|
+
* `default()`: that declares a value the schema promises to write.
|
|
2176
|
+
*
|
|
2177
|
+
* Naming: deliberately not `generated()`. Postgres has GENERATED columns and
|
|
2178
|
+
* they are a different thing; borrowing the word would send a reader — or a
|
|
2179
|
+
* model writing a schema — to the wrong feature.
|
|
2180
|
+
*/
|
|
2181
|
+
dbAssigned() {
|
|
2182
|
+
this._def.dbAssigned = true;
|
|
2183
|
+
return new _ColumnBuilder(this._def.type, this._def);
|
|
2184
|
+
}
|
|
2036
2185
|
/** Add a foreign key reference. */
|
|
2037
2186
|
/**
|
|
2038
2187
|
* Declares that this column used to be called `previous`.
|
|
@@ -2115,6 +2264,28 @@ var ColumnBuilder = class _ColumnBuilder {
|
|
|
2115
2264
|
this._def.unique = true;
|
|
2116
2265
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2117
2266
|
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Declare how this column's value is projected in and out of the process.
|
|
2269
|
+
*
|
|
2270
|
+
* The DDL does not move: `numeric` stays `numeric`, and the driver still hands
|
|
2271
|
+
* back what Postgres sent. What changes is the type the row surface exposes —
|
|
2272
|
+
* it becomes `Target`:
|
|
2273
|
+
*
|
|
2274
|
+
* amount: numeric().transform<number>({ fromDb: Number, toDb: String })
|
|
2275
|
+
*
|
|
2276
|
+
* `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
|
|
2277
|
+
* arbitrary precision), and that is exactly why this exists: application code
|
|
2278
|
+
* that does arithmetic on the column otherwise rewrites the same
|
|
2279
|
+
* `Number(row.amount)` / `String(x)` pair in every controller that touches it,
|
|
2280
|
+
* and each rewrite is a place the two directions can drift apart.
|
|
2281
|
+
*
|
|
2282
|
+
* A transform is a PROJECTION, never a constraint: it lives only in this
|
|
2283
|
+
* process, so it can neither validate nor migrate what is stored.
|
|
2284
|
+
*/
|
|
2285
|
+
transform(fns) {
|
|
2286
|
+
this._def.transform = fns;
|
|
2287
|
+
return new _ColumnBuilder(this._def.type, this._def);
|
|
2288
|
+
}
|
|
2118
2289
|
};
|
|
2119
2290
|
function uuid() {
|
|
2120
2291
|
return new ColumnBuilder("uuid");
|
|
@@ -2180,10 +2351,18 @@ var openai = {
|
|
|
2180
2351
|
function makeTypedTable(name, raw2) {
|
|
2181
2352
|
return {
|
|
2182
2353
|
insert: (data) => raw2.insert(name, data),
|
|
2354
|
+
upsert: (data, opts) => raw2.upsert(name, data, opts),
|
|
2183
2355
|
update: (id, data) => raw2.update(name, id, data),
|
|
2184
2356
|
delete: (id) => raw2.delete(name, id),
|
|
2185
2357
|
findById: (id) => raw2.findById(name, id),
|
|
2186
|
-
findMany: (query) => raw2.findMany(name, query)
|
|
2358
|
+
findMany: (query, opts) => raw2.findMany(name, query, opts),
|
|
2359
|
+
updateMany: (where, set) => raw2.updateMany(
|
|
2360
|
+
name,
|
|
2361
|
+
where,
|
|
2362
|
+
set
|
|
2363
|
+
),
|
|
2364
|
+
deleteMany: (where) => raw2.deleteMany(name, where),
|
|
2365
|
+
count: (where) => raw2.count(name, where)
|
|
2187
2366
|
};
|
|
2188
2367
|
}
|
|
2189
2368
|
function makeTypedDB(schema, raw2) {
|
|
@@ -2505,6 +2684,16 @@ function getRegisteredControllers() {
|
|
|
2505
2684
|
function __resetRegisteredControllers() {
|
|
2506
2685
|
registry().length = 0;
|
|
2507
2686
|
}
|
|
2687
|
+
var APP_DEFAULT_AUTH = /* @__PURE__ */ Symbol.for("palbase.backend.appDefaultAuth");
|
|
2688
|
+
function appAuthSlot() {
|
|
2689
|
+
return globalThis;
|
|
2690
|
+
}
|
|
2691
|
+
function defineDefaultAuth(auth) {
|
|
2692
|
+
appAuthSlot()[APP_DEFAULT_AUTH] = auth;
|
|
2693
|
+
}
|
|
2694
|
+
function __resetDefaultAuth() {
|
|
2695
|
+
delete appAuthSlot()[APP_DEFAULT_AUTH];
|
|
2696
|
+
}
|
|
2508
2697
|
var RESERVED_FIRST_SEGMENT = "webhooks";
|
|
2509
2698
|
function assertNotReserved(path, subject) {
|
|
2510
2699
|
const [first] = path.split("/").filter(Boolean);
|
|
@@ -2546,6 +2735,14 @@ function Controller(basePath, options = {}) {
|
|
|
2546
2735
|
return ctor;
|
|
2547
2736
|
};
|
|
2548
2737
|
}
|
|
2738
|
+
function assertZeroArgConstructor(Ctrl, kind) {
|
|
2739
|
+
const arity = Ctrl.length ?? 0;
|
|
2740
|
+
if (arity === 0) return;
|
|
2741
|
+
const name = Ctrl.name ?? "<anonymous>";
|
|
2742
|
+
throw new Error(
|
|
2743
|
+
`${kind} ${name} declares a constructor with ${arity} parameter(s). A ${kind} is constructed by the runtime with a zero-argument constructor \u2014 there is no injector to supply them, so every parameter would arrive as undefined. Hold the dependency as a module-level singleton the ${kind} imports (\`const repo = makeRepo()\` beside the class), and construct the service directly in tests (e.g. \`new TodoService(fakeDatabase().db)\`).`
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2549
2746
|
|
|
2550
2747
|
// src/decorators/methods.ts
|
|
2551
2748
|
function makeMethodDecorator(method) {
|
|
@@ -2648,6 +2845,7 @@ function getWebhookConfig(ctor) {
|
|
|
2648
2845
|
if (entries.length === 0) {
|
|
2649
2846
|
throw new Error("@Webhook requires at least one @On handler");
|
|
2650
2847
|
}
|
|
2848
|
+
assertZeroArgConstructor(ctor, "webhook class");
|
|
2651
2849
|
const instance = new ctor();
|
|
2652
2850
|
const events = /* @__PURE__ */ Object.create(null);
|
|
2653
2851
|
for (const entry of entries) {
|
|
@@ -2666,8 +2864,8 @@ function getWebhookConfig(ctor) {
|
|
|
2666
2864
|
|
|
2667
2865
|
// src/decorators/hook.ts
|
|
2668
2866
|
var Deny = class extends Error {
|
|
2669
|
-
constructor(
|
|
2670
|
-
super(
|
|
2867
|
+
constructor(reason2) {
|
|
2868
|
+
super(reason2);
|
|
2671
2869
|
this.name = "Deny";
|
|
2672
2870
|
}
|
|
2673
2871
|
};
|
|
@@ -2709,6 +2907,7 @@ function getHookConfig(ctor) {
|
|
|
2709
2907
|
`${ctor.name ?? "a hook class"} carries no handler \u2014 a hook file must declare at least one @Hook (blocking) or @On (listener) method`
|
|
2710
2908
|
);
|
|
2711
2909
|
}
|
|
2910
|
+
assertZeroArgConstructor(ctor, "hook class");
|
|
2712
2911
|
const instance = new ctor();
|
|
2713
2912
|
return {
|
|
2714
2913
|
blocking: bind(instance, blockingEntries, "@Hook"),
|
|
@@ -2750,6 +2949,29 @@ function validateUploadConfigShape(c) {
|
|
|
2750
2949
|
}
|
|
2751
2950
|
}
|
|
2752
2951
|
|
|
2952
|
+
// src/decorators/sse.ts
|
|
2953
|
+
function Sse(subpath = "", config) {
|
|
2954
|
+
const { auth, rateLimit, ...sseConfig } = config;
|
|
2955
|
+
const options = {
|
|
2956
|
+
sseConfig,
|
|
2957
|
+
...auth !== void 0 ? { auth } : {},
|
|
2958
|
+
...rateLimit !== void 0 ? { rateLimit } : {}
|
|
2959
|
+
};
|
|
2960
|
+
return function(target, propertyKey) {
|
|
2961
|
+
recordRoute(target, String(propertyKey), "POST", subpath, options);
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
function SseOut() {
|
|
2965
|
+
return function(target, propertyKey, parameterIndex) {
|
|
2966
|
+
recordParam(target, String(propertyKey), { index: parameterIndex, kind: "sseOut" });
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
function Signal() {
|
|
2970
|
+
return function(target, propertyKey, parameterIndex) {
|
|
2971
|
+
recordParam(target, String(propertyKey), { index: parameterIndex, kind: "signal" });
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2753
2975
|
// src/decorators/params.ts
|
|
2754
2976
|
function makeParamDecorator(kind, extra) {
|
|
2755
2977
|
return function(target, propertyKey, parameterIndex) {
|
|
@@ -2878,6 +3100,29 @@ var Conflict = class extends NamedHttpError {
|
|
|
2878
3100
|
super(409, "conflict", "Conflict", message, code, data);
|
|
2879
3101
|
}
|
|
2880
3102
|
};
|
|
3103
|
+
var UniqueViolation = class extends Conflict {
|
|
3104
|
+
/**
|
|
3105
|
+
* Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
|
|
3106
|
+
* constructed it.
|
|
3107
|
+
*
|
|
3108
|
+
* Use this instead of `instanceof`. Measured on a live stack: a controller
|
|
3109
|
+
* bundle INLINES its own copy of `@palbase/backend`, and the engine that
|
|
3110
|
+
* raises this error is the runtime's copy. Two copies, two class identities,
|
|
3111
|
+
* and `e instanceof UniqueViolation` is false in the one place a caller
|
|
3112
|
+
* writes it — a check that reads as correct and silently never matches.
|
|
3113
|
+
*/
|
|
3114
|
+
static is(e) {
|
|
3115
|
+
return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
|
|
3116
|
+
}
|
|
3117
|
+
/** The unique constraint the statement violated, as Postgres named it.
|
|
3118
|
+
* `""` when the driver did not say which — see `engine/db.ts`. */
|
|
3119
|
+
constraint;
|
|
3120
|
+
constructor(constraint, message, code, data) {
|
|
3121
|
+
super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
|
|
3122
|
+
this.name = "UniqueViolation";
|
|
3123
|
+
this.constraint = constraint;
|
|
3124
|
+
}
|
|
3125
|
+
};
|
|
2881
3126
|
var TooManyRequests = class extends NamedHttpError {
|
|
2882
3127
|
constructor(data, message) {
|
|
2883
3128
|
super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
|
|
@@ -2905,6 +3150,11 @@ function getErrorRegistry() {
|
|
|
2905
3150
|
["forbidden", 403, "Forbidden"],
|
|
2906
3151
|
["not_found", 404, "NotFound"],
|
|
2907
3152
|
["conflict", 409, "Conflict"],
|
|
3153
|
+
// The engine's own 23505 conversion (engine/db.ts) throws this, and an
|
|
3154
|
+
// author may too. Registered like every other built-in so the spec
|
|
3155
|
+
// emitter resolves it — an unregistered code is SKIPPED silently there
|
|
3156
|
+
// (openapi/convert.ts) and would degrade to the client's `.other` case.
|
|
3157
|
+
["unique_violation", 409, "UniqueViolation"],
|
|
2908
3158
|
["too_many_requests", 429, "TooManyRequests"]
|
|
2909
3159
|
]) {
|
|
2910
3160
|
const dataSchema = BUILTIN_DATA_SCHEMAS[code];
|
|
@@ -3107,6 +3357,7 @@ function getJobConfig(ctor) {
|
|
|
3107
3357
|
if (retry > MAX_RETRY) {
|
|
3108
3358
|
throw new Error(`@Job \`retry\` exceeds the ceiling of ${MAX_RETRY}`);
|
|
3109
3359
|
}
|
|
3360
|
+
assertZeroArgConstructor(ctor, "job class");
|
|
3110
3361
|
const instance = new ctor();
|
|
3111
3362
|
if (typeof instance.run !== "function") {
|
|
3112
3363
|
throw new Error("@Job class must declare an async run() method");
|
|
@@ -3156,26 +3407,35 @@ var import_zod2 = require("zod");
|
|
|
3156
3407
|
Req,
|
|
3157
3408
|
RequestId,
|
|
3158
3409
|
Secrets,
|
|
3410
|
+
Signal,
|
|
3411
|
+
Sse,
|
|
3412
|
+
SseOut,
|
|
3159
3413
|
Storage,
|
|
3160
3414
|
TooManyRequests,
|
|
3161
3415
|
TraceId,
|
|
3162
3416
|
TxPlanError,
|
|
3163
3417
|
TxRefError,
|
|
3164
3418
|
Unauthorized,
|
|
3419
|
+
UniqueViolation,
|
|
3165
3420
|
Upload,
|
|
3166
3421
|
UploadedObject,
|
|
3167
3422
|
User,
|
|
3168
3423
|
Webhook,
|
|
3169
3424
|
__getRuntime,
|
|
3170
3425
|
__requestALS,
|
|
3426
|
+
__resetDefaultAuth,
|
|
3427
|
+
__resetLifecycleHooks,
|
|
3171
3428
|
__resetRegisteredControllers,
|
|
3429
|
+
__runStartHooks,
|
|
3172
3430
|
__runWithRuntime,
|
|
3173
3431
|
__setRuntime,
|
|
3432
|
+
assertZeroArgConstructor,
|
|
3174
3433
|
bigint,
|
|
3175
3434
|
boolean,
|
|
3176
3435
|
buildModuleClients,
|
|
3177
3436
|
dec,
|
|
3178
3437
|
defineChannels,
|
|
3438
|
+
defineDefaultAuth,
|
|
3179
3439
|
defineError,
|
|
3180
3440
|
defineMiddleware,
|
|
3181
3441
|
defineSchema,
|
|
@@ -3196,6 +3456,8 @@ var import_zod2 = require("zod");
|
|
|
3196
3456
|
makeTypedDB,
|
|
3197
3457
|
now,
|
|
3198
3458
|
numeric,
|
|
3459
|
+
onShutdown,
|
|
3460
|
+
onStart,
|
|
3199
3461
|
openai,
|
|
3200
3462
|
ownerOnly,
|
|
3201
3463
|
policy,
|