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.
- package/CHANGELOG.md +7 -0
- package/README.md +61 -171
- package/dist/browser/index.d.ts +18 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +95 -0
- package/dist/{chunk-3ETIIPPB.js → chunk-2AACZRII.js} +312 -30
- package/dist/{chunk-3GDRZTAW.js → chunk-5KSGGKET.js} +1 -1
- package/dist/{chunk-MBNBTJCR.js → chunk-6GEZ6DN5.js} +2 -2
- package/dist/chunk-6RWGFC3N.js +403 -0
- package/dist/{chunk-X762XWVC.js → chunk-7DUMWO5L.js} +1 -1
- package/dist/chunk-CAG4FULI.js +121 -0
- package/dist/{chunk-GD5QJXSS.js → chunk-FWRS4CSC.js} +2 -2
- package/dist/{chunk-36VSSTL7.js → chunk-GU374KWZ.js} +1 -1
- package/dist/{chunk-BJ7EZ7FP.js → chunk-JHHEXEDM.js} +17 -0
- package/dist/{chunk-7WQGTFVJ.js → chunk-L5N5PR2S.js} +130 -75
- package/dist/{chunk-BXSTJVE3.js → chunk-N6MYZT4O.js} +2 -1
- package/dist/chunk-NWSW5YNS.js +225 -0
- package/dist/{chunk-HO4TURXW.js → chunk-S3XLFZ27.js} +28 -11
- package/dist/{chunk-BQBUVMRN.js → chunk-S6GBJ2ZB.js} +25 -14
- package/dist/{chunk-B3ZIFA2O.js → chunk-VBYUTRXX.js} +1 -1
- package/dist/cli/deploy-cli.d.ts +18 -0
- package/dist/cli/deploy-cli.d.ts.map +1 -0
- package/dist/cli/deploy-cli.js +67 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +153 -100
- package/dist/client/index.browser.d.ts +29 -0
- package/dist/client/index.browser.d.ts.map +1 -0
- package/dist/client/index.browser.js +64 -0
- package/dist/client/index.js +10 -10
- package/dist/client/sdk.browser.d.ts +131 -0
- package/dist/client/sdk.browser.d.ts.map +1 -0
- package/dist/{sdk-LZ7W3EEY.js → client/sdk.browser.js} +2 -2
- package/dist/client/sdk.js +1 -1
- package/dist/db/index.js +16 -16
- package/dist/db/inspector.js +6 -6
- package/dist/decisions/index.js +2 -2
- package/dist/embeddings/auto-embed.d.ts.map +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/embeddings/store.d.ts.map +1 -1
- package/dist/engine.d.ts +27 -7
- package/dist/engine.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/schema-hooks.js +7 -7
- package/dist/{remote-manager-SG545WNY.js → remote-manager-ODE3T2KR.js} +5 -5
- package/dist/scaffold/seed.d.ts.map +1 -1
- package/dist/scaffold/write.d.ts +1 -1
- package/dist/scaffold/write.d.ts.map +1 -1
- package/dist/schema/index.js +4 -3
- package/dist/server/cors.d.ts +13 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/deploy-meta.d.ts +25 -0
- package/dist/server/deploy-meta.d.ts.map +1 -0
- package/dist/server/deploy.d.ts +8 -3
- package/dist/server/deploy.d.ts.map +1 -1
- package/dist/server/deploy.js +9 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +51 -17
- package/dist/server/realtime.d.ts +4 -1
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/server.d.ts +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/sprites.d.ts +18 -1
- package/dist/server/sprites.d.ts.map +1 -1
- package/dist/server/tenancy.d.ts +4 -0
- package/dist/server/tenancy.d.ts.map +1 -1
- package/dist/server/usage-meter.d.ts +53 -0
- package/dist/server/usage-meter.d.ts.map +1 -0
- package/dist/{server-IGBIIJSF.js → server-OGVWNDNK.js} +6 -1
- package/dist/sprites-KQGTGM6W.js +27 -0
- package/dist/{tenancy-OP34NFFS.js → tenancy-POMWQP6M.js} +5 -3
- package/dist/vcs/index.js +3 -3
- package/dist/vcs/types.d.ts +2 -0
- package/dist/vcs/types.d.ts.map +1 -1
- package/package.json +17 -6
- package/dist/better-sqlite-backend-PII64VE6.js +0 -159
- package/dist/better-sqlite-backend-VUBVQ7SN.js +0 -209
- package/dist/better-sqlite-backend-Y3AQN65E.js +0 -159
- package/dist/better-sqlite-backend-Y3M7DXOX.js +0 -159
- package/dist/boot-middleware-FJDGVUM4.js +0 -9
- package/dist/boot-middleware-MLJUTLUM.js +0 -9
- package/dist/boot-middleware-ZZPDZNYR.js +0 -9
- package/dist/chunk-276LKLZR.js +0 -359
- package/dist/chunk-2CX5UCRV.js +0 -111
- package/dist/chunk-2ESYSVXG.js +0 -48
- package/dist/chunk-2I5JU73Y.js +0 -340
- package/dist/chunk-2UNSRRNB.js +0 -171
- package/dist/chunk-335UEHQR.js +0 -907
- package/dist/chunk-4CF2L46N.js +0 -29
- package/dist/chunk-5CINODKT.js +0 -41
- package/dist/chunk-5ESGWJ5L.js +0 -45
- package/dist/chunk-5FGLQGMF.js +0 -1676
- package/dist/chunk-5IQZFITY.js +0 -357
- package/dist/chunk-5JWFAQDW.js +0 -150
- package/dist/chunk-62UHTVU3.js +0 -141
- package/dist/chunk-66UJQT2I.js +0 -35
- package/dist/chunk-6RNNVOAS.js +0 -355
- package/dist/chunk-6X23PZ6H.js +0 -969
- package/dist/chunk-7C7LL6MB.js +0 -315
- package/dist/chunk-7F4HM3JG.js +0 -394
- package/dist/chunk-7KJ7T4D3.js +0 -910
- package/dist/chunk-7QQQNUB5.js +0 -92
- package/dist/chunk-7QWJBS72.js +0 -946
- package/dist/chunk-7R36OZK3.js +0 -957
- package/dist/chunk-7UPKCOHE.js +0 -946
- package/dist/chunk-A3BONW34.js +0 -35
- package/dist/chunk-AHMDVHBL.js +0 -141
- package/dist/chunk-AW6A4OZY.js +0 -171
- package/dist/chunk-BJ5EKCJ5.js +0 -458
- package/dist/chunk-BRGYDO7D.js +0 -223
- package/dist/chunk-C5KPSEDN.js +0 -359
- package/dist/chunk-CCTNYTUC.js +0 -940
- package/dist/chunk-CYQ2OVD2.js +0 -35
- package/dist/chunk-DDAK7YOE.js +0 -299
- package/dist/chunk-DRILKBY6.js +0 -965
- package/dist/chunk-DXE24HNN.js +0 -45
- package/dist/chunk-E7ICUNW7.js +0 -945
- package/dist/chunk-EAL2PUOE.js +0 -10022
- package/dist/chunk-ELT7MLZB.js +0 -109
- package/dist/chunk-EWEIRF6G.js +0 -171
- package/dist/chunk-F5VDGQJC.js +0 -120
- package/dist/chunk-FCPL3IEQ.js +0 -98
- package/dist/chunk-FDPKYSV7.js +0 -96
- package/dist/chunk-FES3TC24.js +0 -171
- package/dist/chunk-FF36CQHZ.js +0 -61
- package/dist/chunk-FOWKR5TW.js +0 -129
- package/dist/chunk-FZVCXDYW.js +0 -150
- package/dist/chunk-G3XIHPSQ.js +0 -361
- package/dist/chunk-GKJLYLVU.js +0 -359
- package/dist/chunk-GNXHXNVQ.js +0 -113
- package/dist/chunk-GSR6ILTM.js +0 -277
- package/dist/chunk-GWRIMRSX.js +0 -45
- package/dist/chunk-H72UW2HJ.js +0 -45
- package/dist/chunk-HGI74PXB.js +0 -7417
- package/dist/chunk-HGRA37LO.js +0 -145
- package/dist/chunk-HXSRQO3L.js +0 -145
- package/dist/chunk-IG2X2BVO.js +0 -373
- package/dist/chunk-IIJVDD54.js +0 -373
- package/dist/chunk-IUWVEEOX.js +0 -145
- package/dist/chunk-IX4BBOIT.js +0 -394
- package/dist/chunk-IXU4RMQM.js +0 -349
- package/dist/chunk-J6OUIION.js +0 -857
- package/dist/chunk-K46RNFDE.js +0 -171
- package/dist/chunk-KAGU2XMY.js +0 -359
- package/dist/chunk-KCVYJNVX.js +0 -394
- package/dist/chunk-KKDRMIV6.js +0 -419
- package/dist/chunk-KYLIQ3YI.js +0 -399
- package/dist/chunk-KZYJJIRN.js +0 -946
- package/dist/chunk-LFOX744K.js +0 -96
- package/dist/chunk-LHOVJEWR.js +0 -7403
- package/dist/chunk-LOPXTPF3.js +0 -120
- package/dist/chunk-LZR3VIFU.js +0 -907
- package/dist/chunk-MCKGQKYU.js +0 -15
- package/dist/chunk-MU6EOS6F.js +0 -0
- package/dist/chunk-MWRQ2TXT.js +0 -357
- package/dist/chunk-NO73OK2I.js +0 -102
- package/dist/chunk-NPE2YJHK.js +0 -394
- package/dist/chunk-NSDXU6QX.js +0 -2213
- package/dist/chunk-NXIRDSZ6.js +0 -145
- package/dist/chunk-NZWFNHCS.js +0 -407
- package/dist/chunk-OB4CMMX6.js +0 -260
- package/dist/chunk-OBKU74FU.js +0 -7411
- package/dist/chunk-OQNDWRDE.js +0 -145
- package/dist/chunk-OVWAJMB6.js +0 -394
- package/dist/chunk-PDHLBTTD.js +0 -45
- package/dist/chunk-PEQNRKLG.js +0 -210
- package/dist/chunk-QERIS7QY.js +0 -7403
- package/dist/chunk-QHA2DM3C.js +0 -373
- package/dist/chunk-QLM4HSYC.js +0 -45
- package/dist/chunk-S2OT3TQJ.js +0 -283
- package/dist/chunk-S5PHSN5K.js +0 -907
- package/dist/chunk-SIAA4J6H.js +0 -21
- package/dist/chunk-SQBNLUIA.js +0 -171
- package/dist/chunk-STLN6A6B.js +0 -355
- package/dist/chunk-U7HV7GU2.js +0 -17
- package/dist/chunk-UGZHOGRA.js +0 -39
- package/dist/chunk-UICF66FS.js +0 -359
- package/dist/chunk-UK3EQREE.js +0 -1078
- package/dist/chunk-UPKPURVY.js +0 -394
- package/dist/chunk-URC74VCN.js +0 -959
- package/dist/chunk-V3G7Q3QQ.js +0 -102
- package/dist/chunk-VUJ6SA55.js +0 -303
- package/dist/chunk-VUJQLIML.js +0 -141
- package/dist/chunk-VWKLKLYB.js +0 -267
- package/dist/chunk-VYC3RZMK.js +0 -277
- package/dist/chunk-WMERMSWI.js +0 -154
- package/dist/chunk-WMR777PO.js +0 -2213
- package/dist/chunk-X46IECAC.js +0 -517
- package/dist/chunk-XEN3OEYY.js +0 -2234
- package/dist/chunk-XZQ66K7A.js +0 -124
- package/dist/chunk-YY3GF7DH.js +0 -7411
- package/dist/chunk-ZE5R3H4C.js +0 -907
- package/dist/chunk-ZHVI7BQN.js +0 -1077
- package/dist/chunk-ZMN5LUB7.js +0 -7411
- package/dist/chunk-ZUV4MD5U.js +0 -94
- package/dist/config-KMY6RRK3.js +0 -19
- package/dist/config-VDMK2DUJ.js +0 -19
- package/dist/db/trellis.css +0 -1
- package/dist/deploy-66EC752W.js +0 -9
- package/dist/deploy-SO7AFK6L.js +0 -9
- package/dist/engine-7GXBM4IT.js +0 -7
- package/dist/engine-NSR6BIF7.js +0 -7
- package/dist/factory-2P64OC7M.js +0 -34
- package/dist/factory-YP4ZZK5K.js +0 -34
- package/dist/import-AJLYHFIA.js +0 -10
- package/dist/launch-explorer-AI6L2AWJ.js +0 -91
- package/dist/query-GJL3CVGN.js +0 -31
- package/dist/query-MBJSK3EB.js +0 -31
- package/dist/query-TJHEVSKL.js +0 -31
- package/dist/query-WUIKOHPE.js +0 -524
- package/dist/remote-manager-BLJZ6KF6.js +0 -224
- package/dist/remote-manager-BPYE46SI.js +0 -223
- package/dist/remote-manager-FMESEAFA.js +0 -224
- package/dist/remote-manager-K2WZ5RHY.js +0 -223
- package/dist/remote-manager-SG5PQKCH.js +0 -224
- package/dist/remote-manager-WGP6MIQZ.js +0 -224
- package/dist/sdk-3KBB3E6V.js +0 -16
- package/dist/sdk-4ZQO6UTU.js +0 -16
- package/dist/sdk-6JA6GV3G.js +0 -16
- package/dist/sdk-6VTP5UTZ.js +0 -16
- package/dist/sdk-7VS6TYCD.js +0 -16
- package/dist/sdk-HQ3FTLRT.js +0 -16
- package/dist/sdk-HZ7NU3XE.js +0 -15
- package/dist/sdk-KBQE2OXT.js +0 -16
- package/dist/sdk-MSCDZ7JU.js +0 -15
- package/dist/sdk-WNZOGO3S.js +0 -15
- package/dist/sdk-YLV33ZCW.js +0 -9
- package/dist/sdk-ZSUD2LLJ.js +0 -16
- package/dist/server-AFXFFBWA.js +0 -11
- package/dist/server-AGHC4VUL.js +0 -11
- package/dist/server-BOH7M4TT.js +0 -13
- package/dist/server-DBUY2KY5.js +0 -11
- package/dist/server-GWBQJPRG.js +0 -11
- package/dist/server-JZZFULVU.js +0 -11
- package/dist/server-L5XAF4M5.js +0 -11
- package/dist/server-NYFUHANA.js +0 -12
- package/dist/server-OABUB2LO.js +0 -12
- package/dist/server-P2CKZWWV.js +0 -12
- package/dist/sprites-RU3E4XQN.js +0 -15
- package/dist/sprites-XVSMQLSI.js +0 -15
- package/dist/sql-wasm-FORCZFHZ.js +0 -2148
- package/dist/sql-wasm-JZH76RLO.js +0 -2148
- package/dist/sqljs-backend-CJQOIQYE.js +0 -266
- package/dist/sqljs-backend-KWXBUDUO.js +0 -266
- package/dist/state-demo.bundle.js +0 -1033
- package/dist/tenancy-CBWYEVUB.js +0 -11
- package/dist/tenancy-FO6WVYFN.js +0 -13
- package/dist/tenancy-FWDBESMX.js +0 -14
- package/dist/tenancy-HGPIS5QA.js +0 -13
- package/dist/tenancy-HXFALA37.js +0 -14
- package/dist/tenancy-S676GLID.js +0 -14
- package/dist/tenancy-V7ARWNNV.js +0 -14
- package/dist/tenancy-ZDN4GXK5.js +0 -13
- package/dist/tenancy-ZT5VMY7B.js +0 -14
- package/dist/vm-config-YZRJ3KUB.js +0 -21
- /package/dist/{chunk-2BRTCEHB.js → chunk-PZ4XYZ4J.js} +0 -0
- /package/dist/{chunk-EO3J7GRP.js → chunk-WXSWA3MV.js} +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// src/client/sdk.browser.ts
|
|
2
|
+
function isRemote(opts) {
|
|
3
|
+
return "url" in opts;
|
|
4
|
+
}
|
|
5
|
+
function browserOnlyRemoteError(feature) {
|
|
6
|
+
return new Error(
|
|
7
|
+
`${feature} is not available in browser bundles. Use remote mode with \`new TrellisDb({ url })\`, or import \`trellis/client\` in Node.`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
function randomId() {
|
|
11
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
12
|
+
return globalThis.crypto.randomUUID();
|
|
13
|
+
}
|
|
14
|
+
return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 11)}`;
|
|
15
|
+
}
|
|
16
|
+
function needsXhrForBody() {
|
|
17
|
+
if (typeof globalThis.window !== "undefined" && window.__TRELLIS_NATIVE_HTTP__) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (typeof navigator === "undefined") return false;
|
|
21
|
+
const ua = navigator.userAgent;
|
|
22
|
+
if (/iPad|iPhone|iPod/i.test(ua)) return true;
|
|
23
|
+
if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) return true;
|
|
24
|
+
if (/AppleWebKit/i.test(ua) && /KHTML, like Gecko\)/i.test(ua) && !/Safari\/|Chrome\/|Chromium\/|Edg\/|Version\//i.test(ua)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function xhrRequest(method, url, headers, body) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const xhr = new XMLHttpRequest();
|
|
32
|
+
xhr.open(method, url);
|
|
33
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
34
|
+
xhr.setRequestHeader(key, value);
|
|
35
|
+
}
|
|
36
|
+
xhr.onload = () => resolve({ status: xhr.status, text: xhr.responseText });
|
|
37
|
+
xhr.onerror = () => reject(new Error(`XHR network error on ${method} ${url}`));
|
|
38
|
+
xhr.send(body);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var TrellisDb = class {
|
|
42
|
+
opts;
|
|
43
|
+
_ws = null;
|
|
44
|
+
/** In-flight connect — concurrent subscribe() shares one socket open. */
|
|
45
|
+
_wsPromise = null;
|
|
46
|
+
_subCallbacks = /* @__PURE__ */ new Map();
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
if (!isRemote(opts)) {
|
|
49
|
+
throw browserOnlyRemoteError("TrellisDb local mode");
|
|
50
|
+
}
|
|
51
|
+
this.opts = opts;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Browser bundles cannot read `.trellis-db.json`.
|
|
55
|
+
*/
|
|
56
|
+
static async fromConfig(_dir = ".") {
|
|
57
|
+
throw browserOnlyRemoteError("TrellisDb.fromConfig()");
|
|
58
|
+
}
|
|
59
|
+
// -------------------------------------------------------------------------
|
|
60
|
+
// CRUD
|
|
61
|
+
// -------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Create a new entity.
|
|
64
|
+
* Returns the generated entity ID.
|
|
65
|
+
*/
|
|
66
|
+
async create(type, attributes = {}, links) {
|
|
67
|
+
const res = await this._fetch("POST", "/entities", {
|
|
68
|
+
type,
|
|
69
|
+
attributes,
|
|
70
|
+
links
|
|
71
|
+
});
|
|
72
|
+
return res.id;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read an entity by ID.
|
|
76
|
+
* Returns null if not found.
|
|
77
|
+
*/
|
|
78
|
+
async read(id) {
|
|
79
|
+
try {
|
|
80
|
+
return await this._fetch(
|
|
81
|
+
"GET",
|
|
82
|
+
`/entities/${encodeURIComponent(id)}`
|
|
83
|
+
);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err?.status === 404) return null;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Update an entity's attributes (partial update).
|
|
91
|
+
*/
|
|
92
|
+
async update(id, attributes) {
|
|
93
|
+
await this._fetch(
|
|
94
|
+
"PUT",
|
|
95
|
+
`/entities/${encodeURIComponent(id)}`,
|
|
96
|
+
attributes
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Delete an entity by ID.
|
|
101
|
+
*/
|
|
102
|
+
async delete(id) {
|
|
103
|
+
await this._fetch("DELETE", `/entities/${encodeURIComponent(id)}`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* List entities of a given type.
|
|
107
|
+
*/
|
|
108
|
+
async list(type, opts = {}) {
|
|
109
|
+
const params = new URLSearchParams();
|
|
110
|
+
if (type) params.set("type", type);
|
|
111
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
112
|
+
if (opts.offset) params.set("offset", String(opts.offset));
|
|
113
|
+
const qs = params.toString() ? `?${params}` : "";
|
|
114
|
+
return await this._fetch("GET", `/entities${qs}`);
|
|
115
|
+
}
|
|
116
|
+
// -------------------------------------------------------------------------
|
|
117
|
+
// Query
|
|
118
|
+
// -------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Run an EQL-S query string.
|
|
121
|
+
*/
|
|
122
|
+
async query(eql) {
|
|
123
|
+
return await this._fetch("POST", "/query", {
|
|
124
|
+
query: eql
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// -------------------------------------------------------------------------
|
|
128
|
+
// Schema registration
|
|
129
|
+
// -------------------------------------------------------------------------
|
|
130
|
+
/**
|
|
131
|
+
* Register a user/system-tier ontology schema with a remote Trellis server.
|
|
132
|
+
*/
|
|
133
|
+
async registerType(schema) {
|
|
134
|
+
const def = "@id" in schema ? schema : schema.definition;
|
|
135
|
+
const label = def?.label ?? (def?.["@id"] ? String(def["@id"]).replace(/^trellis:/, "") : "unknown");
|
|
136
|
+
if (!def?.["@id"]) {
|
|
137
|
+
throw new Error(`registerType(${label}): schema is missing a definition with @id`);
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await this._fetch("POST", "/ontologies", def);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err instanceof FetchError && err.status === 409) return;
|
|
143
|
+
if (err instanceof FetchError) {
|
|
144
|
+
const serverMsg = typeof err.body?.message === "string" ? String(err.body.message) : err.message;
|
|
145
|
+
throw new FetchError(err.status, serverMsg, err.body, {
|
|
146
|
+
...err.context,
|
|
147
|
+
operation: `registerType(${label})`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
// File upload
|
|
155
|
+
// -------------------------------------------------------------------------
|
|
156
|
+
/**
|
|
157
|
+
* Upload a file to the blob store.
|
|
158
|
+
* Returns a content-addressed hash.
|
|
159
|
+
*/
|
|
160
|
+
async upload(data, contentType = "application/octet-stream") {
|
|
161
|
+
const raw = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
162
|
+
const cleanBuf = raw.buffer.slice(
|
|
163
|
+
raw.byteOffset,
|
|
164
|
+
raw.byteOffset + raw.byteLength
|
|
165
|
+
);
|
|
166
|
+
const res = await fetch(`${this.opts.url}/upload`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": contentType,
|
|
170
|
+
...this.opts.apiKey ? { Authorization: `Bearer ${this.opts.apiKey}` } : {}
|
|
171
|
+
},
|
|
172
|
+
body: cleanBuf
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) throw new FetchError(res.status, await res.text());
|
|
175
|
+
return await res.json();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Download a file by its blob hash.
|
|
179
|
+
*/
|
|
180
|
+
async getFile(hash) {
|
|
181
|
+
const res = await fetch(
|
|
182
|
+
`${this.opts.url}/files/${encodeURIComponent(hash)}`,
|
|
183
|
+
{
|
|
184
|
+
headers: this.opts.apiKey ? { Authorization: `Bearer ${this.opts.apiKey}` } : {}
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
if (res.status === 404) return null;
|
|
188
|
+
if (!res.ok) throw new FetchError(res.status, await res.text());
|
|
189
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
190
|
+
}
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
// Auth helpers
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
async register(email, password, name) {
|
|
195
|
+
return await this._fetch("POST", "/auth/register", {
|
|
196
|
+
email,
|
|
197
|
+
password,
|
|
198
|
+
name
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async login(email, password) {
|
|
202
|
+
return await this._fetch("POST", "/auth/login", {
|
|
203
|
+
email,
|
|
204
|
+
password
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Set the active API key / JWT token for subsequent requests.
|
|
209
|
+
*/
|
|
210
|
+
setToken(token) {
|
|
211
|
+
this.opts.apiKey = token;
|
|
212
|
+
}
|
|
213
|
+
// -------------------------------------------------------------------------
|
|
214
|
+
// Realtime
|
|
215
|
+
// -------------------------------------------------------------------------
|
|
216
|
+
/**
|
|
217
|
+
* Subscribe to a live EQL-S query.
|
|
218
|
+
* Callback is fired immediately with the initial result, then on every update.
|
|
219
|
+
*/
|
|
220
|
+
subscribe(eql, callback, opts) {
|
|
221
|
+
const subId = `sub_${randomId()}`;
|
|
222
|
+
this._subCallbacks.set(subId, callback);
|
|
223
|
+
this._ensureWs().then((ws) => {
|
|
224
|
+
ws.send(
|
|
225
|
+
JSON.stringify({
|
|
226
|
+
type: "subscribe",
|
|
227
|
+
id: subId,
|
|
228
|
+
query: eql,
|
|
229
|
+
...this.opts.tenantId ? { tenantId: this.opts.tenantId } : {},
|
|
230
|
+
...opts?.entityType ? { entityType: opts.entityType } : {},
|
|
231
|
+
...opts?.resolve ? { resolve: opts.resolve } : {}
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
unsubscribe: () => {
|
|
237
|
+
this._subCallbacks.delete(subId);
|
|
238
|
+
this._ws?.send(JSON.stringify({ type: "unsubscribe", id: subId }));
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Close the WebSocket connection.
|
|
244
|
+
*/
|
|
245
|
+
disconnect() {
|
|
246
|
+
const ws = this._ws;
|
|
247
|
+
this._ws = null;
|
|
248
|
+
this._wsPromise = null;
|
|
249
|
+
ws?.close();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Close open client resources.
|
|
253
|
+
*/
|
|
254
|
+
close() {
|
|
255
|
+
this.disconnect();
|
|
256
|
+
}
|
|
257
|
+
// -------------------------------------------------------------------------
|
|
258
|
+
// Private
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
/**
|
|
261
|
+
* Tenant isolation, not authorization: when `tenantId` is supplied out-of-band
|
|
262
|
+
* (not bound to the JWT), the server trusts the query param. Safe for ephemeral
|
|
263
|
+
* showcase rooms with unguessable ids; real multi-tenant auth must bind the
|
|
264
|
+
* tenant to the auth token instead. Skipped when the JWT already carries it.
|
|
265
|
+
*/
|
|
266
|
+
_applyTenant(path) {
|
|
267
|
+
const tenantId = this.opts.tenantId;
|
|
268
|
+
if (!tenantId) return path;
|
|
269
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
270
|
+
return `${path}${sep}tenantId=${encodeURIComponent(tenantId)}`;
|
|
271
|
+
}
|
|
272
|
+
async _fetch(method, path, body) {
|
|
273
|
+
const url = `${this.opts.url}${this._applyTenant(path)}`;
|
|
274
|
+
let jsonBody;
|
|
275
|
+
if (body !== void 0) {
|
|
276
|
+
jsonBody = JSON.stringify(body);
|
|
277
|
+
if (jsonBody === void 0) {
|
|
278
|
+
throw new Error(`Request body is not JSON-serializable (${method} ${path})`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const headers = {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
...this.opts.apiKey ? { Authorization: `Bearer ${this.opts.apiKey}` } : {}
|
|
284
|
+
};
|
|
285
|
+
const transport = jsonBody !== void 0 && needsXhrForBody() ? "xhr" : "fetch";
|
|
286
|
+
headers["X-Trellis-Transport"] = transport;
|
|
287
|
+
const bodyBytes = jsonBody ? new TextEncoder().encode(jsonBody).byteLength : 0;
|
|
288
|
+
let status;
|
|
289
|
+
let text;
|
|
290
|
+
if (transport === "xhr" && jsonBody !== void 0) {
|
|
291
|
+
({ status, text } = await xhrRequest(method, url, headers, jsonBody));
|
|
292
|
+
} else {
|
|
293
|
+
const res = await fetch(url, {
|
|
294
|
+
method,
|
|
295
|
+
headers,
|
|
296
|
+
body: jsonBody
|
|
297
|
+
});
|
|
298
|
+
status = res.status;
|
|
299
|
+
text = await res.text();
|
|
300
|
+
}
|
|
301
|
+
let data = null;
|
|
302
|
+
if (text) {
|
|
303
|
+
try {
|
|
304
|
+
data = JSON.parse(text);
|
|
305
|
+
} catch {
|
|
306
|
+
data = { raw: text };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (status < 200 || status >= 300) {
|
|
310
|
+
const server = data;
|
|
311
|
+
const message = typeof server?.message === "string" && server.message || typeof server?.error === "string" && server.error || "request failed";
|
|
312
|
+
const err = new FetchError(status, message, data, {
|
|
313
|
+
method,
|
|
314
|
+
path,
|
|
315
|
+
url,
|
|
316
|
+
requestBodyBytes: bodyBytes,
|
|
317
|
+
responseBytes: text.length,
|
|
318
|
+
transport
|
|
319
|
+
});
|
|
320
|
+
console.error("[trellis]", err.toString());
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
return data;
|
|
324
|
+
}
|
|
325
|
+
_ensureWs() {
|
|
326
|
+
if (this._ws?.readyState === WebSocket.OPEN) {
|
|
327
|
+
return Promise.resolve(this._ws);
|
|
328
|
+
}
|
|
329
|
+
if (this._wsPromise) {
|
|
330
|
+
return this._wsPromise;
|
|
331
|
+
}
|
|
332
|
+
const wsUrl = this.opts.url.replace(
|
|
333
|
+
/^https?/,
|
|
334
|
+
this.opts.url.startsWith("https") ? "wss" : "ws"
|
|
335
|
+
) + "/realtime";
|
|
336
|
+
this._wsPromise = new Promise((resolve, reject) => {
|
|
337
|
+
const ws = new WebSocket(wsUrl);
|
|
338
|
+
ws.onopen = () => {
|
|
339
|
+
this._ws = ws;
|
|
340
|
+
resolve(ws);
|
|
341
|
+
};
|
|
342
|
+
ws.onerror = (e) => {
|
|
343
|
+
reject(e instanceof Error ? e : new Error("WebSocket error"));
|
|
344
|
+
};
|
|
345
|
+
ws.onmessage = (e) => {
|
|
346
|
+
try {
|
|
347
|
+
const msg = JSON.parse(e.data);
|
|
348
|
+
if (msg.type === "data" && this._subCallbacks.has(msg.id)) {
|
|
349
|
+
const meta = msg.resolved === true ? { resolved: true } : void 0;
|
|
350
|
+
this._subCallbacks.get(msg.id)(msg.result, msg.diff, meta);
|
|
351
|
+
}
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
ws.onclose = () => {
|
|
356
|
+
if (this._ws === ws) {
|
|
357
|
+
this._ws = null;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}).finally(() => {
|
|
361
|
+
this._wsPromise = null;
|
|
362
|
+
});
|
|
363
|
+
return this._wsPromise;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var FetchError = class _FetchError extends Error {
|
|
367
|
+
constructor(status, message, body, context = {}) {
|
|
368
|
+
super(_FetchError.format(status, message, context));
|
|
369
|
+
this.status = status;
|
|
370
|
+
this.body = body;
|
|
371
|
+
this.name = "FetchError";
|
|
372
|
+
this.context = context;
|
|
373
|
+
}
|
|
374
|
+
context;
|
|
375
|
+
static format(status, message, ctx) {
|
|
376
|
+
const parts = [`HTTP ${status}`];
|
|
377
|
+
if (ctx.operation) parts.push(ctx.operation);
|
|
378
|
+
if (ctx.method && ctx.path) parts.push(`${ctx.method} ${ctx.path}`);
|
|
379
|
+
parts.push(message);
|
|
380
|
+
if (ctx.requestBodyBytes !== void 0) {
|
|
381
|
+
parts.push(`sent ${ctx.requestBodyBytes}B`);
|
|
382
|
+
}
|
|
383
|
+
if (ctx.responseBytes !== void 0) {
|
|
384
|
+
parts.push(`response ${ctx.responseBytes}B`);
|
|
385
|
+
}
|
|
386
|
+
if (ctx.transport) parts.push(ctx.transport);
|
|
387
|
+
return parts.join(" \xB7 ");
|
|
388
|
+
}
|
|
389
|
+
toString() {
|
|
390
|
+
const extra = [];
|
|
391
|
+
if (this.body && typeof this.body === "object") {
|
|
392
|
+
const b = this.body;
|
|
393
|
+
if (typeof b.path === "string") extra.push(`server ${b.method} ${b.path}`);
|
|
394
|
+
if (typeof b.bodyBytes === "number") extra.push(`server saw ${b.bodyBytes}B`);
|
|
395
|
+
}
|
|
396
|
+
return extra.length ? `${this.message} (${extra.join(", ")})` : this.message;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
export {
|
|
401
|
+
TrellisDb,
|
|
402
|
+
FetchError
|
|
403
|
+
};
|
|
@@ -330,7 +330,7 @@ var TrellisDb = class _TrellisDb {
|
|
|
330
330
|
async _getPool() {
|
|
331
331
|
if (this._pool) return this._pool;
|
|
332
332
|
if (!this._poolPromise) {
|
|
333
|
-
this._poolPromise = import("./tenancy-
|
|
333
|
+
this._poolPromise = import("./tenancy-POMWQP6M.js").then(({ TenantPool }) => {
|
|
334
334
|
const path = this.opts.path;
|
|
335
335
|
this._pool = new TenantPool(path);
|
|
336
336
|
return this._pool;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/server/sprites.ts
|
|
2
|
+
import { execFile, spawn } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
var execFileAsync = promisify(execFile);
|
|
5
|
+
async function runSpriteCmd(args) {
|
|
6
|
+
try {
|
|
7
|
+
const { stdout } = await execFileAsync("sprite", args);
|
|
8
|
+
return stdout.trim();
|
|
9
|
+
} catch (err) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`sprite ${args[0]} failed (exit ${err.code ?? "?"}): ${err.stderr ?? err.message}`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function runSpriteExec(spriteName, shellCommand, extraArgs = []) {
|
|
16
|
+
return runSpriteCmd([
|
|
17
|
+
"exec",
|
|
18
|
+
"-s",
|
|
19
|
+
spriteName,
|
|
20
|
+
...extraArgs,
|
|
21
|
+
"--",
|
|
22
|
+
"bash",
|
|
23
|
+
"-c",
|
|
24
|
+
shellCommand
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
async function runSpriteCopy(localPath, spriteName, remotePath) {
|
|
28
|
+
try {
|
|
29
|
+
await execFileAsync("sprite", [
|
|
30
|
+
"exec",
|
|
31
|
+
"-s",
|
|
32
|
+
spriteName,
|
|
33
|
+
"--file",
|
|
34
|
+
`${localPath}:${remotePath}`,
|
|
35
|
+
"echo",
|
|
36
|
+
"uploaded"
|
|
37
|
+
]);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`sprite file upload failed (exit ${err.code ?? "?"}): ${err.stderr ?? err.message}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var SPRITE_ENSURE_BUN_SH = `
|
|
45
|
+
set -e
|
|
46
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
47
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
48
|
+
curl -fsSL https://bun.sh/install | bash
|
|
49
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
50
|
+
fi
|
|
51
|
+
command -v bun
|
|
52
|
+
`.trim();
|
|
53
|
+
function parseSpriteUrlOutput(stdout) {
|
|
54
|
+
const match = stdout.match(/URL:\s*(https:\/\/\S+)/i);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new Error(`Could not parse sprite URL from output: ${stdout.trim()}`);
|
|
57
|
+
}
|
|
58
|
+
return match[1].replace(/\/$/, "");
|
|
59
|
+
}
|
|
60
|
+
async function resolveSpritePublicUrl(spriteName) {
|
|
61
|
+
const out = await runSpriteCmd(["url", "-s", spriteName]);
|
|
62
|
+
return parseSpriteUrlOutput(out);
|
|
63
|
+
}
|
|
64
|
+
async function ensureSpritePublicAccess(spriteName) {
|
|
65
|
+
try {
|
|
66
|
+
await runSpriteCmd(["url", "update", "--auth", "public", "-s", spriteName]);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const msg = String(err.message ?? err);
|
|
69
|
+
if (/already|unchanged|public/i.test(msg)) return;
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function ensureSprite(spriteName) {
|
|
74
|
+
try {
|
|
75
|
+
await runSpriteCmd(["create", spriteName, "--skip-console"]);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const msg = String(err.message ?? err);
|
|
78
|
+
if (/already exists|duplicate|conflict/i.test(msg)) return;
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function runSpriteInteractive(args) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const proc = spawn("sprite", args, { stdio: "inherit" });
|
|
85
|
+
proc.on("close", (code) => {
|
|
86
|
+
if (code !== 0)
|
|
87
|
+
reject(new Error(`sprite ${args[0]} failed (exit ${code})`));
|
|
88
|
+
else resolve();
|
|
89
|
+
});
|
|
90
|
+
proc.on("error", reject);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function assertSpriteCli() {
|
|
94
|
+
await runSpriteCmd(["--version"]).catch(() => {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"`sprite` CLI not found. Install it from https://docs.sprites.dev and authenticate."
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function resolveSprite(explicitName) {
|
|
101
|
+
if (explicitName) {
|
|
102
|
+
return explicitName;
|
|
103
|
+
}
|
|
104
|
+
throw new Error(
|
|
105
|
+
"No sprite specified. Use --sprite <name> or set an active sprite with `trellis vm use <name>`"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
runSpriteCmd,
|
|
111
|
+
runSpriteExec,
|
|
112
|
+
runSpriteCopy,
|
|
113
|
+
SPRITE_ENSURE_BUN_SH,
|
|
114
|
+
parseSpriteUrlOutput,
|
|
115
|
+
resolveSpritePublicUrl,
|
|
116
|
+
ensureSpritePublicAccess,
|
|
117
|
+
ensureSprite,
|
|
118
|
+
runSpriteInteractive,
|
|
119
|
+
assertSpriteCli,
|
|
120
|
+
resolveSprite
|
|
121
|
+
};
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
init_merge,
|
|
11
11
|
init_milestone,
|
|
12
12
|
init_op_log
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-VBYUTRXX.js";
|
|
14
14
|
import {
|
|
15
15
|
init_types
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-N6MYZT4O.js";
|
|
17
17
|
import {
|
|
18
18
|
init_ops,
|
|
19
19
|
isVcsOp
|
|
@@ -13,6 +13,12 @@ import { existsSync, mkdirSync } from "fs";
|
|
|
13
13
|
import { join, resolve } from "path";
|
|
14
14
|
var DEFAULT_TENANT = "default";
|
|
15
15
|
var TENANTS_DIR = "tenants";
|
|
16
|
+
function resolvePoolBackendFromEnv() {
|
|
17
|
+
const backend = process.env.TRELLIS_BACKEND;
|
|
18
|
+
if (backend === "sqljs") return { backend: "sqljs" };
|
|
19
|
+
if (backend === "better-sqlite") return { backend: "better-sqlite" };
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
16
22
|
var TenantPool = class {
|
|
17
23
|
pool = /* @__PURE__ */ new Map();
|
|
18
24
|
dbPath;
|
|
@@ -43,6 +49,12 @@ var TenantPool = class {
|
|
|
43
49
|
get(tenantId) {
|
|
44
50
|
const id = tenantId ?? DEFAULT_TENANT;
|
|
45
51
|
if (!this.pool.has(id)) {
|
|
52
|
+
const isBun = typeof process !== "undefined" && Boolean(process.versions?.bun);
|
|
53
|
+
if (!isBun) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"TenantPool.get() on Node requires `await pool.preload()` first. `trellis db serve` preloads automatically; custom servers should call preload before get()."
|
|
56
|
+
);
|
|
57
|
+
}
|
|
46
58
|
this.pool.set(id, this._createKernelSync(id));
|
|
47
59
|
}
|
|
48
60
|
return this.pool.get(id);
|
|
@@ -94,6 +106,10 @@ var TenantPool = class {
|
|
|
94
106
|
}
|
|
95
107
|
this.pool.clear();
|
|
96
108
|
}
|
|
109
|
+
/** Absolute path to the tenant data directory. */
|
|
110
|
+
dataPath() {
|
|
111
|
+
return this.dbPath;
|
|
112
|
+
}
|
|
97
113
|
/**
|
|
98
114
|
* Return the SQLite file path for a given tenant.
|
|
99
115
|
*/
|
|
@@ -141,5 +157,6 @@ var TenantPool = class {
|
|
|
141
157
|
|
|
142
158
|
export {
|
|
143
159
|
DEFAULT_TENANT,
|
|
160
|
+
resolvePoolBackendFromEnv,
|
|
144
161
|
TenantPool
|
|
145
162
|
};
|