@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/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
|
-
|
|
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
|
-
|
|
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) {
|