evikit 2.7.3 → 2.8.1
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/CONTRIBUTING.md +24 -0
- package/README.md +6 -3
- package/db.d.ts +3 -0
- package/index.d.ts +158 -158
- package/index.js +231 -230
- package/package.json +1 -1
- package/query.js +42 -25
- package/server-generic.d.ts +8 -1
- package/server.d.ts +7 -1
- package/server.js +668 -536
- package/sqlite.js +139 -95
- package/use-api.d.ts +13 -12
- package/use-api.js +119 -47
package/sqlite.js
CHANGED
|
@@ -277,109 +277,153 @@ export function sqlite({ pkg, schema }) {
|
|
|
277
277
|
database.exec(sql);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/** @
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
/** @type {(({ table }: { table: string }) => void)[]} */
|
|
281
|
+
const listeners = [];
|
|
282
|
+
if (process.env["i"]) {
|
|
283
|
+
process.on("message", (maybeMessage) => {
|
|
284
|
+
for (const onMessage of listeners) {
|
|
285
|
+
onMessage(/** @type {any} */ (maybeMessage));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/** @param {{ table: string }} message */
|
|
290
|
+
function publish(message) {
|
|
291
|
+
Object.assign(message, { i: process.env["i"] });
|
|
292
|
+
process.send?.(message, undefined, { keepOpen: true });
|
|
293
|
+
for (const onMessage of listeners) {
|
|
294
|
+
onMessage(message);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
.../** @type {any} */ (
|
|
300
|
+
Object.fromEntries(
|
|
301
|
+
Object.entries(schema).map(([maybeTable]) => {
|
|
302
|
+
const table = /** @type {keyof typeof schema & string} */ (
|
|
303
|
+
maybeTable
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
/** @param {{ [K in keyof (typeof schema)[typeof table]["entries"]]?: import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]> | import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]>[]; }} where */
|
|
307
|
+
function ensureArrayRecord(where) {
|
|
308
|
+
for (const key of Object.keys(where)) {
|
|
309
|
+
if (!Array.isArray(where[key])) {
|
|
310
|
+
Object.assign(where, { [key]: [where[key]] });
|
|
311
|
+
}
|
|
290
312
|
}
|
|
291
313
|
}
|
|
292
|
-
}
|
|
293
314
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
315
|
+
ok(schema[table]);
|
|
316
|
+
const columns = Object.entries(schema[table].entries);
|
|
317
|
+
const primaryKey = columns.find(
|
|
318
|
+
([, column]) =>
|
|
319
|
+
/** @type {{ primary?: true }} */ (getMetadata(column)).primary,
|
|
320
|
+
);
|
|
321
|
+
ok(primaryKey);
|
|
322
|
+
const upsert = database.prepare(
|
|
323
|
+
`insert into ${table} (${columns.map(([name]) => name).join(", ")}) values\n` +
|
|
324
|
+
`(${columns.map(() => "?").join(", ")})\n` +
|
|
325
|
+
`on conflict (${primaryKey[0]}) do update set\n` +
|
|
326
|
+
columns.map(([name]) => `${name} = ?`).join(",\n"),
|
|
327
|
+
);
|
|
307
328
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
return [
|
|
330
|
+
table,
|
|
331
|
+
{
|
|
332
|
+
/** @param {{ [K in keyof (typeof schema)[typeof table]["entries"]]?: import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]> | import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]>[]; }} where */
|
|
333
|
+
delete(where) {
|
|
334
|
+
ensureArrayRecord(where);
|
|
335
|
+
if (
|
|
336
|
+
database
|
|
337
|
+
.prepare(
|
|
338
|
+
`delete from ${table} where true\n` +
|
|
339
|
+
Object.entries(where)
|
|
340
|
+
.map(
|
|
341
|
+
([name, values]) =>
|
|
342
|
+
`and ${name} in (${values.map(() => "?").join(", ")})`,
|
|
343
|
+
)
|
|
344
|
+
.join("\n"),
|
|
345
|
+
)
|
|
346
|
+
.run(
|
|
347
|
+
...Object.values(where)
|
|
348
|
+
.flatMap((values) => values)
|
|
349
|
+
.map((value) => value ?? null),
|
|
350
|
+
).changes
|
|
351
|
+
) {
|
|
352
|
+
publish({ table });
|
|
353
|
+
}
|
|
354
|
+
},
|
|
330
355
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
356
|
+
/** @param {any} join */
|
|
357
|
+
select(join) {
|
|
358
|
+
if (join?.where) {
|
|
359
|
+
ensureArrayRecord(join.where);
|
|
360
|
+
}
|
|
361
|
+
return select({ database, flat, join, schema, table });
|
|
362
|
+
},
|
|
338
363
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
/** @param {{ set: Partial<import("valibot").InferOutput<(typeof schema)[typeof table]>>, where: { [K in keyof (typeof schema)[typeof table]["entries"]]?: import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]> | import("valibot").InferOutput<(typeof schema)[typeof table]["entries"][K]>[]; } }} props */
|
|
365
|
+
update({ set, where }) {
|
|
366
|
+
ensureArrayRecord(where);
|
|
367
|
+
if (
|
|
368
|
+
database
|
|
369
|
+
.prepare(
|
|
370
|
+
`update ${table} set\n` +
|
|
371
|
+
Object.keys(set)
|
|
372
|
+
.map((name) => `${name} = ?`)
|
|
373
|
+
.join(",\n") +
|
|
374
|
+
`\nwhere true\n` +
|
|
375
|
+
Object.entries(where)
|
|
376
|
+
.map(
|
|
377
|
+
([name, values]) =>
|
|
378
|
+
`and ${name} in (${values.map(() => "?").join(", ")})`,
|
|
379
|
+
)
|
|
380
|
+
.join("\n"),
|
|
381
|
+
)
|
|
382
|
+
.run(
|
|
383
|
+
...[
|
|
384
|
+
...Object.values(set).map((value) => value ?? null),
|
|
385
|
+
...Object.values(where)
|
|
386
|
+
.flatMap((values) => values)
|
|
387
|
+
.map((value) => value ?? null),
|
|
388
|
+
],
|
|
389
|
+
).changes
|
|
390
|
+
) {
|
|
391
|
+
publish({ table });
|
|
392
|
+
}
|
|
393
|
+
},
|
|
365
394
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
395
|
+
/** @param {any[]} values */
|
|
396
|
+
upsert(...values) {
|
|
397
|
+
ok(schema[table]);
|
|
398
|
+
const outputs = [];
|
|
399
|
+
/** @type {boolean} */
|
|
400
|
+
let changes = false;
|
|
401
|
+
for (const row of values) {
|
|
402
|
+
const output = parse(schema[table], row);
|
|
403
|
+
const values = columns.map(([name]) => output[name] ?? null);
|
|
404
|
+
if (upsert.run(...values, ...values).changes) {
|
|
405
|
+
changes = true;
|
|
406
|
+
}
|
|
407
|
+
outputs.push(output);
|
|
408
|
+
}
|
|
409
|
+
if (changes) {
|
|
410
|
+
publish({ table });
|
|
411
|
+
}
|
|
412
|
+
return outputs;
|
|
413
|
+
},
|
|
377
414
|
},
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
)
|
|
382
|
-
|
|
415
|
+
];
|
|
416
|
+
}),
|
|
417
|
+
)
|
|
418
|
+
),
|
|
419
|
+
schema,
|
|
420
|
+
subscribe(onMessage) {
|
|
421
|
+
listeners.push(onMessage);
|
|
422
|
+
return () => {
|
|
423
|
+
listeners.splice(listeners.indexOf(onMessage), 1);
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
};
|
|
383
427
|
}
|
|
384
428
|
|
|
385
429
|
/** @type {<Schema extends Record<string, import("valibot").ObjectSchema<any, any>>, R extends import("./db.d.ts").Relations<Schema> = import("./db.d.ts").Relations<Schema>, F extends import("./db.d.ts").Flatten<Schema> = import("./db.d.ts").Flatten<Schema>, Table extends keyof F & keyof Schema = keyof F & keyof Schema >(props: { database: DatabaseSync; flat: F; join?: import("./db.d.ts").Join<R, Table> & { where?: { [K in keyof Schema[Table]["entries"]]?: import("valibot").InferOutput<Schema[Table]["entries"][K]> | import("valibot").InferOutput<Schema[Table]["entries"][K]>[]; } }; schema: Schema; table: Table; }) => any[]} */
|
package/use-api.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { type InferOutput, type ObjectSchema } from "valibot";
|
|
2
|
-
|
|
3
|
-
export function queryApi<T, U extends ObjectSchema<T, undefined>>(props: {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { type InferOutput, type ObjectSchema } from "valibot";
|
|
2
|
+
|
|
3
|
+
export function queryApi<T, U extends ObjectSchema<T, undefined>>(props: {
|
|
4
|
+
cookie?: string | undefined;
|
|
5
|
+
lang?: string;
|
|
6
|
+
origin: string | undefined;
|
|
7
|
+
path: string;
|
|
8
|
+
schema?: U;
|
|
9
|
+
}): { queryFn: () => Promise<InferOutput<U>>; queryKey: string[] };
|
|
10
|
+
|
|
11
|
+
export function useApi<T, U extends ObjectSchema<T, undefined>>(routeType: {
|
|
12
|
+
outputs: { 200: U };
|
|
13
|
+
}): { data?: InferOutput<U>; error?: unknown };
|
package/use-api.js
CHANGED
|
@@ -1,47 +1,119 @@
|
|
|
1
|
-
import { useRoute } from "preact-iso";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const { path, query } = useRoute();
|
|
39
|
-
const search = new URLSearchParams(query).toString();
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
import { useRoute } from "preact-iso";
|
|
2
|
+
import { useEffect, useState } from "preact/hooks";
|
|
3
|
+
import { parse } from "valibot";
|
|
4
|
+
|
|
5
|
+
import { jsdomCookie } from "./jsdom-cookie.js";
|
|
6
|
+
import { SetQueryState, useQuery, useQueryClient } from "./query.js";
|
|
7
|
+
|
|
8
|
+
/** @param {{ cookie?: string | undefined, lang?: string, origin: string | undefined, path: string, schema?: any }} props */
|
|
9
|
+
export function queryApi({ cookie, lang, origin, path, schema }) {
|
|
10
|
+
const queryKey =
|
|
11
|
+
path == "/" ? ["home"] : path.split("/").slice(1).map(decodeURIComponent);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
const response = await fetch(ApiPath({ origin, path }), {
|
|
16
|
+
headers: {
|
|
17
|
+
...(lang ? { "Accept-Language": lang } : {}),
|
|
18
|
+
Cookie: [...(cookie ? [cookie] : []), ...jsdomCookie.setCookie].join(
|
|
19
|
+
"; ",
|
|
20
|
+
),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (response.status >= 400) {
|
|
24
|
+
const error = new Error(await response.text());
|
|
25
|
+
error.name = response.status.toString();
|
|
26
|
+
throw Error;
|
|
27
|
+
}
|
|
28
|
+
const json = await response.json();
|
|
29
|
+
return schema ? parse(schema, json) : json;
|
|
30
|
+
},
|
|
31
|
+
queryKey,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @param {{ outputs: { 200: any } }} routeType */
|
|
36
|
+
export function useApi(routeType) {
|
|
37
|
+
const { origin } = location;
|
|
38
|
+
const { path: pathWithoutSearch, query } = useRoute();
|
|
39
|
+
const search = new URLSearchParams(query).toString();
|
|
40
|
+
const path = pathWithoutSearch + (search ? `?${search}` : "");
|
|
41
|
+
const schema = routeType.outputs[200];
|
|
42
|
+
const { queryFn, queryKey } = queryApi({ origin, path, schema });
|
|
43
|
+
const { data, error } = useQuery({ queryFn, queryKey });
|
|
44
|
+
const queryClient = useQueryClient();
|
|
45
|
+
const setQueryState = SetQueryState({ queryClient, queryKey });
|
|
46
|
+
const [sourceUpdatedAt, setSourceUpdatedAt] = useState(Date.now());
|
|
47
|
+
const [cookieChangedAt, setCookieChangedAt] = useState(Date.now());
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (typeof cookieStore == "undefined") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
function onCookie() {
|
|
53
|
+
queryClient.invalidateQueries({ evenIfEventSource: true, queryKey });
|
|
54
|
+
setCookieChangedAt(Date.now());
|
|
55
|
+
}
|
|
56
|
+
cookieStore.addEventListener("change", onCookie);
|
|
57
|
+
return () => cookieStore.removeEventListener("change", onCookie);
|
|
58
|
+
}, []);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (typeof EventSource == "undefined") {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const source = new EventSource(ApiPath({ origin, path: path }));
|
|
64
|
+
source.onerror = () => {
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
setSourceUpdatedAt(Date.now());
|
|
67
|
+
}, 1e3);
|
|
68
|
+
};
|
|
69
|
+
source.onmessage = ({ data }) => {
|
|
70
|
+
data = JSON.parse(data);
|
|
71
|
+
if (!data) {
|
|
72
|
+
source.close();
|
|
73
|
+
setQueryState((existing) =>
|
|
74
|
+
existing ? { ...existing, fromEventSource: false } : undefined,
|
|
75
|
+
);
|
|
76
|
+
} else if (Number(data.statusCode) >= 400) {
|
|
77
|
+
setQueryState((existing) => ({
|
|
78
|
+
data: existing?.data ?? null,
|
|
79
|
+
dataUpdatedAt: Date.now(),
|
|
80
|
+
error,
|
|
81
|
+
fromEventSource: true,
|
|
82
|
+
isInvalidated: false,
|
|
83
|
+
/** @type {"error"} */
|
|
84
|
+
status: "error",
|
|
85
|
+
}));
|
|
86
|
+
} else {
|
|
87
|
+
setQueryState(() => ({
|
|
88
|
+
data: schema
|
|
89
|
+
? parse(schema, JSON.parse(data.body))
|
|
90
|
+
: JSON.parse(data.body),
|
|
91
|
+
dataUpdatedAt: Date.now(),
|
|
92
|
+
error: null,
|
|
93
|
+
fromEventSource: true,
|
|
94
|
+
isInvalidated: false,
|
|
95
|
+
/** @type {"success"} */
|
|
96
|
+
status: "success",
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
setQueryState((existing) =>
|
|
101
|
+
existing ? { ...existing, fromEventSource: true } : undefined,
|
|
102
|
+
);
|
|
103
|
+
return () => {
|
|
104
|
+
source.close();
|
|
105
|
+
setQueryState((existing) =>
|
|
106
|
+
existing ? { ...existing, fromEventSource: false } : undefined,
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
}, [cookieChangedAt, path, sourceUpdatedAt]);
|
|
110
|
+
return { data, error };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @param {{ origin: string | undefined, path: string }} props */
|
|
114
|
+
function ApiPath({ origin, path }) {
|
|
115
|
+
return (
|
|
116
|
+
(origin || "") +
|
|
117
|
+
path.replace(/^\//, "/api/").replace(/^\/api\/(\?|$)/, "/api$1")
|
|
118
|
+
);
|
|
119
|
+
}
|