@palbase/backend 24.2.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/bin/palbase-backend.cjs +101 -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-43A3KGWL.js} +80 -49
  6. package/dist/chunk-43A3KGWL.js.map +1 -0
  7. package/dist/{chunk-ERDL5VAE.js → chunk-5CMLOAEF.js} +2 -2
  8. package/dist/chunk-OEQBHE2Z.js +825 -0
  9. package/dist/chunk-OEQBHE2Z.js.map +1 -0
  10. package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
  11. package/dist/chunk-XJ2RSHEU.js.map +1 -0
  12. package/dist/{chunk-UWSYTUGM.js → chunk-ZQRWW37O.js} +44 -1
  13. package/dist/chunk-ZQRWW37O.js.map +1 -0
  14. package/dist/db/env.cjs.map +1 -1
  15. package/dist/db/env.d.cts +29 -13
  16. package/dist/db/env.d.ts +29 -13
  17. package/dist/db/index.cjs +233 -110
  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 +87 -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-C0PMn5jl.d.ts → index-BF1f0DfA.d.ts} +5 -2
  28. package/dist/{index-DAwHMppB.d.cts → index-CoaDN9dL.d.cts} +5 -2
  29. package/dist/{index-ByBMibIJ.d.ts → index-Ct1iiB4N.d.ts} +232 -60
  30. package/dist/{index-D4rts8T7.d.cts → index-CwaWRhyc.d.cts} +232 -60
  31. package/dist/index.cjs +572 -296
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +124 -20
  34. package/dist/index.d.ts +124 -20
  35. package/dist/index.js +164 -216
  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 +465 -148
  44. package/docs/schema.md +338 -86
  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-EIXCY4SS.js.map +0 -1
  57. package/dist/chunk-LCL7TUAI.js +0 -534
  58. package/dist/chunk-LCL7TUAI.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-5CMLOAEF.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -42,6 +42,12 @@ __export(src_exports, {
42
42
  NotFound: () => NotFound,
43
43
  Notifications: () => Notifications,
44
44
  On: () => On,
45
+ OnAuthorize: () => OnAuthorize,
46
+ OnEmpty: () => OnEmpty,
47
+ OnFirst: () => OnFirst,
48
+ OnJoin: () => OnJoin,
49
+ OnLeave: () => OnLeave,
50
+ OnMessage: () => OnMessage,
45
51
  OptionalUser: () => OptionalUser,
46
52
  PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
47
53
  PalError: () => PalError,
@@ -56,11 +62,13 @@ __export(src_exports, {
56
62
  Realtime: () => Realtime,
57
63
  Req: () => Req,
58
64
  RequestId: () => RequestId,
65
+ Room: () => Room,
59
66
  Secrets: () => Secrets,
60
67
  Signal: () => Signal,
61
68
  Sse: () => Sse,
62
69
  SseOut: () => SseOut,
63
70
  Storage: () => Storage,
71
+ TABLE_META: () => TABLE_META,
64
72
  TooManyRequests: () => TooManyRequests,
65
73
  TraceId: () => TraceId,
66
74
  TxPlanError: () => TxPlanError,
@@ -89,6 +97,7 @@ __export(src_exports, {
89
97
  defineError: () => defineError,
90
98
  defineMiddleware: () => defineMiddleware,
91
99
  defineSchema: () => defineSchema,
100
+ defineTable: () => defineTable,
92
101
  enumType: () => enumType,
93
102
  getErrorRegistry: () => getErrorRegistry,
94
103
  getHookConfig: () => getHookConfig,
@@ -97,6 +106,7 @@ __export(src_exports, {
97
106
  getRoutes: () => getRoutes,
98
107
  getWebhookConfig: () => getWebhookConfig,
99
108
  inc: () => inc,
109
+ installationRef: () => installationRef,
100
110
  integer: () => integer,
101
111
  isPalbaseExtension: () => isPalbaseExtension,
102
112
  jsonb: () => jsonb,
@@ -109,6 +119,7 @@ __export(src_exports, {
109
119
  onShutdown: () => onShutdown,
110
120
  onStart: () => onStart,
111
121
  openai: () => openai,
122
+ ownedByUser: () => ownedByUser,
112
123
  ownerOnly: () => ownerOnly,
113
124
  policy: () => policy,
114
125
  publicChannel: () => publicChannel,
@@ -118,6 +129,7 @@ __export(src_exports, {
118
129
  text: () => text,
119
130
  timestamp: () => timestamp,
120
131
  toSchemaJSON: () => toSchemaJSON,
132
+ userRef: () => userRef,
121
133
  uuid: () => uuid,
122
134
  vector: () => vector,
123
135
  z: () => import_zod2.z
@@ -639,13 +651,13 @@ function makeServiceProxy(key) {
639
651
  };
640
652
  return new Proxy({}, handler);
641
653
  }
642
- function 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;
@@ -1876,6 +1702,7 @@ function columnToJSON(column) {
1876
1702
  if (def.defaultRandom === true) out.defaultRandom = true;
1877
1703
  if (def.defaultNow === true) out.defaultNow = true;
1878
1704
  if (def.renamedFrom !== void 0) out.renamedFrom = def.renamedFrom;
1705
+ if (def.ignored === true) out.ignored = true;
1879
1706
  if (def.references !== void 0) {
1880
1707
  out.references = { table: def.references.table, column: def.references.column };
1881
1708
  }
@@ -1968,13 +1795,14 @@ function memoryToJSON(m) {
1968
1795
  extract: { provider: m.extract.provider, model: m.extract.model }
1969
1796
  };
1970
1797
  }
1971
- function tableToJSON(table) {
1798
+ function tableToJSON(table, schemaName) {
1972
1799
  const columns = {};
1973
1800
  for (const [name, column] of Object.entries(table.columns)) {
1974
1801
  columns[name] = columnToJSON(column);
1975
1802
  }
1976
1803
  const out = {
1977
1804
  name: table.name,
1805
+ schema: schemaName,
1978
1806
  columns,
1979
1807
  // Read, not re-derived. `defineSchema` already resolves the fail-closed
1980
1808
  // default (RLS on unless the author wrote `rls: false`, and forced on by any
@@ -2013,13 +1841,296 @@ function tableToJSON(table) {
2013
1841
  }
2014
1842
  return out;
2015
1843
  }
2016
- function toSchemaJSON(schema) {
1844
+ function qualifiedTableKey(schemaName, tableName) {
1845
+ return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
1846
+ }
1847
+ function toSchemaJSON(schemas) {
2017
1848
  const tables = {};
2018
- for (const table of Object.values(schema.tables)) {
2019
- const json = tableToJSON(table);
2020
- 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 });
2021
1863
  }
2022
- 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 def = {
1969
+ name,
1970
+ columns: input.columns,
1971
+ rls,
1972
+ policies
1973
+ };
1974
+ if (input.primaryKey !== void 0) def.primaryKey = input.primaryKey;
1975
+ if (input.unique !== void 0) def.unique = input.unique;
1976
+ if (input.raw !== void 0 && input.raw.length > 0) def.raw = input.raw.slice();
1977
+ if (input.checks !== void 0 && input.checks.length > 0) def.checks = input.checks.slice();
1978
+ if (input.indexes !== void 0 && input.indexes.length > 0) def.indexes = input.indexes.slice();
1979
+ if (input.search !== void 0) {
1980
+ const s = input.search;
1981
+ if (s.from !== void 0) {
1982
+ if (s.vector !== void 0) {
1983
+ throw new Error(
1984
+ `table ${name}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
1985
+ );
1986
+ }
1987
+ if (s.from.length === 0) {
1988
+ throw new Error(`table ${name}: search.from bo\u015F olamaz`);
1989
+ }
1990
+ if (s.model === void 0) {
1991
+ throw new Error(
1992
+ `table ${name}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
1993
+ );
1994
+ }
1995
+ if (Array.isArray(s.text) && s.text.length === 0) {
1996
+ throw new Error(`table ${name}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
1997
+ }
1998
+ } else {
1999
+ if (typeof s.text === "boolean") {
2000
+ throw new Error(`table ${name}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
2001
+ }
2002
+ if (s.text === void 0 && s.vector === void 0) {
2003
+ throw new Error(
2004
+ `table ${name}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
2005
+ );
2006
+ }
2007
+ if (s.text !== void 0 && s.text.length === 0) {
2008
+ throw new Error(`table ${name}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
2009
+ }
2010
+ const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
2011
+ for (const leg of legs) {
2012
+ if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
2013
+ throw new Error(`table ${name}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
2014
+ }
2015
+ if (leg.model === void 0 && leg.from !== void 0) {
2016
+ throw new Error(`table ${name}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
2017
+ }
2018
+ }
2019
+ }
2020
+ def.search = s;
2021
+ }
2022
+ if (input.memory !== void 0) {
2023
+ if (input.memory.from.length === 0) {
2024
+ throw new Error(`table ${name}: memory.from bo\u015F olamaz`);
2025
+ }
2026
+ def.memory = input.memory;
2027
+ }
2028
+ for (const b of Object.values(input.columns)) {
2029
+ b._def.ownerTable = def;
2030
+ }
2031
+ const handle = { ...input.columns };
2032
+ Object.defineProperty(handle, TABLE_META, { value: def, enumerable: false });
2033
+ return handle;
2034
+ }
2035
+ var declaringSchemaOf = /* @__PURE__ */ new WeakMap();
2036
+ function resolveReferences(tables, schemaName) {
2037
+ for (const table of Object.values(tables)) {
2038
+ for (const [colName, builder] of Object.entries(table.columns)) {
2039
+ const def = builder._def;
2040
+ if (def.selfRefColumn !== void 0) {
2041
+ if (!(def.selfRefColumn in table.columns)) {
2042
+ throw new Error(
2043
+ `table "${table.name}" column "${colName}": selfReferences("${def.selfRefColumn}") \u2014 no such column on this table`
2044
+ );
2045
+ }
2046
+ def.references = {
2047
+ table: qualifiedTableKey(schemaName, table.name),
2048
+ column: def.selfRefColumn
2049
+ };
2050
+ continue;
2051
+ }
2052
+ if (def.referencesThunk === void 0) continue;
2053
+ const target = def.referencesThunk();
2054
+ const owner = target?._def?.ownerTable;
2055
+ if (owner === void 0) {
2056
+ throw new Error(
2057
+ `table "${table.name}" column "${colName}": references(() => \u2026) must point at a column of a table declared with defineTable(...)`
2058
+ );
2059
+ }
2060
+ const targetColumn = Object.entries(owner.columns).find(
2061
+ ([, b]) => b._def === target._def
2062
+ )?.[0];
2063
+ if (targetColumn === void 0) {
2064
+ throw new Error(
2065
+ `table "${table.name}" column "${colName}": the referenced column was not found on table "${owner.name}"`
2066
+ );
2067
+ }
2068
+ const targetSchema = declaringSchemaOf.get(owner);
2069
+ if (targetSchema === void 0) {
2070
+ throw new Error(
2071
+ `table "${table.name}" column "${colName}": references(() => \u2026) points at table "${owner.name}", which no defineSchema(...) has claimed yet \u2014 a foreign key cannot be written without its target's schema. Declare that table's schema first (one file per schema: db/public.ts, db/billing.ts \u2026, imported by this one).`
2072
+ );
2073
+ }
2074
+ def.references = {
2075
+ table: qualifiedTableKey(targetSchema, owner.name),
2076
+ column: targetColumn
2077
+ };
2078
+ }
2079
+ }
2080
+ }
2081
+ function defineSchema(name, input) {
2082
+ if (typeof name !== "string") {
2083
+ throw new Error(
2084
+ `defineSchema(...) takes the schema NAME first: defineSchema("public", { tables: [todos, lists] }). The one-argument form defineSchema({ tables: { \u2026 } }) is gone \u2014 a schema that does not say its own name cannot be told apart from another schema's table of the same name, which is what a cross-schema foreign key has to do. Migrate db/public.ts (one file per schema: db/public.ts, db/billing.ts \u2026): name the schema, and lift each table key onto the table itself with defineTable("todos", { columns: { \u2026 } }), then list the tables in the array.`
2085
+ );
2086
+ }
2087
+ if (name.trim() === "") {
2088
+ throw new Error("defineSchema(name, \u2026): name must not be empty");
2089
+ }
2090
+ const tables = {};
2091
+ for (const handle of input.tables) {
2092
+ const table = handle[TABLE_META];
2093
+ if (tables[table.name] !== void 0) {
2094
+ throw new Error(
2095
+ `defineSchema("${name}"): two tables declare the name "${table.name}" \u2014 table names must be unique within a schema`
2096
+ );
2097
+ }
2098
+ tables[table.name] = table;
2099
+ declaringSchemaOf.set(table, name);
2100
+ }
2101
+ resolveReferences(tables, name);
2102
+ for (const [name2, def] of Object.entries(tables)) {
2103
+ const m = def.memory;
2104
+ if (m === void 0) continue;
2105
+ const target = Object.values(tables).find((t) => t.name === m.into);
2106
+ if (target === void 0) {
2107
+ throw new Error(`table ${name2}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
2108
+ }
2109
+ const subject = m.subject ?? "owner";
2110
+ if (!(subject in def.columns)) {
2111
+ throw new Error(`table ${name2}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
2112
+ }
2113
+ if (!(subject in target.columns)) {
2114
+ throw new Error(`table ${name2}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
2115
+ }
2116
+ const factCol = target.columns["fact"];
2117
+ const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
2118
+ if (factDef === void 0 || factDef.type !== "text") {
2119
+ throw new Error(`table ${name2}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
2120
+ }
2121
+ for (const c of m.from) {
2122
+ if (!(c in def.columns)) {
2123
+ throw new Error(`table ${name2}: memory.from kolonu "${c}" kaynak tabloda yok`);
2124
+ }
2125
+ }
2126
+ }
2127
+ const extensions = [...new Set(input.extensions ?? [])];
2128
+ return {
2129
+ name,
2130
+ tables,
2131
+ extensions,
2132
+ exposed: input.exposed ?? name === "public"
2133
+ };
2023
2134
  }
2024
2135
 
2025
2136
  // src/channels.ts
@@ -2199,59 +2310,68 @@ var ColumnBuilder = class _ColumnBuilder {
2199
2310
  this._def.renamedFrom = previous;
2200
2311
  return this;
2201
2312
  }
2202
- references(table, column) {
2203
- refuseOnVector(this._def, "references");
2204
- this._def.references = { table, column };
2205
- return new _ColumnBuilder(this._def.type, this._def);
2313
+ /**
2314
+ * See {@link ColumnDef.ignored}.
2315
+ *
2316
+ * COPIES the def rather than mutating it. The constructor takes an existing
2317
+ * def BY REFERENCE, so every builder derived from another shares one object —
2318
+ * `const a = slug.unique()` leaves `a._def === slug._def`. An in-place
2319
+ * `ignored = true` therefore marks every column sharing that def, including
2320
+ * one another table actively reads, and the gate would let THAT column be
2321
+ * dropped. Measured before this copy existed.
2322
+ *
2323
+ * The aliasing is older than this method and other fields leak through it too.
2324
+ * The reason this one cannot wait: every other leak produces a VISIBLE schema
2325
+ * difference — the plan shows it, the DDL shows it. This one is invisible by
2326
+ * design (no DDL, no diff, no plan line), so its only effect is to disarm a
2327
+ * safety gate in silence.
2328
+ */
2329
+ ignored() {
2330
+ return new _ColumnBuilder(this._def.type, {
2331
+ ...this._def,
2332
+ ignored: true
2333
+ });
2206
2334
  }
2207
2335
  /**
2208
- * Add a real DB-level foreign key to the built-in auth users
2209
- * (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
2210
- * database cascade/integrity instead of app-layer-only. Sugar for
2211
- * `.references("auth.users", "id")`.
2336
+ * Foreign key onto another table's column.
2212
2337
  *
2213
- * `auth.users` lives in the SAME tenant database (palauth-owned), so this is
2214
- * a genuine cross-schema integrity constraint scoped to THIS tenant's users.
2215
- * The referenced `auth.users.id` is `text` (palauth ids are `usr_<uuid>`), so
2216
- * the referencing column must be `text()` too.
2338
+ * The target is a THUNK, not a direct reference. In a cycle (`x y`, `y → x`)
2339
+ * the second table does not exist yet when the first is built; a direct
2340
+ * reference makes TypeScript chase its own tail (TS7022 measured, and making
2341
+ * the return type independent of the target does NOT help). The thunk is
2342
+ * invoked in `defineSchema`, where every binding exists and every table
2343
+ * already knows its name.
2217
2344
  *
2218
- * ON DELETE is REQUIRED here and may only be `cascade` or `set null`: an
2219
- * account-erasure request must never be blocked by a lingering FK, so
2220
- * `restrict` / `no action` are not accepted (they don't type-check). Example:
2221
- * `text().notNull().referencesAuthUser("cascade")`, or
2222
- * `text().nullable().referencesAuthUser("set null")`. The server
2223
- * (validateAuthUserFK) enforces this — and the remaining rules the type can't
2224
- * express (referencing column is text, `set null` needs a nullable column) —
2225
- * as the real boundary; this signature is the compile-time DX mirror.
2345
+ * In a cycle, ONE side needs an explicit return type:
2346
+ * `references((): AnyColumn => y.id)`. One side is enough measured.
2347
+ * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
2226
2348
  */
2227
- referencesAuthUser(onDelete) {
2228
- refuseOnVector(this._def, "referencesAuthUser");
2229
- this._def.references = { table: "auth.users", column: "id" };
2230
- this._def.onDeleteAction = onDelete;
2349
+ references(target, opts) {
2350
+ refuseOnVector(this._def, "references");
2351
+ if (typeof target !== "function") {
2352
+ throw new Error(
2353
+ `references(...) takes a callback: write references(() => otherTable.column). The two-string form references("table", "column") is gone \u2014 a string cannot be type-checked and cannot point at a table that does not exist yet.`
2354
+ );
2355
+ }
2356
+ this._def.referencesThunk = target;
2357
+ if (opts?.as !== void 0) this._def.refAs = opts.as;
2358
+ if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
2231
2359
  return new _ColumnBuilder(this._def.type, this._def);
2232
2360
  }
2233
2361
  /**
2234
- * Add a real DB-level foreign key to the canonical, server-minted installation
2235
- * anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
2236
- * root (`ins_...`). Sugar for `.references("auth.installations", "id")`.
2362
+ * Foreign key onto THIS table (`parent_id id`) — category trees, comment
2363
+ * replies, org charts.
2237
2364
  *
2238
- * An installation is an APP INSTALL, not a user: this FK is NOT user ownership.
2239
- * A user-owned row STILL needs its own `.referencesAuthUser(...)` FK so account
2240
- * erasure removes it — an installation reference alone does not tie a row to a
2241
- * user's deletion. Use this only for install-scoped state (device prefs, push
2242
- * routing, …), alongside a separate auth-user FK where the row is user-owned.
2243
- *
2244
- * `auth.installations` lives in the SAME tenant DB (palauth-owned); its `id` is
2245
- * `text` (`ins_<uuid>`), so the referencing column must be `text()` too. ON
2246
- * DELETE is REQUIRED and may only be `cascade` or `set null` (same allowed set
2247
- * as an auth-user FK): an installation revoke / orphan cleanup must never be
2248
- * blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
2249
- * boundary; this signature is the compile-time DX mirror.
2365
+ * No thunk and no type annotation: the target table is the one being declared,
2366
+ * so there is nothing to defer and nothing for TypeScript to chase in a circle.
2367
+ * Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
2368
+ * always goes through a callback; measured, we do not need one.
2250
2369
  */
2251
- referencesInstallation(onDelete) {
2252
- refuseOnVector(this._def, "referencesInstallation");
2253
- this._def.references = { table: "auth.installations", column: "id" };
2254
- this._def.onDeleteAction = onDelete;
2370
+ selfReferences(column, opts) {
2371
+ refuseOnVector(this._def, "selfReferences");
2372
+ this._def.selfRefColumn = column;
2373
+ if (opts?.as !== void 0) this._def.refAs = opts.as;
2374
+ if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
2255
2375
  return new _ColumnBuilder(this._def.type, this._def);
2256
2376
  }
2257
2377
  /** Set the ON DELETE action for a foreign key reference. */
@@ -2326,6 +2446,28 @@ function vector(dimensions) {
2326
2446
  b._def.dimensions = dimensions;
2327
2447
  return b;
2328
2448
  }
2449
+ function ownedByUser() {
2450
+ const b = new ColumnBuilder("text");
2451
+ b._def.nullable = false;
2452
+ b._def.references = { table: "auth.users", column: "id" };
2453
+ b._def.onDeleteAction = "cascade";
2454
+ b._def.owns = true;
2455
+ return b;
2456
+ }
2457
+ function userRef(opts) {
2458
+ const b = new ColumnBuilder("text");
2459
+ b._def.references = { table: "auth.users", column: "id" };
2460
+ b._def.onDeleteAction = opts.onDelete;
2461
+ if (opts.as !== void 0) b._def.refAs = opts.as;
2462
+ return b;
2463
+ }
2464
+ function installationRef(opts) {
2465
+ const b = new ColumnBuilder("text");
2466
+ b._def.references = { table: "auth.installations", column: "id" };
2467
+ b._def.onDeleteAction = opts.onDelete;
2468
+ if (opts.as !== void 0) b._def.refAs = opts.as;
2469
+ return b;
2470
+ }
2329
2471
 
2330
2472
  // src/db/raw.ts
2331
2473
  function raw(name, up, opts) {
@@ -2436,50 +2578,73 @@ function rowType(def) {
2436
2578
  function optionalOnInsert(def) {
2437
2579
  return def.nullable === true || def.defaultRandom === true || def.defaultNow === true || def.defaultValue !== void 0;
2438
2580
  }
2439
- var AUTH_USERS_TABLE = "auth.users";
2440
- function ownerColumnOf(table) {
2441
- for (const [col, builder] of Object.entries(table.columns)) {
2442
- if (builder._def.references?.table === AUTH_USERS_TABLE) return col;
2443
- }
2444
- return null;
2581
+ function forwardName(column) {
2582
+ return column.endsWith("_id") ? column.slice(0, -3) : column;
2445
2583
  }
2446
- function buildChildEdges(schema) {
2447
- const edges = /* @__PURE__ */ new Map();
2448
- for (const name of Object.keys(schema.tables)) edges.set(name, []);
2449
- for (const childName of Object.keys(schema.tables)) {
2450
- const table = schema.tables[childName];
2451
- if (table === void 0) continue;
2452
- for (const [col, builder] of Object.entries(table.columns)) {
2453
- const ref = builder._def.references;
2454
- if (ref === void 0) continue;
2455
- if (ref.table === childName) continue;
2456
- const bucket = edges.get(ref.table);
2457
- if (bucket === void 0) continue;
2458
- if (bucket.some((e) => e.child === childName)) continue;
2459
- bucket.push({ child: childName, column: col });
2460
- }
2461
- }
2462
- return edges;
2584
+ function qualify(schemaName, tableName) {
2585
+ return schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
2463
2586
  }
2464
- function pruneCycles(raw2) {
2465
- const pruned = /* @__PURE__ */ new Map();
2466
- for (const table of raw2.keys()) pruned.set(table, []);
2467
- const state = /* @__PURE__ */ new Map();
2468
- function visit(table) {
2469
- state.set(table, "visiting");
2470
- for (const edge of raw2.get(table) ?? []) {
2471
- if (state.get(edge.child) === "visiting") continue;
2472
- pruned.get(table)?.push(edge);
2473
- if (state.get(edge.child) === void 0) visit(edge.child);
2587
+ function buildRelations(schemas) {
2588
+ const out = /* @__PURE__ */ new Map();
2589
+ const keyOf = /* @__PURE__ */ new Map();
2590
+ for (const schema of schemas) {
2591
+ for (const t of Object.values(schema.tables)) {
2592
+ const key = qualify(schema.name, t.name);
2593
+ out.set(key, []);
2594
+ keyOf.set(t.name, key);
2474
2595
  }
2475
- state.set(table, "done");
2476
2596
  }
2477
- for (const table of raw2.keys()) {
2478
- if (state.get(table) === void 0) visit(table);
2597
+ for (const schema of schemas) {
2598
+ for (const table of Object.values(schema.tables)) {
2599
+ const childKey = qualify(schema.name, table.name);
2600
+ const bucket = out.get(childKey);
2601
+ if (bucket === void 0) continue;
2602
+ const byTarget = /* @__PURE__ */ new Map();
2603
+ for (const [col, builder] of Object.entries(table.columns)) {
2604
+ const ref = builder._def.references;
2605
+ if (ref === void 0) continue;
2606
+ const list = byTarget.get(ref.table) ?? [];
2607
+ list.push(col);
2608
+ byTarget.set(ref.table, list);
2609
+ }
2610
+ for (const [target, cols] of byTarget) {
2611
+ if (cols.length < 2) continue;
2612
+ const unnamed = cols.filter((c) => {
2613
+ const d = table.columns[c]?._def;
2614
+ return d?.refAs === void 0 && d?.owns !== true;
2615
+ });
2616
+ if (unnamed.length > 0) {
2617
+ throw new Error(
2618
+ `table "${table.name}" declares ${cols.length} foreign keys onto "${target}" (${cols.join(", ")}) \u2014 name each one: references(() => \u2026, { as: "\u2026" })`
2619
+ );
2620
+ }
2621
+ }
2622
+ for (const [col, builder] of Object.entries(table.columns)) {
2623
+ const def = builder._def;
2624
+ if (def.references === void 0) continue;
2625
+ const name = def.owns === true ? "owner" : def.refAs ?? forwardName(col);
2626
+ if (bucket.some((r) => r.name === name)) {
2627
+ throw new Error(
2628
+ name === "owner" ? `table "${table.name}": two columns claim ownership of the row \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.` : `table "${table.name}": two relations resolve to the name "${name}" \u2014 give one an explicit { as: "\u2026" }`
2629
+ );
2630
+ }
2631
+ const targetKey = keyOf.get(def.references.table) ?? def.references.table;
2632
+ bucket.push({ name, to: targetKey, kind: "one", via: col });
2633
+ const parent = out.get(targetKey);
2634
+ if (parent === void 0 || targetKey === childKey) continue;
2635
+ const reverse = def.refAs ?? table.name;
2636
+ if (parent.some((r) => r.name === reverse)) {
2637
+ throw new Error(
2638
+ `table "${def.references.table}": two reverse relations resolve to the name "${reverse}" \u2014 give one an explicit { as: "\u2026" }`
2639
+ );
2640
+ }
2641
+ parent.push({ name: reverse, to: childKey, kind: "many", via: col });
2642
+ }
2643
+ }
2479
2644
  }
2480
- return pruned;
2645
+ return out;
2481
2646
  }
2482
- function tableBlock(table, children, indent) {
2647
+ function tableBlock(table, relations, indent) {
2483
2648
  const cols = Object.entries(table.columns);
2484
2649
  const rowLines = cols.map(([col, builder]) => {
2485
2650
  return `${indent} ${col}: ${rowType(builder._def)};`;
@@ -2489,8 +2654,9 @@ function tableBlock(table, children, indent) {
2489
2654
  const opt = optionalOnInsert(def) ? "?" : "";
2490
2655
  return `${indent} ${col}${opt}: ${rowType(def)};`;
2491
2656
  });
2492
- const owner = ownerColumnOf(table);
2493
- const childEntries = children.map((e) => `${JSON.stringify(e.child)}: ${JSON.stringify(e.column)}`).join("; ");
2657
+ const relEntries = relations.map(
2658
+ (r) => `${r.name}: { to: ${JSON.stringify(r.to)}; kind: ${JSON.stringify(r.kind)}; via: ${JSON.stringify(r.via)} }`
2659
+ ).join("; ");
2494
2660
  return [
2495
2661
  `${indent}${table.name}: {`,
2496
2662
  `${indent} row: {`,
@@ -2499,8 +2665,7 @@ function tableBlock(table, children, indent) {
2499
2665
  `${indent} insert: {`,
2500
2666
  ...insertLines,
2501
2667
  `${indent} };`,
2502
- `${indent} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
2503
- `${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
2668
+ `${indent} relations: {${relEntries === "" ? "" : ` ${relEntries} `}};`,
2504
2669
  // searchable: vector kolonu YA DA search beyanı → EnvTypedTable'da search()
2505
2670
  // üyesini açan yapısal bayrak (FR-013). Yokken satır hiç üretilmez ki
2506
2671
  // mevcut şemaların d.ts'i bayt-aynı kalsın.
@@ -2508,22 +2673,37 @@ function tableBlock(table, children, indent) {
2508
2673
  `${indent}};`
2509
2674
  ].join("\n");
2510
2675
  }
2511
- function makeEnvDts(schema) {
2512
- const tableNames = Object.keys(schema.tables);
2513
- const children = pruneCycles(buildChildEdges(schema));
2514
- const blocks = tableNames.map(
2515
- (name) => tableBlock(schema.tables[name], children.get(name) ?? [], " ")
2676
+ function makeEnvDts(schemas) {
2677
+ const relations = buildRelations(schemas);
2678
+ const qualify2 = (schemaName, table) => schemaName === "public" ? table : `${schemaName}.${table}`;
2679
+ const publicSchema = schemas.find((s) => s.name === "public");
2680
+ const others = schemas.filter((s) => s.name !== "public");
2681
+ const publicBlocks = Object.keys(publicSchema?.tables ?? {}).map(
2682
+ (name) => tableBlock(publicSchema.tables[name], relations.get(name) ?? [], " ")
2516
2683
  );
2517
- const body = blocks.length > 0 ? `
2518
- ${blocks.join("\n")}
2684
+ const body = publicBlocks.length > 0 ? `
2685
+ ${publicBlocks.join("\n")}
2686
+ ` : "";
2687
+ const schemaBlocks = others.map((schema) => {
2688
+ const inner = Object.keys(schema.tables).map(
2689
+ (name) => tableBlock(schema.tables[name], relations.get(qualify2(schema.name, name)) ?? [], " ")
2690
+ );
2691
+ return ` ${schema.name}: {
2692
+ ${inner.join("\n")}
2693
+ };`;
2694
+ });
2695
+ const schemasBody = schemaBlocks.length > 0 ? `
2696
+ ${schemaBlocks.join("\n")}
2519
2697
  ` : "";
2520
2698
  return `// AUTO-GENERATED by @palbase/backend \u2014 DO NOT EDIT.
2521
- // Regenerated from db/schema.ts by \`palbase build\` and by every deploy.
2699
+ // Regenerated from db/*.ts by \`palbase build\` and by every deploy.
2522
2700
  // Augments the @palbase/backend/env \`Tables\` interface so \`Database.tables.*\`
2523
- // is typed with no import and no generic.
2701
+ // is typed with no import and no generic. Schemas other than \`public\` land
2702
+ // under \`Schemas\`, reached with \`Database.schema("<name>").tables.*\`.
2524
2703
 
2525
2704
  declare module "@palbase/backend/env" {
2526
2705
  interface Tables {${body}}
2706
+ interface Schemas {${schemasBody}}
2527
2707
  }
2528
2708
 
2529
2709
  export {};
@@ -2579,6 +2759,8 @@ export {};
2579
2759
  // src/decorators/registry.ts
2580
2760
  var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
2581
2761
  var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
2762
+ var ROOM = /* @__PURE__ */ Symbol.for("palbase.backend.room");
2763
+ var ROOM_HOOKS = /* @__PURE__ */ Symbol.for("palbase.backend.roomHooks");
2582
2764
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
2583
2765
  var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
2584
2766
  function carrierOf(target) {
@@ -2667,6 +2849,38 @@ function getRoutes(ctor) {
2667
2849
  ...r.throws !== void 0 ? { throws: r.throws.slice() } : {}
2668
2850
  }));
2669
2851
  }
2852
+ function ownRoomBuffer(carrier) {
2853
+ if (!Object.prototype.hasOwnProperty.call(carrier, ROOM_HOOKS)) {
2854
+ carrier[ROOM_HOOKS] = { hooks: {}, messages: {} };
2855
+ }
2856
+ return carrier[ROOM_HOOKS];
2857
+ }
2858
+ function recordRoomHook(target, hook, fnName) {
2859
+ const buffer = ownRoomBuffer(carrierOf(target));
2860
+ const existing = buffer.hooks[hook];
2861
+ if (existing !== void 0 && existing !== fnName) {
2862
+ const label = `On${hook.charAt(0).toUpperCase()}${hook.slice(1)}`;
2863
+ throw new Error(
2864
+ `@${label} is declared twice in one room (${existing} and ${fnName}). A room has one of each hook.`
2865
+ );
2866
+ }
2867
+ buffer.hooks[hook] = fnName;
2868
+ }
2869
+ function recordRoomMessage(target, name, fnName, schema) {
2870
+ const buffer = ownRoomBuffer(carrierOf(target));
2871
+ const existing = buffer.messages[name];
2872
+ if (existing !== void 0 && existing.fnName !== fnName) {
2873
+ throw new Error(
2874
+ `@OnMessage("${name}") is declared twice in one room (${existing.fnName} and ${fnName}).`
2875
+ );
2876
+ }
2877
+ buffer.messages[name] = { fnName, schema };
2878
+ }
2879
+ function recordRoom(ctor, meta) {
2880
+ const carrier = carrierOf(ctor);
2881
+ const buffer = ownRoomBuffer(carrier);
2882
+ carrier[ROOM] = { ...meta, hooks: buffer.hooks, messages: buffer.messages };
2883
+ }
2670
2884
 
2671
2885
  // src/decorators/controller.ts
2672
2886
  var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
@@ -2979,6 +3193,56 @@ function Signal() {
2979
3193
  };
2980
3194
  }
2981
3195
 
3196
+ // src/decorators/room.ts
3197
+ var DEFAULT_GRACE_MS = 6e4;
3198
+ function assertPattern(pattern) {
3199
+ if (pattern.includes("/")) {
3200
+ throw new Error(
3201
+ `@Room("${pattern}"): channel patterns are colon-separated, not slash-separated. Write "${pattern.replace(/\//g, ":")}" \u2014 a slash matches no live topic.`
3202
+ );
3203
+ }
3204
+ const segments = pattern.split(":");
3205
+ if (segments.some((s) => s === "")) {
3206
+ throw new Error(`@Room("${pattern}"): empty segment \u2014 every segment must be non-empty.`);
3207
+ }
3208
+ const seen = /* @__PURE__ */ new Set();
3209
+ for (const s of segments) {
3210
+ if (!s.startsWith("{") || !s.endsWith("}")) continue;
3211
+ const name = s.slice(1, -1);
3212
+ if (name === "") throw new Error(`@Room("${pattern}"): "{}" names no parameter.`);
3213
+ if (seen.has(name)) {
3214
+ throw new Error(`@Room("${pattern}"): parameter "{${name}}" appears twice.`);
3215
+ }
3216
+ seen.add(name);
3217
+ }
3218
+ }
3219
+ function Room(pattern, options) {
3220
+ return function(ctor) {
3221
+ assertPattern(pattern);
3222
+ recordRoom(ctor, {
3223
+ pattern,
3224
+ events: options.events,
3225
+ graceMs: options.graceMs ?? DEFAULT_GRACE_MS
3226
+ });
3227
+ return ctor;
3228
+ };
3229
+ }
3230
+ function hookDecorator(hook) {
3231
+ return () => function(target, propertyKey) {
3232
+ recordRoomHook(target, hook, String(propertyKey));
3233
+ };
3234
+ }
3235
+ var OnAuthorize = hookDecorator("authorize");
3236
+ var OnFirst = hookDecorator("first");
3237
+ var OnJoin = hookDecorator("join");
3238
+ var OnLeave = hookDecorator("leave");
3239
+ var OnEmpty = hookDecorator("empty");
3240
+ function OnMessage(name, schema) {
3241
+ return function(target, propertyKey) {
3242
+ recordRoomMessage(target, name, String(propertyKey), schema);
3243
+ };
3244
+ }
3245
+
2982
3246
  // src/decorators/params.ts
2983
3247
  function makeParamDecorator(kind, extra) {
2984
3248
  return function(target, propertyKey, parameterIndex) {
@@ -3399,6 +3663,12 @@ var import_zod2 = require("zod");
3399
3663
  NotFound,
3400
3664
  Notifications,
3401
3665
  On,
3666
+ OnAuthorize,
3667
+ OnEmpty,
3668
+ OnFirst,
3669
+ OnJoin,
3670
+ OnLeave,
3671
+ OnMessage,
3402
3672
  OptionalUser,
3403
3673
  PALBASE_EXTENSIONS,
3404
3674
  PalError,
@@ -3413,11 +3683,13 @@ var import_zod2 = require("zod");
3413
3683
  Realtime,
3414
3684
  Req,
3415
3685
  RequestId,
3686
+ Room,
3416
3687
  Secrets,
3417
3688
  Signal,
3418
3689
  Sse,
3419
3690
  SseOut,
3420
3691
  Storage,
3692
+ TABLE_META,
3421
3693
  TooManyRequests,
3422
3694
  TraceId,
3423
3695
  TxPlanError,
@@ -3446,6 +3718,7 @@ var import_zod2 = require("zod");
3446
3718
  defineError,
3447
3719
  defineMiddleware,
3448
3720
  defineSchema,
3721
+ defineTable,
3449
3722
  enumType,
3450
3723
  getErrorRegistry,
3451
3724
  getHookConfig,
@@ -3454,6 +3727,7 @@ var import_zod2 = require("zod");
3454
3727
  getRoutes,
3455
3728
  getWebhookConfig,
3456
3729
  inc,
3730
+ installationRef,
3457
3731
  integer,
3458
3732
  isPalbaseExtension,
3459
3733
  jsonb,
@@ -3466,6 +3740,7 @@ var import_zod2 = require("zod");
3466
3740
  onShutdown,
3467
3741
  onStart,
3468
3742
  openai,
3743
+ ownedByUser,
3469
3744
  ownerOnly,
3470
3745
  policy,
3471
3746
  publicChannel,
@@ -3475,6 +3750,7 @@ var import_zod2 = require("zod");
3475
3750
  text,
3476
3751
  timestamp,
3477
3752
  toSchemaJSON,
3753
+ userRef,
3478
3754
  uuid,
3479
3755
  vector,
3480
3756
  z