evikit 2.8.1 → 2.9.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/README.md +7 -12
- package/db.d.ts +19 -17
- package/index.d.ts +167 -158
- package/index.js +236 -231
- package/package.json +1 -1
- package/server.js +668 -668
- package/sqlite.js +59 -54
- package/use-api.d.ts +13 -13
- package/use-api.js +119 -119
package/sqlite.js
CHANGED
|
@@ -302,16 +302,6 @@ export function sqlite({ pkg, schema }) {
|
|
|
302
302
|
const table = /** @type {keyof typeof schema & string} */ (
|
|
303
303
|
maybeTable
|
|
304
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
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
305
|
ok(schema[table]);
|
|
316
306
|
const columns = Object.entries(schema[table].entries);
|
|
317
307
|
const primaryKey = columns.find(
|
|
@@ -329,25 +319,12 @@ export function sqlite({ pkg, schema }) {
|
|
|
329
319
|
return [
|
|
330
320
|
table,
|
|
331
321
|
{
|
|
332
|
-
/** @param {
|
|
322
|
+
/** @param {import("./db.d.ts").Where<typeof schema, typeof table>} where */
|
|
333
323
|
delete(where) {
|
|
334
|
-
ensureArrayRecord(where);
|
|
335
324
|
if (
|
|
336
325
|
database
|
|
337
|
-
.prepare(
|
|
338
|
-
|
|
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
|
|
326
|
+
.prepare(`delete from ${table}` + whereQuery({ where }))
|
|
327
|
+
.run(...whereValues(where)).changes
|
|
351
328
|
) {
|
|
352
329
|
publish({ table });
|
|
353
330
|
}
|
|
@@ -355,15 +332,11 @@ export function sqlite({ pkg, schema }) {
|
|
|
355
332
|
|
|
356
333
|
/** @param {any} join */
|
|
357
334
|
select(join) {
|
|
358
|
-
if (join?.where) {
|
|
359
|
-
ensureArrayRecord(join.where);
|
|
360
|
-
}
|
|
361
335
|
return select({ database, flat, join, schema, table });
|
|
362
336
|
},
|
|
363
337
|
|
|
364
|
-
/** @param {{ set: Partial<import("valibot").InferOutput<(typeof schema)[typeof table]>>, where:
|
|
338
|
+
/** @param {{ set: Partial<import("valibot").InferOutput<(typeof schema)[typeof table]>>, where: import("./db.d.ts").Where<typeof schema, typeof table> }} props */
|
|
365
339
|
update({ set, where }) {
|
|
366
|
-
ensureArrayRecord(where);
|
|
367
340
|
if (
|
|
368
341
|
database
|
|
369
342
|
.prepare(
|
|
@@ -371,20 +344,12 @@ export function sqlite({ pkg, schema }) {
|
|
|
371
344
|
Object.keys(set)
|
|
372
345
|
.map((name) => `${name} = ?`)
|
|
373
346
|
.join(",\n") +
|
|
374
|
-
|
|
375
|
-
Object.entries(where)
|
|
376
|
-
.map(
|
|
377
|
-
([name, values]) =>
|
|
378
|
-
`and ${name} in (${values.map(() => "?").join(", ")})`,
|
|
379
|
-
)
|
|
380
|
-
.join("\n"),
|
|
347
|
+
whereQuery({ where }),
|
|
381
348
|
)
|
|
382
349
|
.run(
|
|
383
350
|
...[
|
|
384
351
|
...Object.values(set).map((value) => value ?? null),
|
|
385
|
-
...
|
|
386
|
-
.flatMap((values) => values)
|
|
387
|
-
.map((value) => value ?? null),
|
|
352
|
+
...whereValues(where),
|
|
388
353
|
],
|
|
389
354
|
).changes
|
|
390
355
|
) {
|
|
@@ -426,7 +391,7 @@ export function sqlite({ pkg, schema }) {
|
|
|
426
391
|
};
|
|
427
392
|
}
|
|
428
393
|
|
|
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?:
|
|
394
|
+
/** @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?: import("./db.d.ts").Where<Schema, Table> }; schema: Schema; table: Table; }) => any[]} */
|
|
430
395
|
function select(props) {
|
|
431
396
|
ok(typeof props.table == "string");
|
|
432
397
|
let alias = `${props.table}_0`;
|
|
@@ -484,19 +449,12 @@ function select(props) {
|
|
|
484
449
|
|
|
485
450
|
join(0, props.table, alias, /** @type {any} */ (props.join));
|
|
486
451
|
sql =
|
|
487
|
-
`select ${columns.join(",\n")} ${sql}
|
|
488
|
-
|
|
489
|
-
.map(
|
|
490
|
-
([name, values]) =>
|
|
491
|
-
`and ${alias}.${name} in (${values.map(() => "?").join(", ")})`,
|
|
492
|
-
)
|
|
493
|
-
.join("\n");
|
|
452
|
+
`select ${columns.join(",\n")} ${sql}` +
|
|
453
|
+
whereQuery({ alias, where: props.join?.where });
|
|
494
454
|
|
|
495
|
-
const rows = props.database
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
.map((value) => value ?? null),
|
|
499
|
-
);
|
|
455
|
+
const rows = props.database
|
|
456
|
+
.prepare(sql)
|
|
457
|
+
.all(...whereValues(props.join?.where));
|
|
500
458
|
if (!(/** @type {any} */ (props.join)?.with)) {
|
|
501
459
|
for (const row of rows) {
|
|
502
460
|
for (const name of Object.keys(row)) {
|
|
@@ -569,3 +527,50 @@ function select(props) {
|
|
|
569
527
|
ok(state);
|
|
570
528
|
return state.entities;
|
|
571
529
|
}
|
|
530
|
+
|
|
531
|
+
/** @param {{ alias?: string, where: import("./db.js").Where<any, string> | undefined }} props */
|
|
532
|
+
function whereQuery({ alias, where }) {
|
|
533
|
+
return (
|
|
534
|
+
"\nwhere true\n" +
|
|
535
|
+
Object.entries(where || {})
|
|
536
|
+
.map(([name, values]) => {
|
|
537
|
+
if (alias) {
|
|
538
|
+
name = `${alias}.${name}`;
|
|
539
|
+
}
|
|
540
|
+
return Array.isArray(values)
|
|
541
|
+
? `and ${name} in (${values.map(() => "?").join(", ")})`
|
|
542
|
+
: typeof values == "number" || typeof values == "string"
|
|
543
|
+
? `and ${name} = ?`
|
|
544
|
+
: Object.keys(values)
|
|
545
|
+
.map(
|
|
546
|
+
(operator) =>
|
|
547
|
+
`and ${name} ` +
|
|
548
|
+
(operator == "gt"
|
|
549
|
+
? ">"
|
|
550
|
+
: operator == "gte"
|
|
551
|
+
? ">="
|
|
552
|
+
: operator == "lt"
|
|
553
|
+
? "<"
|
|
554
|
+
: operator == "lte"
|
|
555
|
+
? "<="
|
|
556
|
+
: "!=") +
|
|
557
|
+
" ?",
|
|
558
|
+
)
|
|
559
|
+
.join("");
|
|
560
|
+
})
|
|
561
|
+
.join("\n")
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** @param {import("./db.js").Where<any, string> | undefined} where */
|
|
566
|
+
function whereValues(where) {
|
|
567
|
+
return Object.values(where || {})
|
|
568
|
+
.flatMap((values) =>
|
|
569
|
+
Array.isArray(values)
|
|
570
|
+
? values
|
|
571
|
+
: typeof values == "number" || typeof values == "string"
|
|
572
|
+
? values
|
|
573
|
+
: Object.values(values),
|
|
574
|
+
)
|
|
575
|
+
.map((value) => value ?? null);
|
|
576
|
+
}
|
package/use-api.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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 };
|
|
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,119 +1,119 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|