@palbase/backend 24.3.0 → 25.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 +101 -60
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +17 -13
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-EIXCY4SS.js → chunk-43A3KGWL.js} +80 -49
- package/dist/chunk-43A3KGWL.js.map +1 -0
- package/dist/{chunk-ERDL5VAE.js → chunk-5CMLOAEF.js} +2 -2
- package/dist/chunk-OEQBHE2Z.js +825 -0
- package/dist/chunk-OEQBHE2Z.js.map +1 -0
- package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
- package/dist/chunk-XJ2RSHEU.js.map +1 -0
- package/dist/{chunk-UWSYTUGM.js → chunk-ZQRWW37O.js} +44 -1
- package/dist/chunk-ZQRWW37O.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +29 -13
- package/dist/db/env.d.ts +29 -13
- package/dist/db/index.cjs +212 -111
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +11 -1
- package/dist/engine/index.cjs +87 -50
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +2 -2
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +3 -3
- package/dist/{index-DEneI8Mn.d.ts → index-BF1f0DfA.d.ts} +5 -2
- package/dist/{index-C-ALG22n.d.cts → index-CoaDN9dL.d.cts} +5 -2
- package/dist/{index-BTMYod_l.d.ts → index-Ct1iiB4N.d.ts} +203 -61
- package/dist/{index-BLAbr9ZH.d.cts → index-CwaWRhyc.d.cts} +203 -61
- package/dist/index.cjs +550 -297
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -20
- package/dist/index.d.ts +122 -20
- package/dist/index.js +164 -217
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +100 -36
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.js +59 -2
- package/dist/openapi/index.js.map +1 -1
- package/docs/README.md +64 -31
- package/docs/endpoints.md +25 -28
- package/docs/llms-full.txt +399 -153
- package/docs/schema.md +272 -91
- package/docs/services.md +39 -4
- package/package.json +1 -1
- package/template/AGENTS.md +119 -314
- package/template/CLAUDE.md +13 -0
- package/template/controllers/notes.controller.ts +6 -13
- package/template/db/public.ts +38 -0
- package/template/models/notes/create.ts +38 -0
- package/template/package.json +6 -3
- package/template/services/note.service.test.ts +45 -0
- package/template/services/note.service.ts +2 -2
- package/dist/chunk-7Z6MGMXQ.js.map +0 -1
- package/dist/chunk-D5CQES25.js +0 -556
- package/dist/chunk-D5CQES25.js.map +0 -1
- package/dist/chunk-EIXCY4SS.js.map +0 -1
- package/dist/chunk-UWSYTUGM.js.map +0 -1
- package/template/db/schema.ts +0 -35
- /package/dist/{chunk-ERDL5VAE.js.map → chunk-5CMLOAEF.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,12 @@ __export(src_exports, {
|
|
|
42
42
|
NotFound: () => NotFound,
|
|
43
43
|
Notifications: () => Notifications,
|
|
44
44
|
On: () => On,
|
|
45
|
+
OnAuthorize: () => OnAuthorize,
|
|
46
|
+
OnEmpty: () => OnEmpty,
|
|
47
|
+
OnFirst: () => OnFirst,
|
|
48
|
+
OnJoin: () => OnJoin,
|
|
49
|
+
OnLeave: () => OnLeave,
|
|
50
|
+
OnMessage: () => OnMessage,
|
|
45
51
|
OptionalUser: () => OptionalUser,
|
|
46
52
|
PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
|
|
47
53
|
PalError: () => PalError,
|
|
@@ -56,11 +62,13 @@ __export(src_exports, {
|
|
|
56
62
|
Realtime: () => Realtime,
|
|
57
63
|
Req: () => Req,
|
|
58
64
|
RequestId: () => RequestId,
|
|
65
|
+
Room: () => Room,
|
|
59
66
|
Secrets: () => Secrets,
|
|
60
67
|
Signal: () => Signal,
|
|
61
68
|
Sse: () => Sse,
|
|
62
69
|
SseOut: () => SseOut,
|
|
63
70
|
Storage: () => Storage,
|
|
71
|
+
TABLE_META: () => TABLE_META,
|
|
64
72
|
TooManyRequests: () => TooManyRequests,
|
|
65
73
|
TraceId: () => TraceId,
|
|
66
74
|
TxPlanError: () => TxPlanError,
|
|
@@ -89,6 +97,7 @@ __export(src_exports, {
|
|
|
89
97
|
defineError: () => defineError,
|
|
90
98
|
defineMiddleware: () => defineMiddleware,
|
|
91
99
|
defineSchema: () => defineSchema,
|
|
100
|
+
defineTable: () => defineTable,
|
|
92
101
|
enumType: () => enumType,
|
|
93
102
|
getErrorRegistry: () => getErrorRegistry,
|
|
94
103
|
getHookConfig: () => getHookConfig,
|
|
@@ -97,6 +106,7 @@ __export(src_exports, {
|
|
|
97
106
|
getRoutes: () => getRoutes,
|
|
98
107
|
getWebhookConfig: () => getWebhookConfig,
|
|
99
108
|
inc: () => inc,
|
|
109
|
+
installationRef: () => installationRef,
|
|
100
110
|
integer: () => integer,
|
|
101
111
|
isPalbaseExtension: () => isPalbaseExtension,
|
|
102
112
|
jsonb: () => jsonb,
|
|
@@ -109,6 +119,7 @@ __export(src_exports, {
|
|
|
109
119
|
onShutdown: () => onShutdown,
|
|
110
120
|
onStart: () => onStart,
|
|
111
121
|
openai: () => openai,
|
|
122
|
+
ownedByUser: () => ownedByUser,
|
|
112
123
|
ownerOnly: () => ownerOnly,
|
|
113
124
|
policy: () => policy,
|
|
114
125
|
publicChannel: () => publicChannel,
|
|
@@ -118,6 +129,7 @@ __export(src_exports, {
|
|
|
118
129
|
text: () => text,
|
|
119
130
|
timestamp: () => timestamp,
|
|
120
131
|
toSchemaJSON: () => toSchemaJSON,
|
|
132
|
+
userRef: () => userRef,
|
|
121
133
|
uuid: () => uuid,
|
|
122
134
|
vector: () => vector,
|
|
123
135
|
z: () => import_zod2.z
|
|
@@ -639,13 +651,13 @@ function makeServiceProxy(key) {
|
|
|
639
651
|
};
|
|
640
652
|
return new Proxy({}, handler);
|
|
641
653
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
654
|
+
function makeTableProxy(ops, prefix) {
|
|
655
|
+
return new Proxy(
|
|
644
656
|
{},
|
|
645
657
|
{
|
|
646
658
|
get(_t, prop) {
|
|
647
659
|
if (typeof prop !== "string") return void 0;
|
|
648
|
-
const name = prop
|
|
660
|
+
const name = `${prefix}${prop}`;
|
|
649
661
|
return {
|
|
650
662
|
insert: (data) => ops().insert(name, data),
|
|
651
663
|
update: (id, data) => ops().update(name, id, data),
|
|
@@ -662,7 +674,12 @@ function makeTablesAccessor(ops) {
|
|
|
662
674
|
}
|
|
663
675
|
}
|
|
664
676
|
);
|
|
665
|
-
|
|
677
|
+
}
|
|
678
|
+
function makeTablesAccessor(ops) {
|
|
679
|
+
return makeTableProxy(ops, "");
|
|
680
|
+
}
|
|
681
|
+
function makeSchemaAccessor(ops, schema) {
|
|
682
|
+
return { tables: makeTableProxy(ops, `${String(schema)}.`) };
|
|
666
683
|
}
|
|
667
684
|
var rawDatabase = makeServiceProxy("Database");
|
|
668
685
|
function makeTypedSurface(raw2) {
|
|
@@ -689,6 +706,7 @@ function makeTypedSurface(raw2) {
|
|
|
689
706
|
// as one on the request's, and each is bound to its own connection.
|
|
690
707
|
attempt: (fn) => raw2.attempt(fn),
|
|
691
708
|
tables: makeTablesAccessor(() => reco),
|
|
709
|
+
schema: (name) => makeSchemaAccessor(() => reco, name),
|
|
692
710
|
transaction(fn) {
|
|
693
711
|
const builder = new TxPlanBuilder();
|
|
694
712
|
return runTxPlan(raw2, makeTxTablesAccessor(builder), builder, fn);
|
|
@@ -1669,198 +1687,6 @@ function buildModuleClients(cfg) {
|
|
|
1669
1687
|
};
|
|
1670
1688
|
}
|
|
1671
1689
|
|
|
1672
|
-
// src/db/policy.ts
|
|
1673
|
-
function quote(name) {
|
|
1674
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
1675
|
-
}
|
|
1676
|
-
var PolicyBuilder = class {
|
|
1677
|
-
_def;
|
|
1678
|
-
constructor(name) {
|
|
1679
|
-
this._def = {
|
|
1680
|
-
name,
|
|
1681
|
-
command: "all",
|
|
1682
|
-
roles: ["authenticated"],
|
|
1683
|
-
using: null,
|
|
1684
|
-
withCheck: null,
|
|
1685
|
-
permissive: true
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
/** Restrict the policy to a single SQL command (default `"all"`). */
|
|
1689
|
-
for(command) {
|
|
1690
|
-
this._def.command = command;
|
|
1691
|
-
return this;
|
|
1692
|
-
}
|
|
1693
|
-
/**
|
|
1694
|
-
* Set the DB roles the policy applies to (the `TO` clause), replacing any
|
|
1695
|
-
* previously-set roles. Call with no arguments to target PUBLIC (all roles).
|
|
1696
|
-
*
|
|
1697
|
-
* @example
|
|
1698
|
-
* policy("p").to("authenticated")
|
|
1699
|
-
* policy("p").to("authenticated", "service_role")
|
|
1700
|
-
* policy("p").to() // PUBLIC
|
|
1701
|
-
*/
|
|
1702
|
-
to(...roles) {
|
|
1703
|
-
this._def.roles = roles;
|
|
1704
|
-
return this;
|
|
1705
|
-
}
|
|
1706
|
-
/** Set the `USING (...)` row-visibility expression (raw SQL). */
|
|
1707
|
-
using(sqlExpr) {
|
|
1708
|
-
this._def.using = sqlExpr;
|
|
1709
|
-
return this;
|
|
1710
|
-
}
|
|
1711
|
-
/**
|
|
1712
|
-
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
1713
|
-
* pattern, written so it cannot recurse.
|
|
1714
|
-
*
|
|
1715
|
-
* THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
|
|
1716
|
-
* other: `channels` is visible to members, so its policy reads
|
|
1717
|
-
* `channel_members`; `channel_members` is visible to members, so its policy
|
|
1718
|
-
* reads `channels`. Postgres refuses the pair at query time with `infinite
|
|
1719
|
-
* recursion detected in policy for relation ...`, and the error names the
|
|
1720
|
-
* relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
|
|
1721
|
-
* is protected by `user_id = auth.uid()` and nothing else, and every other
|
|
1722
|
-
* table subqueries INTO it. That shape was in the platform's own schema and
|
|
1723
|
-
* written down nowhere; a customer recovered it by reading that schema.
|
|
1724
|
-
*
|
|
1725
|
-
* `(select auth.uid())` rather than a bare call: the scalar subquery is
|
|
1726
|
-
* evaluated ONCE per statement instead of per row.
|
|
1727
|
-
*
|
|
1728
|
-
* @example
|
|
1729
|
-
* // channels: visible to members. The membership table gets the simple one.
|
|
1730
|
-
* policy("member_read").for("select").to("authenticated")
|
|
1731
|
-
* .memberOf("channel_members", "channel_id")
|
|
1732
|
-
* // → id IN (SELECT "channel_id" FROM "channel_members"
|
|
1733
|
-
* // WHERE "user_id" = (select auth.uid()))
|
|
1734
|
-
*/
|
|
1735
|
-
memberOf(membershipTable, foreignKey, options = {}) {
|
|
1736
|
-
if (this._def.using !== null) {
|
|
1737
|
-
throw new Error(
|
|
1738
|
-
`policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`
|
|
1739
|
-
);
|
|
1740
|
-
}
|
|
1741
|
-
const column = options.column ?? "id";
|
|
1742
|
-
const userColumn = options.userColumn ?? "user_id";
|
|
1743
|
-
this._def.using = `${quote(column)} IN (SELECT ${quote(foreignKey)} FROM ${quote(membershipTable)} WHERE ${quote(userColumn)} = (select auth.uid()))`;
|
|
1744
|
-
return this;
|
|
1745
|
-
}
|
|
1746
|
-
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
1747
|
-
withCheck(sqlExpr) {
|
|
1748
|
-
this._def.withCheck = sqlExpr;
|
|
1749
|
-
return this;
|
|
1750
|
-
}
|
|
1751
|
-
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
1752
|
-
* `"restrictive"` (AND-combined). */
|
|
1753
|
-
as(mode) {
|
|
1754
|
-
this._def.permissive = mode === "permissive";
|
|
1755
|
-
return this;
|
|
1756
|
-
}
|
|
1757
|
-
};
|
|
1758
|
-
function policy(name) {
|
|
1759
|
-
return new PolicyBuilder(name);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
// src/db/schema.ts
|
|
1763
|
-
function toPolicyDef(p) {
|
|
1764
|
-
return p instanceof PolicyBuilder ? p._def : p;
|
|
1765
|
-
}
|
|
1766
|
-
function defineSchema(input) {
|
|
1767
|
-
const tables = {};
|
|
1768
|
-
for (const name of Object.keys(input.tables)) {
|
|
1769
|
-
const table = input.tables[name];
|
|
1770
|
-
if (table === void 0) continue;
|
|
1771
|
-
const policies = (table.policies ?? []).map(toPolicyDef);
|
|
1772
|
-
const rls = policies.length > 0 || table.rls !== false;
|
|
1773
|
-
const tableDef = {
|
|
1774
|
-
name,
|
|
1775
|
-
columns: table.columns,
|
|
1776
|
-
rls,
|
|
1777
|
-
policies
|
|
1778
|
-
};
|
|
1779
|
-
if (table.primaryKey !== void 0) tableDef.primaryKey = table.primaryKey;
|
|
1780
|
-
if (table.unique !== void 0) tableDef.unique = table.unique;
|
|
1781
|
-
if (table.raw !== void 0 && table.raw.length > 0) tableDef.raw = table.raw.slice();
|
|
1782
|
-
if (table.checks !== void 0 && table.checks.length > 0) tableDef.checks = table.checks.slice();
|
|
1783
|
-
if (table.indexes !== void 0 && table.indexes.length > 0) tableDef.indexes = table.indexes.slice();
|
|
1784
|
-
if (table.search !== void 0) {
|
|
1785
|
-
const s = table.search;
|
|
1786
|
-
if (s.from !== void 0) {
|
|
1787
|
-
if (s.vector !== void 0) {
|
|
1788
|
-
throw new Error(
|
|
1789
|
-
`table ${String(name)}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
|
|
1790
|
-
);
|
|
1791
|
-
}
|
|
1792
|
-
if (s.from.length === 0) {
|
|
1793
|
-
throw new Error(`table ${String(name)}: search.from bo\u015F olamaz`);
|
|
1794
|
-
}
|
|
1795
|
-
if (s.model === void 0) {
|
|
1796
|
-
throw new Error(
|
|
1797
|
-
`table ${String(name)}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
|
|
1798
|
-
);
|
|
1799
|
-
}
|
|
1800
|
-
if (Array.isArray(s.text) && s.text.length === 0) {
|
|
1801
|
-
throw new Error(`table ${String(name)}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
|
|
1802
|
-
}
|
|
1803
|
-
} else {
|
|
1804
|
-
if (typeof s.text === "boolean") {
|
|
1805
|
-
throw new Error(`table ${String(name)}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
|
|
1806
|
-
}
|
|
1807
|
-
if (s.text === void 0 && s.vector === void 0) {
|
|
1808
|
-
throw new Error(
|
|
1809
|
-
`table ${String(name)}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
|
|
1810
|
-
);
|
|
1811
|
-
}
|
|
1812
|
-
if (s.text !== void 0 && s.text.length === 0) {
|
|
1813
|
-
throw new Error(`table ${String(name)}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
|
|
1814
|
-
}
|
|
1815
|
-
const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
|
|
1816
|
-
for (const leg of legs) {
|
|
1817
|
-
if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
|
|
1818
|
-
throw new Error(`table ${String(name)}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
|
|
1819
|
-
}
|
|
1820
|
-
if (leg.model === void 0 && leg.from !== void 0) {
|
|
1821
|
-
throw new Error(`table ${String(name)}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
tableDef.search = s;
|
|
1826
|
-
}
|
|
1827
|
-
if (table.memory !== void 0) {
|
|
1828
|
-
if (table.memory.from.length === 0) {
|
|
1829
|
-
throw new Error(`table ${String(name)}: memory.from bo\u015F olamaz`);
|
|
1830
|
-
}
|
|
1831
|
-
tableDef.memory = table.memory;
|
|
1832
|
-
}
|
|
1833
|
-
tables[name] = tableDef;
|
|
1834
|
-
}
|
|
1835
|
-
for (const [name, def] of Object.entries(tables)) {
|
|
1836
|
-
const m = def.memory;
|
|
1837
|
-
if (m === void 0) continue;
|
|
1838
|
-
const target = Object.values(tables).find((t) => t.name === m.into);
|
|
1839
|
-
if (target === void 0) {
|
|
1840
|
-
throw new Error(`table ${name}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
|
|
1841
|
-
}
|
|
1842
|
-
const subject = m.subject ?? "owner";
|
|
1843
|
-
if (!(subject in def.columns)) {
|
|
1844
|
-
throw new Error(`table ${name}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
|
|
1845
|
-
}
|
|
1846
|
-
if (!(subject in target.columns)) {
|
|
1847
|
-
throw new Error(`table ${name}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
|
|
1848
|
-
}
|
|
1849
|
-
const factCol = target.columns["fact"];
|
|
1850
|
-
const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
|
|
1851
|
-
if (factDef === void 0 || factDef.type !== "text") {
|
|
1852
|
-
throw new Error(`table ${name}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
|
|
1853
|
-
}
|
|
1854
|
-
for (const c of m.from) {
|
|
1855
|
-
if (!(c in def.columns)) {
|
|
1856
|
-
throw new Error(`table ${name}: memory.from kolonu "${c}" kaynak tabloda yok`);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
const extensions = [...new Set(input.extensions ?? [])];
|
|
1861
|
-
return { tables, extensions };
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
1690
|
// src/db/schema-json.ts
|
|
1865
1691
|
function defOf(column) {
|
|
1866
1692
|
return "_def" in column ? column._def : column;
|
|
@@ -1969,13 +1795,14 @@ function memoryToJSON(m) {
|
|
|
1969
1795
|
extract: { provider: m.extract.provider, model: m.extract.model }
|
|
1970
1796
|
};
|
|
1971
1797
|
}
|
|
1972
|
-
function tableToJSON(table) {
|
|
1798
|
+
function tableToJSON(table, schemaName) {
|
|
1973
1799
|
const columns = {};
|
|
1974
1800
|
for (const [name, column] of Object.entries(table.columns)) {
|
|
1975
1801
|
columns[name] = columnToJSON(column);
|
|
1976
1802
|
}
|
|
1977
1803
|
const out = {
|
|
1978
1804
|
name: table.name,
|
|
1805
|
+
schema: schemaName,
|
|
1979
1806
|
columns,
|
|
1980
1807
|
// Read, not re-derived. `defineSchema` already resolves the fail-closed
|
|
1981
1808
|
// default (RLS on unless the author wrote `rls: false`, and forced on by any
|
|
@@ -2014,13 +1841,296 @@ function tableToJSON(table) {
|
|
|
2014
1841
|
}
|
|
2015
1842
|
return out;
|
|
2016
1843
|
}
|
|
2017
|
-
function
|
|
1844
|
+
function qualifiedTableKey(schemaName, tableName) {
|
|
1845
|
+
return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
|
|
1846
|
+
}
|
|
1847
|
+
function toSchemaJSON(schemas) {
|
|
2018
1848
|
const tables = {};
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
1849
|
+
const extensions = [];
|
|
1850
|
+
const meta = [];
|
|
1851
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1852
|
+
for (const schema of schemas) {
|
|
1853
|
+
if (seen.has(schema.name)) {
|
|
1854
|
+
throw new Error(`two schemas declare the name "${schema.name}" \u2014 schema names must be unique`);
|
|
1855
|
+
}
|
|
1856
|
+
seen.add(schema.name);
|
|
1857
|
+
for (const table of Object.values(schema.tables)) {
|
|
1858
|
+
const json = tableToJSON(table, schema.name);
|
|
1859
|
+
tables[qualifiedTableKey(schema.name, json.name)] = json;
|
|
1860
|
+
}
|
|
1861
|
+
extensions.push(...schema.extensions ?? []);
|
|
1862
|
+
meta.push({ name: schema.name, exposed: schema.exposed });
|
|
2022
1863
|
}
|
|
2023
|
-
return { tables, extensions: [...new Set(
|
|
1864
|
+
return { tables, extensions: [...new Set(extensions)], schemas: meta };
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// src/db/policy.ts
|
|
1868
|
+
function quote(name) {
|
|
1869
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
1870
|
+
}
|
|
1871
|
+
var PolicyBuilder = class {
|
|
1872
|
+
_def;
|
|
1873
|
+
constructor(name) {
|
|
1874
|
+
this._def = {
|
|
1875
|
+
name,
|
|
1876
|
+
command: "all",
|
|
1877
|
+
roles: ["authenticated"],
|
|
1878
|
+
using: null,
|
|
1879
|
+
withCheck: null,
|
|
1880
|
+
permissive: true
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
/** Restrict the policy to a single SQL command (default `"all"`). */
|
|
1884
|
+
for(command) {
|
|
1885
|
+
this._def.command = command;
|
|
1886
|
+
return this;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Set the DB roles the policy applies to (the `TO` clause), replacing any
|
|
1890
|
+
* previously-set roles. Call with no arguments to target PUBLIC (all roles).
|
|
1891
|
+
*
|
|
1892
|
+
* @example
|
|
1893
|
+
* policy("p").to("authenticated")
|
|
1894
|
+
* policy("p").to("authenticated", "service_role")
|
|
1895
|
+
* policy("p").to() // PUBLIC
|
|
1896
|
+
*/
|
|
1897
|
+
to(...roles) {
|
|
1898
|
+
this._def.roles = roles;
|
|
1899
|
+
return this;
|
|
1900
|
+
}
|
|
1901
|
+
/** Set the `USING (...)` row-visibility expression (raw SQL). */
|
|
1902
|
+
using(sqlExpr) {
|
|
1903
|
+
this._def.using = sqlExpr;
|
|
1904
|
+
return this;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
1908
|
+
* pattern, written so it cannot recurse.
|
|
1909
|
+
*
|
|
1910
|
+
* THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
|
|
1911
|
+
* other: `channels` is visible to members, so its policy reads
|
|
1912
|
+
* `channel_members`; `channel_members` is visible to members, so its policy
|
|
1913
|
+
* reads `channels`. Postgres refuses the pair at query time with `infinite
|
|
1914
|
+
* recursion detected in policy for relation ...`, and the error names the
|
|
1915
|
+
* relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
|
|
1916
|
+
* is protected by `user_id = auth.uid()` and nothing else, and every other
|
|
1917
|
+
* table subqueries INTO it. That shape was in the platform's own schema and
|
|
1918
|
+
* written down nowhere; a customer recovered it by reading that schema.
|
|
1919
|
+
*
|
|
1920
|
+
* `(select auth.uid())` rather than a bare call: the scalar subquery is
|
|
1921
|
+
* evaluated ONCE per statement instead of per row.
|
|
1922
|
+
*
|
|
1923
|
+
* @example
|
|
1924
|
+
* // channels: visible to members. The membership table gets the simple one.
|
|
1925
|
+
* policy("member_read").for("select").to("authenticated")
|
|
1926
|
+
* .memberOf("channel_members", "channel_id")
|
|
1927
|
+
* // → id IN (SELECT "channel_id" FROM "channel_members"
|
|
1928
|
+
* // WHERE "user_id" = (select auth.uid()))
|
|
1929
|
+
*/
|
|
1930
|
+
memberOf(membershipTable, foreignKey, options = {}) {
|
|
1931
|
+
if (this._def.using !== null) {
|
|
1932
|
+
throw new Error(
|
|
1933
|
+
`policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
const column = options.column ?? "id";
|
|
1937
|
+
const userColumn = options.userColumn ?? "user_id";
|
|
1938
|
+
this._def.using = `${quote(column)} IN (SELECT ${quote(foreignKey)} FROM ${quote(membershipTable)} WHERE ${quote(userColumn)} = (select auth.uid()))`;
|
|
1939
|
+
return this;
|
|
1940
|
+
}
|
|
1941
|
+
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
1942
|
+
withCheck(sqlExpr) {
|
|
1943
|
+
this._def.withCheck = sqlExpr;
|
|
1944
|
+
return this;
|
|
1945
|
+
}
|
|
1946
|
+
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
1947
|
+
* `"restrictive"` (AND-combined). */
|
|
1948
|
+
as(mode) {
|
|
1949
|
+
this._def.permissive = mode === "permissive";
|
|
1950
|
+
return this;
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
function policy(name) {
|
|
1954
|
+
return new PolicyBuilder(name);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/db/schema.ts
|
|
1958
|
+
function toPolicyDef(p) {
|
|
1959
|
+
return p instanceof PolicyBuilder ? p._def : p;
|
|
1960
|
+
}
|
|
1961
|
+
var TABLE_META = /* @__PURE__ */ Symbol.for("palbase.table.meta");
|
|
1962
|
+
function defineTable(name, input) {
|
|
1963
|
+
if (name.trim() === "") {
|
|
1964
|
+
throw new Error("defineTable(name, \u2026): name must not be empty");
|
|
1965
|
+
}
|
|
1966
|
+
const policies = (input.policies ?? []).map(toPolicyDef);
|
|
1967
|
+
const rls = policies.length > 0 || input.rls !== false;
|
|
1968
|
+
const def = {
|
|
1969
|
+
name,
|
|
1970
|
+
columns: input.columns,
|
|
1971
|
+
rls,
|
|
1972
|
+
policies
|
|
1973
|
+
};
|
|
1974
|
+
if (input.primaryKey !== void 0) def.primaryKey = input.primaryKey;
|
|
1975
|
+
if (input.unique !== void 0) def.unique = input.unique;
|
|
1976
|
+
if (input.raw !== void 0 && input.raw.length > 0) def.raw = input.raw.slice();
|
|
1977
|
+
if (input.checks !== void 0 && input.checks.length > 0) def.checks = input.checks.slice();
|
|
1978
|
+
if (input.indexes !== void 0 && input.indexes.length > 0) def.indexes = input.indexes.slice();
|
|
1979
|
+
if (input.search !== void 0) {
|
|
1980
|
+
const s = input.search;
|
|
1981
|
+
if (s.from !== void 0) {
|
|
1982
|
+
if (s.vector !== void 0) {
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`table ${name}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
if (s.from.length === 0) {
|
|
1988
|
+
throw new Error(`table ${name}: search.from bo\u015F olamaz`);
|
|
1989
|
+
}
|
|
1990
|
+
if (s.model === void 0) {
|
|
1991
|
+
throw new Error(
|
|
1992
|
+
`table ${name}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
if (Array.isArray(s.text) && s.text.length === 0) {
|
|
1996
|
+
throw new Error(`table ${name}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
|
|
1997
|
+
}
|
|
1998
|
+
} else {
|
|
1999
|
+
if (typeof s.text === "boolean") {
|
|
2000
|
+
throw new Error(`table ${name}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
|
|
2001
|
+
}
|
|
2002
|
+
if (s.text === void 0 && s.vector === void 0) {
|
|
2003
|
+
throw new Error(
|
|
2004
|
+
`table ${name}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
if (s.text !== void 0 && s.text.length === 0) {
|
|
2008
|
+
throw new Error(`table ${name}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
|
|
2009
|
+
}
|
|
2010
|
+
const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
|
|
2011
|
+
for (const leg of legs) {
|
|
2012
|
+
if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
|
|
2013
|
+
throw new Error(`table ${name}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
|
|
2014
|
+
}
|
|
2015
|
+
if (leg.model === void 0 && leg.from !== void 0) {
|
|
2016
|
+
throw new Error(`table ${name}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
def.search = s;
|
|
2021
|
+
}
|
|
2022
|
+
if (input.memory !== void 0) {
|
|
2023
|
+
if (input.memory.from.length === 0) {
|
|
2024
|
+
throw new Error(`table ${name}: memory.from bo\u015F olamaz`);
|
|
2025
|
+
}
|
|
2026
|
+
def.memory = input.memory;
|
|
2027
|
+
}
|
|
2028
|
+
for (const b of Object.values(input.columns)) {
|
|
2029
|
+
b._def.ownerTable = def;
|
|
2030
|
+
}
|
|
2031
|
+
const handle = { ...input.columns };
|
|
2032
|
+
Object.defineProperty(handle, TABLE_META, { value: def, enumerable: false });
|
|
2033
|
+
return handle;
|
|
2034
|
+
}
|
|
2035
|
+
var declaringSchemaOf = /* @__PURE__ */ new WeakMap();
|
|
2036
|
+
function resolveReferences(tables, schemaName) {
|
|
2037
|
+
for (const table of Object.values(tables)) {
|
|
2038
|
+
for (const [colName, builder] of Object.entries(table.columns)) {
|
|
2039
|
+
const def = builder._def;
|
|
2040
|
+
if (def.selfRefColumn !== void 0) {
|
|
2041
|
+
if (!(def.selfRefColumn in table.columns)) {
|
|
2042
|
+
throw new Error(
|
|
2043
|
+
`table "${table.name}" column "${colName}": selfReferences("${def.selfRefColumn}") \u2014 no such column on this table`
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
def.references = {
|
|
2047
|
+
table: qualifiedTableKey(schemaName, table.name),
|
|
2048
|
+
column: def.selfRefColumn
|
|
2049
|
+
};
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
if (def.referencesThunk === void 0) continue;
|
|
2053
|
+
const target = def.referencesThunk();
|
|
2054
|
+
const owner = target?._def?.ownerTable;
|
|
2055
|
+
if (owner === void 0) {
|
|
2056
|
+
throw new Error(
|
|
2057
|
+
`table "${table.name}" column "${colName}": references(() => \u2026) must point at a column of a table declared with defineTable(...)`
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
const targetColumn = Object.entries(owner.columns).find(
|
|
2061
|
+
([, b]) => b._def === target._def
|
|
2062
|
+
)?.[0];
|
|
2063
|
+
if (targetColumn === void 0) {
|
|
2064
|
+
throw new Error(
|
|
2065
|
+
`table "${table.name}" column "${colName}": the referenced column was not found on table "${owner.name}"`
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
const targetSchema = declaringSchemaOf.get(owner);
|
|
2069
|
+
if (targetSchema === void 0) {
|
|
2070
|
+
throw new Error(
|
|
2071
|
+
`table "${table.name}" column "${colName}": references(() => \u2026) points at table "${owner.name}", which no defineSchema(...) has claimed yet \u2014 a foreign key cannot be written without its target's schema. Declare that table's schema first (one file per schema: db/public.ts, db/billing.ts \u2026, imported by this one).`
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
def.references = {
|
|
2075
|
+
table: qualifiedTableKey(targetSchema, owner.name),
|
|
2076
|
+
column: targetColumn
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
function defineSchema(name, input) {
|
|
2082
|
+
if (typeof name !== "string") {
|
|
2083
|
+
throw new Error(
|
|
2084
|
+
`defineSchema(...) takes the schema NAME first: defineSchema("public", { tables: [todos, lists] }). The one-argument form defineSchema({ tables: { \u2026 } }) is gone \u2014 a schema that does not say its own name cannot be told apart from another schema's table of the same name, which is what a cross-schema foreign key has to do. Migrate db/public.ts (one file per schema: db/public.ts, db/billing.ts \u2026): name the schema, and lift each table key onto the table itself with defineTable("todos", { columns: { \u2026 } }), then list the tables in the array.`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
if (name.trim() === "") {
|
|
2088
|
+
throw new Error("defineSchema(name, \u2026): name must not be empty");
|
|
2089
|
+
}
|
|
2090
|
+
const tables = {};
|
|
2091
|
+
for (const handle of input.tables) {
|
|
2092
|
+
const table = handle[TABLE_META];
|
|
2093
|
+
if (tables[table.name] !== void 0) {
|
|
2094
|
+
throw new Error(
|
|
2095
|
+
`defineSchema("${name}"): two tables declare the name "${table.name}" \u2014 table names must be unique within a schema`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
tables[table.name] = table;
|
|
2099
|
+
declaringSchemaOf.set(table, name);
|
|
2100
|
+
}
|
|
2101
|
+
resolveReferences(tables, name);
|
|
2102
|
+
for (const [name2, def] of Object.entries(tables)) {
|
|
2103
|
+
const m = def.memory;
|
|
2104
|
+
if (m === void 0) continue;
|
|
2105
|
+
const target = Object.values(tables).find((t) => t.name === m.into);
|
|
2106
|
+
if (target === void 0) {
|
|
2107
|
+
throw new Error(`table ${name2}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
|
|
2108
|
+
}
|
|
2109
|
+
const subject = m.subject ?? "owner";
|
|
2110
|
+
if (!(subject in def.columns)) {
|
|
2111
|
+
throw new Error(`table ${name2}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
|
|
2112
|
+
}
|
|
2113
|
+
if (!(subject in target.columns)) {
|
|
2114
|
+
throw new Error(`table ${name2}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
|
|
2115
|
+
}
|
|
2116
|
+
const factCol = target.columns["fact"];
|
|
2117
|
+
const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
|
|
2118
|
+
if (factDef === void 0 || factDef.type !== "text") {
|
|
2119
|
+
throw new Error(`table ${name2}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
|
|
2120
|
+
}
|
|
2121
|
+
for (const c of m.from) {
|
|
2122
|
+
if (!(c in def.columns)) {
|
|
2123
|
+
throw new Error(`table ${name2}: memory.from kolonu "${c}" kaynak tabloda yok`);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
const extensions = [...new Set(input.extensions ?? [])];
|
|
2128
|
+
return {
|
|
2129
|
+
name,
|
|
2130
|
+
tables,
|
|
2131
|
+
extensions,
|
|
2132
|
+
exposed: input.exposed ?? name === "public"
|
|
2133
|
+
};
|
|
2024
2134
|
}
|
|
2025
2135
|
|
|
2026
2136
|
// src/channels.ts
|
|
@@ -2222,59 +2332,46 @@ var ColumnBuilder = class _ColumnBuilder {
|
|
|
2222
2332
|
ignored: true
|
|
2223
2333
|
});
|
|
2224
2334
|
}
|
|
2225
|
-
references(table, column) {
|
|
2226
|
-
refuseOnVector(this._def, "references");
|
|
2227
|
-
this._def.references = { table, column };
|
|
2228
|
-
return new _ColumnBuilder(this._def.type, this._def);
|
|
2229
|
-
}
|
|
2230
2335
|
/**
|
|
2231
|
-
*
|
|
2232
|
-
* (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
|
|
2233
|
-
* database cascade/integrity instead of app-layer-only. Sugar for
|
|
2234
|
-
* `.references("auth.users", "id")`.
|
|
2336
|
+
* Foreign key onto another table's column.
|
|
2235
2337
|
*
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
*
|
|
2239
|
-
* the
|
|
2338
|
+
* The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
|
|
2339
|
+
* the second table does not exist yet when the first is built; a direct
|
|
2340
|
+
* reference makes TypeScript chase its own tail (TS7022 — measured, and making
|
|
2341
|
+
* the return type independent of the target does NOT help). The thunk is
|
|
2342
|
+
* invoked in `defineSchema`, where every binding exists and every table
|
|
2343
|
+
* already knows its name.
|
|
2240
2344
|
*
|
|
2241
|
-
*
|
|
2242
|
-
*
|
|
2243
|
-
*
|
|
2244
|
-
* `text().notNull().referencesAuthUser("cascade")`, or
|
|
2245
|
-
* `text().nullable().referencesAuthUser("set null")`. The server
|
|
2246
|
-
* (validateAuthUserFK) enforces this — and the remaining rules the type can't
|
|
2247
|
-
* express (referencing column is text, `set null` needs a nullable column) —
|
|
2248
|
-
* as the real boundary; this signature is the compile-time DX mirror.
|
|
2345
|
+
* In a cycle, ONE side needs an explicit return type:
|
|
2346
|
+
* `references((): AnyColumn => y.id)`. One side is enough — measured.
|
|
2347
|
+
* For a self-reference use `selfReferences(column)`: no thunk, no annotation.
|
|
2249
2348
|
*/
|
|
2250
|
-
|
|
2251
|
-
refuseOnVector(this._def, "
|
|
2252
|
-
|
|
2253
|
-
|
|
2349
|
+
references(target, opts) {
|
|
2350
|
+
refuseOnVector(this._def, "references");
|
|
2351
|
+
if (typeof target !== "function") {
|
|
2352
|
+
throw new Error(
|
|
2353
|
+
`references(...) takes a callback: write references(() => otherTable.column). The two-string form references("table", "column") is gone \u2014 a string cannot be type-checked and cannot point at a table that does not exist yet.`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
this._def.referencesThunk = target;
|
|
2357
|
+
if (opts?.as !== void 0) this._def.refAs = opts.as;
|
|
2358
|
+
if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
|
|
2254
2359
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2255
2360
|
}
|
|
2256
2361
|
/**
|
|
2257
|
-
*
|
|
2258
|
-
*
|
|
2259
|
-
* root (`ins_...`). Sugar for `.references("auth.installations", "id")`.
|
|
2260
|
-
*
|
|
2261
|
-
* An installation is an APP INSTALL, not a user: this FK is NOT user ownership.
|
|
2262
|
-
* A user-owned row STILL needs its own `.referencesAuthUser(...)` FK so account
|
|
2263
|
-
* erasure removes it — an installation reference alone does not tie a row to a
|
|
2264
|
-
* user's deletion. Use this only for install-scoped state (device prefs, push
|
|
2265
|
-
* routing, …), alongside a separate auth-user FK where the row is user-owned.
|
|
2362
|
+
* Foreign key onto THIS table (`parent_id → id`) — category trees, comment
|
|
2363
|
+
* replies, org charts.
|
|
2266
2364
|
*
|
|
2267
|
-
*
|
|
2268
|
-
*
|
|
2269
|
-
*
|
|
2270
|
-
*
|
|
2271
|
-
* blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
|
|
2272
|
-
* boundary; this signature is the compile-time DX mirror.
|
|
2365
|
+
* No thunk and no type annotation: the target table is the one being declared,
|
|
2366
|
+
* so there is nothing to defer and nothing for TypeScript to chase in a circle.
|
|
2367
|
+
* Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
|
|
2368
|
+
* always goes through a callback; measured, we do not need one.
|
|
2273
2369
|
*/
|
|
2274
|
-
|
|
2275
|
-
refuseOnVector(this._def, "
|
|
2276
|
-
this._def.
|
|
2277
|
-
this._def.
|
|
2370
|
+
selfReferences(column, opts) {
|
|
2371
|
+
refuseOnVector(this._def, "selfReferences");
|
|
2372
|
+
this._def.selfRefColumn = column;
|
|
2373
|
+
if (opts?.as !== void 0) this._def.refAs = opts.as;
|
|
2374
|
+
if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
|
|
2278
2375
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2279
2376
|
}
|
|
2280
2377
|
/** Set the ON DELETE action for a foreign key reference. */
|
|
@@ -2349,6 +2446,28 @@ function vector(dimensions) {
|
|
|
2349
2446
|
b._def.dimensions = dimensions;
|
|
2350
2447
|
return b;
|
|
2351
2448
|
}
|
|
2449
|
+
function ownedByUser() {
|
|
2450
|
+
const b = new ColumnBuilder("text");
|
|
2451
|
+
b._def.nullable = false;
|
|
2452
|
+
b._def.references = { table: "auth.users", column: "id" };
|
|
2453
|
+
b._def.onDeleteAction = "cascade";
|
|
2454
|
+
b._def.owns = true;
|
|
2455
|
+
return b;
|
|
2456
|
+
}
|
|
2457
|
+
function userRef(opts) {
|
|
2458
|
+
const b = new ColumnBuilder("text");
|
|
2459
|
+
b._def.references = { table: "auth.users", column: "id" };
|
|
2460
|
+
b._def.onDeleteAction = opts.onDelete;
|
|
2461
|
+
if (opts.as !== void 0) b._def.refAs = opts.as;
|
|
2462
|
+
return b;
|
|
2463
|
+
}
|
|
2464
|
+
function installationRef(opts) {
|
|
2465
|
+
const b = new ColumnBuilder("text");
|
|
2466
|
+
b._def.references = { table: "auth.installations", column: "id" };
|
|
2467
|
+
b._def.onDeleteAction = opts.onDelete;
|
|
2468
|
+
if (opts.as !== void 0) b._def.refAs = opts.as;
|
|
2469
|
+
return b;
|
|
2470
|
+
}
|
|
2352
2471
|
|
|
2353
2472
|
// src/db/raw.ts
|
|
2354
2473
|
function raw(name, up, opts) {
|
|
@@ -2459,50 +2578,73 @@ function rowType(def) {
|
|
|
2459
2578
|
function optionalOnInsert(def) {
|
|
2460
2579
|
return def.nullable === true || def.defaultRandom === true || def.defaultNow === true || def.defaultValue !== void 0;
|
|
2461
2580
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
for (const [col, builder] of Object.entries(table.columns)) {
|
|
2465
|
-
if (builder._def.references?.table === AUTH_USERS_TABLE) return col;
|
|
2466
|
-
}
|
|
2467
|
-
return null;
|
|
2581
|
+
function forwardName(column) {
|
|
2582
|
+
return column.endsWith("_id") ? column.slice(0, -3) : column;
|
|
2468
2583
|
}
|
|
2469
|
-
function
|
|
2470
|
-
|
|
2471
|
-
for (const name of Object.keys(schema.tables)) edges.set(name, []);
|
|
2472
|
-
for (const childName of Object.keys(schema.tables)) {
|
|
2473
|
-
const table = schema.tables[childName];
|
|
2474
|
-
if (table === void 0) continue;
|
|
2475
|
-
for (const [col, builder] of Object.entries(table.columns)) {
|
|
2476
|
-
const ref = builder._def.references;
|
|
2477
|
-
if (ref === void 0) continue;
|
|
2478
|
-
if (ref.table === childName) continue;
|
|
2479
|
-
const bucket = edges.get(ref.table);
|
|
2480
|
-
if (bucket === void 0) continue;
|
|
2481
|
-
if (bucket.some((e) => e.child === childName)) continue;
|
|
2482
|
-
bucket.push({ child: childName, column: col });
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
return edges;
|
|
2584
|
+
function qualify(schemaName, tableName) {
|
|
2585
|
+
return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
|
|
2486
2586
|
}
|
|
2487
|
-
function
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
const
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
pruned.get(table)?.push(edge);
|
|
2496
|
-
if (state.get(edge.child) === void 0) visit(edge.child);
|
|
2587
|
+
function buildRelations(schemas) {
|
|
2588
|
+
const out = /* @__PURE__ */ new Map();
|
|
2589
|
+
const keyOf = /* @__PURE__ */ new Map();
|
|
2590
|
+
for (const schema of schemas) {
|
|
2591
|
+
for (const t of Object.values(schema.tables)) {
|
|
2592
|
+
const key = qualify(schema.name, t.name);
|
|
2593
|
+
out.set(key, []);
|
|
2594
|
+
keyOf.set(t.name, key);
|
|
2497
2595
|
}
|
|
2498
|
-
state.set(table, "done");
|
|
2499
2596
|
}
|
|
2500
|
-
for (const
|
|
2501
|
-
|
|
2597
|
+
for (const schema of schemas) {
|
|
2598
|
+
for (const table of Object.values(schema.tables)) {
|
|
2599
|
+
const childKey = qualify(schema.name, table.name);
|
|
2600
|
+
const bucket = out.get(childKey);
|
|
2601
|
+
if (bucket === void 0) continue;
|
|
2602
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
2603
|
+
for (const [col, builder] of Object.entries(table.columns)) {
|
|
2604
|
+
const ref = builder._def.references;
|
|
2605
|
+
if (ref === void 0) continue;
|
|
2606
|
+
const list = byTarget.get(ref.table) ?? [];
|
|
2607
|
+
list.push(col);
|
|
2608
|
+
byTarget.set(ref.table, list);
|
|
2609
|
+
}
|
|
2610
|
+
for (const [target, cols] of byTarget) {
|
|
2611
|
+
if (cols.length < 2) continue;
|
|
2612
|
+
const unnamed = cols.filter((c) => {
|
|
2613
|
+
const d = table.columns[c]?._def;
|
|
2614
|
+
return d?.refAs === void 0 && d?.owns !== true;
|
|
2615
|
+
});
|
|
2616
|
+
if (unnamed.length > 0) {
|
|
2617
|
+
throw new Error(
|
|
2618
|
+
`table "${table.name}" declares ${cols.length} foreign keys onto "${target}" (${cols.join(", ")}) \u2014 name each one: references(() => \u2026, { as: "\u2026" })`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
for (const [col, builder] of Object.entries(table.columns)) {
|
|
2623
|
+
const def = builder._def;
|
|
2624
|
+
if (def.references === void 0) continue;
|
|
2625
|
+
const name = def.owns === true ? "owner" : def.refAs ?? forwardName(col);
|
|
2626
|
+
if (bucket.some((r) => r.name === name)) {
|
|
2627
|
+
throw new Error(
|
|
2628
|
+
name === "owner" ? `table "${table.name}": two columns claim ownership of the row \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.` : `table "${table.name}": two relations resolve to the name "${name}" \u2014 give one an explicit { as: "\u2026" }`
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
const targetKey = keyOf.get(def.references.table) ?? def.references.table;
|
|
2632
|
+
bucket.push({ name, to: targetKey, kind: "one", via: col });
|
|
2633
|
+
const parent = out.get(targetKey);
|
|
2634
|
+
if (parent === void 0 || targetKey === childKey) continue;
|
|
2635
|
+
const reverse = def.refAs ?? table.name;
|
|
2636
|
+
if (parent.some((r) => r.name === reverse)) {
|
|
2637
|
+
throw new Error(
|
|
2638
|
+
`table "${def.references.table}": two reverse relations resolve to the name "${reverse}" \u2014 give one an explicit { as: "\u2026" }`
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
parent.push({ name: reverse, to: childKey, kind: "many", via: col });
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2502
2644
|
}
|
|
2503
|
-
return
|
|
2645
|
+
return out;
|
|
2504
2646
|
}
|
|
2505
|
-
function tableBlock(table,
|
|
2647
|
+
function tableBlock(table, relations, indent) {
|
|
2506
2648
|
const cols = Object.entries(table.columns);
|
|
2507
2649
|
const rowLines = cols.map(([col, builder]) => {
|
|
2508
2650
|
return `${indent} ${col}: ${rowType(builder._def)};`;
|
|
@@ -2512,8 +2654,9 @@ function tableBlock(table, children, indent) {
|
|
|
2512
2654
|
const opt = optionalOnInsert(def) ? "?" : "";
|
|
2513
2655
|
return `${indent} ${col}${opt}: ${rowType(def)};`;
|
|
2514
2656
|
});
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2657
|
+
const relEntries = relations.map(
|
|
2658
|
+
(r) => `${r.name}: { to: ${JSON.stringify(r.to)}; kind: ${JSON.stringify(r.kind)}; via: ${JSON.stringify(r.via)} }`
|
|
2659
|
+
).join("; ");
|
|
2517
2660
|
return [
|
|
2518
2661
|
`${indent}${table.name}: {`,
|
|
2519
2662
|
`${indent} row: {`,
|
|
@@ -2522,8 +2665,7 @@ function tableBlock(table, children, indent) {
|
|
|
2522
2665
|
`${indent} insert: {`,
|
|
2523
2666
|
...insertLines,
|
|
2524
2667
|
`${indent} };`,
|
|
2525
|
-
`${indent}
|
|
2526
|
-
`${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
|
|
2668
|
+
`${indent} relations: {${relEntries === "" ? "" : ` ${relEntries} `}};`,
|
|
2527
2669
|
// searchable: vector kolonu YA DA search beyanı → EnvTypedTable'da search()
|
|
2528
2670
|
// üyesini açan yapısal bayrak (FR-013). Yokken satır hiç üretilmez ki
|
|
2529
2671
|
// mevcut şemaların d.ts'i bayt-aynı kalsın.
|
|
@@ -2531,22 +2673,37 @@ function tableBlock(table, children, indent) {
|
|
|
2531
2673
|
`${indent}};`
|
|
2532
2674
|
].join("\n");
|
|
2533
2675
|
}
|
|
2534
|
-
function makeEnvDts(
|
|
2535
|
-
const
|
|
2536
|
-
const
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2676
|
+
function makeEnvDts(schemas) {
|
|
2677
|
+
const relations = buildRelations(schemas);
|
|
2678
|
+
const qualify2 = (schemaName, table) => schemaName === "public" ? table : `${schemaName}.${table}`;
|
|
2679
|
+
const publicSchema = schemas.find((s) => s.name === "public");
|
|
2680
|
+
const others = schemas.filter((s) => s.name !== "public");
|
|
2681
|
+
const publicBlocks = Object.keys(publicSchema?.tables ?? {}).map(
|
|
2682
|
+
(name) => tableBlock(publicSchema.tables[name], relations.get(name) ?? [], " ")
|
|
2539
2683
|
);
|
|
2540
|
-
const body =
|
|
2541
|
-
${
|
|
2684
|
+
const body = publicBlocks.length > 0 ? `
|
|
2685
|
+
${publicBlocks.join("\n")}
|
|
2686
|
+
` : "";
|
|
2687
|
+
const schemaBlocks = others.map((schema) => {
|
|
2688
|
+
const inner = Object.keys(schema.tables).map(
|
|
2689
|
+
(name) => tableBlock(schema.tables[name], relations.get(qualify2(schema.name, name)) ?? [], " ")
|
|
2690
|
+
);
|
|
2691
|
+
return ` ${schema.name}: {
|
|
2692
|
+
${inner.join("\n")}
|
|
2693
|
+
};`;
|
|
2694
|
+
});
|
|
2695
|
+
const schemasBody = schemaBlocks.length > 0 ? `
|
|
2696
|
+
${schemaBlocks.join("\n")}
|
|
2542
2697
|
` : "";
|
|
2543
2698
|
return `// AUTO-GENERATED by @palbase/backend \u2014 DO NOT EDIT.
|
|
2544
|
-
// Regenerated from db
|
|
2699
|
+
// Regenerated from db/*.ts by \`palbase build\` and by every deploy.
|
|
2545
2700
|
// Augments the @palbase/backend/env \`Tables\` interface so \`Database.tables.*\`
|
|
2546
|
-
// is typed with no import and no generic.
|
|
2701
|
+
// is typed with no import and no generic. Schemas other than \`public\` land
|
|
2702
|
+
// under \`Schemas\`, reached with \`Database.schema("<name>").tables.*\`.
|
|
2547
2703
|
|
|
2548
2704
|
declare module "@palbase/backend/env" {
|
|
2549
2705
|
interface Tables {${body}}
|
|
2706
|
+
interface Schemas {${schemasBody}}
|
|
2550
2707
|
}
|
|
2551
2708
|
|
|
2552
2709
|
export {};
|
|
@@ -2602,6 +2759,8 @@ export {};
|
|
|
2602
2759
|
// src/decorators/registry.ts
|
|
2603
2760
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
2604
2761
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
2762
|
+
var ROOM = /* @__PURE__ */ Symbol.for("palbase.backend.room");
|
|
2763
|
+
var ROOM_HOOKS = /* @__PURE__ */ Symbol.for("palbase.backend.roomHooks");
|
|
2605
2764
|
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
2606
2765
|
var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
|
|
2607
2766
|
function carrierOf(target) {
|
|
@@ -2690,6 +2849,38 @@ function getRoutes(ctor) {
|
|
|
2690
2849
|
...r.throws !== void 0 ? { throws: r.throws.slice() } : {}
|
|
2691
2850
|
}));
|
|
2692
2851
|
}
|
|
2852
|
+
function ownRoomBuffer(carrier) {
|
|
2853
|
+
if (!Object.prototype.hasOwnProperty.call(carrier, ROOM_HOOKS)) {
|
|
2854
|
+
carrier[ROOM_HOOKS] = { hooks: {}, messages: {} };
|
|
2855
|
+
}
|
|
2856
|
+
return carrier[ROOM_HOOKS];
|
|
2857
|
+
}
|
|
2858
|
+
function recordRoomHook(target, hook, fnName) {
|
|
2859
|
+
const buffer = ownRoomBuffer(carrierOf(target));
|
|
2860
|
+
const existing = buffer.hooks[hook];
|
|
2861
|
+
if (existing !== void 0 && existing !== fnName) {
|
|
2862
|
+
const label = `On${hook.charAt(0).toUpperCase()}${hook.slice(1)}`;
|
|
2863
|
+
throw new Error(
|
|
2864
|
+
`@${label} is declared twice in one room (${existing} and ${fnName}). A room has one of each hook.`
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
buffer.hooks[hook] = fnName;
|
|
2868
|
+
}
|
|
2869
|
+
function recordRoomMessage(target, name, fnName, schema) {
|
|
2870
|
+
const buffer = ownRoomBuffer(carrierOf(target));
|
|
2871
|
+
const existing = buffer.messages[name];
|
|
2872
|
+
if (existing !== void 0 && existing.fnName !== fnName) {
|
|
2873
|
+
throw new Error(
|
|
2874
|
+
`@OnMessage("${name}") is declared twice in one room (${existing.fnName} and ${fnName}).`
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
buffer.messages[name] = { fnName, schema };
|
|
2878
|
+
}
|
|
2879
|
+
function recordRoom(ctor, meta) {
|
|
2880
|
+
const carrier = carrierOf(ctor);
|
|
2881
|
+
const buffer = ownRoomBuffer(carrier);
|
|
2882
|
+
carrier[ROOM] = { ...meta, hooks: buffer.hooks, messages: buffer.messages };
|
|
2883
|
+
}
|
|
2693
2884
|
|
|
2694
2885
|
// src/decorators/controller.ts
|
|
2695
2886
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
@@ -3002,6 +3193,56 @@ function Signal() {
|
|
|
3002
3193
|
};
|
|
3003
3194
|
}
|
|
3004
3195
|
|
|
3196
|
+
// src/decorators/room.ts
|
|
3197
|
+
var DEFAULT_GRACE_MS = 6e4;
|
|
3198
|
+
function assertPattern(pattern) {
|
|
3199
|
+
if (pattern.includes("/")) {
|
|
3200
|
+
throw new Error(
|
|
3201
|
+
`@Room("${pattern}"): channel patterns are colon-separated, not slash-separated. Write "${pattern.replace(/\//g, ":")}" \u2014 a slash matches no live topic.`
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
const segments = pattern.split(":");
|
|
3205
|
+
if (segments.some((s) => s === "")) {
|
|
3206
|
+
throw new Error(`@Room("${pattern}"): empty segment \u2014 every segment must be non-empty.`);
|
|
3207
|
+
}
|
|
3208
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3209
|
+
for (const s of segments) {
|
|
3210
|
+
if (!s.startsWith("{") || !s.endsWith("}")) continue;
|
|
3211
|
+
const name = s.slice(1, -1);
|
|
3212
|
+
if (name === "") throw new Error(`@Room("${pattern}"): "{}" names no parameter.`);
|
|
3213
|
+
if (seen.has(name)) {
|
|
3214
|
+
throw new Error(`@Room("${pattern}"): parameter "{${name}}" appears twice.`);
|
|
3215
|
+
}
|
|
3216
|
+
seen.add(name);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function Room(pattern, options) {
|
|
3220
|
+
return function(ctor) {
|
|
3221
|
+
assertPattern(pattern);
|
|
3222
|
+
recordRoom(ctor, {
|
|
3223
|
+
pattern,
|
|
3224
|
+
events: options.events,
|
|
3225
|
+
graceMs: options.graceMs ?? DEFAULT_GRACE_MS
|
|
3226
|
+
});
|
|
3227
|
+
return ctor;
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
function hookDecorator(hook) {
|
|
3231
|
+
return () => function(target, propertyKey) {
|
|
3232
|
+
recordRoomHook(target, hook, String(propertyKey));
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
var OnAuthorize = hookDecorator("authorize");
|
|
3236
|
+
var OnFirst = hookDecorator("first");
|
|
3237
|
+
var OnJoin = hookDecorator("join");
|
|
3238
|
+
var OnLeave = hookDecorator("leave");
|
|
3239
|
+
var OnEmpty = hookDecorator("empty");
|
|
3240
|
+
function OnMessage(name, schema) {
|
|
3241
|
+
return function(target, propertyKey) {
|
|
3242
|
+
recordRoomMessage(target, name, String(propertyKey), schema);
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3005
3246
|
// src/decorators/params.ts
|
|
3006
3247
|
function makeParamDecorator(kind, extra) {
|
|
3007
3248
|
return function(target, propertyKey, parameterIndex) {
|
|
@@ -3422,6 +3663,12 @@ var import_zod2 = require("zod");
|
|
|
3422
3663
|
NotFound,
|
|
3423
3664
|
Notifications,
|
|
3424
3665
|
On,
|
|
3666
|
+
OnAuthorize,
|
|
3667
|
+
OnEmpty,
|
|
3668
|
+
OnFirst,
|
|
3669
|
+
OnJoin,
|
|
3670
|
+
OnLeave,
|
|
3671
|
+
OnMessage,
|
|
3425
3672
|
OptionalUser,
|
|
3426
3673
|
PALBASE_EXTENSIONS,
|
|
3427
3674
|
PalError,
|
|
@@ -3436,11 +3683,13 @@ var import_zod2 = require("zod");
|
|
|
3436
3683
|
Realtime,
|
|
3437
3684
|
Req,
|
|
3438
3685
|
RequestId,
|
|
3686
|
+
Room,
|
|
3439
3687
|
Secrets,
|
|
3440
3688
|
Signal,
|
|
3441
3689
|
Sse,
|
|
3442
3690
|
SseOut,
|
|
3443
3691
|
Storage,
|
|
3692
|
+
TABLE_META,
|
|
3444
3693
|
TooManyRequests,
|
|
3445
3694
|
TraceId,
|
|
3446
3695
|
TxPlanError,
|
|
@@ -3469,6 +3718,7 @@ var import_zod2 = require("zod");
|
|
|
3469
3718
|
defineError,
|
|
3470
3719
|
defineMiddleware,
|
|
3471
3720
|
defineSchema,
|
|
3721
|
+
defineTable,
|
|
3472
3722
|
enumType,
|
|
3473
3723
|
getErrorRegistry,
|
|
3474
3724
|
getHookConfig,
|
|
@@ -3477,6 +3727,7 @@ var import_zod2 = require("zod");
|
|
|
3477
3727
|
getRoutes,
|
|
3478
3728
|
getWebhookConfig,
|
|
3479
3729
|
inc,
|
|
3730
|
+
installationRef,
|
|
3480
3731
|
integer,
|
|
3481
3732
|
isPalbaseExtension,
|
|
3482
3733
|
jsonb,
|
|
@@ -3489,6 +3740,7 @@ var import_zod2 = require("zod");
|
|
|
3489
3740
|
onShutdown,
|
|
3490
3741
|
onStart,
|
|
3491
3742
|
openai,
|
|
3743
|
+
ownedByUser,
|
|
3492
3744
|
ownerOnly,
|
|
3493
3745
|
policy,
|
|
3494
3746
|
publicChannel,
|
|
@@ -3498,6 +3750,7 @@ var import_zod2 = require("zod");
|
|
|
3498
3750
|
text,
|
|
3499
3751
|
timestamp,
|
|
3500
3752
|
toSchemaJSON,
|
|
3753
|
+
userRef,
|
|
3501
3754
|
uuid,
|
|
3502
3755
|
vector,
|
|
3503
3756
|
z
|