@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.
Files changed (74) hide show
  1. package/dist/bin/palbase-backend.cjs +695 -61
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -5
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-FSGSB42K.js → chunk-7Z6MGMXQ.js} +64 -4
  6. package/dist/chunk-7Z6MGMXQ.js.map +1 -0
  7. package/dist/{chunk-OMRTHM4X.js → chunk-H3JAISUY.js} +136 -1
  8. package/dist/chunk-H3JAISUY.js.map +1 -0
  9. package/dist/{chunk-REZU6UKT.js → chunk-NXDH6VQJ.js} +549 -42
  10. package/dist/chunk-NXDH6VQJ.js.map +1 -0
  11. package/dist/{chunk-W5ODXPY3.js → chunk-P2Q27SGP.js} +32 -3
  12. package/dist/chunk-P2Q27SGP.js.map +1 -0
  13. package/dist/{chunk-ZC6Q2BRD.js → chunk-T5IOSOE5.js} +7 -2
  14. package/dist/chunk-T5IOSOE5.js.map +1 -0
  15. package/dist/{chunk-HAF67F2H.js → chunk-ZUGY7RGS.js} +86 -3
  16. package/dist/chunk-ZUGY7RGS.js.map +1 -0
  17. package/dist/db/index.cjs +115 -3
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +2 -2
  20. package/dist/db/index.d.ts +2 -2
  21. package/dist/db/index.js +2 -2
  22. package/dist/{endpoint-BavvbW4P.d.ts → endpoint-0_DGBajf.d.ts} +168 -9
  23. package/dist/{endpoint-i8TTCohk.d.cts → endpoint-CcQ1a36a.d.cts} +168 -9
  24. package/dist/engine/index.cjs +684 -48
  25. package/dist/engine/index.cjs.map +1 -1
  26. package/dist/engine/index.d.cts +4 -4
  27. package/dist/engine/index.d.ts +4 -4
  28. package/dist/engine/index.js +4 -4
  29. package/dist/{index-B3jmmItD.d.ts → index-CJiJU9ux.d.ts} +209 -36
  30. package/dist/{index-Bmvx1EvJ.d.cts → index-D-4-PNuQ.d.cts} +209 -36
  31. package/dist/{index-B7YBEG5w.d.ts → index-D17r-MKb.d.ts} +177 -7
  32. package/dist/{index-E7OscPJT.d.cts → index-DRFxf07H.d.cts} +177 -7
  33. package/dist/index.cjs +269 -7
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +54 -12
  36. package/dist/index.d.ts +54 -12
  37. package/dist/index.js +54 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/openapi/index.cjs +45 -5
  40. package/dist/openapi/index.cjs.map +1 -1
  41. package/dist/openapi/index.d.cts +9 -4
  42. package/dist/openapi/index.d.ts +9 -4
  43. package/dist/openapi/index.js +35 -11
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-C3H2uPeZ.d.cts → registry-1X-skBNu.d.cts} +101 -7
  46. package/dist/{registry-DY3d9l1k.d.ts → registry-CEod_5sz.d.ts} +101 -7
  47. package/dist/test/index.cjs +509 -9
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +35 -3
  50. package/dist/test/index.d.ts +35 -3
  51. package/dist/test/index.js +507 -8
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +4 -4
  54. package/docs/database.md +115 -11
  55. package/docs/getting-started.md +5 -4
  56. package/docs/llms-full.txt +385 -89
  57. package/docs/migrations.md +81 -59
  58. package/docs/schema.md +82 -2
  59. package/docs/services.md +98 -9
  60. package/package.json +3 -2
  61. package/stager/return_types.js +23 -0
  62. package/template/AGENTS.md +121 -41
  63. package/template/controllers/notes.controller.ts +64 -0
  64. package/template/package.json +1 -1
  65. package/template/services/note.service.ts +74 -0
  66. package/template/tsconfig.json +11 -1
  67. package/dist/chunk-FSGSB42K.js.map +0 -1
  68. package/dist/chunk-HAF67F2H.js.map +0 -1
  69. package/dist/chunk-OMRTHM4X.js.map +0 -1
  70. package/dist/chunk-REZU6UKT.js.map +0 -1
  71. package/dist/chunk-W5ODXPY3.js.map +0 -1
  72. package/dist/chunk-Y5HXVUMP.js +0 -90
  73. package/dist/chunk-Y5HXVUMP.js.map +0 -1
  74. 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
- insertMany: (rows) => {
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
- return this.push({ op: "insertMany", table: name, rows: encoded }, `${name}.insertMany()`);
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(reason) {
2670
- super(reason);
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,