@palbase/backend 23.1.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 (73) hide show
  1. package/dist/bin/palbase-backend.cjs +389 -44
  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-OHALWEOG.js → chunk-7Z6MGMXQ.js} +57 -2
  6. package/dist/chunk-7Z6MGMXQ.js.map +1 -0
  7. package/dist/{chunk-RCLNBJCM.js → chunk-H3JAISUY.js} +136 -1
  8. package/dist/chunk-H3JAISUY.js.map +1 -0
  9. package/dist/{chunk-M5MCBWJI.js → chunk-NXDH6VQJ.js} +264 -28
  10. package/dist/chunk-NXDH6VQJ.js.map +1 -0
  11. package/dist/{chunk-NS5V43YQ.js → chunk-P2Q27SGP.js} +19 -3
  12. package/dist/chunk-P2Q27SGP.js.map +1 -0
  13. package/dist/{chunk-R3KN6RHD.js → chunk-T5IOSOE5.js} +7 -2
  14. package/dist/chunk-T5IOSOE5.js.map +1 -0
  15. package/dist/{chunk-PY7YJDCT.js → chunk-ZUGY7RGS.js} +32 -3
  16. package/dist/chunk-ZUGY7RGS.js.map +1 -0
  17. package/dist/db/index.cjs +48 -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-CVWXh6oG.d.ts → endpoint-0_DGBajf.d.ts} +100 -3
  23. package/dist/{endpoint-c9h5jriX.d.cts → endpoint-CcQ1a36a.d.cts} +100 -3
  24. package/dist/engine/index.cjs +378 -31
  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-CwAJ7HEe.d.ts → index-CJiJU9ux.d.ts} +125 -21
  30. package/dist/{index-By8Dle5U.d.cts → index-D-4-PNuQ.d.cts} +125 -21
  31. package/dist/{index-BZrJXnVh.d.ts → index-D17r-MKb.d.ts} +134 -9
  32. package/dist/{index-CxeQSfJP.d.cts → index-DRFxf07H.d.cts} +134 -9
  33. package/dist/index.cjs +166 -5
  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 +28 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/openapi/index.cjs +16 -1
  40. package/dist/openapi/index.cjs.map +1 -1
  41. package/dist/openapi/index.d.cts +6 -4
  42. package/dist/openapi/index.d.ts +6 -4
  43. package/dist/openapi/index.js +6 -7
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-CqPK2Qby.d.cts → registry-1X-skBNu.d.cts} +1 -1
  46. package/dist/{registry-B3niOVYp.d.ts → registry-CEod_5sz.d.ts} +1 -1
  47. package/dist/test/index.cjs +27 -0
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +1 -1
  50. package/dist/test/index.d.ts +1 -1
  51. package/dist/test/index.js +27 -0
  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 +2 -2
  61. package/template/AGENTS.md +121 -41
  62. package/template/controllers/notes.controller.ts +64 -0
  63. package/template/package.json +1 -1
  64. package/template/services/note.service.ts +74 -0
  65. package/template/tsconfig.json +11 -1
  66. package/dist/chunk-HQRJDARQ.js +0 -90
  67. package/dist/chunk-HQRJDARQ.js.map +0 -1
  68. package/dist/chunk-M5MCBWJI.js.map +0 -1
  69. package/dist/chunk-NS5V43YQ.js.map +0 -1
  70. package/dist/chunk-OHALWEOG.js.map +0 -1
  71. package/dist/chunk-PY7YJDCT.js.map +0 -1
  72. package/dist/chunk-R3KN6RHD.js.map +0 -1
  73. package/dist/chunk-RCLNBJCM.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -66,20 +66,26 @@ __export(src_exports, {
66
66
  TxPlanError: () => TxPlanError,
67
67
  TxRefError: () => TxRefError,
68
68
  Unauthorized: () => Unauthorized,
69
+ UniqueViolation: () => UniqueViolation,
69
70
  Upload: () => Upload,
70
71
  UploadedObject: () => UploadedObject,
71
72
  User: () => User,
72
73
  Webhook: () => Webhook,
73
74
  __getRuntime: () => __getRuntime,
74
75
  __requestALS: () => __requestALS,
76
+ __resetDefaultAuth: () => __resetDefaultAuth,
77
+ __resetLifecycleHooks: () => __resetLifecycleHooks,
75
78
  __resetRegisteredControllers: () => __resetRegisteredControllers,
79
+ __runStartHooks: () => __runStartHooks,
76
80
  __runWithRuntime: () => __runWithRuntime,
77
81
  __setRuntime: () => __setRuntime,
82
+ assertZeroArgConstructor: () => assertZeroArgConstructor,
78
83
  bigint: () => bigint,
79
84
  boolean: () => boolean,
80
85
  buildModuleClients: () => buildModuleClients,
81
86
  dec: () => dec,
82
87
  defineChannels: () => defineChannels,
88
+ defineDefaultAuth: () => defineDefaultAuth,
83
89
  defineError: () => defineError,
84
90
  defineMiddleware: () => defineMiddleware,
85
91
  defineSchema: () => defineSchema,
@@ -100,6 +106,8 @@ __export(src_exports, {
100
106
  makeTypedDB: () => makeTypedDB,
101
107
  now: () => now,
102
108
  numeric: () => numeric,
109
+ onShutdown: () => onShutdown,
110
+ onStart: () => onStart,
103
111
  openai: () => openai,
104
112
  ownerOnly: () => ownerOnly,
105
113
  policy: () => policy,
@@ -370,7 +378,7 @@ var TxPlanBuilder = class {
370
378
  `${name}.upsert()`
371
379
  );
372
380
  },
373
- insertMany: (rows) => {
381
+ insertMany: (rows, opts) => {
374
382
  if (rows.length === 0) {
375
383
  return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
376
384
  }
@@ -381,7 +389,23 @@ var TxPlanBuilder = class {
381
389
  }
382
390
  const encoded = rows.map((row) => encodeMap(row, false));
383
391
  assertUniformRows(encoded, name);
384
- 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
+ );
385
409
  },
386
410
  updateWhere: (where, set) => {
387
411
  const encodedWhere = encodeMap(where, false);
@@ -558,6 +582,52 @@ function __getRuntime() {
558
582
  }
559
583
  return runtime;
560
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
+ }
561
631
  function makeServiceProxy(key) {
562
632
  const handler = {
563
633
  get(_target, prop, receiver) {
@@ -585,6 +655,7 @@ function makeTablesAccessor(ops) {
585
655
  search: (params) => ops().search(name, params),
586
656
  similar: (id, params) => ops().similar(name, id, params),
587
657
  recommend: (params) => ops().recommend(name, params),
658
+ facets: (params) => ops().facets(name, params),
588
659
  supersede: (id, row) => ops().supersede(name, id, row)
589
660
  };
590
661
  }
@@ -603,9 +674,13 @@ function makeTypedSurface(raw2) {
603
674
  findById: (table, id) => raw2.findById(table, id),
604
675
  findMany: (table, query, opts) => raw2.findMany(table, query, opts),
605
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),
606
680
  search: (table, params) => raw2.search(table, params),
607
681
  similar: (table, id, params) => reco.similar(table, id, params),
608
682
  recommend: (table, params) => reco.recommend(table, params),
683
+ facets: (table, params) => reco.facets(table, params),
609
684
  supersede: (table, id, row) => raw2.supersede(table, id, row)
610
685
  };
611
686
  return Object.assign(ops, {
@@ -2189,6 +2264,28 @@ var ColumnBuilder = class _ColumnBuilder {
2189
2264
  this._def.unique = true;
2190
2265
  return new _ColumnBuilder(this._def.type, this._def);
2191
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
+ }
2192
2289
  };
2193
2290
  function uuid() {
2194
2291
  return new ColumnBuilder("uuid");
@@ -2258,7 +2355,14 @@ function makeTypedTable(name, raw2) {
2258
2355
  update: (id, data) => raw2.update(name, id, data),
2259
2356
  delete: (id) => raw2.delete(name, id),
2260
2357
  findById: (id) => raw2.findById(name, id),
2261
- findMany: (query, opts) => raw2.findMany(name, query, opts)
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)
2262
2366
  };
2263
2367
  }
2264
2368
  function makeTypedDB(schema, raw2) {
@@ -2580,6 +2684,16 @@ function getRegisteredControllers() {
2580
2684
  function __resetRegisteredControllers() {
2581
2685
  registry().length = 0;
2582
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
+ }
2583
2697
  var RESERVED_FIRST_SEGMENT = "webhooks";
2584
2698
  function assertNotReserved(path, subject) {
2585
2699
  const [first] = path.split("/").filter(Boolean);
@@ -2621,6 +2735,14 @@ function Controller(basePath, options = {}) {
2621
2735
  return ctor;
2622
2736
  };
2623
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
+ }
2624
2746
 
2625
2747
  // src/decorators/methods.ts
2626
2748
  function makeMethodDecorator(method) {
@@ -2723,6 +2845,7 @@ function getWebhookConfig(ctor) {
2723
2845
  if (entries.length === 0) {
2724
2846
  throw new Error("@Webhook requires at least one @On handler");
2725
2847
  }
2848
+ assertZeroArgConstructor(ctor, "webhook class");
2726
2849
  const instance = new ctor();
2727
2850
  const events = /* @__PURE__ */ Object.create(null);
2728
2851
  for (const entry of entries) {
@@ -2741,8 +2864,8 @@ function getWebhookConfig(ctor) {
2741
2864
 
2742
2865
  // src/decorators/hook.ts
2743
2866
  var Deny = class extends Error {
2744
- constructor(reason) {
2745
- super(reason);
2867
+ constructor(reason2) {
2868
+ super(reason2);
2746
2869
  this.name = "Deny";
2747
2870
  }
2748
2871
  };
@@ -2784,6 +2907,7 @@ function getHookConfig(ctor) {
2784
2907
  `${ctor.name ?? "a hook class"} carries no handler \u2014 a hook file must declare at least one @Hook (blocking) or @On (listener) method`
2785
2908
  );
2786
2909
  }
2910
+ assertZeroArgConstructor(ctor, "hook class");
2787
2911
  const instance = new ctor();
2788
2912
  return {
2789
2913
  blocking: bind(instance, blockingEntries, "@Hook"),
@@ -2976,6 +3100,29 @@ var Conflict = class extends NamedHttpError {
2976
3100
  super(409, "conflict", "Conflict", message, code, data);
2977
3101
  }
2978
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
+ };
2979
3126
  var TooManyRequests = class extends NamedHttpError {
2980
3127
  constructor(data, message) {
2981
3128
  super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
@@ -3003,6 +3150,11 @@ function getErrorRegistry() {
3003
3150
  ["forbidden", 403, "Forbidden"],
3004
3151
  ["not_found", 404, "NotFound"],
3005
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"],
3006
3158
  ["too_many_requests", 429, "TooManyRequests"]
3007
3159
  ]) {
3008
3160
  const dataSchema = BUILTIN_DATA_SCHEMAS[code];
@@ -3205,6 +3357,7 @@ function getJobConfig(ctor) {
3205
3357
  if (retry > MAX_RETRY) {
3206
3358
  throw new Error(`@Job \`retry\` exceeds the ceiling of ${MAX_RETRY}`);
3207
3359
  }
3360
+ assertZeroArgConstructor(ctor, "job class");
3208
3361
  const instance = new ctor();
3209
3362
  if (typeof instance.run !== "function") {
3210
3363
  throw new Error("@Job class must declare an async run() method");
@@ -3263,20 +3416,26 @@ var import_zod2 = require("zod");
3263
3416
  TxPlanError,
3264
3417
  TxRefError,
3265
3418
  Unauthorized,
3419
+ UniqueViolation,
3266
3420
  Upload,
3267
3421
  UploadedObject,
3268
3422
  User,
3269
3423
  Webhook,
3270
3424
  __getRuntime,
3271
3425
  __requestALS,
3426
+ __resetDefaultAuth,
3427
+ __resetLifecycleHooks,
3272
3428
  __resetRegisteredControllers,
3429
+ __runStartHooks,
3273
3430
  __runWithRuntime,
3274
3431
  __setRuntime,
3432
+ assertZeroArgConstructor,
3275
3433
  bigint,
3276
3434
  boolean,
3277
3435
  buildModuleClients,
3278
3436
  dec,
3279
3437
  defineChannels,
3438
+ defineDefaultAuth,
3280
3439
  defineError,
3281
3440
  defineMiddleware,
3282
3441
  defineSchema,
@@ -3297,6 +3456,8 @@ var import_zod2 = require("zod");
3297
3456
  makeTypedDB,
3298
3457
  now,
3299
3458
  numeric,
3459
+ onShutdown,
3460
+ onStart,
3300
3461
  openai,
3301
3462
  ownerOnly,
3302
3463
  policy,