trellis 3.2.0 → 3.2.3

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 (260) 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-2AACZRII.js} +312 -30
  7. package/dist/{chunk-3GDRZTAW.js → chunk-5KSGGKET.js} +1 -1
  8. package/dist/{chunk-MBNBTJCR.js → chunk-6GEZ6DN5.js} +2 -2
  9. package/dist/chunk-6RWGFC3N.js +403 -0
  10. package/dist/{chunk-X762XWVC.js → chunk-7DUMWO5L.js} +1 -1
  11. package/dist/chunk-CAG4FULI.js +121 -0
  12. package/dist/{chunk-GD5QJXSS.js → chunk-FWRS4CSC.js} +2 -2
  13. package/dist/{chunk-36VSSTL7.js → chunk-GU374KWZ.js} +1 -1
  14. package/dist/{chunk-BJ7EZ7FP.js → chunk-JHHEXEDM.js} +17 -0
  15. package/dist/{chunk-7WQGTFVJ.js → chunk-L5N5PR2S.js} +130 -75
  16. package/dist/{chunk-BXSTJVE3.js → chunk-N6MYZT4O.js} +2 -1
  17. package/dist/chunk-NWSW5YNS.js +225 -0
  18. package/dist/{chunk-HO4TURXW.js → chunk-S3XLFZ27.js} +28 -11
  19. package/dist/{chunk-BQBUVMRN.js → chunk-S6GBJ2ZB.js} +25 -14
  20. package/dist/{chunk-B3ZIFA2O.js → chunk-VBYUTRXX.js} +1 -1
  21. package/dist/cli/deploy-cli.d.ts +18 -0
  22. package/dist/cli/deploy-cli.d.ts.map +1 -0
  23. package/dist/cli/deploy-cli.js +67 -0
  24. package/dist/cli/index.d.ts +1 -1
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +153 -100
  27. package/dist/client/index.browser.d.ts +29 -0
  28. package/dist/client/index.browser.d.ts.map +1 -0
  29. package/dist/client/index.browser.js +64 -0
  30. package/dist/client/index.js +10 -10
  31. package/dist/client/sdk.browser.d.ts +131 -0
  32. package/dist/client/sdk.browser.d.ts.map +1 -0
  33. package/dist/{sdk-LZ7W3EEY.js → client/sdk.browser.js} +2 -2
  34. package/dist/client/sdk.js +1 -1
  35. package/dist/db/index.js +16 -16
  36. package/dist/db/inspector.js +6 -6
  37. package/dist/decisions/index.js +2 -2
  38. package/dist/embeddings/auto-embed.d.ts.map +1 -1
  39. package/dist/embeddings/index.js +1 -1
  40. package/dist/embeddings/store.d.ts.map +1 -1
  41. package/dist/engine.d.ts +27 -7
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/index.js +6 -6
  44. package/dist/mcp/server.d.ts.map +1 -1
  45. package/dist/react/index.js +2 -2
  46. package/dist/react/schema-hooks.js +7 -7
  47. package/dist/{remote-manager-SG545WNY.js → remote-manager-ODE3T2KR.js} +5 -5
  48. package/dist/scaffold/seed.d.ts.map +1 -1
  49. package/dist/scaffold/write.d.ts +1 -1
  50. package/dist/scaffold/write.d.ts.map +1 -1
  51. package/dist/schema/index.js +4 -3
  52. package/dist/server/cors.d.ts +13 -0
  53. package/dist/server/cors.d.ts.map +1 -0
  54. package/dist/server/deploy-meta.d.ts +25 -0
  55. package/dist/server/deploy-meta.d.ts.map +1 -0
  56. package/dist/server/deploy.d.ts +8 -3
  57. package/dist/server/deploy.d.ts.map +1 -1
  58. package/dist/server/deploy.js +9 -0
  59. package/dist/server/index.d.ts +5 -2
  60. package/dist/server/index.d.ts.map +1 -1
  61. package/dist/server/index.js +51 -17
  62. package/dist/server/realtime.d.ts +4 -1
  63. package/dist/server/realtime.d.ts.map +1 -1
  64. package/dist/server/server.d.ts +1 -0
  65. package/dist/server/server.d.ts.map +1 -1
  66. package/dist/server/sprites.d.ts +18 -1
  67. package/dist/server/sprites.d.ts.map +1 -1
  68. package/dist/server/tenancy.d.ts +4 -0
  69. package/dist/server/tenancy.d.ts.map +1 -1
  70. package/dist/server/usage-meter.d.ts +53 -0
  71. package/dist/server/usage-meter.d.ts.map +1 -0
  72. package/dist/{server-IGBIIJSF.js → server-OGVWNDNK.js} +6 -1
  73. package/dist/sprites-KQGTGM6W.js +27 -0
  74. package/dist/{tenancy-OP34NFFS.js → tenancy-POMWQP6M.js} +5 -3
  75. package/dist/vcs/index.js +3 -3
  76. package/dist/vcs/types.d.ts +2 -0
  77. package/dist/vcs/types.d.ts.map +1 -1
  78. package/package.json +17 -6
  79. package/dist/better-sqlite-backend-PII64VE6.js +0 -159
  80. package/dist/better-sqlite-backend-VUBVQ7SN.js +0 -209
  81. package/dist/better-sqlite-backend-Y3AQN65E.js +0 -159
  82. package/dist/better-sqlite-backend-Y3M7DXOX.js +0 -159
  83. package/dist/boot-middleware-FJDGVUM4.js +0 -9
  84. package/dist/boot-middleware-MLJUTLUM.js +0 -9
  85. package/dist/boot-middleware-ZZPDZNYR.js +0 -9
  86. package/dist/chunk-276LKLZR.js +0 -359
  87. package/dist/chunk-2CX5UCRV.js +0 -111
  88. package/dist/chunk-2ESYSVXG.js +0 -48
  89. package/dist/chunk-2I5JU73Y.js +0 -340
  90. package/dist/chunk-2UNSRRNB.js +0 -171
  91. package/dist/chunk-335UEHQR.js +0 -907
  92. package/dist/chunk-4CF2L46N.js +0 -29
  93. package/dist/chunk-5CINODKT.js +0 -41
  94. package/dist/chunk-5ESGWJ5L.js +0 -45
  95. package/dist/chunk-5FGLQGMF.js +0 -1676
  96. package/dist/chunk-5IQZFITY.js +0 -357
  97. package/dist/chunk-5JWFAQDW.js +0 -150
  98. package/dist/chunk-62UHTVU3.js +0 -141
  99. package/dist/chunk-66UJQT2I.js +0 -35
  100. package/dist/chunk-6RNNVOAS.js +0 -355
  101. package/dist/chunk-6X23PZ6H.js +0 -969
  102. package/dist/chunk-7C7LL6MB.js +0 -315
  103. package/dist/chunk-7F4HM3JG.js +0 -394
  104. package/dist/chunk-7KJ7T4D3.js +0 -910
  105. package/dist/chunk-7QQQNUB5.js +0 -92
  106. package/dist/chunk-7QWJBS72.js +0 -946
  107. package/dist/chunk-7R36OZK3.js +0 -957
  108. package/dist/chunk-7UPKCOHE.js +0 -946
  109. package/dist/chunk-A3BONW34.js +0 -35
  110. package/dist/chunk-AHMDVHBL.js +0 -141
  111. package/dist/chunk-AW6A4OZY.js +0 -171
  112. package/dist/chunk-BJ5EKCJ5.js +0 -458
  113. package/dist/chunk-BRGYDO7D.js +0 -223
  114. package/dist/chunk-C5KPSEDN.js +0 -359
  115. package/dist/chunk-CCTNYTUC.js +0 -940
  116. package/dist/chunk-CYQ2OVD2.js +0 -35
  117. package/dist/chunk-DDAK7YOE.js +0 -299
  118. package/dist/chunk-DRILKBY6.js +0 -965
  119. package/dist/chunk-DXE24HNN.js +0 -45
  120. package/dist/chunk-E7ICUNW7.js +0 -945
  121. package/dist/chunk-EAL2PUOE.js +0 -10022
  122. package/dist/chunk-ELT7MLZB.js +0 -109
  123. package/dist/chunk-EWEIRF6G.js +0 -171
  124. package/dist/chunk-F5VDGQJC.js +0 -120
  125. package/dist/chunk-FCPL3IEQ.js +0 -98
  126. package/dist/chunk-FDPKYSV7.js +0 -96
  127. package/dist/chunk-FES3TC24.js +0 -171
  128. package/dist/chunk-FF36CQHZ.js +0 -61
  129. package/dist/chunk-FOWKR5TW.js +0 -129
  130. package/dist/chunk-FZVCXDYW.js +0 -150
  131. package/dist/chunk-G3XIHPSQ.js +0 -361
  132. package/dist/chunk-GKJLYLVU.js +0 -359
  133. package/dist/chunk-GNXHXNVQ.js +0 -113
  134. package/dist/chunk-GSR6ILTM.js +0 -277
  135. package/dist/chunk-GWRIMRSX.js +0 -45
  136. package/dist/chunk-H72UW2HJ.js +0 -45
  137. package/dist/chunk-HGI74PXB.js +0 -7417
  138. package/dist/chunk-HGRA37LO.js +0 -145
  139. package/dist/chunk-HXSRQO3L.js +0 -145
  140. package/dist/chunk-IG2X2BVO.js +0 -373
  141. package/dist/chunk-IIJVDD54.js +0 -373
  142. package/dist/chunk-IUWVEEOX.js +0 -145
  143. package/dist/chunk-IX4BBOIT.js +0 -394
  144. package/dist/chunk-IXU4RMQM.js +0 -349
  145. package/dist/chunk-J6OUIION.js +0 -857
  146. package/dist/chunk-K46RNFDE.js +0 -171
  147. package/dist/chunk-KAGU2XMY.js +0 -359
  148. package/dist/chunk-KCVYJNVX.js +0 -394
  149. package/dist/chunk-KKDRMIV6.js +0 -419
  150. package/dist/chunk-KYLIQ3YI.js +0 -399
  151. package/dist/chunk-KZYJJIRN.js +0 -946
  152. package/dist/chunk-LFOX744K.js +0 -96
  153. package/dist/chunk-LHOVJEWR.js +0 -7403
  154. package/dist/chunk-LOPXTPF3.js +0 -120
  155. package/dist/chunk-LZR3VIFU.js +0 -907
  156. package/dist/chunk-MCKGQKYU.js +0 -15
  157. package/dist/chunk-MU6EOS6F.js +0 -0
  158. package/dist/chunk-MWRQ2TXT.js +0 -357
  159. package/dist/chunk-NO73OK2I.js +0 -102
  160. package/dist/chunk-NPE2YJHK.js +0 -394
  161. package/dist/chunk-NSDXU6QX.js +0 -2213
  162. package/dist/chunk-NXIRDSZ6.js +0 -145
  163. package/dist/chunk-NZWFNHCS.js +0 -407
  164. package/dist/chunk-OB4CMMX6.js +0 -260
  165. package/dist/chunk-OBKU74FU.js +0 -7411
  166. package/dist/chunk-OQNDWRDE.js +0 -145
  167. package/dist/chunk-OVWAJMB6.js +0 -394
  168. package/dist/chunk-PDHLBTTD.js +0 -45
  169. package/dist/chunk-PEQNRKLG.js +0 -210
  170. package/dist/chunk-QERIS7QY.js +0 -7403
  171. package/dist/chunk-QHA2DM3C.js +0 -373
  172. package/dist/chunk-QLM4HSYC.js +0 -45
  173. package/dist/chunk-S2OT3TQJ.js +0 -283
  174. package/dist/chunk-S5PHSN5K.js +0 -907
  175. package/dist/chunk-SIAA4J6H.js +0 -21
  176. package/dist/chunk-SQBNLUIA.js +0 -171
  177. package/dist/chunk-STLN6A6B.js +0 -355
  178. package/dist/chunk-U7HV7GU2.js +0 -17
  179. package/dist/chunk-UGZHOGRA.js +0 -39
  180. package/dist/chunk-UICF66FS.js +0 -359
  181. package/dist/chunk-UK3EQREE.js +0 -1078
  182. package/dist/chunk-UPKPURVY.js +0 -394
  183. package/dist/chunk-URC74VCN.js +0 -959
  184. package/dist/chunk-V3G7Q3QQ.js +0 -102
  185. package/dist/chunk-VUJ6SA55.js +0 -303
  186. package/dist/chunk-VUJQLIML.js +0 -141
  187. package/dist/chunk-VWKLKLYB.js +0 -267
  188. package/dist/chunk-VYC3RZMK.js +0 -277
  189. package/dist/chunk-WMERMSWI.js +0 -154
  190. package/dist/chunk-WMR777PO.js +0 -2213
  191. package/dist/chunk-X46IECAC.js +0 -517
  192. package/dist/chunk-XEN3OEYY.js +0 -2234
  193. package/dist/chunk-XZQ66K7A.js +0 -124
  194. package/dist/chunk-YY3GF7DH.js +0 -7411
  195. package/dist/chunk-ZE5R3H4C.js +0 -907
  196. package/dist/chunk-ZHVI7BQN.js +0 -1077
  197. package/dist/chunk-ZMN5LUB7.js +0 -7411
  198. package/dist/chunk-ZUV4MD5U.js +0 -94
  199. package/dist/config-KMY6RRK3.js +0 -19
  200. package/dist/config-VDMK2DUJ.js +0 -19
  201. package/dist/db/trellis.css +0 -1
  202. package/dist/deploy-66EC752W.js +0 -9
  203. package/dist/deploy-SO7AFK6L.js +0 -9
  204. package/dist/engine-7GXBM4IT.js +0 -7
  205. package/dist/engine-NSR6BIF7.js +0 -7
  206. package/dist/factory-2P64OC7M.js +0 -34
  207. package/dist/factory-YP4ZZK5K.js +0 -34
  208. package/dist/import-AJLYHFIA.js +0 -10
  209. package/dist/launch-explorer-AI6L2AWJ.js +0 -91
  210. package/dist/query-GJL3CVGN.js +0 -31
  211. package/dist/query-MBJSK3EB.js +0 -31
  212. package/dist/query-TJHEVSKL.js +0 -31
  213. package/dist/query-WUIKOHPE.js +0 -524
  214. package/dist/remote-manager-BLJZ6KF6.js +0 -224
  215. package/dist/remote-manager-BPYE46SI.js +0 -223
  216. package/dist/remote-manager-FMESEAFA.js +0 -224
  217. package/dist/remote-manager-K2WZ5RHY.js +0 -223
  218. package/dist/remote-manager-SG5PQKCH.js +0 -224
  219. package/dist/remote-manager-WGP6MIQZ.js +0 -224
  220. package/dist/sdk-3KBB3E6V.js +0 -16
  221. package/dist/sdk-4ZQO6UTU.js +0 -16
  222. package/dist/sdk-6JA6GV3G.js +0 -16
  223. package/dist/sdk-6VTP5UTZ.js +0 -16
  224. package/dist/sdk-7VS6TYCD.js +0 -16
  225. package/dist/sdk-HQ3FTLRT.js +0 -16
  226. package/dist/sdk-HZ7NU3XE.js +0 -15
  227. package/dist/sdk-KBQE2OXT.js +0 -16
  228. package/dist/sdk-MSCDZ7JU.js +0 -15
  229. package/dist/sdk-WNZOGO3S.js +0 -15
  230. package/dist/sdk-YLV33ZCW.js +0 -9
  231. package/dist/sdk-ZSUD2LLJ.js +0 -16
  232. package/dist/server-AFXFFBWA.js +0 -11
  233. package/dist/server-AGHC4VUL.js +0 -11
  234. package/dist/server-BOH7M4TT.js +0 -13
  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-RU3E4XQN.js +0 -15
  243. package/dist/sprites-XVSMQLSI.js +0 -15
  244. package/dist/sql-wasm-FORCZFHZ.js +0 -2148
  245. package/dist/sql-wasm-JZH76RLO.js +0 -2148
  246. package/dist/sqljs-backend-CJQOIQYE.js +0 -266
  247. package/dist/sqljs-backend-KWXBUDUO.js +0 -266
  248. package/dist/state-demo.bundle.js +0 -1033
  249. package/dist/tenancy-CBWYEVUB.js +0 -11
  250. package/dist/tenancy-FO6WVYFN.js +0 -13
  251. package/dist/tenancy-FWDBESMX.js +0 -14
  252. package/dist/tenancy-HGPIS5QA.js +0 -13
  253. package/dist/tenancy-HXFALA37.js +0 -14
  254. package/dist/tenancy-S676GLID.js +0 -14
  255. package/dist/tenancy-V7ARWNNV.js +0 -14
  256. package/dist/tenancy-ZDN4GXK5.js +0 -13
  257. package/dist/tenancy-ZT5VMY7B.js +0 -14
  258. package/dist/vm-config-YZRJ3KUB.js +0 -21
  259. /package/dist/{chunk-2BRTCEHB.js → chunk-PZ4XYZ4J.js} +0 -0
  260. /package/dist/{chunk-EO3J7GRP.js → chunk-WXSWA3MV.js} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ DEFAULT_TENANT
3
+ } from "./chunk-JHHEXEDM.js";
1
4
  import {
2
5
  rel
3
6
  } from "./chunk-44FFQKQX.js";
@@ -11,8 +14,8 @@ import {
11
14
  } from "./chunk-X5FLTRUB.js";
12
15
 
13
16
  // src/server/server.ts
14
- import { existsSync, readFileSync } from "fs";
15
- import { dirname, join } from "path";
17
+ import { existsSync as existsSync2, readFileSync } from "fs";
18
+ import { dirname, join as join2 } from "path";
16
19
  import { fileURLToPath } from "url";
17
20
 
18
21
  // src/server/auth.ts
@@ -367,14 +370,124 @@ async function hydrateAndResolve(kernel, bindings, entityType, resolve) {
367
370
  });
368
371
  }
369
372
 
373
+ // src/server/usage-meter.ts
374
+ import { existsSync, readdirSync, statSync } from "fs";
375
+ import { join } from "path";
376
+ var EMPTY_BUCKET = () => ({
377
+ graph_io: 0,
378
+ storage_bytes: 0,
379
+ egress_bytes: 0
380
+ });
381
+ function resolveUsageTenantId(tenantId) {
382
+ return tenantId ?? DEFAULT_TENANT;
383
+ }
384
+ function dayKey(date) {
385
+ return date.toISOString().slice(0, 10);
386
+ }
387
+ function dirSize(dir) {
388
+ let total = 0;
389
+ for (const ent of readdirSync(dir, { withFileTypes: true })) {
390
+ const p = join(dir, ent.name);
391
+ if (ent.isDirectory()) total += dirSize(p);
392
+ else if (ent.isFile()) total += statSync(p).size;
393
+ }
394
+ return total;
395
+ }
396
+ function sampleTenantStorage(pool, tenantId) {
397
+ let total = 0;
398
+ const sqlitePath = pool.dbFilePath(tenantId);
399
+ if (existsSync(sqlitePath)) {
400
+ total += statSync(sqlitePath).size;
401
+ }
402
+ const blobDir = join(pool.dataPath(), "blobs");
403
+ if (existsSync(blobDir)) {
404
+ total += dirSize(blobDir);
405
+ }
406
+ return total;
407
+ }
408
+ function verifyAdminKey(req) {
409
+ const expected = process.env.TURTLEDB_ADMIN_KEY;
410
+ if (!expected) return false;
411
+ const auth = req.headers.get("authorization");
412
+ if (auth?.startsWith("Bearer ")) {
413
+ return auth.slice("Bearer ".length) === expected;
414
+ }
415
+ return req.headers.get("x-turtledb-admin-key") === expected;
416
+ }
417
+ var UsageMeter = class {
418
+ buckets = /* @__PURE__ */ new Map();
419
+ storageSampledAt = /* @__PURE__ */ new Map();
420
+ now;
421
+ constructor(opts = {}) {
422
+ this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
423
+ }
424
+ recordGraphIo(tenantId, count = 1) {
425
+ if (count <= 0) return;
426
+ const bucket = this._bucket(tenantId);
427
+ bucket.graph_io += count;
428
+ }
429
+ recordEgress(tenantId, bytes) {
430
+ if (bytes <= 0) return;
431
+ const bucket = this._bucket(tenantId);
432
+ bucket.egress_bytes += bytes;
433
+ }
434
+ /** Set storage gauge for the current day (from sampler, not incremental). */
435
+ recordStorage(tenantId, bytes) {
436
+ const bucket = this._bucket(tenantId);
437
+ bucket.storage_bytes = bytes;
438
+ this.storageSampledAt.set(`${tenantId}:${this._today()}`, this.now().toISOString());
439
+ }
440
+ sampleStorage(pool, tenantId) {
441
+ const id = resolveUsageTenantId(tenantId);
442
+ const bytes = sampleTenantStorage(pool, id);
443
+ this.recordStorage(id, bytes);
444
+ return bytes;
445
+ }
446
+ getUsage(tenantId, day) {
447
+ const d = day ?? this._today();
448
+ const tenantBuckets = this.buckets.get(tenantId);
449
+ const meters = tenantBuckets?.get(d) ?? EMPTY_BUCKET();
450
+ const sampledAt = this.storageSampledAt.get(`${tenantId}:${d}`);
451
+ return {
452
+ tenantId,
453
+ day: d,
454
+ meters: { ...meters },
455
+ ...sampledAt ? { storageSampledAt: sampledAt } : {}
456
+ };
457
+ }
458
+ /** All day keys recorded for a tenant (sorted ascending). */
459
+ listDays(tenantId) {
460
+ const tenantBuckets = this.buckets.get(tenantId);
461
+ if (!tenantBuckets) return [];
462
+ return Array.from(tenantBuckets.keys()).sort();
463
+ }
464
+ _today() {
465
+ return dayKey(this.now());
466
+ }
467
+ _bucket(tenantId) {
468
+ const id = resolveUsageTenantId(tenantId);
469
+ const d = this._today();
470
+ if (!this.buckets.has(id)) {
471
+ this.buckets.set(id, /* @__PURE__ */ new Map());
472
+ }
473
+ const tenantBuckets = this.buckets.get(id);
474
+ if (!tenantBuckets.has(d)) {
475
+ tenantBuckets.set(d, EMPTY_BUCKET());
476
+ }
477
+ return tenantBuckets.get(d);
478
+ }
479
+ };
480
+
370
481
  // src/server/realtime.ts
371
482
  var SubscriptionManager = class {
372
483
  clients = /* @__PURE__ */ new Map();
373
484
  pool;
374
485
  permissions;
375
- constructor(pool, permissions = null) {
486
+ meter;
487
+ constructor(pool, permissions = null, meter = null) {
376
488
  this.pool = pool;
377
489
  this.permissions = permissions;
490
+ this.meter = meter;
378
491
  }
379
492
  // -------------------------------------------------------------------------
380
493
  // Client lifecycle
@@ -463,10 +576,11 @@ var SubscriptionManager = class {
463
576
  });
464
577
  return;
465
578
  }
466
- const kernel = this.pool.get(tid);
579
+ const kernel = await this.pool.preload(tid);
467
580
  let result;
468
581
  try {
469
582
  const qr = await kernel.query(parsedQuery);
583
+ this._recordGraphIo(tid);
470
584
  result = await hydrateAndResolve(
471
585
  kernel,
472
586
  qr.bindings,
@@ -502,11 +616,12 @@ var SubscriptionManager = class {
502
616
  });
503
617
  }
504
618
  async _pushUpdate(client, sub) {
505
- const kernel = this.pool.get(sub.tenantId);
619
+ const kernel = await this.pool.preload(sub.tenantId);
506
620
  let newResult;
507
621
  try {
508
622
  const parsed = parseSimple(sub.query);
509
623
  const qr = await kernel.query(parsed);
624
+ this._recordGraphIo(sub.tenantId);
510
625
  newResult = await hydrateAndResolve(
511
626
  kernel,
512
627
  qr.bindings,
@@ -530,9 +645,17 @@ var SubscriptionManager = class {
530
645
  ...resolved ? { resolved: true } : {}
531
646
  });
532
647
  }
648
+ _recordGraphIo(tenantId) {
649
+ this.meter?.recordGraphIo(resolveUsageTenantId(tenantId));
650
+ }
533
651
  _send(client, payload) {
652
+ const data = JSON.stringify(payload);
653
+ if (this.meter) {
654
+ const tid = resolveUsageTenantId(client.tenantId ?? DEFAULT_TENANT);
655
+ this.meter.recordEgress(tid, new TextEncoder().encode(data).length);
656
+ }
534
657
  try {
535
- client.ws.send(JSON.stringify(payload));
658
+ client.ws.send(data);
536
659
  } catch {
537
660
  }
538
661
  }
@@ -559,6 +682,55 @@ function computeDiff(prev, next) {
559
682
  return { added, updated, removed };
560
683
  }
561
684
 
685
+ // src/server/cors.ts
686
+ var DEFAULT_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
687
+ var DEFAULT_HEADERS = "Content-Type, Authorization";
688
+ function corsEnabledForConfig(apiKey) {
689
+ return Boolean(process.env.TRELLIS_CORS_ORIGINS) || Boolean(apiKey);
690
+ }
691
+ function allowedOrigins() {
692
+ const raw = process.env.TRELLIS_CORS_ORIGINS?.trim();
693
+ if (!raw || raw === "*") return "*";
694
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
695
+ }
696
+ function corsHeaders(req) {
697
+ const origins = allowedOrigins();
698
+ const requestOrigin = req.headers.get("Origin");
699
+ let allowOrigin = "*";
700
+ if (origins !== "*") {
701
+ if (requestOrigin && origins.includes(requestOrigin)) {
702
+ allowOrigin = requestOrigin;
703
+ } else if (origins.length === 1) {
704
+ allowOrigin = origins[0];
705
+ } else {
706
+ allowOrigin = origins[0] ?? "*";
707
+ }
708
+ }
709
+ return {
710
+ "Access-Control-Allow-Origin": allowOrigin,
711
+ "Access-Control-Allow-Methods": DEFAULT_METHODS,
712
+ "Access-Control-Allow-Headers": DEFAULT_HEADERS,
713
+ Vary: "Origin"
714
+ };
715
+ }
716
+ function withCors(req, res) {
717
+ const headers = new Headers(res.headers);
718
+ for (const [key, value] of Object.entries(corsHeaders(req))) {
719
+ headers.set(key, value);
720
+ }
721
+ return new Response(res.body, {
722
+ status: res.status,
723
+ statusText: res.statusText,
724
+ headers
725
+ });
726
+ }
727
+ function corsPreflightResponse(req) {
728
+ return new Response(null, {
729
+ status: 204,
730
+ headers: corsHeaders(req)
731
+ });
732
+ }
733
+
562
734
  // src/server/server.ts
563
735
  var __moduleDir = import.meta.dir ?? dirname(fileURLToPath(import.meta.url));
564
736
  async function startServer(opts) {
@@ -573,8 +745,10 @@ function buildServerContext(opts) {
573
745
  apiKey: config.apiKey,
574
746
  allowPublic: true
575
747
  };
576
- const subs = new SubscriptionManager(pool, permissions ?? null);
577
- const handleHttp = async (req) => {
748
+ const meter = new UsageMeter();
749
+ const subs = new SubscriptionManager(pool, permissions ?? null, meter);
750
+ const enableCors = corsEnabledForConfig(config.apiKey);
751
+ const handleHttpInner = async (req) => {
578
752
  const url = new URL(req.url);
579
753
  const path = url.pathname;
580
754
  const auth = await resolveAuth(
@@ -587,6 +761,7 @@ function buildServerContext(opts) {
587
761
  pool,
588
762
  permissions: permissions ?? null,
589
763
  subs,
764
+ meter,
590
765
  authConfig,
591
766
  config,
592
767
  oauthProviders: opts.oauthProviders ?? {}
@@ -596,9 +771,30 @@ function buildServerContext(opts) {
596
771
  return json(err.toResponse(), 403);
597
772
  }
598
773
  const msg = err instanceof Error ? err.message : String(err);
599
- return json({ error: "Internal Server Error", message: msg }, 500);
774
+ if (process.env.TRELLIS_DEBUG) {
775
+ console.error(
776
+ `[trellis] ${req.method} ${path} \u2192 500:`,
777
+ msg
778
+ );
779
+ }
780
+ return json(
781
+ {
782
+ error: "Internal Server Error",
783
+ message: msg,
784
+ method: req.method,
785
+ path
786
+ },
787
+ 500
788
+ );
600
789
  }
601
790
  };
791
+ const handleHttp = async (req) => {
792
+ if (enableCors && req.method === "OPTIONS") {
793
+ return corsPreflightResponse(req);
794
+ }
795
+ const res = await handleHttpInner(req);
796
+ return enableCors ? withCors(req, res) : res;
797
+ };
602
798
  return { port, authConfig, subs, handleHttp };
603
799
  }
604
800
  async function startServerNode(opts) {
@@ -638,17 +834,20 @@ async function startServerNode(opts) {
638
834
  }
639
835
  });
640
836
  }
837
+ function recordGraphIo(ctx, tenantId) {
838
+ ctx.meter.recordGraphIo(resolveUsageTenantId(tenantId));
839
+ }
641
840
  async function route(req, url, path, auth, tenantId, ctx) {
642
841
  const method = req.method.toUpperCase();
643
842
  if (method === "GET" && path === "/__trellis/inspector.js") {
644
843
  const candidates = [
645
- join(__moduleDir, "db", "inspector.js"),
844
+ join2(__moduleDir, "db", "inspector.js"),
646
845
  // chunk lives in dist/, inspector in dist/db/
647
- join(__moduleDir, "inspector.js")
846
+ join2(__moduleDir, "inspector.js")
648
847
  // chunk lives in dist/db/, inspector alongside
649
848
  ];
650
849
  for (const p of candidates) {
651
- if (existsSync(p)) {
850
+ if (existsSync2(p)) {
652
851
  return new Response(readFileSync(p, "utf-8"), {
653
852
  headers: { "Content-Type": "application/javascript; charset=utf-8" }
654
853
  });
@@ -663,11 +862,11 @@ async function route(req, url, path, auth, tenantId, ctx) {
663
862
  }
664
863
  if (method === "GET" && path === "/__trellis/trellis.css") {
665
864
  const candidates = [
666
- join(__moduleDir, "db", "trellis.css"),
667
- join(__moduleDir, "trellis.css")
865
+ join2(__moduleDir, "db", "trellis.css"),
866
+ join2(__moduleDir, "trellis.css")
668
867
  ];
669
868
  for (const p of candidates) {
670
- if (existsSync(p)) {
869
+ if (existsSync2(p)) {
671
870
  return new Response(readFileSync(p, "utf-8"), {
672
871
  headers: { "Content-Type": "text/css; charset=utf-8" }
673
872
  });
@@ -695,7 +894,7 @@ async function route(req, url, path, auth, tenantId, ctx) {
695
894
  });
696
895
  }
697
896
  if (method === "GET" && path === "/health") {
698
- const kernel = ctx.pool.get(tenantId);
897
+ const kernel = await ctx.pool.preload(tenantId);
699
898
  const ops = kernel.readAllOps().length;
700
899
  return json({
701
900
  status: "ok",
@@ -703,6 +902,9 @@ async function route(req, url, path, auth, tenantId, ctx) {
703
902
  tenants: ctx.pool.activeTenants().length
704
903
  });
705
904
  }
905
+ if (method === "GET" && path === "/admin/usage") {
906
+ return handleAdminUsage(req, url, ctx);
907
+ }
706
908
  if (path === "/entities" || path === "/entities/") {
707
909
  if (method === "POST") return handleCreate(req, auth, tenantId, ctx);
708
910
  if (method === "GET") return handleList(url, auth, tenantId, ctx);
@@ -748,7 +950,7 @@ async function handleCreate(req, auth, tenantId, ctx) {
748
950
  const body = await req.json();
749
951
  if (!body.type) return json({ error: "type is required" }, 400);
750
952
  ctx.permissions?.assert(auth, body.type, "create");
751
- const kernel = ctx.pool.get(tenantId);
953
+ const kernel = await ctx.pool.preload(tenantId);
752
954
  const entityId2 = `${body.type.toLowerCase()}:${crypto.randomUUID()}`;
753
955
  const attrs = {
754
956
  ...body.attributes
@@ -761,32 +963,35 @@ async function handleCreate(req, auth, tenantId, ctx) {
761
963
  attrs,
762
964
  body.links
763
965
  );
966
+ recordGraphIo(ctx, tenantId);
764
967
  await ctx.subs.notify(tenantId);
765
968
  return json({ id: entityId2, op: result.op.hash }, 201);
766
969
  }
767
970
  async function handleRead(id, auth, tenantId, ctx) {
768
- const kernel = ctx.pool.get(tenantId);
971
+ const kernel = await ctx.pool.preload(tenantId);
769
972
  const entity = kernel.getEntity(id);
770
973
  if (!entity) return json({ error: "Not Found" }, 404);
771
974
  ctx.permissions?.assert(auth, entity.type, "read", entity);
772
975
  return json(entityToJson(entity));
773
976
  }
774
977
  async function handleUpdate(req, id, auth, tenantId, ctx) {
775
- const kernel = ctx.pool.get(tenantId);
978
+ const kernel = await ctx.pool.preload(tenantId);
776
979
  const entity = kernel.getEntity(id);
777
980
  if (!entity) return json({ error: "Not Found" }, 404);
778
981
  ctx.permissions?.assert(auth, entity.type, "update", entity);
779
982
  const updates = await req.json();
780
983
  await kernel.updateEntity(id, updates);
984
+ recordGraphIo(ctx, tenantId);
781
985
  await ctx.subs.notify(tenantId);
782
986
  return json({ id, updated: true });
783
987
  }
784
988
  async function handleDelete(id, auth, tenantId, ctx) {
785
- const kernel = ctx.pool.get(tenantId);
989
+ const kernel = await ctx.pool.preload(tenantId);
786
990
  const entity = kernel.getEntity(id);
787
991
  if (!entity) return json({ error: "Not Found" }, 404);
788
992
  ctx.permissions?.assert(auth, entity.type, "delete", entity);
789
993
  await kernel.deleteEntity(id);
994
+ recordGraphIo(ctx, tenantId);
790
995
  await ctx.subs.notify(tenantId);
791
996
  return json({ id, deleted: true });
792
997
  }
@@ -794,7 +999,7 @@ async function handleList(url, auth, tenantId, ctx) {
794
999
  const type = url.searchParams.get("type") ?? void 0;
795
1000
  const limit = parseInt(url.searchParams.get("limit") ?? "100");
796
1001
  const offset = parseInt(url.searchParams.get("offset") ?? "0");
797
- const kernel = ctx.pool.get(tenantId);
1002
+ const kernel = await ctx.pool.preload(tenantId);
798
1003
  let entities = kernel.listEntities(type);
799
1004
  if (ctx.permissions && type) {
800
1005
  entities = entities.filter(
@@ -818,8 +1023,9 @@ async function handleQuery(req, auth, tenantId, ctx) {
818
1023
  } catch (err) {
819
1024
  return json({ error: "Invalid query", message: String(err) }, 400);
820
1025
  }
821
- const kernel = ctx.pool.get(tenantId);
1026
+ const kernel = await ctx.pool.preload(tenantId);
822
1027
  const result = await kernel.query(parsed);
1028
+ recordGraphIo(ctx, tenantId);
823
1029
  const bindings = hydrateBindings(
824
1030
  kernel,
825
1031
  result.bindings
@@ -829,21 +1035,73 @@ async function handleQuery(req, auth, tenantId, ctx) {
829
1035
  executionTime: result.executionTime
830
1036
  });
831
1037
  }
1038
+ async function readJsonObject(req, route2) {
1039
+ const raw = await req.text();
1040
+ if (!raw.trim()) {
1041
+ if (process.env.TRELLIS_DEBUG) {
1042
+ console.warn(`[trellis] ${req.method} ${route2}: empty request body`);
1043
+ }
1044
+ return {
1045
+ ok: false,
1046
+ response: json(
1047
+ {
1048
+ error: "Bad Request",
1049
+ message: "Request body is empty \u2014 client may not have sent JSON (common on iOS HTTP dev)",
1050
+ method: req.method,
1051
+ path: route2,
1052
+ bodyBytes: 0
1053
+ },
1054
+ 400
1055
+ )
1056
+ };
1057
+ }
1058
+ try {
1059
+ const data = JSON.parse(raw);
1060
+ return { ok: true, data };
1061
+ } catch (err) {
1062
+ const msg = err instanceof Error ? err.message : String(err);
1063
+ if (process.env.TRELLIS_DEBUG) {
1064
+ console.warn(
1065
+ `[trellis] ${req.method} ${route2}: invalid JSON (${raw.length}B):`,
1066
+ msg
1067
+ );
1068
+ }
1069
+ return {
1070
+ ok: false,
1071
+ response: json(
1072
+ {
1073
+ error: "Bad Request",
1074
+ message: `Invalid JSON: ${msg}`,
1075
+ method: req.method,
1076
+ path: route2,
1077
+ bodyBytes: raw.length
1078
+ },
1079
+ 400
1080
+ )
1081
+ };
1082
+ }
1083
+ }
832
1084
  async function handleCreateOntology(req, auth, tenantId, ctx) {
833
- const schema = await req.json();
1085
+ const parsed = await readJsonObject(req, "/ontologies");
1086
+ if (!parsed.ok) return parsed.response;
1087
+ const schema = parsed.data;
834
1088
  if (!schema || !schema["@id"] || !Array.isArray(schema.fields)) {
835
- return json({ error: "A SchemaDefinition (with @id and fields) is required" }, 400);
1089
+ return json(
1090
+ { error: "A SchemaDefinition (with @id and fields) is required" },
1091
+ 400
1092
+ );
836
1093
  }
837
1094
  if ((schema.tier ?? "user") === "core") {
838
1095
  return json({ error: "Core ontologies are immutable" }, 403);
839
1096
  }
840
- const kernel = ctx.pool.get(tenantId);
1097
+ const kernel = await ctx.pool.preload(tenantId);
841
1098
  const exists = kernel.listOntologies().some((ont) => ont["@id"] === schema["@id"]);
842
1099
  if (exists) {
843
1100
  return json({ id: schema["@id"], registered: false, existed: true }, 200);
844
1101
  }
845
1102
  try {
846
1103
  kernel.createOntology(schema);
1104
+ recordGraphIo(ctx, tenantId);
847
1105
  } catch (err) {
848
1106
  const msg = err instanceof Error ? err.message : String(err);
849
1107
  if (msg.includes("already exists")) {
@@ -861,7 +1119,7 @@ async function handleUpload(req, auth, tenantId, ctx) {
861
1119
  const buffer = new Uint8Array(await req.arrayBuffer());
862
1120
  const hashBuf = await crypto.subtle.digest("SHA-256", buffer);
863
1121
  const hash = `blob:${Array.from(new Uint8Array(hashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
864
- const kernel = ctx.pool.get(tenantId);
1122
+ const kernel = await ctx.pool.preload(tenantId);
865
1123
  const backend = kernel.getBackend();
866
1124
  if (!backend.hasBlob(hash)) {
867
1125
  backend.putBlob(hash, buffer);
@@ -869,7 +1127,7 @@ async function handleUpload(req, auth, tenantId, ctx) {
869
1127
  return json({ hash, size: buffer.length, contentType }, 201);
870
1128
  }
871
1129
  async function handleFileDownload(hash, ctx, tenantId) {
872
- const kernel = ctx.pool.get(tenantId);
1130
+ const kernel = await ctx.pool.preload(tenantId);
873
1131
  const backend = kernel.getBackend();
874
1132
  const blob = backend.getBlob(hash);
875
1133
  if (!blob) return json({ error: "Not Found" }, 404);
@@ -877,6 +1135,7 @@ async function handleFileDownload(hash, ctx, tenantId) {
877
1135
  blob.byteOffset,
878
1136
  blob.byteOffset + blob.byteLength
879
1137
  );
1138
+ ctx.meter.recordEgress(resolveUsageTenantId(tenantId), blob.byteLength);
880
1139
  return new Response(cleanBuf, {
881
1140
  headers: { "Content-Type": "application/octet-stream" }
882
1141
  });
@@ -889,7 +1148,7 @@ async function handleRegister(req, tenantId, ctx) {
889
1148
  if (!body.email || !body.password) {
890
1149
  return json({ error: "email and password are required" }, 400);
891
1150
  }
892
- const kernel = ctx.pool.get(tenantId);
1151
+ const kernel = await ctx.pool.preload(tenantId);
893
1152
  const existing = kernel.listEntities("User", { email: body.email });
894
1153
  if (existing.length > 0) {
895
1154
  return json({ error: "Email already registered" }, 409);
@@ -917,7 +1176,7 @@ async function handleLogin(req, tenantId, ctx) {
917
1176
  if (!body.email || !body.password) {
918
1177
  return json({ error: "email and password are required" }, 400);
919
1178
  }
920
- const kernel = ctx.pool.get(tenantId);
1179
+ const kernel = await ctx.pool.preload(tenantId);
921
1180
  const users = kernel.listEntities("User", { email: body.email });
922
1181
  if (users.length === 0) {
923
1182
  return json({ error: "Invalid credentials" }, 401);
@@ -960,7 +1219,7 @@ async function handleOAuthCallback(url, providerName, tenantId, ctx) {
960
1219
  } catch (err) {
961
1220
  return json({ error: "OAuth exchange failed", message: String(err) }, 400);
962
1221
  }
963
- const kernel = ctx.pool.get(tenantId);
1222
+ const kernel = await ctx.pool.preload(tenantId);
964
1223
  const oauthId = `oauth:${providerName}:${profile.id}`;
965
1224
  let users = kernel.listEntities("User", { oauthId });
966
1225
  let userId;
@@ -1046,6 +1305,24 @@ async function verifyPassword(password, stored) {
1046
1305
  const hash = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, "0")).join("");
1047
1306
  return hash === expectedHash;
1048
1307
  }
1308
+ function handleAdminUsage(req, url, ctx) {
1309
+ if (!process.env.TURTLEDB_ADMIN_KEY) {
1310
+ return json({ error: "Admin usage not configured" }, 501);
1311
+ }
1312
+ if (!verifyAdminKey(req)) {
1313
+ return json({ error: "Unauthorized" }, 401);
1314
+ }
1315
+ const tenant = url.searchParams.get("tenant");
1316
+ if (!tenant) {
1317
+ return json({ error: "tenant query param is required" }, 400);
1318
+ }
1319
+ const refresh = url.searchParams.get("refresh") === "1";
1320
+ if (refresh) {
1321
+ ctx.meter.sampleStorage(ctx.pool, tenant);
1322
+ }
1323
+ const day = url.searchParams.get("day") ?? void 0;
1324
+ return json(ctx.meter.getUsage(tenant, day));
1325
+ }
1049
1326
  function entityToJson(entity) {
1050
1327
  return entityRecordToPlain(entity);
1051
1328
  }
@@ -1071,6 +1348,11 @@ export {
1071
1348
  FULLY_PUBLIC,
1072
1349
  OWNER_ONLY,
1073
1350
  ADMIN_ONLY,
1351
+ resolveUsageTenantId,
1352
+ dayKey,
1353
+ sampleTenantStorage,
1354
+ verifyAdminKey,
1355
+ UsageMeter,
1074
1356
  SubscriptionManager,
1075
1357
  startServer,
1076
1358
  startServerCrossRuntime
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TrellisDb
3
- } from "./chunk-NZWFNHCS.js";
3
+ } from "./chunk-6RWGFC3N.js";
4
4
 
5
5
  // src/react/provider.tsx
6
6
  import React, {
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  TrellisVcsEngine,
3
3
  init_engine
4
- } from "./chunk-YY3GF7DH.js";
4
+ } from "./chunk-L5N5PR2S.js";
5
5
  import {
6
6
  IdbOpLog
7
7
  } from "./chunk-MPXUVGT3.js";
8
8
  import {
9
9
  JsonOpLog,
10
10
  init_op_log
11
- } from "./chunk-B3ZIFA2O.js";
11
+ } from "./chunk-VBYUTRXX.js";
12
12
  import {
13
13
  PartyKitRoomTransport,
14
14
  TrellisVcsSyncPeer