trellis 3.2.0 → 3.2.2

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 (258) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +61 -171
  3. package/dist/browser/index.d.ts +18 -0
  4. package/dist/browser/index.d.ts.map +1 -0
  5. package/dist/browser/index.js +95 -0
  6. package/dist/{chunk-3ETIIPPB.js → chunk-2W3MHTMG.js} +190 -27
  7. package/dist/{chunk-MBNBTJCR.js → chunk-6GEZ6DN5.js} +2 -2
  8. package/dist/chunk-7ZFRVCUE.js +211 -0
  9. package/dist/chunk-CAG4FULI.js +121 -0
  10. package/dist/{chunk-GD5QJXSS.js → chunk-FWRS4CSC.js} +2 -2
  11. package/dist/{chunk-36VSSTL7.js → chunk-GU374KWZ.js} +1 -1
  12. package/dist/{chunk-3GDRZTAW.js → chunk-H6JB3PZ3.js} +1 -1
  13. package/dist/{chunk-ZHVI7BQN.js → chunk-IZX2PLIB.js} +191 -28
  14. package/dist/chunk-J2TXUFCZ.js +289 -0
  15. package/dist/{chunk-BJ7EZ7FP.js → chunk-JHHEXEDM.js} +17 -0
  16. package/dist/{chunk-X762XWVC.js → chunk-KIJITUZB.js} +1 -1
  17. package/dist/{chunk-7WQGTFVJ.js → chunk-L5N5PR2S.js} +130 -75
  18. package/dist/{chunk-BXSTJVE3.js → chunk-N6MYZT4O.js} +2 -1
  19. package/dist/{chunk-KKDRMIV6.js → chunk-QB5ISE47.js} +1 -1
  20. package/dist/{chunk-HO4TURXW.js → chunk-S3XLFZ27.js} +28 -11
  21. package/dist/{chunk-BQBUVMRN.js → chunk-S6GBJ2ZB.js} +25 -14
  22. package/dist/{chunk-IUWVEEOX.js → chunk-SGMDTWJJ.js} +4 -0
  23. package/dist/{chunk-FZVCXDYW.js → chunk-UQISRW6T.js} +65 -12
  24. package/dist/{chunk-B3ZIFA2O.js → chunk-VBYUTRXX.js} +1 -1
  25. package/dist/cli/deploy-cli.d.ts +18 -0
  26. package/dist/cli/deploy-cli.d.ts.map +1 -0
  27. package/dist/cli/index.d.ts +1 -1
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/index.js +153 -100
  30. package/dist/client/index.browser.d.ts +29 -0
  31. package/dist/client/index.browser.d.ts.map +1 -0
  32. package/dist/client/index.browser.js +64 -0
  33. package/dist/client/index.js +10 -10
  34. package/dist/client/sdk.browser.d.ts +104 -0
  35. package/dist/client/sdk.browser.d.ts.map +1 -0
  36. package/dist/{sdk-LZ7W3EEY.js → client/sdk.browser.js} +2 -2
  37. package/dist/client/sdk.js +1 -1
  38. package/dist/core/index.js +6 -6
  39. package/dist/db/index.js +18 -18
  40. package/dist/db/inspector.js +6 -6
  41. package/dist/db/trellis.css +1 -1
  42. package/dist/decisions/index.js +2 -2
  43. package/dist/deploy-LQ7GUGJ5.js +9 -0
  44. package/dist/{deploy-66EC752W.js → deploy-YNVG5E4E.js} +1 -1
  45. package/dist/deploy-cli-24BCHAXY.js +67 -0
  46. package/dist/deploy-cli-C35HTITO.js +67 -0
  47. package/dist/embeddings/auto-embed.d.ts.map +1 -1
  48. package/dist/embeddings/index.js +1 -1
  49. package/dist/embeddings/store.d.ts.map +1 -1
  50. package/dist/engine.d.ts +27 -7
  51. package/dist/engine.d.ts.map +1 -1
  52. package/dist/index.js +6 -6
  53. package/dist/mcp/server.d.ts.map +1 -1
  54. package/dist/react/index.js +2 -2
  55. package/dist/react/schema-hooks.js +7 -7
  56. package/dist/{remote-manager-SG545WNY.js → remote-manager-ODE3T2KR.js} +5 -5
  57. package/dist/scaffold/seed.d.ts.map +1 -1
  58. package/dist/scaffold/write.d.ts +1 -1
  59. package/dist/scaffold/write.d.ts.map +1 -1
  60. package/dist/schema/index.js +4 -3
  61. package/dist/server/deploy-meta.d.ts +25 -0
  62. package/dist/server/deploy-meta.d.ts.map +1 -0
  63. package/dist/server/deploy.d.ts +7 -2
  64. package/dist/server/deploy.d.ts.map +1 -1
  65. package/dist/server/index.d.ts +5 -2
  66. package/dist/server/index.d.ts.map +1 -1
  67. package/dist/server/index.js +52 -18
  68. package/dist/server/realtime.d.ts +4 -1
  69. package/dist/server/realtime.d.ts.map +1 -1
  70. package/dist/server/server.d.ts +1 -0
  71. package/dist/server/server.d.ts.map +1 -1
  72. package/dist/server/sprites.d.ts +18 -1
  73. package/dist/server/sprites.d.ts.map +1 -1
  74. package/dist/server/tenancy.d.ts +4 -0
  75. package/dist/server/tenancy.d.ts.map +1 -1
  76. package/dist/server/usage-meter.d.ts +53 -0
  77. package/dist/server/usage-meter.d.ts.map +1 -0
  78. package/dist/{server-IGBIIJSF.js → server-MB3OPSPI.js} +6 -1
  79. package/dist/{server-BOH7M4TT.js → server-ZVWGLLRF.js} +7 -2
  80. package/dist/sprites-KQGTGM6W.js +27 -0
  81. package/dist/{tenancy-S676GLID.js → tenancy-NBLLGCNJ.js} +1 -1
  82. package/dist/{tenancy-OP34NFFS.js → tenancy-POMWQP6M.js} +5 -3
  83. package/dist/vcs/index.js +3 -3
  84. package/dist/vcs/types.d.ts +2 -0
  85. package/dist/vcs/types.d.ts.map +1 -1
  86. package/package.json +17 -6
  87. package/dist/better-sqlite-backend-PII64VE6.js +0 -159
  88. package/dist/better-sqlite-backend-VUBVQ7SN.js +0 -209
  89. package/dist/better-sqlite-backend-Y3AQN65E.js +0 -159
  90. package/dist/better-sqlite-backend-Y3M7DXOX.js +0 -159
  91. package/dist/boot-middleware-FJDGVUM4.js +0 -9
  92. package/dist/boot-middleware-MLJUTLUM.js +0 -9
  93. package/dist/boot-middleware-ZZPDZNYR.js +0 -9
  94. package/dist/chunk-276LKLZR.js +0 -359
  95. package/dist/chunk-2CX5UCRV.js +0 -111
  96. package/dist/chunk-2ESYSVXG.js +0 -48
  97. package/dist/chunk-2I5JU73Y.js +0 -340
  98. package/dist/chunk-2UNSRRNB.js +0 -171
  99. package/dist/chunk-335UEHQR.js +0 -907
  100. package/dist/chunk-4CF2L46N.js +0 -29
  101. package/dist/chunk-5CINODKT.js +0 -41
  102. package/dist/chunk-5ESGWJ5L.js +0 -45
  103. package/dist/chunk-5FGLQGMF.js +0 -1676
  104. package/dist/chunk-5IQZFITY.js +0 -357
  105. package/dist/chunk-5JWFAQDW.js +0 -150
  106. package/dist/chunk-62UHTVU3.js +0 -141
  107. package/dist/chunk-66UJQT2I.js +0 -35
  108. package/dist/chunk-6RNNVOAS.js +0 -355
  109. package/dist/chunk-6X23PZ6H.js +0 -969
  110. package/dist/chunk-7C7LL6MB.js +0 -315
  111. package/dist/chunk-7F4HM3JG.js +0 -394
  112. package/dist/chunk-7KJ7T4D3.js +0 -910
  113. package/dist/chunk-7QQQNUB5.js +0 -92
  114. package/dist/chunk-7QWJBS72.js +0 -946
  115. package/dist/chunk-7R36OZK3.js +0 -957
  116. package/dist/chunk-7UPKCOHE.js +0 -946
  117. package/dist/chunk-A3BONW34.js +0 -35
  118. package/dist/chunk-AHMDVHBL.js +0 -141
  119. package/dist/chunk-AW6A4OZY.js +0 -171
  120. package/dist/chunk-BJ5EKCJ5.js +0 -458
  121. package/dist/chunk-BRGYDO7D.js +0 -223
  122. package/dist/chunk-C5KPSEDN.js +0 -359
  123. package/dist/chunk-CCTNYTUC.js +0 -940
  124. package/dist/chunk-CYQ2OVD2.js +0 -35
  125. package/dist/chunk-DDAK7YOE.js +0 -299
  126. package/dist/chunk-DRILKBY6.js +0 -965
  127. package/dist/chunk-DXE24HNN.js +0 -45
  128. package/dist/chunk-E7ICUNW7.js +0 -945
  129. package/dist/chunk-EAL2PUOE.js +0 -10022
  130. package/dist/chunk-ELT7MLZB.js +0 -109
  131. package/dist/chunk-EWEIRF6G.js +0 -171
  132. package/dist/chunk-F5VDGQJC.js +0 -120
  133. package/dist/chunk-FCPL3IEQ.js +0 -98
  134. package/dist/chunk-FDPKYSV7.js +0 -96
  135. package/dist/chunk-FES3TC24.js +0 -171
  136. package/dist/chunk-FOWKR5TW.js +0 -129
  137. package/dist/chunk-G3XIHPSQ.js +0 -361
  138. package/dist/chunk-GKJLYLVU.js +0 -359
  139. package/dist/chunk-GNXHXNVQ.js +0 -113
  140. package/dist/chunk-GSR6ILTM.js +0 -277
  141. package/dist/chunk-GWRIMRSX.js +0 -45
  142. package/dist/chunk-H72UW2HJ.js +0 -45
  143. package/dist/chunk-HGI74PXB.js +0 -7417
  144. package/dist/chunk-HGRA37LO.js +0 -145
  145. package/dist/chunk-HXSRQO3L.js +0 -145
  146. package/dist/chunk-IG2X2BVO.js +0 -373
  147. package/dist/chunk-IIJVDD54.js +0 -373
  148. package/dist/chunk-IX4BBOIT.js +0 -394
  149. package/dist/chunk-IXU4RMQM.js +0 -349
  150. package/dist/chunk-J6OUIION.js +0 -857
  151. package/dist/chunk-K46RNFDE.js +0 -171
  152. package/dist/chunk-KAGU2XMY.js +0 -359
  153. package/dist/chunk-KCVYJNVX.js +0 -394
  154. package/dist/chunk-KYLIQ3YI.js +0 -399
  155. package/dist/chunk-KZYJJIRN.js +0 -946
  156. package/dist/chunk-LFOX744K.js +0 -96
  157. package/dist/chunk-LHOVJEWR.js +0 -7403
  158. package/dist/chunk-LOPXTPF3.js +0 -120
  159. package/dist/chunk-LZR3VIFU.js +0 -907
  160. package/dist/chunk-MCKGQKYU.js +0 -15
  161. package/dist/chunk-MU6EOS6F.js +0 -0
  162. package/dist/chunk-MWRQ2TXT.js +0 -357
  163. package/dist/chunk-NO73OK2I.js +0 -102
  164. package/dist/chunk-NPE2YJHK.js +0 -394
  165. package/dist/chunk-NSDXU6QX.js +0 -2213
  166. package/dist/chunk-NXIRDSZ6.js +0 -145
  167. package/dist/chunk-NZWFNHCS.js +0 -407
  168. package/dist/chunk-OB4CMMX6.js +0 -260
  169. package/dist/chunk-OBKU74FU.js +0 -7411
  170. package/dist/chunk-OQNDWRDE.js +0 -145
  171. package/dist/chunk-OVWAJMB6.js +0 -394
  172. package/dist/chunk-PDHLBTTD.js +0 -45
  173. package/dist/chunk-PEQNRKLG.js +0 -210
  174. package/dist/chunk-QERIS7QY.js +0 -7403
  175. package/dist/chunk-QHA2DM3C.js +0 -373
  176. package/dist/chunk-QLM4HSYC.js +0 -45
  177. package/dist/chunk-S2OT3TQJ.js +0 -283
  178. package/dist/chunk-S5PHSN5K.js +0 -907
  179. package/dist/chunk-SIAA4J6H.js +0 -21
  180. package/dist/chunk-SQBNLUIA.js +0 -171
  181. package/dist/chunk-STLN6A6B.js +0 -355
  182. package/dist/chunk-U7HV7GU2.js +0 -17
  183. package/dist/chunk-UGZHOGRA.js +0 -39
  184. package/dist/chunk-UICF66FS.js +0 -359
  185. package/dist/chunk-UK3EQREE.js +0 -1078
  186. package/dist/chunk-UPKPURVY.js +0 -394
  187. package/dist/chunk-URC74VCN.js +0 -959
  188. package/dist/chunk-V3G7Q3QQ.js +0 -102
  189. package/dist/chunk-VUJ6SA55.js +0 -303
  190. package/dist/chunk-VUJQLIML.js +0 -141
  191. package/dist/chunk-VWKLKLYB.js +0 -267
  192. package/dist/chunk-VYC3RZMK.js +0 -277
  193. package/dist/chunk-WMERMSWI.js +0 -154
  194. package/dist/chunk-WMR777PO.js +0 -2213
  195. package/dist/chunk-X46IECAC.js +0 -517
  196. package/dist/chunk-XEN3OEYY.js +0 -2234
  197. package/dist/chunk-XZQ66K7A.js +0 -124
  198. package/dist/chunk-YY3GF7DH.js +0 -7411
  199. package/dist/chunk-ZE5R3H4C.js +0 -907
  200. package/dist/chunk-ZMN5LUB7.js +0 -7411
  201. package/dist/chunk-ZUV4MD5U.js +0 -94
  202. package/dist/config-KMY6RRK3.js +0 -19
  203. package/dist/config-VDMK2DUJ.js +0 -19
  204. package/dist/deploy-SO7AFK6L.js +0 -9
  205. package/dist/engine-7GXBM4IT.js +0 -7
  206. package/dist/engine-NSR6BIF7.js +0 -7
  207. package/dist/factory-2P64OC7M.js +0 -34
  208. package/dist/factory-YP4ZZK5K.js +0 -34
  209. package/dist/import-AJLYHFIA.js +0 -10
  210. package/dist/launch-explorer-AI6L2AWJ.js +0 -91
  211. package/dist/query-GJL3CVGN.js +0 -31
  212. package/dist/query-MBJSK3EB.js +0 -31
  213. package/dist/query-TJHEVSKL.js +0 -31
  214. package/dist/query-WUIKOHPE.js +0 -524
  215. package/dist/remote-manager-BLJZ6KF6.js +0 -224
  216. package/dist/remote-manager-BPYE46SI.js +0 -223
  217. package/dist/remote-manager-FMESEAFA.js +0 -224
  218. package/dist/remote-manager-K2WZ5RHY.js +0 -223
  219. package/dist/remote-manager-SG5PQKCH.js +0 -224
  220. package/dist/remote-manager-WGP6MIQZ.js +0 -224
  221. package/dist/sdk-3KBB3E6V.js +0 -16
  222. package/dist/sdk-4ZQO6UTU.js +0 -16
  223. package/dist/sdk-6JA6GV3G.js +0 -16
  224. package/dist/sdk-6VTP5UTZ.js +0 -16
  225. package/dist/sdk-7VS6TYCD.js +0 -16
  226. package/dist/sdk-HQ3FTLRT.js +0 -16
  227. package/dist/sdk-HZ7NU3XE.js +0 -15
  228. package/dist/sdk-KBQE2OXT.js +0 -16
  229. package/dist/sdk-MSCDZ7JU.js +0 -15
  230. package/dist/sdk-WNZOGO3S.js +0 -15
  231. package/dist/sdk-YLV33ZCW.js +0 -9
  232. package/dist/sdk-ZSUD2LLJ.js +0 -16
  233. package/dist/server-AFXFFBWA.js +0 -11
  234. package/dist/server-AGHC4VUL.js +0 -11
  235. package/dist/server-DBUY2KY5.js +0 -11
  236. package/dist/server-GWBQJPRG.js +0 -11
  237. package/dist/server-JZZFULVU.js +0 -11
  238. package/dist/server-L5XAF4M5.js +0 -11
  239. package/dist/server-NYFUHANA.js +0 -12
  240. package/dist/server-OABUB2LO.js +0 -12
  241. package/dist/server-P2CKZWWV.js +0 -12
  242. package/dist/sprites-XVSMQLSI.js +0 -15
  243. package/dist/sql-wasm-FORCZFHZ.js +0 -2148
  244. package/dist/sql-wasm-JZH76RLO.js +0 -2148
  245. package/dist/sqljs-backend-CJQOIQYE.js +0 -266
  246. package/dist/sqljs-backend-KWXBUDUO.js +0 -266
  247. package/dist/state-demo.bundle.js +0 -1033
  248. package/dist/tenancy-CBWYEVUB.js +0 -11
  249. package/dist/tenancy-FO6WVYFN.js +0 -13
  250. package/dist/tenancy-FWDBESMX.js +0 -14
  251. package/dist/tenancy-HGPIS5QA.js +0 -13
  252. package/dist/tenancy-HXFALA37.js +0 -14
  253. package/dist/tenancy-V7ARWNNV.js +0 -14
  254. package/dist/tenancy-ZDN4GXK5.js +0 -13
  255. package/dist/tenancy-ZT5VMY7B.js +0 -14
  256. package/dist/vm-config-YZRJ3KUB.js +0 -21
  257. /package/dist/{chunk-2BRTCEHB.js → chunk-PZ4XYZ4J.js} +0 -0
  258. /package/dist/{chunk-EO3J7GRP.js → chunk-WXSWA3MV.js} +0 -0
@@ -1,969 +0,0 @@
1
- import {
2
- entityRecordToPlain,
3
- hydrateBindings
4
- } from "./chunk-5CINODKT.js";
5
- import {
6
- parseSimple
7
- } from "./chunk-X5FLTRUB.js";
8
-
9
- // src/server/server.ts
10
- import { existsSync, readFileSync } from "fs";
11
- import { dirname, join } from "path";
12
- import { fileURLToPath } from "url";
13
-
14
- // src/server/auth.ts
15
- var ANONYMOUS = {
16
- userId: null,
17
- tenantId: null,
18
- roles: [],
19
- claims: {},
20
- authenticated: false
21
- };
22
- function base64UrlEncode(input) {
23
- return btoa(String.fromCharCode(...input)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
24
- }
25
- function base64UrlDecode(input) {
26
- const padded = input.replace(/-/g, "+").replace(/_/g, "/");
27
- const padLen = (4 - padded.length % 4) % 4;
28
- const base64 = padded + "=".repeat(padLen);
29
- const binary = atob(base64);
30
- return Uint8Array.from(binary, (c) => c.charCodeAt(0));
31
- }
32
- async function hmacKey(secret) {
33
- return crypto.subtle.importKey(
34
- "raw",
35
- new TextEncoder().encode(secret),
36
- { name: "HMAC", hash: "SHA-256" },
37
- false,
38
- ["sign", "verify"]
39
- );
40
- }
41
- async function signJwt(payload, secret, expiresInSeconds = 86400) {
42
- const header = { alg: "HS256", typ: "JWT" };
43
- const now = Math.floor(Date.now() / 1e3);
44
- const claims = { iat: now, exp: now + expiresInSeconds, ...payload };
45
- const enc = new TextEncoder();
46
- const headerB64 = base64UrlEncode(enc.encode(JSON.stringify(header)));
47
- const payloadB64 = base64UrlEncode(enc.encode(JSON.stringify(claims)));
48
- const signing = `${headerB64}.${payloadB64}`;
49
- const key = await hmacKey(secret);
50
- const sig = await crypto.subtle.sign("HMAC", key, enc.encode(signing));
51
- const sigB64 = base64UrlEncode(new Uint8Array(sig));
52
- return `${signing}.${sigB64}`;
53
- }
54
- async function verifyJwt(token, secret) {
55
- const parts = token.split(".");
56
- if (parts.length !== 3) return null;
57
- const [headerB64, payloadB64, sigB64] = parts;
58
- const enc = new TextEncoder();
59
- const signing = `${headerB64}.${payloadB64}`;
60
- try {
61
- const key = await hmacKey(secret);
62
- const sigRaw = base64UrlDecode(sigB64);
63
- const sigBuf = sigRaw.buffer.slice(
64
- sigRaw.byteOffset,
65
- sigRaw.byteOffset + sigRaw.byteLength
66
- );
67
- const valid = await crypto.subtle.verify(
68
- "HMAC",
69
- key,
70
- sigBuf,
71
- enc.encode(signing)
72
- );
73
- if (!valid) return null;
74
- const claims = JSON.parse(
75
- new TextDecoder().decode(base64UrlDecode(payloadB64))
76
- );
77
- const now = Math.floor(Date.now() / 1e3);
78
- if (typeof claims.exp === "number" && claims.exp < now) return null;
79
- return claims;
80
- } catch {
81
- return null;
82
- }
83
- }
84
- async function resolveAuth(authHeader, config) {
85
- if (!authHeader) {
86
- return config.allowPublic === false ? { ...ANONYMOUS, authenticated: false } : ANONYMOUS;
87
- }
88
- const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
89
- if (config.apiKey && token === config.apiKey) {
90
- return {
91
- userId: "service",
92
- tenantId: null,
93
- roles: ["admin"],
94
- claims: { sub: "service" },
95
- authenticated: true
96
- };
97
- }
98
- if (config.jwtSecret) {
99
- const claims = await verifyJwt(token, config.jwtSecret);
100
- if (claims) {
101
- return {
102
- userId: claims.sub ?? null,
103
- tenantId: claims.tenantId ?? null,
104
- roles: Array.isArray(claims.roles) ? claims.roles : typeof claims.role === "string" ? [claims.role] : [],
105
- claims,
106
- authenticated: true
107
- };
108
- }
109
- }
110
- return ANONYMOUS;
111
- }
112
- var GOOGLE_PROVIDER = {
113
- name: "google",
114
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
115
- tokenUrl: "https://oauth2.googleapis.com/token",
116
- userInfoUrl: "https://www.googleapis.com/oauth2/v3/userinfo",
117
- scopes: ["openid", "email", "profile"]
118
- };
119
- var GITHUB_PROVIDER = {
120
- name: "github",
121
- authUrl: "https://github.com/login/oauth/authorize",
122
- tokenUrl: "https://github.com/login/oauth/access_token",
123
- userInfoUrl: "https://api.github.com/user",
124
- scopes: ["read:user", "user:email"]
125
- };
126
- function buildOAuthUrl(provider, redirectUri, state) {
127
- const params = new URLSearchParams({
128
- client_id: provider.clientId,
129
- redirect_uri: redirectUri,
130
- response_type: "code",
131
- scope: provider.scopes.join(" "),
132
- state
133
- });
134
- return `${provider.authUrl}?${params}`;
135
- }
136
- async function exchangeOAuthCode(provider, code, redirectUri) {
137
- const tokenRes = await fetch(provider.tokenUrl, {
138
- method: "POST",
139
- headers: {
140
- "Content-Type": "application/x-www-form-urlencoded",
141
- Accept: "application/json"
142
- },
143
- body: new URLSearchParams({
144
- client_id: provider.clientId,
145
- client_secret: provider.clientSecret,
146
- code,
147
- redirect_uri: redirectUri,
148
- grant_type: "authorization_code"
149
- })
150
- });
151
- if (!tokenRes.ok) {
152
- throw new Error(`OAuth token exchange failed: ${tokenRes.status}`);
153
- }
154
- const tokenData = await tokenRes.json();
155
- const accessToken = tokenData.access_token;
156
- const userRes = await fetch(provider.userInfoUrl, {
157
- headers: {
158
- Authorization: `Bearer ${accessToken}`,
159
- Accept: "application/json"
160
- }
161
- });
162
- if (!userRes.ok) {
163
- throw new Error(`OAuth user info fetch failed: ${userRes.status}`);
164
- }
165
- const user = await userRes.json();
166
- return {
167
- id: String(user.id ?? user.sub ?? ""),
168
- email: String(user.email ?? ""),
169
- name: String(user.name ?? user.login ?? ""),
170
- avatarUrl: user.picture ?? user.avatar_url ?? void 0
171
- };
172
- }
173
-
174
- // src/server/permissions.ts
175
- var PermissionRegistry = class {
176
- rules = /* @__PURE__ */ new Map();
177
- defaultRule = "authenticated";
178
- /**
179
- * Register permissions for an entity type.
180
- */
181
- register(entityType, permissions) {
182
- this.rules.set(entityType, permissions);
183
- }
184
- /**
185
- * Set the fallback rule used when an entity type has no declared permissions.
186
- */
187
- setDefault(rule) {
188
- this.defaultRule = rule;
189
- }
190
- /**
191
- * Get the permission rule for a specific operation on an entity type.
192
- * Falls back to the default rule if not declared.
193
- */
194
- getRule(entityType, op) {
195
- const def = this.rules.get(entityType);
196
- return def?.[op] ?? this.defaultRule;
197
- }
198
- /**
199
- * Check whether an auth context is allowed to perform an operation.
200
- */
201
- check(auth, entityType, op, entity = null) {
202
- const rule = this.getRule(entityType, op);
203
- return evaluateRule(rule, auth, entity);
204
- }
205
- /**
206
- * Assert access — throws a PermissionError if denied.
207
- */
208
- assert(auth, entityType, op, entity = null) {
209
- if (!this.check(auth, entityType, op, entity)) {
210
- throw new PermissionError(auth, entityType, op);
211
- }
212
- }
213
- };
214
- function evaluateRule(rule, auth, entity) {
215
- if (rule === "public") {
216
- return true;
217
- }
218
- if (rule === "authenticated") {
219
- return auth.authenticated;
220
- }
221
- if (rule === "own") {
222
- if (!auth.authenticated || !auth.userId) return false;
223
- const ownerFact = entity?.facts.find(
224
- (f) => f.a === "ownerId" || f.a === "createdBy"
225
- );
226
- return ownerFact?.v === auth.userId;
227
- }
228
- if (typeof rule === "object") {
229
- if ("role" in rule) {
230
- return auth.roles.includes(rule.role);
231
- }
232
- if ("roles" in rule) {
233
- return rule.roles.some((r) => auth.roles.includes(r));
234
- }
235
- if ("fn" in rule) {
236
- try {
237
- return rule.fn(auth, entity);
238
- } catch {
239
- return false;
240
- }
241
- }
242
- }
243
- return false;
244
- }
245
- var PermissionError = class extends Error {
246
- constructor(auth, entityType, op) {
247
- const who = auth.authenticated ? `user:${auth.userId}` : "anonymous";
248
- super(`Permission denied: ${who} cannot ${op} ${entityType}`);
249
- this.auth = auth;
250
- this.entityType = entityType;
251
- this.op = op;
252
- this.name = "PermissionError";
253
- }
254
- toResponse() {
255
- return {
256
- error: "Forbidden",
257
- message: this.message,
258
- code: 403
259
- };
260
- }
261
- };
262
- var PUBLIC_READ = {
263
- read: "public",
264
- create: "authenticated",
265
- update: "authenticated",
266
- delete: "authenticated"
267
- };
268
- var FULLY_PUBLIC = {
269
- read: "public",
270
- create: "public",
271
- update: "public",
272
- delete: "public"
273
- };
274
- var OWNER_ONLY = {
275
- read: "own",
276
- create: "authenticated",
277
- update: "own",
278
- delete: "own"
279
- };
280
- var ADMIN_ONLY = {
281
- read: { role: "admin" },
282
- create: { role: "admin" },
283
- update: { role: "admin" },
284
- delete: { role: "admin" }
285
- };
286
-
287
- // src/server/realtime.ts
288
- var SubscriptionManager = class {
289
- clients = /* @__PURE__ */ new Map();
290
- pool;
291
- permissions;
292
- constructor(pool, permissions = null) {
293
- this.pool = pool;
294
- this.permissions = permissions;
295
- }
296
- // -------------------------------------------------------------------------
297
- // Client lifecycle
298
- // -------------------------------------------------------------------------
299
- addClient(clientId, ws, auth, tenantId) {
300
- this.clients.set(clientId, {
301
- id: clientId,
302
- ws,
303
- subscriptions: /* @__PURE__ */ new Map(),
304
- auth,
305
- tenantId
306
- });
307
- }
308
- removeClient(clientId) {
309
- this.clients.delete(clientId);
310
- }
311
- // -------------------------------------------------------------------------
312
- // Message handling
313
- // -------------------------------------------------------------------------
314
- async handleMessage(clientId, raw) {
315
- const client = this.clients.get(clientId);
316
- if (!client) return;
317
- let msg;
318
- try {
319
- msg = JSON.parse(raw);
320
- } catch {
321
- this._send(client, { type: "error", id: "", message: "Invalid JSON" });
322
- return;
323
- }
324
- if (msg.type === "ping") {
325
- this._send(client, { type: "pong" });
326
- return;
327
- }
328
- if (msg.type === "subscribe") {
329
- await this._handleSubscribe(client, msg.id, msg.query, msg.tenantId);
330
- return;
331
- }
332
- if (msg.type === "unsubscribe") {
333
- client.subscriptions.delete(msg.id);
334
- return;
335
- }
336
- }
337
- // -------------------------------------------------------------------------
338
- // Notify — called after every mutation
339
- // -------------------------------------------------------------------------
340
- /**
341
- * Re-evaluate all subscriptions for a given tenant and push diffs.
342
- * Called after every write op lands.
343
- */
344
- async notify(tenantId) {
345
- const tid = tenantId ?? null;
346
- const dead = [];
347
- for (const [clientId, client] of this.clients) {
348
- if (client.ws.readyState !== 1) {
349
- dead.push(clientId);
350
- continue;
351
- }
352
- if (client.tenantId !== tid) continue;
353
- for (const [subId, sub] of client.subscriptions) {
354
- if (sub.tenantId !== tid) continue;
355
- await this._pushUpdate(client, sub);
356
- }
357
- }
358
- for (const id of dead) this.clients.delete(id);
359
- }
360
- get clientCount() {
361
- return this.clients.size;
362
- }
363
- // -------------------------------------------------------------------------
364
- // Private
365
- // -------------------------------------------------------------------------
366
- async _handleSubscribe(client, subId, queryStr, tenantId) {
367
- const tid = tenantId ?? client.tenantId ?? null;
368
- let parsedQuery;
369
- try {
370
- parsedQuery = parseSimple(queryStr);
371
- } catch (err) {
372
- this._send(client, {
373
- type: "error",
374
- id: subId,
375
- message: `Invalid query: ${err instanceof Error ? err.message : String(err)}`
376
- });
377
- return;
378
- }
379
- const kernel = this.pool.get(tid);
380
- let result;
381
- try {
382
- const qr = await kernel.query(parsedQuery);
383
- result = hydrateBindings(kernel, qr.bindings);
384
- } catch (err) {
385
- this._send(client, {
386
- type: "error",
387
- id: subId,
388
- message: `Query failed: ${err instanceof Error ? err.message : String(err)}`
389
- });
390
- return;
391
- }
392
- const sub = {
393
- id: subId,
394
- query: queryStr,
395
- tenantId: tid,
396
- auth: client.auth,
397
- lastResult: result
398
- };
399
- client.subscriptions.set(subId, sub);
400
- this._send(client, { type: "subscribed", id: subId });
401
- this._send(client, {
402
- type: "data",
403
- id: subId,
404
- result,
405
- diff: { added: result, updated: [], removed: [] }
406
- });
407
- }
408
- async _pushUpdate(client, sub) {
409
- const kernel = this.pool.get(sub.tenantId);
410
- let newResult;
411
- try {
412
- const parsed = parseSimple(sub.query);
413
- const qr = await kernel.query(parsed);
414
- newResult = hydrateBindings(kernel, qr.bindings);
415
- } catch {
416
- return;
417
- }
418
- const diff = computeDiff(sub.lastResult, newResult);
419
- if (diff.added.length === 0 && diff.updated.length === 0 && diff.removed.length === 0) {
420
- return;
421
- }
422
- sub.lastResult = newResult;
423
- this._send(client, { type: "data", id: sub.id, result: newResult, diff });
424
- }
425
- _send(client, payload) {
426
- try {
427
- client.ws.send(JSON.stringify(payload));
428
- } catch {
429
- }
430
- }
431
- };
432
- function entityId(row) {
433
- return String(row["?e"] ?? row.id ?? row.e ?? JSON.stringify(row));
434
- }
435
- function computeDiff(prev, next) {
436
- const prevMap = new Map(prev.map((r) => [entityId(r), r]));
437
- const nextMap = new Map(next.map((r) => [entityId(r), r]));
438
- const added = [];
439
- const updated = [];
440
- const removed = [];
441
- for (const [id, row] of nextMap) {
442
- if (!prevMap.has(id)) {
443
- added.push(row);
444
- } else if (JSON.stringify(prevMap.get(id)) !== JSON.stringify(row)) {
445
- updated.push(row);
446
- }
447
- }
448
- for (const [id, row] of prevMap) {
449
- if (!nextMap.has(id)) removed.push(row);
450
- }
451
- return { added, updated, removed };
452
- }
453
-
454
- // src/server/server.ts
455
- var __moduleDir = import.meta.dir ?? dirname(fileURLToPath(import.meta.url));
456
- async function startServer(opts) {
457
- return startServerNode(opts);
458
- }
459
- var startServerCrossRuntime = startServer;
460
- function buildServerContext(opts) {
461
- const { pool, permissions, config } = opts;
462
- const port = opts.port ?? config.port ?? 3e3;
463
- const authConfig = {
464
- jwtSecret: config.jwtSecret,
465
- apiKey: config.apiKey,
466
- allowPublic: true
467
- };
468
- const subs = new SubscriptionManager(pool, permissions ?? null);
469
- const handleHttp = async (req) => {
470
- const url = new URL(req.url);
471
- const path = url.pathname;
472
- const auth = await resolveAuth(
473
- req.headers.get("authorization"),
474
- authConfig
475
- );
476
- const tenantId = auth.tenantId ?? url.searchParams.get("tenantId") ?? null;
477
- try {
478
- return await route(req, url, path, auth, tenantId, {
479
- pool,
480
- permissions: permissions ?? null,
481
- subs,
482
- authConfig,
483
- config,
484
- oauthProviders: opts.oauthProviders ?? {}
485
- });
486
- } catch (err) {
487
- if (err instanceof PermissionError) {
488
- return json(err.toResponse(), 403);
489
- }
490
- const msg = err instanceof Error ? err.message : String(err);
491
- return json({ error: "Internal Server Error", message: msg }, 500);
492
- }
493
- };
494
- return { port, authConfig, subs, handleHttp };
495
- }
496
- async function startServerNode(opts) {
497
- const { port, subs, handleHttp } = buildServerContext(opts);
498
- const { startNodeServer } = await import("./server/node-adapter.js");
499
- return startNodeServer({
500
- port,
501
- fetch: handleHttp,
502
- websocket: {
503
- open(ws) {
504
- const id = crypto.randomUUID();
505
- ws.__clientId = id;
506
- subs.addClient(
507
- id,
508
- ws,
509
- {
510
- userId: null,
511
- tenantId: null,
512
- roles: [],
513
- claims: {},
514
- authenticated: false
515
- },
516
- null
517
- );
518
- },
519
- async message(ws, raw) {
520
- const id = ws.__clientId;
521
- await subs.handleMessage(
522
- id,
523
- typeof raw === "string" ? raw : raw.toString()
524
- );
525
- },
526
- close(ws) {
527
- const id = ws.__clientId;
528
- subs.removeClient(id);
529
- }
530
- }
531
- });
532
- }
533
- async function route(req, url, path, auth, tenantId, ctx) {
534
- const method = req.method.toUpperCase();
535
- if (method === "GET" && path === "/__trellis/inspector.js") {
536
- const candidates = [
537
- join(__moduleDir, "db", "inspector.js"),
538
- // chunk lives in dist/, inspector in dist/db/
539
- join(__moduleDir, "inspector.js")
540
- // chunk lives in dist/db/, inspector alongside
541
- ];
542
- for (const p of candidates) {
543
- if (existsSync(p)) {
544
- return new Response(readFileSync(p, "utf-8"), {
545
- headers: { "Content-Type": "application/javascript; charset=utf-8" }
546
- });
547
- }
548
- }
549
- return new Response(
550
- "/* Trellis DB Inspector: run `bun run build:inspector` first */",
551
- {
552
- headers: { "Content-Type": "application/javascript" }
553
- }
554
- );
555
- }
556
- if (method === "GET" && path === "/__trellis/trellis.css") {
557
- const candidates = [
558
- join(__moduleDir, "db", "trellis.css"),
559
- join(__moduleDir, "trellis.css")
560
- ];
561
- for (const p of candidates) {
562
- if (existsSync(p)) {
563
- return new Response(readFileSync(p, "utf-8"), {
564
- headers: { "Content-Type": "text/css; charset=utf-8" }
565
- });
566
- }
567
- }
568
- return new Response("/* Trellis CSS not found */", {
569
- headers: { "Content-Type": "text/css" }
570
- });
571
- }
572
- if (method === "GET" && (path === "/" || path === "/inspector")) {
573
- const html = `<!DOCTYPE html>
574
- <html lang="en">
575
- <head>
576
- <meta charset="UTF-8">
577
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
578
- <title>Trellis DB Inspector</title>
579
- <link rel="stylesheet" href="/__trellis/trellis.css">
580
- </head>
581
- <body>
582
- <script src="/__trellis/inspector.js"></script>
583
- </body>
584
- </html>`;
585
- return new Response(html, {
586
- headers: { "Content-Type": "text/html; charset=utf-8" }
587
- });
588
- }
589
- if (method === "GET" && path === "/health") {
590
- const kernel = ctx.pool.get(tenantId);
591
- const ops = kernel.readAllOps().length;
592
- return json({
593
- status: "ok",
594
- ops,
595
- tenants: ctx.pool.activeTenants().length
596
- });
597
- }
598
- if (path === "/entities" || path === "/entities/") {
599
- if (method === "POST") return handleCreate(req, auth, tenantId, ctx);
600
- if (method === "GET") return handleList(url, auth, tenantId, ctx);
601
- }
602
- const entityMatch = path.match(/^\/entities\/([^/]+)$/);
603
- if (entityMatch) {
604
- const id = decodeURIComponent(entityMatch[1]);
605
- if (method === "GET") return handleRead(id, auth, tenantId, ctx);
606
- if (method === "PUT" || method === "PATCH")
607
- return handleUpdate(req, id, auth, tenantId, ctx);
608
- if (method === "DELETE") return handleDelete(id, auth, tenantId, ctx);
609
- }
610
- if (method === "POST" && path === "/query") {
611
- return handleQuery(req, auth, tenantId, ctx);
612
- }
613
- if (method === "POST" && (path === "/ontologies" || path === "/ontologies/")) {
614
- return handleCreateOntology(req, auth, tenantId, ctx);
615
- }
616
- if (method === "POST" && path === "/upload") {
617
- return handleUpload(req, auth, tenantId, ctx);
618
- }
619
- const fileMatch = path.match(/^\/files\/([^/]+)$/);
620
- if (method === "GET" && fileMatch) {
621
- return handleFileDownload(fileMatch[1], ctx, tenantId);
622
- }
623
- if (method === "POST" && path === "/auth/register") {
624
- return handleRegister(req, tenantId, ctx);
625
- }
626
- if (method === "POST" && path === "/auth/login") {
627
- return handleLogin(req, tenantId, ctx);
628
- }
629
- const oauthMatch = path.match(/^\/auth\/oauth\/([^/]+)(\/callback)?$/);
630
- if (oauthMatch) {
631
- const providerName = oauthMatch[1];
632
- const isCallback = !!oauthMatch[2];
633
- if (isCallback)
634
- return handleOAuthCallback(url, providerName, tenantId, ctx);
635
- return handleOAuthRedirect(providerName, url, ctx);
636
- }
637
- return json({ error: "Not Found" }, 404);
638
- }
639
- async function handleCreate(req, auth, tenantId, ctx) {
640
- const body = await req.json();
641
- if (!body.type) return json({ error: "type is required" }, 400);
642
- ctx.permissions?.assert(auth, body.type, "create");
643
- const kernel = ctx.pool.get(tenantId);
644
- const entityId2 = `${body.type.toLowerCase()}:${crypto.randomUUID()}`;
645
- const attrs = {
646
- ...body.attributes
647
- };
648
- if (auth.userId) attrs.createdBy = auth.userId;
649
- if (auth.tenantId) attrs.tenantId = auth.tenantId;
650
- const result = await kernel.createEntity(
651
- entityId2,
652
- body.type,
653
- attrs,
654
- body.links
655
- );
656
- await ctx.subs.notify(tenantId);
657
- return json({ id: entityId2, op: result.op.hash }, 201);
658
- }
659
- async function handleRead(id, auth, tenantId, ctx) {
660
- const kernel = ctx.pool.get(tenantId);
661
- const entity = kernel.getEntity(id);
662
- if (!entity) return json({ error: "Not Found" }, 404);
663
- ctx.permissions?.assert(auth, entity.type, "read", entity);
664
- return json(entityToJson(entity));
665
- }
666
- async function handleUpdate(req, id, auth, tenantId, ctx) {
667
- const kernel = ctx.pool.get(tenantId);
668
- const entity = kernel.getEntity(id);
669
- if (!entity) return json({ error: "Not Found" }, 404);
670
- ctx.permissions?.assert(auth, entity.type, "update", entity);
671
- const updates = await req.json();
672
- await kernel.updateEntity(id, updates);
673
- await ctx.subs.notify(tenantId);
674
- return json({ id, updated: true });
675
- }
676
- async function handleDelete(id, auth, tenantId, ctx) {
677
- const kernel = ctx.pool.get(tenantId);
678
- const entity = kernel.getEntity(id);
679
- if (!entity) return json({ error: "Not Found" }, 404);
680
- ctx.permissions?.assert(auth, entity.type, "delete", entity);
681
- await kernel.deleteEntity(id);
682
- await ctx.subs.notify(tenantId);
683
- return json({ id, deleted: true });
684
- }
685
- async function handleList(url, auth, tenantId, ctx) {
686
- const type = url.searchParams.get("type") ?? void 0;
687
- const limit = parseInt(url.searchParams.get("limit") ?? "100");
688
- const offset = parseInt(url.searchParams.get("offset") ?? "0");
689
- const kernel = ctx.pool.get(tenantId);
690
- let entities = kernel.listEntities(type);
691
- if (ctx.permissions && type) {
692
- entities = entities.filter(
693
- (e) => ctx.permissions.check(auth, e.type, "read", e)
694
- );
695
- }
696
- const page = entities.slice(offset, offset + limit);
697
- return json({
698
- data: page.map(entityToJson),
699
- total: entities.length,
700
- limit,
701
- offset
702
- });
703
- }
704
- async function handleQuery(req, auth, tenantId, ctx) {
705
- const body = await req.json();
706
- if (!body.query) return json({ error: "query is required" }, 400);
707
- let parsed;
708
- try {
709
- parsed = parseSimple(body.query);
710
- } catch (err) {
711
- return json({ error: "Invalid query", message: String(err) }, 400);
712
- }
713
- const kernel = ctx.pool.get(tenantId);
714
- const result = await kernel.query(parsed);
715
- const bindings = hydrateBindings(
716
- kernel,
717
- result.bindings
718
- );
719
- return json({
720
- bindings,
721
- executionTime: result.executionTime
722
- });
723
- }
724
- async function handleCreateOntology(req, auth, tenantId, ctx) {
725
- const schema = await req.json();
726
- if (!schema || !schema["@id"] || !Array.isArray(schema.fields)) {
727
- return json({ error: "A SchemaDefinition (with @id and fields) is required" }, 400);
728
- }
729
- if ((schema.tier ?? "user") === "core") {
730
- return json({ error: "Core ontologies are immutable" }, 403);
731
- }
732
- const kernel = ctx.pool.get(tenantId);
733
- const exists = kernel.listOntologies().some((ont) => ont["@id"] === schema["@id"]);
734
- if (exists) {
735
- return json({ id: schema["@id"], registered: false, existed: true }, 200);
736
- }
737
- try {
738
- kernel.createOntology(schema);
739
- } catch (err) {
740
- const msg = err instanceof Error ? err.message : String(err);
741
- if (msg.includes("already exists")) {
742
- return json({ id: schema["@id"], registered: false, existed: true }, 200);
743
- }
744
- return json({ error: "Could not register schema", message: msg }, 409);
745
- }
746
- return json({ id: schema["@id"], registered: true }, 201);
747
- }
748
- async function handleUpload(req, auth, tenantId, ctx) {
749
- if (!auth.authenticated && ctx.config.apiKey) {
750
- return json({ error: "Unauthorized" }, 401);
751
- }
752
- const contentType = req.headers.get("content-type") ?? "application/octet-stream";
753
- const buffer = new Uint8Array(await req.arrayBuffer());
754
- const hashBuf = await crypto.subtle.digest("SHA-256", buffer);
755
- const hash = `blob:${Array.from(new Uint8Array(hashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
756
- const kernel = ctx.pool.get(tenantId);
757
- const backend = kernel.getBackend();
758
- if (!backend.hasBlob(hash)) {
759
- backend.putBlob(hash, buffer);
760
- }
761
- return json({ hash, size: buffer.length, contentType }, 201);
762
- }
763
- async function handleFileDownload(hash, ctx, tenantId) {
764
- const kernel = ctx.pool.get(tenantId);
765
- const backend = kernel.getBackend();
766
- const blob = backend.getBlob(hash);
767
- if (!blob) return json({ error: "Not Found" }, 404);
768
- const cleanBuf = blob.buffer.slice(
769
- blob.byteOffset,
770
- blob.byteOffset + blob.byteLength
771
- );
772
- return new Response(cleanBuf, {
773
- headers: { "Content-Type": "application/octet-stream" }
774
- });
775
- }
776
- async function handleRegister(req, tenantId, ctx) {
777
- if (!ctx.config.jwtSecret) {
778
- return json({ error: "Auth not configured (no jwtSecret)" }, 501);
779
- }
780
- const body = await req.json();
781
- if (!body.email || !body.password) {
782
- return json({ error: "email and password are required" }, 400);
783
- }
784
- const kernel = ctx.pool.get(tenantId);
785
- const existing = kernel.listEntities("User", { email: body.email });
786
- if (existing.length > 0) {
787
- return json({ error: "Email already registered" }, 409);
788
- }
789
- const userId = `user:${crypto.randomUUID()}`;
790
- const pwHash = await hashPassword(body.password);
791
- await kernel.createEntity(userId, "User", {
792
- email: body.email,
793
- name: body.name ?? "",
794
- passwordHash: pwHash,
795
- role: "user",
796
- ...tenantId ? { tenantId } : {}
797
- });
798
- const token = await signJwt(
799
- { sub: userId, email: body.email, roles: ["user"], tenantId },
800
- ctx.config.jwtSecret
801
- );
802
- return json({ token, userId }, 201);
803
- }
804
- async function handleLogin(req, tenantId, ctx) {
805
- if (!ctx.config.jwtSecret) {
806
- return json({ error: "Auth not configured (no jwtSecret)" }, 501);
807
- }
808
- const body = await req.json();
809
- if (!body.email || !body.password) {
810
- return json({ error: "email and password are required" }, 400);
811
- }
812
- const kernel = ctx.pool.get(tenantId);
813
- const users = kernel.listEntities("User", { email: body.email });
814
- if (users.length === 0) {
815
- return json({ error: "Invalid credentials" }, 401);
816
- }
817
- const user = users[0];
818
- const pwHashFact = user.facts.find((f) => f.a === "passwordHash");
819
- const roleFact = user.facts.find((f) => f.a === "role");
820
- if (!pwHashFact || !await verifyPassword(body.password, String(pwHashFact.v))) {
821
- return json({ error: "Invalid credentials" }, 401);
822
- }
823
- const role = String(roleFact?.v ?? "user");
824
- const token = await signJwt(
825
- { sub: user.id, email: body.email, roles: [role], tenantId },
826
- ctx.config.jwtSecret
827
- );
828
- return json({ token, userId: user.id });
829
- }
830
- function handleOAuthRedirect(providerName, url, ctx) {
831
- const provider = ctx.oauthProviders[providerName] ?? getBuiltinProvider(providerName, ctx);
832
- if (!provider)
833
- return json({ error: `Unknown provider: ${providerName}` }, 400);
834
- const redirectUri = `${url.origin}/auth/oauth/${providerName}/callback`;
835
- const state = crypto.randomUUID();
836
- const authUrl = buildOAuthUrl(provider, redirectUri, state);
837
- return Response.redirect(authUrl, 302);
838
- }
839
- async function handleOAuthCallback(url, providerName, tenantId, ctx) {
840
- if (!ctx.config.jwtSecret) {
841
- return json({ error: "Auth not configured (no jwtSecret)" }, 501);
842
- }
843
- const code = url.searchParams.get("code");
844
- if (!code) return json({ error: "Missing code" }, 400);
845
- const provider = ctx.oauthProviders[providerName] ?? getBuiltinProvider(providerName, ctx);
846
- if (!provider)
847
- return json({ error: `Unknown provider: ${providerName}` }, 400);
848
- const redirectUri = `${url.origin}/auth/oauth/${providerName}/callback`;
849
- let profile;
850
- try {
851
- profile = await exchangeOAuthCode(provider, code, redirectUri);
852
- } catch (err) {
853
- return json({ error: "OAuth exchange failed", message: String(err) }, 400);
854
- }
855
- const kernel = ctx.pool.get(tenantId);
856
- const oauthId = `oauth:${providerName}:${profile.id}`;
857
- let users = kernel.listEntities("User", { oauthId });
858
- let userId;
859
- if (users.length === 0) {
860
- userId = `user:${crypto.randomUUID()}`;
861
- await kernel.createEntity(userId, "User", {
862
- email: profile.email,
863
- name: profile.name,
864
- avatarUrl: profile.avatarUrl ?? "",
865
- oauthId,
866
- oauthProvider: providerName,
867
- role: "user",
868
- ...tenantId ? { tenantId } : {}
869
- });
870
- } else {
871
- userId = users[0].id;
872
- }
873
- const token = await signJwt(
874
- { sub: userId, email: profile.email, roles: ["user"], tenantId },
875
- ctx.config.jwtSecret
876
- );
877
- return json({ token, userId });
878
- }
879
- function getBuiltinProvider(name, ctx) {
880
- if (name === "google") {
881
- const cid = process.env.GOOGLE_CLIENT_ID;
882
- const csec = process.env.GOOGLE_CLIENT_SECRET;
883
- if (!cid || !csec) return null;
884
- return { ...GOOGLE_PROVIDER, clientId: cid, clientSecret: csec };
885
- }
886
- if (name === "github") {
887
- const cid = process.env.GITHUB_CLIENT_ID;
888
- const csec = process.env.GITHUB_CLIENT_SECRET;
889
- if (!cid || !csec) return null;
890
- return { ...GITHUB_PROVIDER, clientId: cid, clientSecret: csec };
891
- }
892
- return null;
893
- }
894
- async function hashPassword(password) {
895
- const salt = crypto.randomUUID().replace(/-/g, "");
896
- const enc = new TextEncoder();
897
- const keyMat = await crypto.subtle.importKey(
898
- "raw",
899
- enc.encode(password),
900
- "PBKDF2",
901
- false,
902
- ["deriveBits"]
903
- );
904
- const bits = await crypto.subtle.deriveBits(
905
- {
906
- name: "PBKDF2",
907
- salt: enc.encode(salt),
908
- iterations: 1e5,
909
- hash: "SHA-256"
910
- },
911
- keyMat,
912
- 256
913
- );
914
- const hash = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, "0")).join("");
915
- return `${salt}:${hash}`;
916
- }
917
- async function verifyPassword(password, stored) {
918
- const [salt, expectedHash] = stored.split(":");
919
- if (!salt || !expectedHash) return false;
920
- const enc = new TextEncoder();
921
- const keyMat = await crypto.subtle.importKey(
922
- "raw",
923
- enc.encode(password),
924
- "PBKDF2",
925
- false,
926
- ["deriveBits"]
927
- );
928
- const bits = await crypto.subtle.deriveBits(
929
- {
930
- name: "PBKDF2",
931
- salt: enc.encode(salt),
932
- iterations: 1e5,
933
- hash: "SHA-256"
934
- },
935
- keyMat,
936
- 256
937
- );
938
- const hash = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, "0")).join("");
939
- return hash === expectedHash;
940
- }
941
- function entityToJson(entity) {
942
- return entityRecordToPlain(entity);
943
- }
944
- function json(data, status = 200) {
945
- return new Response(JSON.stringify(data), {
946
- status,
947
- headers: { "Content-Type": "application/json" }
948
- });
949
- }
950
-
951
- export {
952
- ANONYMOUS,
953
- signJwt,
954
- verifyJwt,
955
- resolveAuth,
956
- GOOGLE_PROVIDER,
957
- GITHUB_PROVIDER,
958
- buildOAuthUrl,
959
- exchangeOAuthCode,
960
- PermissionRegistry,
961
- PermissionError,
962
- PUBLIC_READ,
963
- FULLY_PUBLIC,
964
- OWNER_ONLY,
965
- ADMIN_ONLY,
966
- SubscriptionManager,
967
- startServer,
968
- startServerCrossRuntime
969
- };