@palbase/backend 24.3.0 → 25.0.1
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 +103 -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-34I4GB7D.js} +82 -49
- package/dist/chunk-34I4GB7D.js.map +1 -0
- package/dist/{chunk-UWSYTUGM.js → chunk-35PNTIRN.js} +48 -1
- package/dist/chunk-35PNTIRN.js.map +1 -0
- package/dist/chunk-HBOJLP2Z.js +840 -0
- package/dist/chunk-HBOJLP2Z.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-ERDL5VAE.js → chunk-YOY5DFQS.js} +2 -2
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +31 -14
- package/dist/db/env.d.ts +31 -14
- package/dist/db/index.cjs +226 -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 +89 -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-BTMYod_l.d.ts → index-B4CcpqLb.d.ts} +224 -75
- package/dist/{index-DEneI8Mn.d.ts → index-B8v6hVyU.d.ts} +5 -2
- package/dist/{index-BLAbr9ZH.d.cts → index-DmVyY6N7.d.cts} +224 -75
- package/dist/{index-C-ALG22n.d.cts → index-VsjBQ4Kw.d.cts} +5 -2
- package/dist/index.cjs +580 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -22
- package/dist/index.d.ts +125 -22
- package/dist/index.js +173 -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 +430 -153
- package/docs/schema.md +303 -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-YOY5DFQS.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,303 @@ 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 owned = {};
|
|
1969
|
+
for (const [colName, b] of Object.entries(input.columns)) {
|
|
1970
|
+
const builder = b;
|
|
1971
|
+
const clone = Object.create(Object.getPrototypeOf(builder));
|
|
1972
|
+
Object.defineProperty(clone, "_def", { value: { ...builder._def }, enumerable: true, writable: true });
|
|
1973
|
+
owned[colName] = clone;
|
|
1974
|
+
}
|
|
1975
|
+
const def = {
|
|
1976
|
+
name,
|
|
1977
|
+
columns: owned,
|
|
1978
|
+
rls,
|
|
1979
|
+
policies
|
|
1980
|
+
};
|
|
1981
|
+
if (input.primaryKey !== void 0) def.primaryKey = input.primaryKey;
|
|
1982
|
+
if (input.unique !== void 0) def.unique = input.unique;
|
|
1983
|
+
if (input.raw !== void 0 && input.raw.length > 0) def.raw = input.raw.slice();
|
|
1984
|
+
if (input.checks !== void 0 && input.checks.length > 0) def.checks = input.checks.slice();
|
|
1985
|
+
if (input.indexes !== void 0 && input.indexes.length > 0) def.indexes = input.indexes.slice();
|
|
1986
|
+
if (input.search !== void 0) {
|
|
1987
|
+
const s = input.search;
|
|
1988
|
+
if (s.from !== void 0) {
|
|
1989
|
+
if (s.vector !== void 0) {
|
|
1990
|
+
throw new Error(
|
|
1991
|
+
`table ${name}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
if (s.from.length === 0) {
|
|
1995
|
+
throw new Error(`table ${name}: search.from bo\u015F olamaz`);
|
|
1996
|
+
}
|
|
1997
|
+
if (s.model === void 0) {
|
|
1998
|
+
throw new Error(
|
|
1999
|
+
`table ${name}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
if (Array.isArray(s.text) && s.text.length === 0) {
|
|
2003
|
+
throw new Error(`table ${name}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
|
|
2004
|
+
}
|
|
2005
|
+
} else {
|
|
2006
|
+
if (typeof s.text === "boolean") {
|
|
2007
|
+
throw new Error(`table ${name}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
|
|
2008
|
+
}
|
|
2009
|
+
if (s.text === void 0 && s.vector === void 0) {
|
|
2010
|
+
throw new Error(
|
|
2011
|
+
`table ${name}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
if (s.text !== void 0 && s.text.length === 0) {
|
|
2015
|
+
throw new Error(`table ${name}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
|
|
2016
|
+
}
|
|
2017
|
+
const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
|
|
2018
|
+
for (const leg of legs) {
|
|
2019
|
+
if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
|
|
2020
|
+
throw new Error(`table ${name}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
|
|
2021
|
+
}
|
|
2022
|
+
if (leg.model === void 0 && leg.from !== void 0) {
|
|
2023
|
+
throw new Error(`table ${name}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
def.search = s;
|
|
2028
|
+
}
|
|
2029
|
+
if (input.memory !== void 0) {
|
|
2030
|
+
if (input.memory.from.length === 0) {
|
|
2031
|
+
throw new Error(`table ${name}: memory.from bo\u015F olamaz`);
|
|
2032
|
+
}
|
|
2033
|
+
def.memory = input.memory;
|
|
2034
|
+
}
|
|
2035
|
+
for (const b of Object.values(owned)) {
|
|
2036
|
+
b._def.ownerTable = def;
|
|
2037
|
+
}
|
|
2038
|
+
const handle = { ...owned };
|
|
2039
|
+
Object.defineProperty(handle, TABLE_META, { value: def, enumerable: false });
|
|
2040
|
+
return handle;
|
|
2041
|
+
}
|
|
2042
|
+
var declaringSchemaOf = /* @__PURE__ */ new WeakMap();
|
|
2043
|
+
function resolveReferences(tables, schemaName) {
|
|
2044
|
+
for (const table of Object.values(tables)) {
|
|
2045
|
+
for (const [colName, builder] of Object.entries(table.columns)) {
|
|
2046
|
+
const def = builder._def;
|
|
2047
|
+
if (def.selfRefColumn !== void 0) {
|
|
2048
|
+
if (!(def.selfRefColumn in table.columns)) {
|
|
2049
|
+
throw new Error(
|
|
2050
|
+
`table "${table.name}" column "${colName}": selfReferences("${def.selfRefColumn}") \u2014 no such column on this table`
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
def.references = {
|
|
2054
|
+
table: qualifiedTableKey(schemaName, table.name),
|
|
2055
|
+
column: def.selfRefColumn
|
|
2056
|
+
};
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
2059
|
+
if (def.referencesThunk === void 0) continue;
|
|
2060
|
+
const target = def.referencesThunk();
|
|
2061
|
+
const owner = target?._def?.ownerTable;
|
|
2062
|
+
if (owner === void 0) {
|
|
2063
|
+
throw new Error(
|
|
2064
|
+
`table "${table.name}" column "${colName}": references(() => \u2026) must point at a column of a table declared with defineTable(...)`
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
const targetColumn = Object.entries(owner.columns).find(
|
|
2068
|
+
([, b]) => b._def === target._def
|
|
2069
|
+
)?.[0];
|
|
2070
|
+
if (targetColumn === void 0) {
|
|
2071
|
+
throw new Error(
|
|
2072
|
+
`table "${table.name}" column "${colName}": the referenced column was not found on table "${owner.name}"`
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
const targetSchema = declaringSchemaOf.get(owner);
|
|
2076
|
+
if (targetSchema === void 0) {
|
|
2077
|
+
throw new Error(
|
|
2078
|
+
`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).`
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
def.references = {
|
|
2082
|
+
table: qualifiedTableKey(targetSchema, owner.name),
|
|
2083
|
+
column: targetColumn
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
function defineSchema(name, input) {
|
|
2089
|
+
if (typeof name !== "string") {
|
|
2090
|
+
throw new Error(
|
|
2091
|
+
`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.`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
if (name.trim() === "") {
|
|
2095
|
+
throw new Error("defineSchema(name, \u2026): name must not be empty");
|
|
2096
|
+
}
|
|
2097
|
+
const tables = {};
|
|
2098
|
+
for (const handle of input.tables) {
|
|
2099
|
+
const table = handle[TABLE_META];
|
|
2100
|
+
if (tables[table.name] !== void 0) {
|
|
2101
|
+
throw new Error(
|
|
2102
|
+
`defineSchema("${name}"): two tables declare the name "${table.name}" \u2014 table names must be unique within a schema`
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
tables[table.name] = table;
|
|
2106
|
+
declaringSchemaOf.set(table, name);
|
|
2107
|
+
}
|
|
2108
|
+
resolveReferences(tables, name);
|
|
2109
|
+
for (const [name2, def] of Object.entries(tables)) {
|
|
2110
|
+
const m = def.memory;
|
|
2111
|
+
if (m === void 0) continue;
|
|
2112
|
+
const target = Object.values(tables).find((t) => t.name === m.into);
|
|
2113
|
+
if (target === void 0) {
|
|
2114
|
+
throw new Error(`table ${name2}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
|
|
2115
|
+
}
|
|
2116
|
+
const subject = m.subject ?? "owner";
|
|
2117
|
+
if (!(subject in def.columns)) {
|
|
2118
|
+
throw new Error(`table ${name2}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
|
|
2119
|
+
}
|
|
2120
|
+
if (!(subject in target.columns)) {
|
|
2121
|
+
throw new Error(`table ${name2}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
|
|
2122
|
+
}
|
|
2123
|
+
const factCol = target.columns["fact"];
|
|
2124
|
+
const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
|
|
2125
|
+
if (factDef === void 0 || factDef.type !== "text") {
|
|
2126
|
+
throw new Error(`table ${name2}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
|
|
2127
|
+
}
|
|
2128
|
+
for (const c of m.from) {
|
|
2129
|
+
if (!(c in def.columns)) {
|
|
2130
|
+
throw new Error(`table ${name2}: memory.from kolonu "${c}" kaynak tabloda yok`);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
const extensions = [...new Set(input.extensions ?? [])];
|
|
2135
|
+
return {
|
|
2136
|
+
name,
|
|
2137
|
+
tables,
|
|
2138
|
+
extensions,
|
|
2139
|
+
exposed: input.exposed ?? name === "public"
|
|
2140
|
+
};
|
|
2024
2141
|
}
|
|
2025
2142
|
|
|
2026
2143
|
// src/channels.ts
|
|
@@ -2222,59 +2339,53 @@ var ColumnBuilder = class _ColumnBuilder {
|
|
|
2222
2339
|
ignored: true
|
|
2223
2340
|
});
|
|
2224
2341
|
}
|
|
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
2342
|
/**
|
|
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")`.
|
|
2343
|
+
* Foreign key onto another table's column.
|
|
2235
2344
|
*
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
*
|
|
2239
|
-
* the
|
|
2345
|
+
* The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
|
|
2346
|
+
* the second table does not exist yet when the first is built; a direct
|
|
2347
|
+
* reference makes TypeScript chase its own tail (TS7022 — measured, and making
|
|
2348
|
+
* the return type independent of the target does NOT help). The thunk is
|
|
2349
|
+
* invoked in `defineSchema`, where every binding exists and every table
|
|
2350
|
+
* already knows its name.
|
|
2240
2351
|
*
|
|
2241
|
-
*
|
|
2242
|
-
*
|
|
2243
|
-
*
|
|
2244
|
-
*
|
|
2245
|
-
* `
|
|
2246
|
-
*
|
|
2247
|
-
*
|
|
2248
|
-
*
|
|
2352
|
+
* In a cycle, ONE side needs an explicit return type:
|
|
2353
|
+
* `references((): AnyColumn => y.id)`. One side is enough — measured.
|
|
2354
|
+
* For a self-reference use `selfReferences(column)`: no thunk, no annotation.
|
|
2355
|
+
*
|
|
2356
|
+
* `as` names the FORWARD relation (`author_id` → `author` by default);
|
|
2357
|
+
* `reverseAs` names the REVERSE one on the parent, whose default is this
|
|
2358
|
+
* table's own name (`users.posts`). Two foreign keys from one table onto one
|
|
2359
|
+
* parent therefore need a `reverseAs` on at least one of them — the reverse
|
|
2360
|
+
* names would otherwise both be this table's name.
|
|
2249
2361
|
*/
|
|
2250
|
-
|
|
2251
|
-
refuseOnVector(this._def, "
|
|
2252
|
-
|
|
2253
|
-
|
|
2362
|
+
references(target, opts) {
|
|
2363
|
+
refuseOnVector(this._def, "references");
|
|
2364
|
+
if (typeof target !== "function") {
|
|
2365
|
+
throw new Error(
|
|
2366
|
+
`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.`
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
this._def.referencesThunk = target;
|
|
2370
|
+
if (opts?.as !== void 0) this._def.refAs = opts.as;
|
|
2371
|
+
if (opts?.reverseAs !== void 0) this._def.reverseAs = opts.reverseAs;
|
|
2372
|
+
if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
|
|
2254
2373
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2255
2374
|
}
|
|
2256
2375
|
/**
|
|
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.
|
|
2376
|
+
* Foreign key onto THIS table (`parent_id → id`) — category trees, comment
|
|
2377
|
+
* replies, org charts.
|
|
2266
2378
|
*
|
|
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.
|
|
2379
|
+
* No thunk and no type annotation: the target table is the one being declared,
|
|
2380
|
+
* so there is nothing to defer and nothing for TypeScript to chase in a circle.
|
|
2381
|
+
* Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
|
|
2382
|
+
* always goes through a callback; measured, we do not need one.
|
|
2273
2383
|
*/
|
|
2274
|
-
|
|
2275
|
-
refuseOnVector(this._def, "
|
|
2276
|
-
this._def.
|
|
2277
|
-
this._def.
|
|
2384
|
+
selfReferences(column, opts) {
|
|
2385
|
+
refuseOnVector(this._def, "selfReferences");
|
|
2386
|
+
this._def.selfRefColumn = column;
|
|
2387
|
+
if (opts?.as !== void 0) this._def.refAs = opts.as;
|
|
2388
|
+
if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
|
|
2278
2389
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
2279
2390
|
}
|
|
2280
2391
|
/** Set the ON DELETE action for a foreign key reference. */
|
|
@@ -2349,6 +2460,28 @@ function vector(dimensions) {
|
|
|
2349
2460
|
b._def.dimensions = dimensions;
|
|
2350
2461
|
return b;
|
|
2351
2462
|
}
|
|
2463
|
+
function ownedByUser() {
|
|
2464
|
+
const b = new ColumnBuilder("text");
|
|
2465
|
+
b._def.nullable = false;
|
|
2466
|
+
b._def.references = { table: "auth.users", column: "id" };
|
|
2467
|
+
b._def.onDeleteAction = "cascade";
|
|
2468
|
+
b._def.owns = true;
|
|
2469
|
+
return b;
|
|
2470
|
+
}
|
|
2471
|
+
function userRef(opts) {
|
|
2472
|
+
const b = new ColumnBuilder("text");
|
|
2473
|
+
b._def.references = { table: "auth.users", column: "id" };
|
|
2474
|
+
b._def.onDeleteAction = opts.onDelete;
|
|
2475
|
+
if (opts.as !== void 0) b._def.refAs = opts.as;
|
|
2476
|
+
return b;
|
|
2477
|
+
}
|
|
2478
|
+
function installationRef(opts) {
|
|
2479
|
+
const b = new ColumnBuilder("text");
|
|
2480
|
+
b._def.references = { table: "auth.installations", column: "id" };
|
|
2481
|
+
b._def.onDeleteAction = opts.onDelete;
|
|
2482
|
+
if (opts.as !== void 0) b._def.refAs = opts.as;
|
|
2483
|
+
return b;
|
|
2484
|
+
}
|
|
2352
2485
|
|
|
2353
2486
|
// src/db/raw.ts
|
|
2354
2487
|
function raw(name, up, opts) {
|
|
@@ -2459,50 +2592,82 @@ function rowType(def) {
|
|
|
2459
2592
|
function optionalOnInsert(def) {
|
|
2460
2593
|
return def.nullable === true || def.defaultRandom === true || def.defaultNow === true || def.defaultValue !== void 0;
|
|
2461
2594
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2595
|
+
function forwardName(column) {
|
|
2596
|
+
return column.endsWith("_id") ? column.slice(0, -3) : column;
|
|
2597
|
+
}
|
|
2598
|
+
function describeOrigin(o) {
|
|
2599
|
+
return o.direction === "forward" ? `the foreign key "${o.table}.${o.column}"` : `the reverse of "${o.table}.${o.column}"`;
|
|
2600
|
+
}
|
|
2601
|
+
function renameCall(o) {
|
|
2602
|
+
if (o.direction === "reverse") return `references(() => \u2026, { reverseAs: "\u2026" })`;
|
|
2603
|
+
if (o.def.owns === true) {
|
|
2604
|
+
return `ownedByUser() takes no { as } \u2014 if "${o.column}" only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead`;
|
|
2605
|
+
}
|
|
2606
|
+
const target = o.def.references?.table;
|
|
2607
|
+
if (target === "auth.users") return `userRef({ onDelete: \u2026, as: "\u2026" })`;
|
|
2608
|
+
if (target === "auth.installations") return `installationRef({ onDelete: \u2026, as: "\u2026" })`;
|
|
2609
|
+
if (o.def.selfRefColumn !== void 0) return `selfReferences("\u2026", { as: "\u2026" })`;
|
|
2610
|
+
return `references(() => \u2026, { as: "\u2026" })`;
|
|
2611
|
+
}
|
|
2612
|
+
function buildRelations(schemas) {
|
|
2613
|
+
const out = /* @__PURE__ */ new Map();
|
|
2614
|
+
const taken = /* @__PURE__ */ new Map();
|
|
2615
|
+
for (const schema of schemas) {
|
|
2616
|
+
for (const t of Object.values(schema.tables)) {
|
|
2617
|
+
const key = qualifiedTableKey(schema.name, t.name);
|
|
2618
|
+
out.set(key, []);
|
|
2619
|
+
taken.set(key, /* @__PURE__ */ new Map());
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
const claim = (tableKey, name, origin, edge) => {
|
|
2623
|
+
const names = taken.get(tableKey);
|
|
2624
|
+
if (names === void 0) return;
|
|
2625
|
+
const held = names.get(name);
|
|
2626
|
+
if (held !== void 0) {
|
|
2627
|
+
if (held.def.owns === true && origin.def.owns === true) {
|
|
2628
|
+
throw new Error(
|
|
2629
|
+
`table "${tableKey}" declares TWO owner columns (${held.column}, ${origin.column}) \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.`
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
const heldFix = renameCall(held);
|
|
2633
|
+
const originFix = renameCall(origin);
|
|
2634
|
+
const remedy = heldFix === originFix ? `rename one of them: ${heldFix}` : `rename one of them \u2014 "${held.table}.${held.column}": ${heldFix}; "${origin.table}.${origin.column}": ${originFix}`;
|
|
2635
|
+
throw new Error(
|
|
2636
|
+
`table "${tableKey}": ${describeOrigin(held)} and ${describeOrigin(origin)} both resolve to the relation name "${name}" \u2014 ${remedy}.`
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
names.set(name, origin);
|
|
2640
|
+
out.get(tableKey)?.push(edge);
|
|
2641
|
+
};
|
|
2642
|
+
for (const schema of schemas) {
|
|
2643
|
+
for (const table of Object.values(schema.tables)) {
|
|
2644
|
+
const childKey = qualifiedTableKey(schema.name, table.name);
|
|
2645
|
+
if (!out.has(childKey)) continue;
|
|
2646
|
+
for (const [col, builder] of Object.entries(table.columns)) {
|
|
2647
|
+
const def = builder._def;
|
|
2648
|
+
if (def.references === void 0) continue;
|
|
2649
|
+
const targetKey = def.references.table;
|
|
2650
|
+
const name = def.owns === true ? "owner" : def.refAs ?? forwardName(col);
|
|
2651
|
+
claim(
|
|
2652
|
+
childKey,
|
|
2653
|
+
name,
|
|
2654
|
+
{ table: table.name, column: col, direction: "forward", def },
|
|
2655
|
+
{ name, to: targetKey, kind: "one", via: col }
|
|
2656
|
+
);
|
|
2657
|
+
if (!out.has(targetKey) || targetKey === childKey) continue;
|
|
2658
|
+
const reverse = def.reverseAs ?? table.name;
|
|
2659
|
+
claim(
|
|
2660
|
+
targetKey,
|
|
2661
|
+
reverse,
|
|
2662
|
+
{ table: table.name, column: col, direction: "reverse", def },
|
|
2663
|
+
{ name: reverse, to: childKey, kind: "many", via: col }
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2466
2667
|
}
|
|
2467
|
-
return
|
|
2668
|
+
return out;
|
|
2468
2669
|
}
|
|
2469
|
-
function
|
|
2470
|
-
const edges = /* @__PURE__ */ new Map();
|
|
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;
|
|
2486
|
-
}
|
|
2487
|
-
function pruneCycles(raw2) {
|
|
2488
|
-
const pruned = /* @__PURE__ */ new Map();
|
|
2489
|
-
for (const table of raw2.keys()) pruned.set(table, []);
|
|
2490
|
-
const state = /* @__PURE__ */ new Map();
|
|
2491
|
-
function visit(table) {
|
|
2492
|
-
state.set(table, "visiting");
|
|
2493
|
-
for (const edge of raw2.get(table) ?? []) {
|
|
2494
|
-
if (state.get(edge.child) === "visiting") continue;
|
|
2495
|
-
pruned.get(table)?.push(edge);
|
|
2496
|
-
if (state.get(edge.child) === void 0) visit(edge.child);
|
|
2497
|
-
}
|
|
2498
|
-
state.set(table, "done");
|
|
2499
|
-
}
|
|
2500
|
-
for (const table of raw2.keys()) {
|
|
2501
|
-
if (state.get(table) === void 0) visit(table);
|
|
2502
|
-
}
|
|
2503
|
-
return pruned;
|
|
2504
|
-
}
|
|
2505
|
-
function tableBlock(table, children, indent) {
|
|
2670
|
+
function tableBlock(table, relations, indent) {
|
|
2506
2671
|
const cols = Object.entries(table.columns);
|
|
2507
2672
|
const rowLines = cols.map(([col, builder]) => {
|
|
2508
2673
|
return `${indent} ${col}: ${rowType(builder._def)};`;
|
|
@@ -2512,8 +2677,9 @@ function tableBlock(table, children, indent) {
|
|
|
2512
2677
|
const opt = optionalOnInsert(def) ? "?" : "";
|
|
2513
2678
|
return `${indent} ${col}${opt}: ${rowType(def)};`;
|
|
2514
2679
|
});
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2680
|
+
const relEntries = relations.map(
|
|
2681
|
+
(r) => `${r.name}: { to: ${JSON.stringify(r.to)}; kind: ${JSON.stringify(r.kind)}; via: ${JSON.stringify(r.via)} }`
|
|
2682
|
+
).join("; ");
|
|
2517
2683
|
return [
|
|
2518
2684
|
`${indent}${table.name}: {`,
|
|
2519
2685
|
`${indent} row: {`,
|
|
@@ -2522,8 +2688,7 @@ function tableBlock(table, children, indent) {
|
|
|
2522
2688
|
`${indent} insert: {`,
|
|
2523
2689
|
...insertLines,
|
|
2524
2690
|
`${indent} };`,
|
|
2525
|
-
`${indent}
|
|
2526
|
-
`${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
|
|
2691
|
+
`${indent} relations: {${relEntries === "" ? "" : ` ${relEntries} `}};`,
|
|
2527
2692
|
// searchable: vector kolonu YA DA search beyanı → EnvTypedTable'da search()
|
|
2528
2693
|
// üyesini açan yapısal bayrak (FR-013). Yokken satır hiç üretilmez ki
|
|
2529
2694
|
// mevcut şemaların d.ts'i bayt-aynı kalsın.
|
|
@@ -2531,22 +2696,36 @@ function tableBlock(table, children, indent) {
|
|
|
2531
2696
|
`${indent}};`
|
|
2532
2697
|
].join("\n");
|
|
2533
2698
|
}
|
|
2534
|
-
function makeEnvDts(
|
|
2535
|
-
const
|
|
2536
|
-
const
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2699
|
+
function makeEnvDts(schemas) {
|
|
2700
|
+
const relations = buildRelations(schemas);
|
|
2701
|
+
const publicSchema = schemas.find((s) => s.name === "public");
|
|
2702
|
+
const others = schemas.filter((s) => s.name !== "public");
|
|
2703
|
+
const publicBlocks = Object.keys(publicSchema?.tables ?? {}).map(
|
|
2704
|
+
(name) => tableBlock(publicSchema.tables[name], relations.get(name) ?? [], " ")
|
|
2539
2705
|
);
|
|
2540
|
-
const body =
|
|
2541
|
-
${
|
|
2706
|
+
const body = publicBlocks.length > 0 ? `
|
|
2707
|
+
${publicBlocks.join("\n")}
|
|
2708
|
+
` : "";
|
|
2709
|
+
const schemaBlocks = others.map((schema) => {
|
|
2710
|
+
const inner = Object.keys(schema.tables).map(
|
|
2711
|
+
(name) => tableBlock(schema.tables[name], relations.get(qualifiedTableKey(schema.name, name)) ?? [], " ")
|
|
2712
|
+
);
|
|
2713
|
+
return ` ${schema.name}: {
|
|
2714
|
+
${inner.join("\n")}
|
|
2715
|
+
};`;
|
|
2716
|
+
});
|
|
2717
|
+
const schemasBody = schemaBlocks.length > 0 ? `
|
|
2718
|
+
${schemaBlocks.join("\n")}
|
|
2542
2719
|
` : "";
|
|
2543
2720
|
return `// AUTO-GENERATED by @palbase/backend \u2014 DO NOT EDIT.
|
|
2544
|
-
// Regenerated from db
|
|
2721
|
+
// Regenerated from db/*.ts by \`palbase build\` and by every deploy.
|
|
2545
2722
|
// Augments the @palbase/backend/env \`Tables\` interface so \`Database.tables.*\`
|
|
2546
|
-
// is typed with no import and no generic.
|
|
2723
|
+
// is typed with no import and no generic. Schemas other than \`public\` land
|
|
2724
|
+
// under \`Schemas\`, reached with \`Database.schema("<name>").tables.*\`.
|
|
2547
2725
|
|
|
2548
2726
|
declare module "@palbase/backend/env" {
|
|
2549
2727
|
interface Tables {${body}}
|
|
2728
|
+
interface Schemas {${schemasBody}}
|
|
2550
2729
|
}
|
|
2551
2730
|
|
|
2552
2731
|
export {};
|
|
@@ -2602,6 +2781,8 @@ export {};
|
|
|
2602
2781
|
// src/decorators/registry.ts
|
|
2603
2782
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
2604
2783
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
2784
|
+
var ROOM = /* @__PURE__ */ Symbol.for("palbase.backend.room");
|
|
2785
|
+
var ROOM_HOOKS = /* @__PURE__ */ Symbol.for("palbase.backend.roomHooks");
|
|
2605
2786
|
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
2606
2787
|
var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
|
|
2607
2788
|
function carrierOf(target) {
|
|
@@ -2690,6 +2871,42 @@ function getRoutes(ctor) {
|
|
|
2690
2871
|
...r.throws !== void 0 ? { throws: r.throws.slice() } : {}
|
|
2691
2872
|
}));
|
|
2692
2873
|
}
|
|
2874
|
+
function ownRoomBuffer(carrier) {
|
|
2875
|
+
if (!Object.prototype.hasOwnProperty.call(carrier, ROOM_HOOKS)) {
|
|
2876
|
+
carrier[ROOM_HOOKS] = { hooks: {}, messages: {} };
|
|
2877
|
+
}
|
|
2878
|
+
return carrier[ROOM_HOOKS];
|
|
2879
|
+
}
|
|
2880
|
+
function recordRoomHook(target, hook, fnName) {
|
|
2881
|
+
const buffer = ownRoomBuffer(carrierOf(target));
|
|
2882
|
+
const existing = buffer.hooks[hook];
|
|
2883
|
+
if (existing !== void 0 && existing !== fnName) {
|
|
2884
|
+
const label = `On${hook.charAt(0).toUpperCase()}${hook.slice(1)}`;
|
|
2885
|
+
throw new Error(
|
|
2886
|
+
`@${label} is declared twice in one room (${existing} and ${fnName}). A room has one of each hook.`
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
buffer.hooks[hook] = fnName;
|
|
2890
|
+
}
|
|
2891
|
+
function recordRoomMessage(target, name, fnName, schema) {
|
|
2892
|
+
const buffer = ownRoomBuffer(carrierOf(target));
|
|
2893
|
+
const existing = buffer.messages[name];
|
|
2894
|
+
if (existing !== void 0 && existing.fnName !== fnName) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`@OnMessage("${name}") is declared twice in one room (${existing.fnName} and ${fnName}).`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
buffer.messages[name] = { fnName, schema };
|
|
2900
|
+
}
|
|
2901
|
+
function recordRoom(ctor, meta) {
|
|
2902
|
+
const carrier = carrierOf(ctor);
|
|
2903
|
+
const buffer = ownRoomBuffer(carrier);
|
|
2904
|
+
carrier[ROOM] = { ...meta, hooks: buffer.hooks, messages: buffer.messages };
|
|
2905
|
+
const g = globalThis;
|
|
2906
|
+
const REGISTRY2 = /* @__PURE__ */ Symbol.for("palbase.backend.allControllers");
|
|
2907
|
+
const all = g[REGISTRY2] ??= [];
|
|
2908
|
+
if (!all.includes(ctor)) all.push(ctor);
|
|
2909
|
+
}
|
|
2693
2910
|
|
|
2694
2911
|
// src/decorators/controller.ts
|
|
2695
2912
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
@@ -3002,6 +3219,56 @@ function Signal() {
|
|
|
3002
3219
|
};
|
|
3003
3220
|
}
|
|
3004
3221
|
|
|
3222
|
+
// src/decorators/room.ts
|
|
3223
|
+
var DEFAULT_GRACE_MS = 6e4;
|
|
3224
|
+
function assertPattern(pattern) {
|
|
3225
|
+
if (pattern.includes("/")) {
|
|
3226
|
+
throw new Error(
|
|
3227
|
+
`@Room("${pattern}"): channel patterns are colon-separated, not slash-separated. Write "${pattern.replace(/\//g, ":")}" \u2014 a slash matches no live topic.`
|
|
3228
|
+
);
|
|
3229
|
+
}
|
|
3230
|
+
const segments = pattern.split(":");
|
|
3231
|
+
if (segments.some((s) => s === "")) {
|
|
3232
|
+
throw new Error(`@Room("${pattern}"): empty segment \u2014 every segment must be non-empty.`);
|
|
3233
|
+
}
|
|
3234
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3235
|
+
for (const s of segments) {
|
|
3236
|
+
if (!s.startsWith("{") || !s.endsWith("}")) continue;
|
|
3237
|
+
const name = s.slice(1, -1);
|
|
3238
|
+
if (name === "") throw new Error(`@Room("${pattern}"): "{}" names no parameter.`);
|
|
3239
|
+
if (seen.has(name)) {
|
|
3240
|
+
throw new Error(`@Room("${pattern}"): parameter "{${name}}" appears twice.`);
|
|
3241
|
+
}
|
|
3242
|
+
seen.add(name);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
function Room(pattern, options) {
|
|
3246
|
+
return function(ctor) {
|
|
3247
|
+
assertPattern(pattern);
|
|
3248
|
+
recordRoom(ctor, {
|
|
3249
|
+
pattern,
|
|
3250
|
+
events: options.events,
|
|
3251
|
+
graceMs: options.graceMs ?? DEFAULT_GRACE_MS
|
|
3252
|
+
});
|
|
3253
|
+
return ctor;
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
function hookDecorator(hook) {
|
|
3257
|
+
return () => function(target, propertyKey) {
|
|
3258
|
+
recordRoomHook(target, hook, String(propertyKey));
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
var OnAuthorize = hookDecorator("authorize");
|
|
3262
|
+
var OnFirst = hookDecorator("first");
|
|
3263
|
+
var OnJoin = hookDecorator("join");
|
|
3264
|
+
var OnLeave = hookDecorator("leave");
|
|
3265
|
+
var OnEmpty = hookDecorator("empty");
|
|
3266
|
+
function OnMessage(name, schema) {
|
|
3267
|
+
return function(target, propertyKey) {
|
|
3268
|
+
recordRoomMessage(target, name, String(propertyKey), schema);
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3005
3272
|
// src/decorators/params.ts
|
|
3006
3273
|
function makeParamDecorator(kind, extra) {
|
|
3007
3274
|
return function(target, propertyKey, parameterIndex) {
|
|
@@ -3422,6 +3689,12 @@ var import_zod2 = require("zod");
|
|
|
3422
3689
|
NotFound,
|
|
3423
3690
|
Notifications,
|
|
3424
3691
|
On,
|
|
3692
|
+
OnAuthorize,
|
|
3693
|
+
OnEmpty,
|
|
3694
|
+
OnFirst,
|
|
3695
|
+
OnJoin,
|
|
3696
|
+
OnLeave,
|
|
3697
|
+
OnMessage,
|
|
3425
3698
|
OptionalUser,
|
|
3426
3699
|
PALBASE_EXTENSIONS,
|
|
3427
3700
|
PalError,
|
|
@@ -3436,11 +3709,13 @@ var import_zod2 = require("zod");
|
|
|
3436
3709
|
Realtime,
|
|
3437
3710
|
Req,
|
|
3438
3711
|
RequestId,
|
|
3712
|
+
Room,
|
|
3439
3713
|
Secrets,
|
|
3440
3714
|
Signal,
|
|
3441
3715
|
Sse,
|
|
3442
3716
|
SseOut,
|
|
3443
3717
|
Storage,
|
|
3718
|
+
TABLE_META,
|
|
3444
3719
|
TooManyRequests,
|
|
3445
3720
|
TraceId,
|
|
3446
3721
|
TxPlanError,
|
|
@@ -3469,6 +3744,7 @@ var import_zod2 = require("zod");
|
|
|
3469
3744
|
defineError,
|
|
3470
3745
|
defineMiddleware,
|
|
3471
3746
|
defineSchema,
|
|
3747
|
+
defineTable,
|
|
3472
3748
|
enumType,
|
|
3473
3749
|
getErrorRegistry,
|
|
3474
3750
|
getHookConfig,
|
|
@@ -3477,6 +3753,7 @@ var import_zod2 = require("zod");
|
|
|
3477
3753
|
getRoutes,
|
|
3478
3754
|
getWebhookConfig,
|
|
3479
3755
|
inc,
|
|
3756
|
+
installationRef,
|
|
3480
3757
|
integer,
|
|
3481
3758
|
isPalbaseExtension,
|
|
3482
3759
|
jsonb,
|
|
@@ -3489,6 +3766,7 @@ var import_zod2 = require("zod");
|
|
|
3489
3766
|
onShutdown,
|
|
3490
3767
|
onStart,
|
|
3491
3768
|
openai,
|
|
3769
|
+
ownedByUser,
|
|
3492
3770
|
ownerOnly,
|
|
3493
3771
|
policy,
|
|
3494
3772
|
publicChannel,
|
|
@@ -3498,6 +3776,7 @@ var import_zod2 = require("zod");
|
|
|
3498
3776
|
text,
|
|
3499
3777
|
timestamp,
|
|
3500
3778
|
toSchemaJSON,
|
|
3779
|
+
userRef,
|
|
3501
3780
|
uuid,
|
|
3502
3781
|
vector,
|
|
3503
3782
|
z
|