@tailorkit/app 0.1.0-beta.29 → 0.1.0-beta.32
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/LICENSE.md +47 -174
- package/README.md +37 -38
- package/dist/chunk-7ZQPXG7O.js +34 -0
- package/dist/chunk-PJBDA2RU.js +415 -0
- package/dist/{chunk-STTKSRAI.js → chunk-UDXPIO6D.js} +267 -112
- package/dist/{chunk-ANTVMHDZ.js → chunk-YX3JURMG.js} +100 -59
- package/dist/client/connection.d.ts +11 -11
- package/dist/client/query-store.d.ts +2 -2
- package/dist/client/reference.d.ts +4 -4
- package/dist/client/session.d.ts +27 -4
- package/dist/client/tools.d.ts +11 -5
- package/dist/client-connection.js +4 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +3 -3
- package/dist/database/definition.d.ts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/preact.d.ts +5 -5
- package/dist/protocol.d.ts +23 -23
- package/dist/protocol.js +11 -9
- package/dist/routing.d.ts +4 -4
- package/dist/server/builder.d.ts +6 -6
- package/dist/server/bundle.d.ts +1 -6
- package/dist/server/functions.d.ts +19 -19
- package/dist/server/instances.d.ts +5 -4
- package/dist/server.d.ts +1 -1
- package/dist/server.js +4 -4
- package/dist/views.d.ts +4 -3
- package/package.json +34 -22
- package/dist/builder/client-views.d.ts +0 -16
- package/dist/builder/database-paths.d.ts +0 -4
- package/dist/builder/define-view.d.ts +0 -3
- package/dist/builder/drizzle.d.ts +0 -2
- package/dist/builder/file-routes.d.ts +0 -34
- package/dist/builder/generate-migrations.d.ts +0 -6
- package/dist/builder/index.d.ts +0 -11
- package/dist/builder/instances.d.ts +0 -19
- package/dist/builder/migrations.d.ts +0 -6
- package/dist/builder/schema-check.d.ts +0 -2
- package/dist/builder/server.d.ts +0 -6
- package/dist/builder/upload-manifest.d.ts +0 -20
- package/dist/builder.js +0 -1531
- package/dist/chunk-DA2TE2FW.js +0 -27
- package/dist/chunk-IJBQ77AH.js +0 -35
- package/dist/chunk-L5ECQGR7.js +0 -26
- package/dist/chunk-SWBQZDWT.js +0 -321
- package/dist/config/config.d.ts +0 -86
- package/dist/config/index.d.ts +0 -1
- package/dist/config/loader.d.ts +0 -7
- package/dist/config-loader.js +0 -7
- package/dist/config.js +0 -8
- package/dist/preact-version.d.ts +0 -7
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppError,
|
|
3
|
+
appError
|
|
4
|
+
} from "./chunk-7ZQPXG7O.js";
|
|
5
|
+
|
|
6
|
+
// src/client/connection.ts
|
|
7
|
+
import { createORPCClient } from "@orpc/client";
|
|
8
|
+
import { RPCLink as FetchLink } from "@orpc/client/fetch";
|
|
9
|
+
import { RPCLink } from "@orpc/client/websocket";
|
|
10
|
+
|
|
11
|
+
// src/client/session.ts
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var isObjectValue = (value) => typeof value === "object" && value !== null;
|
|
14
|
+
var identitySchema = z.object({
|
|
15
|
+
appId: z.string(),
|
|
16
|
+
deploymentId: z.string(),
|
|
17
|
+
expiresAt: z.number(),
|
|
18
|
+
installationId: z.string(),
|
|
19
|
+
projectId: z.string(),
|
|
20
|
+
scope: z.object({ name: z.string(), value: z.record(z.string(), z.json()) }),
|
|
21
|
+
subjectId: z.string().optional(),
|
|
22
|
+
toolUrl: z.string()
|
|
23
|
+
});
|
|
24
|
+
var sessionSchema = z.object({
|
|
25
|
+
expiresAt: z.number(),
|
|
26
|
+
identity: identitySchema.optional(),
|
|
27
|
+
subjectId: z.string().optional(),
|
|
28
|
+
token: z.string(),
|
|
29
|
+
toolUrl: z.string().optional(),
|
|
30
|
+
url: z.string()
|
|
31
|
+
});
|
|
32
|
+
var parseSession = (value) => sessionSchema.parse(value);
|
|
33
|
+
var APP_SESSION_RENEWAL_MS = 6e4;
|
|
34
|
+
var freezeIdentity = (value) => {
|
|
35
|
+
if (isObjectValue(value) && !Object.isFrozen(value)) {
|
|
36
|
+
for (const child of Object.values(value)) {
|
|
37
|
+
freezeIdentity(child);
|
|
38
|
+
}
|
|
39
|
+
Object.freeze(value);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
var sessionRenewalDelay = (session) => Math.max(500, session.expiresAt - Date.now() - APP_SESSION_RENEWAL_MS);
|
|
44
|
+
var createSessionProvider = (options) => {
|
|
45
|
+
const base = new URL(options.baseUrl, globalThis.location?.href);
|
|
46
|
+
if (!base.pathname.endsWith("/")) {
|
|
47
|
+
base.pathname += "/";
|
|
48
|
+
}
|
|
49
|
+
let cached;
|
|
50
|
+
let pending;
|
|
51
|
+
return async (input) => {
|
|
52
|
+
if (!input.refresh && cached && cached.expiresAt > Date.now() + APP_SESSION_RENEWAL_MS) {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
pending ??= (async () => {
|
|
56
|
+
try {
|
|
57
|
+
return await (async () => {
|
|
58
|
+
const response = await (async () => {
|
|
59
|
+
try {
|
|
60
|
+
return await (options.fetch ?? fetch)(
|
|
61
|
+
new URL("backend/session", base),
|
|
62
|
+
{
|
|
63
|
+
body: JSON.stringify({ appId: options.appId }),
|
|
64
|
+
credentials: "same-origin",
|
|
65
|
+
headers: { "content-type": "application/json" },
|
|
66
|
+
method: "POST"
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new AppError(
|
|
71
|
+
"UNAVAILABLE",
|
|
72
|
+
"Unable to reach the app session provider"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const code = response.status === 403 ? "FORBIDDEN" : "UNAVAILABLE";
|
|
78
|
+
throw new AppError(
|
|
79
|
+
response.status === 401 ? "UNAUTHORIZED" : code,
|
|
80
|
+
"Unable to authorize the app backend"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const parsed = sessionSchema.safeParse(await response.json());
|
|
84
|
+
if (!parsed.success) {
|
|
85
|
+
throw new AppError("UNAVAILABLE", "Invalid app session");
|
|
86
|
+
}
|
|
87
|
+
cached = parsed.data;
|
|
88
|
+
if (cached.identity) {
|
|
89
|
+
cached.identity = freezeIdentity(cached.identity);
|
|
90
|
+
}
|
|
91
|
+
return cached;
|
|
92
|
+
})();
|
|
93
|
+
} finally {
|
|
94
|
+
pending = void 0;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
return await pending;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/client/reference.ts
|
|
102
|
+
var isStringValue = (value) => typeof value === "string";
|
|
103
|
+
var reference = (name, _kind) => Object.freeze({ name });
|
|
104
|
+
var createApi = () => {
|
|
105
|
+
const group = (path) => new Proxy(
|
|
106
|
+
/* oxlint-disable typescript/no-unsafe-type-assertion -- The proxy synthesizes every declared reference from its property path; its empty target is never used as an implemented reference. */
|
|
107
|
+
/* @__PURE__ */ Object.create(null),
|
|
108
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
109
|
+
{
|
|
110
|
+
get: (_target, name) => {
|
|
111
|
+
if (!isStringValue(name) || name === "then") {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
if (name === "name" && path) {
|
|
115
|
+
return path;
|
|
116
|
+
}
|
|
117
|
+
return group(path ? `${path}.${name}` : name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
return group("");
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/client/connection.ts
|
|
125
|
+
var backendUrl = (session, route) => {
|
|
126
|
+
const url = new URL(session.url);
|
|
127
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
128
|
+
throw new AppError("BAD_REQUEST", "Invalid app backend URL");
|
|
129
|
+
}
|
|
130
|
+
if (url.protocol === "wss:") {
|
|
131
|
+
url.protocol = "https:";
|
|
132
|
+
}
|
|
133
|
+
if (url.protocol === "ws:") {
|
|
134
|
+
url.protocol = "http:";
|
|
135
|
+
}
|
|
136
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
137
|
+
throw new AppError("BAD_REQUEST", "App backends require HTTPS");
|
|
138
|
+
}
|
|
139
|
+
if (session.expiresAt <= Date.now() + 1e3) {
|
|
140
|
+
throw new AppError("UNAUTHORIZED", "App token expired");
|
|
141
|
+
}
|
|
142
|
+
url.pathname = `${url.pathname.replace(/\/$/u, "")}/${route}`;
|
|
143
|
+
return url;
|
|
144
|
+
};
|
|
145
|
+
var waitForRetry = async (signal, delayMs) => {
|
|
146
|
+
if (signal.aborted) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
await (async () => {
|
|
150
|
+
const {
|
|
151
|
+
promise: pendingPromise,
|
|
152
|
+
resolve,
|
|
153
|
+
reject: rejectPending
|
|
154
|
+
} = Promise.withResolvers();
|
|
155
|
+
try {
|
|
156
|
+
const wake = () => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
signal.removeEventListener("abort", wake);
|
|
159
|
+
resolve();
|
|
160
|
+
};
|
|
161
|
+
const timer = setTimeout(
|
|
162
|
+
wake,
|
|
163
|
+
delayMs
|
|
164
|
+
);
|
|
165
|
+
signal.addEventListener("abort", wake, { once: true });
|
|
166
|
+
} catch (error) {
|
|
167
|
+
rejectPending(error);
|
|
168
|
+
}
|
|
169
|
+
await pendingPromise;
|
|
170
|
+
})();
|
|
171
|
+
};
|
|
172
|
+
var createClient = (options = {}) => {
|
|
173
|
+
let closed = false;
|
|
174
|
+
let socket;
|
|
175
|
+
let timer;
|
|
176
|
+
const stops = /* @__PURE__ */ new Set();
|
|
177
|
+
const requests = /* @__PURE__ */ new Set();
|
|
178
|
+
const getSession = options.getSession ?? (async (request) => {
|
|
179
|
+
const bridge = globalThis.__tailorkitBackendSession;
|
|
180
|
+
if (!bridge) {
|
|
181
|
+
throw new AppError("UNAVAILABLE", "The host JWT bridge is unavailable");
|
|
182
|
+
}
|
|
183
|
+
return await bridge(request);
|
|
184
|
+
});
|
|
185
|
+
const wire = createORPCClient(
|
|
186
|
+
new RPCLink({
|
|
187
|
+
connect: async (info) => {
|
|
188
|
+
if (closed) {
|
|
189
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
190
|
+
}
|
|
191
|
+
const session = await getSession({ refresh: info.totalAttempt > 1 });
|
|
192
|
+
if (closed) {
|
|
193
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
194
|
+
}
|
|
195
|
+
const url = backendUrl(session, "queries");
|
|
196
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
197
|
+
socket = await (options.connect ?? ((urlValue, protocols) => new WebSocket(urlValue, protocols)))(url.href, ["tailorkit", `jwt.${session.token}`]);
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
timer = setTimeout(
|
|
200
|
+
() => socket?.close(1e3, "Renew authentication"),
|
|
201
|
+
sessionRenewalDelay(session)
|
|
202
|
+
);
|
|
203
|
+
return socket;
|
|
204
|
+
},
|
|
205
|
+
reconnect: {
|
|
206
|
+
delay: (info) => info.attempt === 1 ? 0 : options.retryDelayMs ?? 1e3,
|
|
207
|
+
enabled: true
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
const call = async (kind, input) => {
|
|
212
|
+
if (closed) {
|
|
213
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
214
|
+
}
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
requests.add(controller);
|
|
217
|
+
try {
|
|
218
|
+
const invocation = kind === "mutations" ? structuredClone(input) : input;
|
|
219
|
+
let attempt = 0;
|
|
220
|
+
let refresh = false;
|
|
221
|
+
let refreshed = false;
|
|
222
|
+
while (true) {
|
|
223
|
+
try {
|
|
224
|
+
if (closed) {
|
|
225
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
226
|
+
}
|
|
227
|
+
const session = await getSession({ refresh });
|
|
228
|
+
refresh = false;
|
|
229
|
+
if (closed) {
|
|
230
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
231
|
+
}
|
|
232
|
+
const url = backendUrl(session, kind);
|
|
233
|
+
url.pathname = url.pathname.slice(0, -kind.length).replace(/\/$/u, "");
|
|
234
|
+
const http = createORPCClient(
|
|
235
|
+
new FetchLink({
|
|
236
|
+
fetch: async (urlValue2, init) => {
|
|
237
|
+
let response;
|
|
238
|
+
try {
|
|
239
|
+
response = await (options.fetch ?? fetch)(urlValue2, {
|
|
240
|
+
...init,
|
|
241
|
+
credentials: "omit"
|
|
242
|
+
});
|
|
243
|
+
} catch {
|
|
244
|
+
throw new AppError(
|
|
245
|
+
"UNAVAILABLE",
|
|
246
|
+
"App connection interrupted"
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if ([502, 503, 504].includes(response.status)) {
|
|
250
|
+
throw new AppError(
|
|
251
|
+
"UNAVAILABLE",
|
|
252
|
+
"App backend temporarily unavailable"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (kind === "mutations" && response.body) {
|
|
256
|
+
try {
|
|
257
|
+
return new Response(await response.arrayBuffer(), {
|
|
258
|
+
headers: response.headers,
|
|
259
|
+
status: response.status
|
|
260
|
+
});
|
|
261
|
+
} catch {
|
|
262
|
+
throw new AppError(
|
|
263
|
+
"UNAVAILABLE",
|
|
264
|
+
"App connection interrupted"
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return response;
|
|
269
|
+
},
|
|
270
|
+
headers: { authorization: `Bearer ${session.token}` },
|
|
271
|
+
origin: url.origin,
|
|
272
|
+
// SAFETY: URL.pathname always starts with a slash for this HTTP session URL, satisfying the transport's absolute route-path type.
|
|
273
|
+
url: (
|
|
274
|
+
/* oxlint-disable typescript/no-unsafe-type-assertion -- URL.pathname always starts with a slash for this HTTP session URL, satisfying the transport's absolute route-path type. */
|
|
275
|
+
url.pathname
|
|
276
|
+
)
|
|
277
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
if (kind === "mutations") {
|
|
281
|
+
return await http.mutations(
|
|
282
|
+
/* oxlint-disable typescript/no-unsafe-type-assertion -- The mutation branch assigned invocation.requestId before entering this retry loop. */
|
|
283
|
+
invocation,
|
|
284
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
285
|
+
{
|
|
286
|
+
signal: controller.signal
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return await http[kind](invocation, { signal: controller.signal });
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const failure = appError(error);
|
|
293
|
+
if (kind !== "mutations" || closed || controller.signal.aborted) {
|
|
294
|
+
throw failure;
|
|
295
|
+
}
|
|
296
|
+
if (failure.code === "UNAUTHORIZED" && !refreshed) {
|
|
297
|
+
refresh = true;
|
|
298
|
+
refreshed = true;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (failure.code !== "UNAVAILABLE") {
|
|
302
|
+
throw failure;
|
|
303
|
+
}
|
|
304
|
+
await waitForRetry(
|
|
305
|
+
controller.signal,
|
|
306
|
+
Math.min(
|
|
307
|
+
(options.retryDelayMs ?? 1e3) * 2 ** Math.min((attempt += 1) - 1, 5),
|
|
308
|
+
3e4
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
throw appError(error);
|
|
315
|
+
} finally {
|
|
316
|
+
requests.delete(controller);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
return {
|
|
320
|
+
// SAFETY: The action reference owns its output type, and the server validates that registered action before the shared transport returns its erased result.
|
|
321
|
+
action: async (ref, ...[args]) => await /* oxlint-disable typescript/no-unsafe-type-assertion -- The action reference owns its output type, and the server validates that registered action before the shared transport returns its erased result. */
|
|
322
|
+
call("actions", { args, name: ref.name }),
|
|
323
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
324
|
+
close: () => {
|
|
325
|
+
closed = true;
|
|
326
|
+
for (const stop of stops) {
|
|
327
|
+
stop();
|
|
328
|
+
}
|
|
329
|
+
for (const request of requests) {
|
|
330
|
+
request.abort();
|
|
331
|
+
}
|
|
332
|
+
clearTimeout(timer);
|
|
333
|
+
socket?.close();
|
|
334
|
+
},
|
|
335
|
+
// SAFETY: The mutation reference owns its output type, and the server validates that registered mutation before the shared transport returns its erased result.
|
|
336
|
+
mutate: async (ref, ...[args, settings]) => await /* oxlint-disable typescript/no-unsafe-type-assertion -- The mutation reference owns its output type, and the server validates that registered mutation before the shared transport returns its erased result. */
|
|
337
|
+
call("mutations", {
|
|
338
|
+
args,
|
|
339
|
+
name: ref.name,
|
|
340
|
+
requestId: settings?.requestId ?? crypto.randomUUID()
|
|
341
|
+
}),
|
|
342
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
343
|
+
// SAFETY: The query reference owns its output type, and the server validates that registered query before the shared transport returns its erased result.
|
|
344
|
+
query: async (ref, ...[args]) => await /* oxlint-disable typescript/no-unsafe-type-assertion -- The query reference owns its output type, and the server validates that registered query before the shared transport returns its erased result. */
|
|
345
|
+
call("queries", { args, name: ref.name }),
|
|
346
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
347
|
+
subscribe: (ref, args, next, settings = {}) => {
|
|
348
|
+
if (closed) {
|
|
349
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
350
|
+
}
|
|
351
|
+
const controller = new AbortController();
|
|
352
|
+
const stop = () => {
|
|
353
|
+
controller.abort();
|
|
354
|
+
stops.delete(stop);
|
|
355
|
+
};
|
|
356
|
+
stops.add(stop);
|
|
357
|
+
const run = async () => {
|
|
358
|
+
while (!controller.signal.aborted) {
|
|
359
|
+
try {
|
|
360
|
+
const stream = await wire.subscribe(
|
|
361
|
+
{ args, name: ref.name },
|
|
362
|
+
{ signal: controller.signal }
|
|
363
|
+
);
|
|
364
|
+
for await (const value of stream) {
|
|
365
|
+
if (controller.signal.aborted || closed) {
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
next(
|
|
369
|
+
/* oxlint-disable typescript/no-unsafe-type-assertion -- This subscription was opened with ref.name and args; its listener receives only results from that same registered query, whose output generic is erased by the transport. */
|
|
370
|
+
value
|
|
371
|
+
/* oxlint-enable typescript/no-unsafe-type-assertion */
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (controller.signal.aborted || closed) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
const failure = appError(error);
|
|
379
|
+
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
380
|
+
settings.onError?.(failure);
|
|
381
|
+
}
|
|
382
|
+
if (![
|
|
383
|
+
"UNAUTHORIZED",
|
|
384
|
+
"UNAVAILABLE",
|
|
385
|
+
"INTERNAL_SERVER_ERROR"
|
|
386
|
+
].includes(failure.code)) {
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!controller.signal.aborted && !closed) {
|
|
391
|
+
await waitForRetry(controller.signal, options.retryDelayMs ?? 1e3);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
stop();
|
|
395
|
+
};
|
|
396
|
+
void (async () => {
|
|
397
|
+
try {
|
|
398
|
+
await run();
|
|
399
|
+
} catch (error) {
|
|
400
|
+
stop();
|
|
401
|
+
settings.onError?.(appError(error));
|
|
402
|
+
}
|
|
403
|
+
})();
|
|
404
|
+
return stop;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export {
|
|
410
|
+
parseSession,
|
|
411
|
+
createSessionProvider,
|
|
412
|
+
reference,
|
|
413
|
+
createApi,
|
|
414
|
+
createClient
|
|
415
|
+
};
|