@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.
Files changed (61) hide show
  1. package/dist/bin/palbase-backend.cjs +103 -60
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +17 -13
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-EIXCY4SS.js → chunk-34I4GB7D.js} +82 -49
  6. package/dist/chunk-34I4GB7D.js.map +1 -0
  7. package/dist/{chunk-UWSYTUGM.js → chunk-35PNTIRN.js} +48 -1
  8. package/dist/chunk-35PNTIRN.js.map +1 -0
  9. package/dist/chunk-HBOJLP2Z.js +840 -0
  10. package/dist/chunk-HBOJLP2Z.js.map +1 -0
  11. package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
  12. package/dist/chunk-XJ2RSHEU.js.map +1 -0
  13. package/dist/{chunk-ERDL5VAE.js → chunk-YOY5DFQS.js} +2 -2
  14. package/dist/db/env.cjs.map +1 -1
  15. package/dist/db/env.d.cts +31 -14
  16. package/dist/db/env.d.ts +31 -14
  17. package/dist/db/index.cjs +226 -111
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +1 -1
  20. package/dist/db/index.d.ts +1 -1
  21. package/dist/db/index.js +11 -1
  22. package/dist/engine/index.cjs +89 -50
  23. package/dist/engine/index.cjs.map +1 -1
  24. package/dist/engine/index.d.cts +2 -2
  25. package/dist/engine/index.d.ts +2 -2
  26. package/dist/engine/index.js +3 -3
  27. package/dist/{index-BTMYod_l.d.ts → index-B4CcpqLb.d.ts} +224 -75
  28. package/dist/{index-DEneI8Mn.d.ts → index-B8v6hVyU.d.ts} +5 -2
  29. package/dist/{index-BLAbr9ZH.d.cts → index-DmVyY6N7.d.cts} +224 -75
  30. package/dist/{index-C-ALG22n.d.cts → index-VsjBQ4Kw.d.cts} +5 -2
  31. package/dist/index.cjs +580 -301
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +125 -22
  34. package/dist/index.d.ts +125 -22
  35. package/dist/index.js +173 -217
  36. package/dist/index.js.map +1 -1
  37. package/dist/openapi/index.cjs +100 -36
  38. package/dist/openapi/index.cjs.map +1 -1
  39. package/dist/openapi/index.js +59 -2
  40. package/dist/openapi/index.js.map +1 -1
  41. package/docs/README.md +64 -31
  42. package/docs/endpoints.md +25 -28
  43. package/docs/llms-full.txt +430 -153
  44. package/docs/schema.md +303 -91
  45. package/docs/services.md +39 -4
  46. package/package.json +1 -1
  47. package/template/AGENTS.md +119 -314
  48. package/template/CLAUDE.md +13 -0
  49. package/template/controllers/notes.controller.ts +6 -13
  50. package/template/db/public.ts +38 -0
  51. package/template/models/notes/create.ts +38 -0
  52. package/template/package.json +6 -3
  53. package/template/services/note.service.test.ts +45 -0
  54. package/template/services/note.service.ts +2 -2
  55. package/dist/chunk-7Z6MGMXQ.js.map +0 -1
  56. package/dist/chunk-D5CQES25.js +0 -556
  57. package/dist/chunk-D5CQES25.js.map +0 -1
  58. package/dist/chunk-EIXCY4SS.js.map +0 -1
  59. package/dist/chunk-UWSYTUGM.js.map +0 -1
  60. package/template/db/schema.ts +0 -35
  61. /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 makeTablesAccessor(ops) {
643
- const tablesProxy = new Proxy(
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
- return tablesProxy;
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 toSchemaJSON(schema) {
1844
+ function qualifiedTableKey(schemaName, tableName) {
1845
+ return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
1846
+ }
1847
+ function toSchemaJSON(schemas) {
2018
1848
  const tables = {};
2019
- for (const table of Object.values(schema.tables)) {
2020
- const json = tableToJSON(table);
2021
- tables[json.name] = json;
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(schema.extensions ?? [])] };
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
- * Add a real DB-level foreign key to the built-in auth users
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
- * `auth.users` lives in the SAME tenant database (palauth-owned), so this is
2237
- * a genuine cross-schema integrity constraint scoped to THIS tenant's users.
2238
- * The referenced `auth.users.id` is `text` (palauth ids are `usr_<uuid>`), so
2239
- * the referencing column must be `text()` too.
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
- * ON DELETE is REQUIRED here and may only be `cascade` or `set null`: an
2242
- * account-erasure request must never be blocked by a lingering FK, so
2243
- * `restrict` / `no action` are not accepted (they don't type-check). Example:
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.
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
- referencesAuthUser(onDelete) {
2251
- refuseOnVector(this._def, "referencesAuthUser");
2252
- this._def.references = { table: "auth.users", column: "id" };
2253
- this._def.onDeleteAction = onDelete;
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
- * Add a real DB-level foreign key to the canonical, server-minted installation
2258
- * anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
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
- * `auth.installations` lives in the SAME tenant DB (palauth-owned); its `id` is
2268
- * `text` (`ins_<uuid>`), so the referencing column must be `text()` too. ON
2269
- * DELETE is REQUIRED and may only be `cascade` or `set null` (same allowed set
2270
- * as an auth-user FK): an installation revoke / orphan cleanup must never be
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
- referencesInstallation(onDelete) {
2275
- refuseOnVector(this._def, "referencesInstallation");
2276
- this._def.references = { table: "auth.installations", column: "id" };
2277
- this._def.onDeleteAction = onDelete;
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
- var AUTH_USERS_TABLE = "auth.users";
2463
- function ownerColumnOf(table) {
2464
- for (const [col, builder] of Object.entries(table.columns)) {
2465
- if (builder._def.references?.table === AUTH_USERS_TABLE) return col;
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 null;
2668
+ return out;
2468
2669
  }
2469
- function buildChildEdges(schema) {
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 owner = ownerColumnOf(table);
2516
- const childEntries = children.map((e) => `${JSON.stringify(e.child)}: ${JSON.stringify(e.column)}`).join("; ");
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} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
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(schema) {
2535
- const tableNames = Object.keys(schema.tables);
2536
- const children = pruneCycles(buildChildEdges(schema));
2537
- const blocks = tableNames.map(
2538
- (name) => tableBlock(schema.tables[name], children.get(name) ?? [], " ")
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 = blocks.length > 0 ? `
2541
- ${blocks.join("\n")}
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/schema.ts by \`palbase build\` and by every deploy.
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