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,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
  }
@@ -573,7 +696,8 @@ function buildServerContext(opts) {
573
696
  apiKey: config.apiKey,
574
697
  allowPublic: true
575
698
  };
576
- const subs = new SubscriptionManager(pool, permissions ?? null);
699
+ const meter = new UsageMeter();
700
+ const subs = new SubscriptionManager(pool, permissions ?? null, meter);
577
701
  const handleHttp = async (req) => {
578
702
  const url = new URL(req.url);
579
703
  const path = url.pathname;
@@ -587,6 +711,7 @@ function buildServerContext(opts) {
587
711
  pool,
588
712
  permissions: permissions ?? null,
589
713
  subs,
714
+ meter,
590
715
  authConfig,
591
716
  config,
592
717
  oauthProviders: opts.oauthProviders ?? {}
@@ -638,17 +763,20 @@ async function startServerNode(opts) {
638
763
  }
639
764
  });
640
765
  }
766
+ function recordGraphIo(ctx, tenantId) {
767
+ ctx.meter.recordGraphIo(resolveUsageTenantId(tenantId));
768
+ }
641
769
  async function route(req, url, path, auth, tenantId, ctx) {
642
770
  const method = req.method.toUpperCase();
643
771
  if (method === "GET" && path === "/__trellis/inspector.js") {
644
772
  const candidates = [
645
- join(__moduleDir, "db", "inspector.js"),
773
+ join2(__moduleDir, "db", "inspector.js"),
646
774
  // chunk lives in dist/, inspector in dist/db/
647
- join(__moduleDir, "inspector.js")
775
+ join2(__moduleDir, "inspector.js")
648
776
  // chunk lives in dist/db/, inspector alongside
649
777
  ];
650
778
  for (const p of candidates) {
651
- if (existsSync(p)) {
779
+ if (existsSync2(p)) {
652
780
  return new Response(readFileSync(p, "utf-8"), {
653
781
  headers: { "Content-Type": "application/javascript; charset=utf-8" }
654
782
  });
@@ -663,11 +791,11 @@ async function route(req, url, path, auth, tenantId, ctx) {
663
791
  }
664
792
  if (method === "GET" && path === "/__trellis/trellis.css") {
665
793
  const candidates = [
666
- join(__moduleDir, "db", "trellis.css"),
667
- join(__moduleDir, "trellis.css")
794
+ join2(__moduleDir, "db", "trellis.css"),
795
+ join2(__moduleDir, "trellis.css")
668
796
  ];
669
797
  for (const p of candidates) {
670
- if (existsSync(p)) {
798
+ if (existsSync2(p)) {
671
799
  return new Response(readFileSync(p, "utf-8"), {
672
800
  headers: { "Content-Type": "text/css; charset=utf-8" }
673
801
  });
@@ -695,7 +823,7 @@ async function route(req, url, path, auth, tenantId, ctx) {
695
823
  });
696
824
  }
697
825
  if (method === "GET" && path === "/health") {
698
- const kernel = ctx.pool.get(tenantId);
826
+ const kernel = await ctx.pool.preload(tenantId);
699
827
  const ops = kernel.readAllOps().length;
700
828
  return json({
701
829
  status: "ok",
@@ -703,6 +831,9 @@ async function route(req, url, path, auth, tenantId, ctx) {
703
831
  tenants: ctx.pool.activeTenants().length
704
832
  });
705
833
  }
834
+ if (method === "GET" && path === "/admin/usage") {
835
+ return handleAdminUsage(req, url, ctx);
836
+ }
706
837
  if (path === "/entities" || path === "/entities/") {
707
838
  if (method === "POST") return handleCreate(req, auth, tenantId, ctx);
708
839
  if (method === "GET") return handleList(url, auth, tenantId, ctx);
@@ -748,7 +879,7 @@ async function handleCreate(req, auth, tenantId, ctx) {
748
879
  const body = await req.json();
749
880
  if (!body.type) return json({ error: "type is required" }, 400);
750
881
  ctx.permissions?.assert(auth, body.type, "create");
751
- const kernel = ctx.pool.get(tenantId);
882
+ const kernel = await ctx.pool.preload(tenantId);
752
883
  const entityId2 = `${body.type.toLowerCase()}:${crypto.randomUUID()}`;
753
884
  const attrs = {
754
885
  ...body.attributes
@@ -761,32 +892,35 @@ async function handleCreate(req, auth, tenantId, ctx) {
761
892
  attrs,
762
893
  body.links
763
894
  );
895
+ recordGraphIo(ctx, tenantId);
764
896
  await ctx.subs.notify(tenantId);
765
897
  return json({ id: entityId2, op: result.op.hash }, 201);
766
898
  }
767
899
  async function handleRead(id, auth, tenantId, ctx) {
768
- const kernel = ctx.pool.get(tenantId);
900
+ const kernel = await ctx.pool.preload(tenantId);
769
901
  const entity = kernel.getEntity(id);
770
902
  if (!entity) return json({ error: "Not Found" }, 404);
771
903
  ctx.permissions?.assert(auth, entity.type, "read", entity);
772
904
  return json(entityToJson(entity));
773
905
  }
774
906
  async function handleUpdate(req, id, auth, tenantId, ctx) {
775
- const kernel = ctx.pool.get(tenantId);
907
+ const kernel = await ctx.pool.preload(tenantId);
776
908
  const entity = kernel.getEntity(id);
777
909
  if (!entity) return json({ error: "Not Found" }, 404);
778
910
  ctx.permissions?.assert(auth, entity.type, "update", entity);
779
911
  const updates = await req.json();
780
912
  await kernel.updateEntity(id, updates);
913
+ recordGraphIo(ctx, tenantId);
781
914
  await ctx.subs.notify(tenantId);
782
915
  return json({ id, updated: true });
783
916
  }
784
917
  async function handleDelete(id, auth, tenantId, ctx) {
785
- const kernel = ctx.pool.get(tenantId);
918
+ const kernel = await ctx.pool.preload(tenantId);
786
919
  const entity = kernel.getEntity(id);
787
920
  if (!entity) return json({ error: "Not Found" }, 404);
788
921
  ctx.permissions?.assert(auth, entity.type, "delete", entity);
789
922
  await kernel.deleteEntity(id);
923
+ recordGraphIo(ctx, tenantId);
790
924
  await ctx.subs.notify(tenantId);
791
925
  return json({ id, deleted: true });
792
926
  }
@@ -794,7 +928,7 @@ async function handleList(url, auth, tenantId, ctx) {
794
928
  const type = url.searchParams.get("type") ?? void 0;
795
929
  const limit = parseInt(url.searchParams.get("limit") ?? "100");
796
930
  const offset = parseInt(url.searchParams.get("offset") ?? "0");
797
- const kernel = ctx.pool.get(tenantId);
931
+ const kernel = await ctx.pool.preload(tenantId);
798
932
  let entities = kernel.listEntities(type);
799
933
  if (ctx.permissions && type) {
800
934
  entities = entities.filter(
@@ -818,8 +952,9 @@ async function handleQuery(req, auth, tenantId, ctx) {
818
952
  } catch (err) {
819
953
  return json({ error: "Invalid query", message: String(err) }, 400);
820
954
  }
821
- const kernel = ctx.pool.get(tenantId);
955
+ const kernel = await ctx.pool.preload(tenantId);
822
956
  const result = await kernel.query(parsed);
957
+ recordGraphIo(ctx, tenantId);
823
958
  const bindings = hydrateBindings(
824
959
  kernel,
825
960
  result.bindings
@@ -832,18 +967,22 @@ async function handleQuery(req, auth, tenantId, ctx) {
832
967
  async function handleCreateOntology(req, auth, tenantId, ctx) {
833
968
  const schema = await req.json();
834
969
  if (!schema || !schema["@id"] || !Array.isArray(schema.fields)) {
835
- return json({ error: "A SchemaDefinition (with @id and fields) is required" }, 400);
970
+ return json(
971
+ { error: "A SchemaDefinition (with @id and fields) is required" },
972
+ 400
973
+ );
836
974
  }
837
975
  if ((schema.tier ?? "user") === "core") {
838
976
  return json({ error: "Core ontologies are immutable" }, 403);
839
977
  }
840
- const kernel = ctx.pool.get(tenantId);
978
+ const kernel = await ctx.pool.preload(tenantId);
841
979
  const exists = kernel.listOntologies().some((ont) => ont["@id"] === schema["@id"]);
842
980
  if (exists) {
843
981
  return json({ id: schema["@id"], registered: false, existed: true }, 200);
844
982
  }
845
983
  try {
846
984
  kernel.createOntology(schema);
985
+ recordGraphIo(ctx, tenantId);
847
986
  } catch (err) {
848
987
  const msg = err instanceof Error ? err.message : String(err);
849
988
  if (msg.includes("already exists")) {
@@ -861,7 +1000,7 @@ async function handleUpload(req, auth, tenantId, ctx) {
861
1000
  const buffer = new Uint8Array(await req.arrayBuffer());
862
1001
  const hashBuf = await crypto.subtle.digest("SHA-256", buffer);
863
1002
  const hash = `blob:${Array.from(new Uint8Array(hashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
864
- const kernel = ctx.pool.get(tenantId);
1003
+ const kernel = await ctx.pool.preload(tenantId);
865
1004
  const backend = kernel.getBackend();
866
1005
  if (!backend.hasBlob(hash)) {
867
1006
  backend.putBlob(hash, buffer);
@@ -869,7 +1008,7 @@ async function handleUpload(req, auth, tenantId, ctx) {
869
1008
  return json({ hash, size: buffer.length, contentType }, 201);
870
1009
  }
871
1010
  async function handleFileDownload(hash, ctx, tenantId) {
872
- const kernel = ctx.pool.get(tenantId);
1011
+ const kernel = await ctx.pool.preload(tenantId);
873
1012
  const backend = kernel.getBackend();
874
1013
  const blob = backend.getBlob(hash);
875
1014
  if (!blob) return json({ error: "Not Found" }, 404);
@@ -877,6 +1016,7 @@ async function handleFileDownload(hash, ctx, tenantId) {
877
1016
  blob.byteOffset,
878
1017
  blob.byteOffset + blob.byteLength
879
1018
  );
1019
+ ctx.meter.recordEgress(resolveUsageTenantId(tenantId), blob.byteLength);
880
1020
  return new Response(cleanBuf, {
881
1021
  headers: { "Content-Type": "application/octet-stream" }
882
1022
  });
@@ -889,7 +1029,7 @@ async function handleRegister(req, tenantId, ctx) {
889
1029
  if (!body.email || !body.password) {
890
1030
  return json({ error: "email and password are required" }, 400);
891
1031
  }
892
- const kernel = ctx.pool.get(tenantId);
1032
+ const kernel = await ctx.pool.preload(tenantId);
893
1033
  const existing = kernel.listEntities("User", { email: body.email });
894
1034
  if (existing.length > 0) {
895
1035
  return json({ error: "Email already registered" }, 409);
@@ -917,7 +1057,7 @@ async function handleLogin(req, tenantId, ctx) {
917
1057
  if (!body.email || !body.password) {
918
1058
  return json({ error: "email and password are required" }, 400);
919
1059
  }
920
- const kernel = ctx.pool.get(tenantId);
1060
+ const kernel = await ctx.pool.preload(tenantId);
921
1061
  const users = kernel.listEntities("User", { email: body.email });
922
1062
  if (users.length === 0) {
923
1063
  return json({ error: "Invalid credentials" }, 401);
@@ -960,7 +1100,7 @@ async function handleOAuthCallback(url, providerName, tenantId, ctx) {
960
1100
  } catch (err) {
961
1101
  return json({ error: "OAuth exchange failed", message: String(err) }, 400);
962
1102
  }
963
- const kernel = ctx.pool.get(tenantId);
1103
+ const kernel = await ctx.pool.preload(tenantId);
964
1104
  const oauthId = `oauth:${providerName}:${profile.id}`;
965
1105
  let users = kernel.listEntities("User", { oauthId });
966
1106
  let userId;
@@ -1046,6 +1186,24 @@ async function verifyPassword(password, stored) {
1046
1186
  const hash = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, "0")).join("");
1047
1187
  return hash === expectedHash;
1048
1188
  }
1189
+ function handleAdminUsage(req, url, ctx) {
1190
+ if (!process.env.TURTLEDB_ADMIN_KEY) {
1191
+ return json({ error: "Admin usage not configured" }, 501);
1192
+ }
1193
+ if (!verifyAdminKey(req)) {
1194
+ return json({ error: "Unauthorized" }, 401);
1195
+ }
1196
+ const tenant = url.searchParams.get("tenant");
1197
+ if (!tenant) {
1198
+ return json({ error: "tenant query param is required" }, 400);
1199
+ }
1200
+ const refresh = url.searchParams.get("refresh") === "1";
1201
+ if (refresh) {
1202
+ ctx.meter.sampleStorage(ctx.pool, tenant);
1203
+ }
1204
+ const day = url.searchParams.get("day") ?? void 0;
1205
+ return json(ctx.meter.getUsage(tenant, day));
1206
+ }
1049
1207
  function entityToJson(entity) {
1050
1208
  return entityRecordToPlain(entity);
1051
1209
  }
@@ -1071,6 +1229,11 @@ export {
1071
1229
  FULLY_PUBLIC,
1072
1230
  OWNER_ONLY,
1073
1231
  ADMIN_ONLY,
1232
+ resolveUsageTenantId,
1233
+ dayKey,
1234
+ sampleTenantStorage,
1235
+ verifyAdminKey,
1236
+ UsageMeter,
1074
1237
  SubscriptionManager,
1075
1238
  startServer,
1076
1239
  startServerCrossRuntime
@@ -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
@@ -0,0 +1,211 @@
1
+ import {
2
+ SPRITE_ENSURE_BUN_SH,
3
+ assertSpriteCli,
4
+ ensureSprite,
5
+ ensureSpritePublicAccess,
6
+ resolveSpritePublicUrl,
7
+ runSpriteCopy,
8
+ runSpriteExec
9
+ } from "./chunk-CAG4FULI.js";
10
+ import {
11
+ updateConfig
12
+ } from "./chunk-JA7AIHRK.js";
13
+ import {
14
+ __require
15
+ } from "./chunk-KFQGP6VL.js";
16
+
17
+ // src/server/deploy.ts
18
+ import { existsSync, writeFileSync, mkdirSync } from "fs";
19
+ import { join, resolve } from "path";
20
+
21
+ // src/server/deploy-meta.ts
22
+ var DEPLOY_NAME_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
23
+ var MIN_LEN = 3;
24
+ var MAX_LEN = 32;
25
+ var DeployNameError = class extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = "DeployNameError";
29
+ }
30
+ };
31
+ function normalizeDeployName(name) {
32
+ return name.trim().toLowerCase();
33
+ }
34
+ function validateDeployName(name) {
35
+ const normalized = normalizeDeployName(name);
36
+ if (!normalized) {
37
+ throw new DeployNameError("Deploy name is required.");
38
+ }
39
+ if (normalized.length < MIN_LEN || normalized.length > MAX_LEN) {
40
+ throw new DeployNameError(
41
+ `Deploy name must be ${MIN_LEN}\u2013${MAX_LEN} characters (got ${normalized.length}).`
42
+ );
43
+ }
44
+ if (normalized.includes("--")) {
45
+ throw new DeployNameError(
46
+ "Deploy name cannot contain consecutive hyphens."
47
+ );
48
+ }
49
+ if (!DEPLOY_NAME_RE.test(normalized)) {
50
+ throw new DeployNameError(
51
+ "Deploy name must start with a letter and use only lowercase letters, numbers, and single hyphens (e.g. my-app)."
52
+ );
53
+ }
54
+ return normalized;
55
+ }
56
+ function buildDeployUrl(name) {
57
+ const slug = validateDeployName(name);
58
+ return `https://${slug}.sprites.app`;
59
+ }
60
+ var SPRITE_PUBLIC_HTTP_PORT = 8080;
61
+
62
+ // src/server/deploy.ts
63
+ async function deploy(opts) {
64
+ const { configDir = ".", onProgress = () => {
65
+ } } = opts;
66
+ const listenPort = opts.port ?? SPRITE_PUBLIC_HTTP_PORT;
67
+ const name = validateDeployName(opts.name);
68
+ const apiKey = opts.apiKey ?? generateSecret("spk_");
69
+ const jwtSecret = opts.jwtSecret ?? generateSecret("jws_");
70
+ if (opts.stub) {
71
+ const url2 = buildDeployUrl(name);
72
+ onProgress("Stub deploy \u2014 skipping Sprites provisioning");
73
+ onProgress("Writing .trellis-db.json...");
74
+ writeRemoteDeployConfig({ name, url: url2, apiKey, configDir });
75
+ return { url: url2, name, apiKey };
76
+ }
77
+ onProgress("Checking Sprites CLI...");
78
+ await assertSpriteCli();
79
+ onProgress("Writing server entrypoint...");
80
+ const tmpDir = resolve(configDir, ".trellis-deploy");
81
+ if (!existsSync(tmpDir)) mkdirSync(tmpDir, { recursive: true });
82
+ const entrypoint = join(tmpDir, "server-entry.ts");
83
+ writeFileSync(
84
+ entrypoint,
85
+ generateServerEntrypoint({ port: listenPort, apiKey, jwtSecret })
86
+ );
87
+ onProgress("Bundling server with Bun...");
88
+ const bundlePath = join(tmpDir, "server.js");
89
+ await runBun([
90
+ "build",
91
+ entrypoint,
92
+ "--outfile",
93
+ bundlePath,
94
+ "--target",
95
+ "bun",
96
+ "--format",
97
+ "esm"
98
+ ]);
99
+ onProgress(`Ensuring Sprite: ${name}...`);
100
+ await ensureSprite(name);
101
+ onProgress("Configuring public URL...");
102
+ await ensureSpritePublicAccess(name);
103
+ const url = await resolveSpritePublicUrl(name);
104
+ onProgress(`Uploading to Sprite: ${name}...`);
105
+ await runSpriteExec(name, "mkdir -p /home/sprite/trellis-db");
106
+ await runSpriteCopy(bundlePath, name, "/home/sprite/trellis-db/server.js");
107
+ onProgress("Ensuring Bun is installed...");
108
+ const bunPath = await runSpriteExec(name, SPRITE_ENSURE_BUN_SH);
109
+ if (!bunPath.includes("bun")) {
110
+ throw new Error(`Bun install failed on sprite ${name}: ${bunPath || "(no output)"}`);
111
+ }
112
+ onProgress("Starting server...");
113
+ await runSpriteExec(
114
+ name,
115
+ 'pkill -f "/home/sprite/trellis-db/server.js" 2>/dev/null || true'
116
+ ).catch(() => {
117
+ });
118
+ const bun = bunPath.trim().split("\n").pop().trim();
119
+ await runSpriteExec(
120
+ name,
121
+ `cd /home/sprite/trellis-db && (nohup ${bun} run server.js >> server.log 2>&1 </dev/null &); exit 0`
122
+ );
123
+ onProgress("Waiting for health check...");
124
+ await waitForDeployHealth(url, 6e4);
125
+ onProgress("Writing .trellis-db.json...");
126
+ writeRemoteDeployConfig({ name, url, apiKey, configDir });
127
+ return { url, name, apiKey };
128
+ }
129
+ function writeRemoteDeployConfig(opts) {
130
+ updateConfig(
131
+ {
132
+ mode: "remote",
133
+ url: opts.url,
134
+ apiKey: opts.apiKey,
135
+ spriteName: opts.name,
136
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString()
137
+ },
138
+ opts.configDir
139
+ );
140
+ }
141
+ async function waitForDeployHealth(url, timeoutMs) {
142
+ const deadline = Date.now() + timeoutMs;
143
+ const healthUrl = `${url.replace(/\/$/, "")}/health`;
144
+ let lastStatus = 0;
145
+ while (Date.now() < deadline) {
146
+ try {
147
+ const res = await fetch(healthUrl);
148
+ lastStatus = res.status;
149
+ if (res.ok) return;
150
+ } catch {
151
+ }
152
+ await new Promise((r) => setTimeout(r, 2e3));
153
+ }
154
+ throw new Error(
155
+ `Deploy health check timed out (${healthUrl}${lastStatus ? `, last status ${lastStatus}` : ""})`
156
+ );
157
+ }
158
+ function generateSecret(prefix) {
159
+ const bytes = new Uint8Array(24);
160
+ crypto.getRandomValues(bytes);
161
+ const b64 = btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
162
+ return `${prefix}${b64}`;
163
+ }
164
+ async function runBun(args) {
165
+ const { execFile } = __require("child_process");
166
+ const { promisify } = __require("util");
167
+ const execFileAsync = promisify(execFile);
168
+ try {
169
+ await execFileAsync("bun", args);
170
+ } catch (err) {
171
+ throw new Error(
172
+ `bun ${args[0]} failed (exit ${err.code ?? "?"}): ${err.stderr ?? err.message}`
173
+ );
174
+ }
175
+ }
176
+ function generateServerEntrypoint(opts) {
177
+ return `
178
+ import { TenantPool, startServer } from 'trellis/server';
179
+ import { readConfig, defaultLocalConfig, writeConfig } from 'trellis/client';
180
+ import { join } from 'path';
181
+ import { existsSync } from 'fs';
182
+
183
+ const dbPath = '/home/sprite/trellis-db/data';
184
+ const configDir = '/home/sprite/trellis-db';
185
+
186
+ writeConfig(defaultLocalConfig(dbPath, {
187
+ apiKey: '${opts.apiKey}',
188
+ jwtSecret: '${opts.jwtSecret}',
189
+ port: ${opts.port},
190
+ }), configDir);
191
+
192
+ const config = readConfig(configDir)!;
193
+ // Sprites VMs lack better-sqlite3 native bindings \u2014 use WASM sql.js backend.
194
+ const pool = new TenantPool(dbPath, { backend: { backend: 'sqljs' } });
195
+ await pool.preload();
196
+
197
+ const server = startServer({ port: ${opts.port}, config, pool });
198
+
199
+ console.log('Trellis DB running on port ${opts.port}');
200
+ console.log(\`Listening on port ${opts.port}\`);
201
+ `;
202
+ }
203
+
204
+ export {
205
+ DeployNameError,
206
+ normalizeDeployName,
207
+ validateDeployName,
208
+ buildDeployUrl,
209
+ SPRITE_PUBLIC_HTTP_PORT,
210
+ deploy
211
+ };