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/sqlite.js CHANGED
@@ -277,109 +277,153 @@ export function sqlite({ pkg, schema }) {
277
277
  database.exec(sql);
278
278
  }
279
279
 
280
- return /** @type {any} */ (
281
- Object.fromEntries(
282
- Object.entries(schema).map(([maybeTable]) => {
283
- const table = /** @type {keyof typeof schema & string} */ (maybeTable);
284
-
285
- /** @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 */
286
- function ensureArrayRecord(where) {
287
- for (const key of Object.keys(where)) {
288
- if (!Array.isArray(where[key])) {
289
- Object.assign(where, { [key]: [where[key]] });
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
- ok(schema[table]);
295
- const columns = Object.entries(schema[table].entries);
296
- const primaryKey = columns.find(
297
- ([, column]) =>
298
- /** @type {{ primary?: true }} */ (getMetadata(column)).primary,
299
- );
300
- ok(primaryKey);
301
- const upsert = database.prepare(
302
- `insert into ${table} (${columns.map(([name]) => name).join(", ")}) values\n` +
303
- `(${columns.map(() => "?").join(", ")})\n` +
304
- `on conflict (${primaryKey[0]}) do update set\n` +
305
- columns.map(([name]) => `${name} = ?`).join(",\n"),
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
- return [
309
- table,
310
- {
311
- /** @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 */
312
- delete(where) {
313
- ensureArrayRecord(where);
314
- database
315
- .prepare(
316
- `delete from ${table} where true\n` +
317
- Object.entries(where)
318
- .map(
319
- ([name, values]) =>
320
- `and ${name} in (${values.map(() => "?").join(", ")})`,
321
- )
322
- .join("\n"),
323
- )
324
- .run(
325
- ...Object.values(where)
326
- .flatMap((values) => values)
327
- .map((value) => value ?? null),
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
- /** @param {any} join */
332
- select(join) {
333
- if (join?.where) {
334
- ensureArrayRecord(join.where);
335
- }
336
- return select({ database, flat, join, schema, table });
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
- /** @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 */
340
- update({ set, where }) {
341
- ensureArrayRecord(where);
342
- database
343
- .prepare(
344
- `update ${table} set\n` +
345
- Object.keys(set)
346
- .map((name) => `${name} = ?`)
347
- .join(",\n") +
348
- `\nwhere true\n` +
349
- Object.entries(where)
350
- .map(
351
- ([name, values]) =>
352
- `and ${name} in (${values.map(() => "?").join(", ")})`,
353
- )
354
- .join("\n"),
355
- )
356
- .run(
357
- ...[
358
- ...Object.values(set).map((value) => value ?? null),
359
- ...Object.values(where)
360
- .flatMap((values) => values)
361
- .map((value) => value ?? null),
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
- /** @param {any[]} values */
367
- upsert(...values) {
368
- ok(schema[table]);
369
- const outputs = [];
370
- for (const row of values) {
371
- const output = parse(schema[table], row);
372
- const values = columns.map(([name]) => output[name] ?? null);
373
- upsert.run(...values, ...values);
374
- outputs.push(output);
375
- }
376
- return outputs;
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
- lang?: string;
5
- origin: string | undefined;
6
- path: string;
7
- schema?: U;
8
- }): { queryFn: () => Promise<InferOutput<U>>; queryKey: string[] };
9
-
10
- export function useApi<T, U extends ObjectSchema<T, undefined>>(routeType: {
11
- outputs: { 200: U };
12
- }): { 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,47 +1,119 @@
1
- import { useRoute } from "preact-iso";
2
- import { parse } from "valibot";
3
-
4
- import { jsdomCookie } from "./jsdom-cookie.js";
5
- import { useQuery } from "./query.js";
6
-
7
- /** @param {{ lang?: string, origin: string | undefined, path: string, schema?: any }} props */
8
- export function queryApi({ lang, origin, path, schema }) {
9
- const queryKey =
10
- path == "/" ? ["home"] : path.split("/").slice(1).map(decodeURIComponent);
11
-
12
- return {
13
- queryFn: async () => {
14
- const response = await fetch(
15
- (origin || "") +
16
- path.replace(/^\//, "/api/").replace(/^\/api\/(\?|$)/, "/api$1"),
17
- {
18
- headers: {
19
- ...(lang ? { "Accept-Language": lang } : {}),
20
- Cookie: jsdomCookie.setCookie.join("; "),
21
- },
22
- },
23
- );
24
- if (response.status >= 400) {
25
- const error = new Error(await response.text());
26
- error.name = response.status.toString();
27
- throw Error;
28
- }
29
- const json = await response.json();
30
- return schema ? parse(schema, json) : json;
31
- },
32
- queryKey,
33
- };
34
- }
35
-
36
- /** @param {{ outputs: { 200: any } }} routeType */
37
- export function useApi(routeType) {
38
- const { path, query } = useRoute();
39
- const search = new URLSearchParams(query).toString();
40
- const { queryFn, queryKey } = queryApi({
41
- origin: location.origin,
42
- path: path + (search ? `?${search}` : ""),
43
- schema: routeType.outputs[200],
44
- });
45
- const { data, error } = useQuery({ queryFn, queryKey });
46
- return { data, error };
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
+ }