@tailorkit/app 0.1.0-beta.14 → 0.1.0-beta.16
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/README.md +184 -0
- package/dist/builder/drizzle.d.ts +2 -0
- package/dist/builder/generate-migrations.d.ts +6 -0
- package/dist/builder/index.d.ts +1 -1
- package/dist/builder/migrations.d.ts +6 -0
- package/dist/builder/schema-check.d.ts +2 -0
- package/dist/builder/server.d.ts +3 -0
- package/dist/builder/upload-manifest.d.ts +3 -2
- package/dist/builder.js +362 -24
- package/dist/chunk-3XA36UOQ.js +612 -0
- package/dist/chunk-46CTW3XJ.js +120 -0
- package/dist/{chunk-EKCUETAV.js → chunk-DEU6RITM.js} +6 -1
- package/dist/{chunk-ZFYKVXDO.js → chunk-GVB2PAK7.js} +7 -6
- package/dist/chunk-IJBQ77AH.js +35 -0
- package/dist/client/connection.d.ts +31 -0
- package/dist/client/query-store.d.ts +23 -0
- package/dist/client/reference.d.ts +20 -0
- package/dist/client/session.d.ts +14 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +32 -0
- package/dist/config/config.d.ts +6 -2
- package/dist/config-loader.js +2 -2
- package/dist/config.js +1 -1
- package/dist/database/schema.d.ts +78 -0
- package/dist/database/types.d.ts +7 -0
- package/dist/errors.d.ts +6 -0
- package/dist/index.d.ts +2 -87
- package/dist/index.js +78 -69
- package/dist/preact.d.ts +52 -0
- package/dist/protocol.d.ts +74 -0
- package/dist/protocol.js +28 -0
- package/dist/server/builder.d.ts +40 -0
- package/dist/server/bundle.d.ts +17 -0
- package/dist/server/functions.d.ts +90 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +56 -0
- package/dist/views.d.ts +89 -0
- package/package.json +27 -8
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppError,
|
|
3
|
+
appError
|
|
4
|
+
} from "./chunk-IJBQ77AH.js";
|
|
5
|
+
|
|
6
|
+
// src/views.ts
|
|
7
|
+
import { createContext, h, render } from "preact";
|
|
8
|
+
import { useContext } from "preact/hooks";
|
|
9
|
+
var preactVersion = "10.29.8";
|
|
10
|
+
var createView = (path, options) => {
|
|
11
|
+
const Context2 = createContext(null);
|
|
12
|
+
const View = (props) => {
|
|
13
|
+
if (props.status !== "ready") {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return h(
|
|
17
|
+
Context2.Provider,
|
|
18
|
+
{ value: props.context },
|
|
19
|
+
h(options.component, {})
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
component: View,
|
|
24
|
+
path,
|
|
25
|
+
useContext: () => {
|
|
26
|
+
const context = useContext(Context2);
|
|
27
|
+
if (context === null) {
|
|
28
|
+
throw new Error(`View context is only available while rendering "${path}".`);
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
var defineClient = (client) => ({
|
|
35
|
+
...client,
|
|
36
|
+
$meta: { preactVersion },
|
|
37
|
+
$runtime: {
|
|
38
|
+
h,
|
|
39
|
+
render: (vnode, parent) => render(vnode && client.component ? h(client.component, { children: vnode }) : vnode, parent)
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var componentTagPrefix = "tailorkit-";
|
|
43
|
+
var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
|
|
44
|
+
var toCallbackEventName = (name) => `tailorkitcallback${name.replaceAll(/[^A-Za-z0-9_$]/gu, "").toLowerCase()}`;
|
|
45
|
+
var toEventProp = (event) => `on${event}`;
|
|
46
|
+
var createRemoteComponent = (name, options = {}) => {
|
|
47
|
+
const tagName = toComponentTagName(name);
|
|
48
|
+
const callbacks = options.callbacks ?? {};
|
|
49
|
+
return function RemoteComponent({ children, ...props }) {
|
|
50
|
+
const nextProps = { ...props };
|
|
51
|
+
const callbackMap = {};
|
|
52
|
+
for (const [key, inputCount] of Object.entries(callbacks)) {
|
|
53
|
+
const callback = nextProps[key];
|
|
54
|
+
Reflect.deleteProperty(nextProps, key);
|
|
55
|
+
if (typeof callback !== "function") {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const eventName = toCallbackEventName(key);
|
|
59
|
+
callbackMap[eventName] = { callback: key, inputCount };
|
|
60
|
+
nextProps[toEventProp(eventName)] = (event) => {
|
|
61
|
+
callback(...(event.detail ?? []).slice(0, inputCount));
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const serializedProps = Object.fromEntries(
|
|
65
|
+
Object.entries(nextProps).filter(([, value]) => typeof value !== "function")
|
|
66
|
+
);
|
|
67
|
+
nextProps["data-tailorkit-props"] = JSON.stringify(serializedProps);
|
|
68
|
+
if (Object.keys(callbackMap).length > 0) {
|
|
69
|
+
nextProps["data-tailorkit-callbacks"] = JSON.stringify(callbackMap);
|
|
70
|
+
}
|
|
71
|
+
return h(tagName, nextProps, options.children ? children : void 0);
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/preact.ts
|
|
76
|
+
import { createStore as createStore2 } from "@tanstack/store";
|
|
77
|
+
import { useSelector } from "@tanstack/preact-store";
|
|
78
|
+
import { createContext as createContext2, h as h2 } from "preact";
|
|
79
|
+
import { useCallback, useContext as useContext2, useEffect, useMemo, useRef } from "preact/hooks";
|
|
80
|
+
|
|
81
|
+
// src/client/connection.ts
|
|
82
|
+
import { createORPCClient } from "@orpc/client";
|
|
83
|
+
import { RPCLink as FetchLink } from "@orpc/client/fetch";
|
|
84
|
+
import { RPCLink } from "@orpc/client/websocket";
|
|
85
|
+
|
|
86
|
+
// src/client/session.ts
|
|
87
|
+
var APP_SESSION_RENEWAL_MS = 6e4;
|
|
88
|
+
function sessionRenewalDelay(session) {
|
|
89
|
+
return Math.max(500, session.expiresAt - Date.now() - APP_SESSION_RENEWAL_MS);
|
|
90
|
+
}
|
|
91
|
+
function createSessionProvider(options) {
|
|
92
|
+
const base = new URL(options.baseUrl, globalThis.location?.href);
|
|
93
|
+
if (!base.pathname.endsWith("/")) {
|
|
94
|
+
base.pathname += "/";
|
|
95
|
+
}
|
|
96
|
+
let cached;
|
|
97
|
+
let pending;
|
|
98
|
+
return (input) => {
|
|
99
|
+
if (!input.refresh && cached && cached.expiresAt > Date.now() + APP_SESSION_RENEWAL_MS) {
|
|
100
|
+
return Promise.resolve(cached);
|
|
101
|
+
}
|
|
102
|
+
pending ??= (async () => {
|
|
103
|
+
const response = await (options.fetch ?? fetch)(new URL("backend/session", base), {
|
|
104
|
+
method: "POST",
|
|
105
|
+
credentials: "same-origin",
|
|
106
|
+
headers: { "content-type": "application/json" },
|
|
107
|
+
body: JSON.stringify({ appId: options.appId })
|
|
108
|
+
}).catch(() => {
|
|
109
|
+
throw new AppError("UNAVAILABLE", "Unable to reach the app session provider");
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const code = response.status === 403 ? "FORBIDDEN" : "UNAVAILABLE";
|
|
113
|
+
throw new AppError(
|
|
114
|
+
response.status === 401 ? "UNAUTHORIZED" : code,
|
|
115
|
+
"Unable to authorize the app backend"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const value = await response.json();
|
|
119
|
+
if (typeof value.token !== "string" || typeof value.expiresAt !== "number" || typeof value.url !== "string") {
|
|
120
|
+
throw new AppError("UNAVAILABLE", "Invalid app session");
|
|
121
|
+
}
|
|
122
|
+
cached = { token: value.token, expiresAt: value.expiresAt, url: value.url };
|
|
123
|
+
return cached;
|
|
124
|
+
})().finally(() => {
|
|
125
|
+
pending = void 0;
|
|
126
|
+
});
|
|
127
|
+
return pending;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/client/reference.ts
|
|
132
|
+
function reference(name, _kind) {
|
|
133
|
+
return Object.freeze({ name });
|
|
134
|
+
}
|
|
135
|
+
function createApi() {
|
|
136
|
+
function group(path) {
|
|
137
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
138
|
+
get(_target, name) {
|
|
139
|
+
if (typeof name !== "string" || name === "then") return;
|
|
140
|
+
if (name === "name" && path) return path;
|
|
141
|
+
return group(path ? `${path}.${name}` : name);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return group("");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/client/connection.ts
|
|
149
|
+
function createClient(options = {}) {
|
|
150
|
+
let closed = false;
|
|
151
|
+
let socket;
|
|
152
|
+
let timer;
|
|
153
|
+
const stops = /* @__PURE__ */ new Set();
|
|
154
|
+
const requests = /* @__PURE__ */ new Set();
|
|
155
|
+
const getSession = options.getSession ?? ((request) => {
|
|
156
|
+
const bridge = globalThis.__tailorkitBackendSession;
|
|
157
|
+
if (!bridge) {
|
|
158
|
+
throw new AppError("UNAVAILABLE", "The host JWT bridge is unavailable");
|
|
159
|
+
}
|
|
160
|
+
return bridge(request);
|
|
161
|
+
});
|
|
162
|
+
const wire = createORPCClient(
|
|
163
|
+
new RPCLink({
|
|
164
|
+
connect: async (info) => {
|
|
165
|
+
if (closed) {
|
|
166
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
167
|
+
}
|
|
168
|
+
const session = await getSession({ refresh: info.totalAttempt > 1 });
|
|
169
|
+
if (closed) {
|
|
170
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
171
|
+
}
|
|
172
|
+
const url = backendUrl(session, "queries");
|
|
173
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
174
|
+
socket = await (options.connect ?? ((url2, protocols) => new WebSocket(url2, protocols)))(
|
|
175
|
+
url.href,
|
|
176
|
+
["tailorkit", `jwt.${session.token}`]
|
|
177
|
+
);
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
timer = setTimeout(
|
|
180
|
+
() => socket?.close(1e3, "Renew authentication"),
|
|
181
|
+
sessionRenewalDelay(session)
|
|
182
|
+
);
|
|
183
|
+
return socket;
|
|
184
|
+
},
|
|
185
|
+
reconnect: {
|
|
186
|
+
enabled: true,
|
|
187
|
+
delay: (info) => info.attempt === 1 ? 0 : options.retryDelayMs ?? 1e3
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
async function call(kind, input) {
|
|
192
|
+
if (closed) {
|
|
193
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
194
|
+
}
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
requests.add(controller);
|
|
197
|
+
try {
|
|
198
|
+
const invocation = kind === "mutations" ? structuredClone(input) : input;
|
|
199
|
+
let attempt = 0;
|
|
200
|
+
let refresh = false;
|
|
201
|
+
let refreshed = false;
|
|
202
|
+
while (true) {
|
|
203
|
+
try {
|
|
204
|
+
if (closed) {
|
|
205
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
206
|
+
}
|
|
207
|
+
const session = await getSession({ refresh });
|
|
208
|
+
refresh = false;
|
|
209
|
+
if (closed) {
|
|
210
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
211
|
+
}
|
|
212
|
+
const url = backendUrl(session, kind);
|
|
213
|
+
url.pathname = url.pathname.slice(0, -kind.length).replace(/\/$/u, "");
|
|
214
|
+
const http = createORPCClient(
|
|
215
|
+
new FetchLink({
|
|
216
|
+
url: url.pathname,
|
|
217
|
+
origin: url.origin,
|
|
218
|
+
headers: { authorization: `Bearer ${session.token}` },
|
|
219
|
+
fetch: async (url2, init) => {
|
|
220
|
+
let response;
|
|
221
|
+
try {
|
|
222
|
+
response = await (options.fetch ?? fetch)(url2, { ...init, credentials: "omit" });
|
|
223
|
+
} catch {
|
|
224
|
+
throw new AppError("UNAVAILABLE", "App connection interrupted");
|
|
225
|
+
}
|
|
226
|
+
if ([502, 503, 504].includes(response.status)) {
|
|
227
|
+
throw new AppError("UNAVAILABLE", "App backend temporarily unavailable");
|
|
228
|
+
}
|
|
229
|
+
if (kind === "mutations" && response.body) {
|
|
230
|
+
try {
|
|
231
|
+
return new Response(await response.arrayBuffer(), {
|
|
232
|
+
status: response.status,
|
|
233
|
+
headers: response.headers
|
|
234
|
+
});
|
|
235
|
+
} catch {
|
|
236
|
+
throw new AppError("UNAVAILABLE", "App connection interrupted");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return response;
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
if (kind === "mutations") {
|
|
244
|
+
return await http.mutations(invocation, {
|
|
245
|
+
signal: controller.signal
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return await http[kind](invocation, { signal: controller.signal });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const failure = appError(error);
|
|
251
|
+
if (kind !== "mutations" || closed || controller.signal.aborted) {
|
|
252
|
+
throw failure;
|
|
253
|
+
}
|
|
254
|
+
if (failure.code === "UNAUTHORIZED" && !refreshed) {
|
|
255
|
+
refresh = true;
|
|
256
|
+
refreshed = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (failure.code !== "UNAVAILABLE") {
|
|
260
|
+
throw failure;
|
|
261
|
+
}
|
|
262
|
+
await waitForRetry(
|
|
263
|
+
controller.signal,
|
|
264
|
+
Math.min((options.retryDelayMs ?? 1e3) * 2 ** Math.min(attempt++, 5), 3e4)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
throw appError(error);
|
|
270
|
+
} finally {
|
|
271
|
+
requests.delete(controller);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
query: (ref, ...[args]) => call("queries", { name: ref.name, args }),
|
|
276
|
+
mutate: (ref, ...[args, settings]) => call("mutations", {
|
|
277
|
+
name: ref.name,
|
|
278
|
+
args,
|
|
279
|
+
requestId: settings?.requestId ?? crypto.randomUUID()
|
|
280
|
+
}),
|
|
281
|
+
action: (ref, ...[args]) => call("actions", { name: ref.name, args }),
|
|
282
|
+
subscribe(ref, args, next, settings = {}) {
|
|
283
|
+
if (closed) {
|
|
284
|
+
throw new AppError("UNAVAILABLE", "App client closed");
|
|
285
|
+
}
|
|
286
|
+
const controller = new AbortController();
|
|
287
|
+
const stop = () => {
|
|
288
|
+
controller.abort();
|
|
289
|
+
stops.delete(stop);
|
|
290
|
+
};
|
|
291
|
+
stops.add(stop);
|
|
292
|
+
const run = async () => {
|
|
293
|
+
while (!controller.signal.aborted) {
|
|
294
|
+
try {
|
|
295
|
+
const stream = await wire.subscribe(
|
|
296
|
+
{ name: ref.name, args },
|
|
297
|
+
{ signal: controller.signal }
|
|
298
|
+
);
|
|
299
|
+
for await (const value of stream) {
|
|
300
|
+
if (controller.signal.aborted || closed) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
next(value);
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (controller.signal.aborted || closed) {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
const failure = appError(error);
|
|
310
|
+
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
311
|
+
settings.onError?.(failure);
|
|
312
|
+
}
|
|
313
|
+
if (!["UNAUTHORIZED", "UNAVAILABLE", "INTERNAL_SERVER_ERROR"].includes(failure.code)) {
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!controller.signal.aborted && !closed) {
|
|
318
|
+
await waitForRetry(controller.signal, options.retryDelayMs ?? 1e3);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
stop();
|
|
322
|
+
};
|
|
323
|
+
void run().catch((error) => {
|
|
324
|
+
stop();
|
|
325
|
+
settings.onError?.(appError(error));
|
|
326
|
+
});
|
|
327
|
+
return stop;
|
|
328
|
+
},
|
|
329
|
+
close() {
|
|
330
|
+
closed = true;
|
|
331
|
+
for (const stop of stops) {
|
|
332
|
+
stop();
|
|
333
|
+
}
|
|
334
|
+
for (const request of requests) {
|
|
335
|
+
request.abort();
|
|
336
|
+
}
|
|
337
|
+
clearTimeout(timer);
|
|
338
|
+
socket?.close();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function backendUrl(session, route) {
|
|
343
|
+
const url = new URL(session.url);
|
|
344
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
345
|
+
throw new AppError("BAD_REQUEST", "Invalid app backend URL");
|
|
346
|
+
}
|
|
347
|
+
if (url.protocol === "wss:") {
|
|
348
|
+
url.protocol = "https:";
|
|
349
|
+
}
|
|
350
|
+
if (url.protocol === "ws:") {
|
|
351
|
+
url.protocol = "http:";
|
|
352
|
+
}
|
|
353
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
354
|
+
throw new AppError("BAD_REQUEST", "App backends require HTTPS");
|
|
355
|
+
}
|
|
356
|
+
if (session.expiresAt <= Date.now() + 1e3) {
|
|
357
|
+
throw new AppError("UNAUTHORIZED", "App token expired");
|
|
358
|
+
}
|
|
359
|
+
url.pathname = `${url.pathname.replace(/\/$/u, "")}/${route}`;
|
|
360
|
+
return url;
|
|
361
|
+
}
|
|
362
|
+
function waitForRetry(signal, delayMs) {
|
|
363
|
+
if (signal.aborted) {
|
|
364
|
+
return Promise.resolve();
|
|
365
|
+
}
|
|
366
|
+
return new Promise((resolve) => {
|
|
367
|
+
const wake = () => {
|
|
368
|
+
clearTimeout(timer);
|
|
369
|
+
signal.removeEventListener("abort", wake);
|
|
370
|
+
resolve();
|
|
371
|
+
};
|
|
372
|
+
const timer = setTimeout(wake, delayMs);
|
|
373
|
+
signal.addEventListener("abort", wake, { once: true });
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/client/query-store.ts
|
|
378
|
+
import { createStore } from "@tanstack/store";
|
|
379
|
+
var pendingQuery = { data: void 0, error: null, status: "pending" };
|
|
380
|
+
function serializeInput(input) {
|
|
381
|
+
return JSON.stringify({ input, defined: input !== void 0 }, (_key, value) => {
|
|
382
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
383
|
+
return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
384
|
+
}
|
|
385
|
+
return value;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
var QueryStore = class {
|
|
389
|
+
state = createStore(/* @__PURE__ */ new Map());
|
|
390
|
+
entries = /* @__PURE__ */ new Map();
|
|
391
|
+
disposed = false;
|
|
392
|
+
client;
|
|
393
|
+
ownsClient;
|
|
394
|
+
errorHandler;
|
|
395
|
+
constructor(client, ownsClient, errorHandler = () => {
|
|
396
|
+
}) {
|
|
397
|
+
this.client = client;
|
|
398
|
+
this.ownsClient = ownsClient;
|
|
399
|
+
this.errorHandler = errorHandler;
|
|
400
|
+
}
|
|
401
|
+
watch(key, ref, input) {
|
|
402
|
+
let entry = this.entries.get(key);
|
|
403
|
+
const first = !entry;
|
|
404
|
+
if (!entry) {
|
|
405
|
+
entry = { observers: 0 };
|
|
406
|
+
this.entries.set(key, entry);
|
|
407
|
+
}
|
|
408
|
+
const current = entry;
|
|
409
|
+
current.observers += 1;
|
|
410
|
+
const publish = (state) => {
|
|
411
|
+
if (!this.disposed && this.entries.get(key) === current) {
|
|
412
|
+
this.state.setState((previous) => new Map(previous).set(key, state));
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
if (first) {
|
|
416
|
+
const onError = (error) => {
|
|
417
|
+
if (this.disposed || this.entries.get(key) !== current) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
publish({ data: this.state.get().get(key)?.data, error, status: "error" });
|
|
421
|
+
this.onError(error);
|
|
422
|
+
};
|
|
423
|
+
try {
|
|
424
|
+
current.stop = this.client.subscribe(
|
|
425
|
+
ref,
|
|
426
|
+
input,
|
|
427
|
+
(data) => publish({ data, error: null, status: "success" }),
|
|
428
|
+
{ onError }
|
|
429
|
+
);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
onError(appError(error));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
let active = true;
|
|
435
|
+
return () => {
|
|
436
|
+
if (!active) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
active = false;
|
|
440
|
+
current.observers -= 1;
|
|
441
|
+
if (current.observers === 0 && this.entries.get(key) === current) {
|
|
442
|
+
this.entries.delete(key);
|
|
443
|
+
current.stop?.();
|
|
444
|
+
this.state.setState((previous) => {
|
|
445
|
+
const next = new Map(previous);
|
|
446
|
+
next.delete(key);
|
|
447
|
+
return next;
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
dispose() {
|
|
453
|
+
this.disposed = true;
|
|
454
|
+
const entries = [...this.entries.values()];
|
|
455
|
+
this.entries.clear();
|
|
456
|
+
for (const entry of entries) {
|
|
457
|
+
entry.stop?.();
|
|
458
|
+
entry.stop = void 0;
|
|
459
|
+
}
|
|
460
|
+
this.state.setState(() => /* @__PURE__ */ new Map());
|
|
461
|
+
if (this.ownsClient) {
|
|
462
|
+
this.client.close();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
onError(error) {
|
|
466
|
+
if (!this.disposed) {
|
|
467
|
+
this.errorHandler(error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/preact.ts
|
|
473
|
+
var Context = createContext2(null);
|
|
474
|
+
function ClientProvider({ children, client, onError }) {
|
|
475
|
+
const errorHandler = useRef(onError);
|
|
476
|
+
errorHandler.current = onError;
|
|
477
|
+
const store = useMemo(
|
|
478
|
+
() => new QueryStore(client ?? createClient(), !client, (error) => errorHandler.current?.(error)),
|
|
479
|
+
[client]
|
|
480
|
+
);
|
|
481
|
+
useEffect(() => () => store.dispose(), [store]);
|
|
482
|
+
return h2(Context.Provider, { value: store }, children);
|
|
483
|
+
}
|
|
484
|
+
function useStore() {
|
|
485
|
+
const store = useContext2(Context);
|
|
486
|
+
if (!store) {
|
|
487
|
+
throw new Error("TailorKit backend hooks require a ClientProvider at the app root.");
|
|
488
|
+
}
|
|
489
|
+
return store;
|
|
490
|
+
}
|
|
491
|
+
function useQuery(ref, ...[input, options]) {
|
|
492
|
+
const store = useStore();
|
|
493
|
+
const enabled = options?.enabled ?? true;
|
|
494
|
+
const name = ref.name;
|
|
495
|
+
const serialized = serializeInput(input);
|
|
496
|
+
const key = `${name}\0${serialized}`;
|
|
497
|
+
const stableInput = useMemo(() => JSON.parse(serialized).input, [serialized]);
|
|
498
|
+
const stableReference = useMemo(() => reference(name, "query"), [name]);
|
|
499
|
+
const state = useSelector(
|
|
500
|
+
store.state,
|
|
501
|
+
(queries) => enabled ? queries.get(key) ?? pendingQuery : pendingQuery
|
|
502
|
+
);
|
|
503
|
+
useEffect(() => {
|
|
504
|
+
if (enabled) {
|
|
505
|
+
return store.watch(key, stableReference, stableInput);
|
|
506
|
+
}
|
|
507
|
+
}, [store, key, enabled, stableInput, stableReference]);
|
|
508
|
+
return {
|
|
509
|
+
...state,
|
|
510
|
+
isPending: state.status === "pending",
|
|
511
|
+
isLoading: enabled && state.status === "pending",
|
|
512
|
+
isSuccess: state.status === "success",
|
|
513
|
+
isError: state.status === "error"
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
var idleCall = { data: void 0, error: null, status: "idle" };
|
|
517
|
+
function useCall(kind, ref, options) {
|
|
518
|
+
const store = useStore();
|
|
519
|
+
const name = ref.name;
|
|
520
|
+
const identity = useMemo(
|
|
521
|
+
() => ({
|
|
522
|
+
store,
|
|
523
|
+
name,
|
|
524
|
+
kind,
|
|
525
|
+
state: createStore2(idleCall)
|
|
526
|
+
}),
|
|
527
|
+
[store, name, kind]
|
|
528
|
+
);
|
|
529
|
+
const state = useSelector(identity.state);
|
|
530
|
+
const current = useRef({ identity, sequence: 0, mounted: true });
|
|
531
|
+
if (current.current.identity !== identity) {
|
|
532
|
+
current.current = { identity, sequence: current.current.sequence + 1, mounted: true };
|
|
533
|
+
}
|
|
534
|
+
const callbacks = useRef(options);
|
|
535
|
+
callbacks.current = options;
|
|
536
|
+
useEffect(() => {
|
|
537
|
+
current.current.mounted = true;
|
|
538
|
+
return () => {
|
|
539
|
+
current.current.mounted = false;
|
|
540
|
+
current.current.sequence += 1;
|
|
541
|
+
};
|
|
542
|
+
}, []);
|
|
543
|
+
const runAsync = useCallback(
|
|
544
|
+
async (...[input]) => {
|
|
545
|
+
const sequence = ++current.current.sequence;
|
|
546
|
+
const settings = callbacks.current;
|
|
547
|
+
const args = input;
|
|
548
|
+
const isCurrent = () => current.current.mounted && current.current.identity === identity && current.current.sequence === sequence;
|
|
549
|
+
identity.state.setState(() => ({ ...idleCall, status: "pending" }));
|
|
550
|
+
let data;
|
|
551
|
+
try {
|
|
552
|
+
data = kind === "mutation" ? await store.client.mutate(reference(name, "mutation"), args) : await store.client.action(reference(name, "action"), args);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
const failure = appError(error);
|
|
555
|
+
if (isCurrent()) {
|
|
556
|
+
identity.state.setState(() => ({ data: void 0, error: failure, status: "error" }));
|
|
557
|
+
settings.onError?.(failure, args);
|
|
558
|
+
}
|
|
559
|
+
store.onError(failure);
|
|
560
|
+
throw failure;
|
|
561
|
+
}
|
|
562
|
+
if (isCurrent()) {
|
|
563
|
+
identity.state.setState(() => ({ data, error: null, status: "success" }));
|
|
564
|
+
settings.onSuccess?.(data, args);
|
|
565
|
+
}
|
|
566
|
+
return data;
|
|
567
|
+
},
|
|
568
|
+
[identity, kind, store, name]
|
|
569
|
+
);
|
|
570
|
+
const run = useCallback(
|
|
571
|
+
(...args) => {
|
|
572
|
+
void runAsync(...args).catch(() => {
|
|
573
|
+
});
|
|
574
|
+
},
|
|
575
|
+
[runAsync]
|
|
576
|
+
);
|
|
577
|
+
const reset = useCallback(() => {
|
|
578
|
+
current.current.sequence += 1;
|
|
579
|
+
identity.state.setState(() => idleCall);
|
|
580
|
+
}, [identity]);
|
|
581
|
+
return {
|
|
582
|
+
...state,
|
|
583
|
+
isPending: state.status === "pending",
|
|
584
|
+
isSuccess: state.status === "success",
|
|
585
|
+
isError: state.status === "error",
|
|
586
|
+
reset,
|
|
587
|
+
run,
|
|
588
|
+
runAsync
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function useMutation(ref, options = {}) {
|
|
592
|
+
const { run, runAsync, ...state } = useCall("mutation", ref, options);
|
|
593
|
+
return { ...state, mutate: run, mutateAsync: runAsync };
|
|
594
|
+
}
|
|
595
|
+
function useAction(ref, options = {}) {
|
|
596
|
+
const { run, runAsync, ...state } = useCall("action", ref, options);
|
|
597
|
+
return { ...state, execute: run, executeAsync: runAsync };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export {
|
|
601
|
+
createView,
|
|
602
|
+
defineClient,
|
|
603
|
+
createRemoteComponent,
|
|
604
|
+
createSessionProvider,
|
|
605
|
+
reference,
|
|
606
|
+
createApi,
|
|
607
|
+
createClient,
|
|
608
|
+
ClientProvider,
|
|
609
|
+
useQuery,
|
|
610
|
+
useMutation,
|
|
611
|
+
useAction
|
|
612
|
+
};
|