@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/db/index.cjs CHANGED
@@ -47,6 +47,9 @@ __export(db_exports, {
47
47
  module.exports = __toCommonJS(db_exports);
48
48
 
49
49
  // src/db/policy.ts
50
+ function quote(name) {
51
+ return `"${name.replace(/"/g, '""')}"`;
52
+ }
50
53
  var PolicyBuilder = class {
51
54
  _def;
52
55
  constructor(name) {
@@ -82,6 +85,41 @@ var PolicyBuilder = class {
82
85
  this._def.using = sqlExpr;
83
86
  return this;
84
87
  }
88
+ /**
89
+ * "Rows of THIS table whose owner the caller is a member of" — the membership
90
+ * pattern, written so it cannot recurse.
91
+ *
92
+ * THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
93
+ * other: `channels` is visible to members, so its policy reads
94
+ * `channel_members`; `channel_members` is visible to members, so its policy
95
+ * reads `channels`. Postgres refuses the pair at query time with `infinite
96
+ * recursion detected in policy for relation ...`, and the error names the
97
+ * relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
98
+ * is protected by `user_id = auth.uid()` and nothing else, and every other
99
+ * table subqueries INTO it. That shape was in the platform's own schema and
100
+ * written down nowhere; a customer recovered it by reading that schema.
101
+ *
102
+ * `(select auth.uid())` rather than a bare call: the scalar subquery is
103
+ * evaluated ONCE per statement instead of per row.
104
+ *
105
+ * @example
106
+ * // channels: visible to members. The membership table gets the simple one.
107
+ * policy("member_read").for("select").to("authenticated")
108
+ * .memberOf("channel_members", "channel_id")
109
+ * // → id IN (SELECT "channel_id" FROM "channel_members"
110
+ * // WHERE "user_id" = (select auth.uid()))
111
+ */
112
+ memberOf(membershipTable, foreignKey, options = {}) {
113
+ if (this._def.using !== null) {
114
+ throw new Error(
115
+ `policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`
116
+ );
117
+ }
118
+ const column = options.column ?? "id";
119
+ const userColumn = options.userColumn ?? "user_id";
120
+ this._def.using = `${quote(column)} IN (SELECT ${quote(foreignKey)} FROM ${quote(membershipTable)} WHERE ${quote(userColumn)} = (select auth.uid()))`;
121
+ return this;
122
+ }
85
123
  /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
86
124
  withCheck(sqlExpr) {
87
125
  this._def.withCheck = sqlExpr;
@@ -289,6 +327,21 @@ var ColumnBuilder = class _ColumnBuilder {
289
327
  this._def.defaultNow = true;
290
328
  return new _ColumnBuilder(this._def.type, this._def);
291
329
  }
330
+ /**
331
+ * The DATABASE assigns this column's value — a trigger, a rule, an identity.
332
+ *
333
+ * The column becomes optional on INSERT (the author has nothing to send) while
334
+ * the DDL stays free of a DEFAULT this schema would not honour. It is NOT
335
+ * `default()`: that declares a value the schema promises to write.
336
+ *
337
+ * Naming: deliberately not `generated()`. Postgres has GENERATED columns and
338
+ * they are a different thing; borrowing the word would send a reader — or a
339
+ * model writing a schema — to the wrong feature.
340
+ */
341
+ dbAssigned() {
342
+ this._def.dbAssigned = true;
343
+ return new _ColumnBuilder(this._def.type, this._def);
344
+ }
292
345
  /** Add a foreign key reference. */
293
346
  /**
294
347
  * Declares that this column used to be called `previous`.
@@ -371,6 +424,28 @@ var ColumnBuilder = class _ColumnBuilder {
371
424
  this._def.unique = true;
372
425
  return new _ColumnBuilder(this._def.type, this._def);
373
426
  }
427
+ /**
428
+ * Declare how this column's value is projected in and out of the process.
429
+ *
430
+ * The DDL does not move: `numeric` stays `numeric`, and the driver still hands
431
+ * back what Postgres sent. What changes is the type the row surface exposes —
432
+ * it becomes `Target`:
433
+ *
434
+ * amount: numeric().transform<number>({ fromDb: Number, toDb: String })
435
+ *
436
+ * `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
437
+ * arbitrary precision), and that is exactly why this exists: application code
438
+ * that does arithmetic on the column otherwise rewrites the same
439
+ * `Number(row.amount)` / `String(x)` pair in every controller that touches it,
440
+ * and each rewrite is a place the two directions can drift apart.
441
+ *
442
+ * A transform is a PROJECTION, never a constraint: it lives only in this
443
+ * process, so it can neither validate nor migrate what is stored.
444
+ */
445
+ transform(fns) {
446
+ this._def.transform = fns;
447
+ return new _ColumnBuilder(this._def.type, this._def);
448
+ }
374
449
  };
375
450
  function uuid() {
376
451
  return new ColumnBuilder("uuid");
@@ -668,7 +743,20 @@ var TxPlanBuilder = class {
668
743
  }
669
744
  return this.push({ op: "insert", table: name, values: encoded }, `${name}.insert()`);
670
745
  },
671
- insertMany: (rows) => {
746
+ upsert: (values, options) => {
747
+ const encoded = encodeMap(values, false);
748
+ if (Object.keys(encoded).length === 0) {
749
+ throw new TxPlanError(`${name}.upsert() needs at least one column`);
750
+ }
751
+ if (options.onConflict.length === 0) {
752
+ throw new TxPlanError(`${name}.upsert() needs at least one onConflict column`);
753
+ }
754
+ return this.push(
755
+ { op: "upsert", table: name, values: encoded, onConflict: options.onConflict },
756
+ `${name}.upsert()`
757
+ );
758
+ },
759
+ insertMany: (rows, opts) => {
672
760
  if (rows.length === 0) {
673
761
  return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
674
762
  }
@@ -679,7 +767,23 @@ var TxPlanBuilder = class {
679
767
  }
680
768
  const encoded = rows.map((row) => encodeMap(row, false));
681
769
  assertUniformRows(encoded, name);
682
- return this.push({ op: "insertMany", table: name, rows: encoded }, `${name}.insertMany()`);
770
+ if (opts !== void 0 && opts.onConflict.length === 0) {
771
+ throw new TxPlanError(
772
+ `${name}.insertMany() was given a conflict action with no onConflict columns. Postgres matches a collision on columns, so name them.`
773
+ );
774
+ }
775
+ return this.push(
776
+ {
777
+ op: "insertMany",
778
+ table: name,
779
+ rows: encoded,
780
+ // Omitted entirely when no options were given, so the op a plain
781
+ // insertMany produces is byte-identical to the one it produced
782
+ // before this option existed.
783
+ ...opts !== void 0 ? { onConflict: opts.onConflict, action: opts.action ?? "ignore" } : {}
784
+ },
785
+ `${name}.insertMany()`
786
+ );
683
787
  },
684
788
  updateWhere: (where, set) => {
685
789
  const encodedWhere = encodeMap(where, false);
@@ -841,10 +945,18 @@ function translateRejection(err, builder) {
841
945
  function makeTypedTable(name, raw2) {
842
946
  return {
843
947
  insert: (data) => raw2.insert(name, data),
948
+ upsert: (data, opts) => raw2.upsert(name, data, opts),
844
949
  update: (id, data) => raw2.update(name, id, data),
845
950
  delete: (id) => raw2.delete(name, id),
846
951
  findById: (id) => raw2.findById(name, id),
847
- findMany: (query) => raw2.findMany(name, query)
952
+ findMany: (query, opts) => raw2.findMany(name, query, opts),
953
+ updateMany: (where, set) => raw2.updateMany(
954
+ name,
955
+ where,
956
+ set
957
+ ),
958
+ deleteMany: (where) => raw2.deleteMany(name, where),
959
+ count: (where) => raw2.count(name, where)
848
960
  };
849
961
  }
850
962
  function makeTypedDB(schema, raw2) {