evikit 2.5.1 → 2.5.3
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/index.d.ts +5 -0
- package/index.js +18 -4
- package/package.json +2 -2
- package/server.js +5 -1
- package/sqlite.js +496 -489
package/index.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export const del: H<"del">;
|
|
|
98
98
|
export const details: H<"details">;
|
|
99
99
|
export const dialog: H<"dialog">;
|
|
100
100
|
export const div: H<"div">;
|
|
101
|
+
export const ellipse: H<"ellipse">;
|
|
101
102
|
export const em: H<"em">;
|
|
102
103
|
export const fieldset: H<"fieldset">;
|
|
103
104
|
export const figcaption: H<"figcaption">;
|
|
@@ -128,7 +129,9 @@ export const option: H<"option">;
|
|
|
128
129
|
export const p: H<"p">;
|
|
129
130
|
export const path: H<"path">;
|
|
130
131
|
export const picture: H<"picture">;
|
|
132
|
+
export const polyline: H<"polyline">;
|
|
131
133
|
export const pre: H<"pre">;
|
|
134
|
+
export const rect: H<"rect">;
|
|
132
135
|
export const section: H<"section">;
|
|
133
136
|
export const select: H<"select">;
|
|
134
137
|
export const small: H<"small">;
|
|
@@ -142,7 +145,9 @@ export const svg: H<"svg">;
|
|
|
142
145
|
export const table: H<"table">;
|
|
143
146
|
export const tbody: H<"tbody">;
|
|
144
147
|
export const td: H<"td">;
|
|
148
|
+
export const text: H<"text">;
|
|
145
149
|
export const textarea: H<"textarea">;
|
|
150
|
+
export const th: H<"th">;
|
|
146
151
|
export const thead: H<"thead">;
|
|
147
152
|
export const title: H<"title">;
|
|
148
153
|
export const tr: H<"tr">;
|
package/index.js
CHANGED
|
@@ -116,13 +116,22 @@ export async function ssr(App, data) {
|
|
|
116
116
|
const { html, links } = await prerender(h(/** @type {any} */ (App), {}), {
|
|
117
117
|
props: { dehydratedState, i18n },
|
|
118
118
|
});
|
|
119
|
-
const { metas, title } = toStatic();
|
|
119
|
+
const { links: hoofdLinks, metas, title } = toStatic();
|
|
120
120
|
|
|
121
121
|
return {
|
|
122
122
|
data: { ...data, dehydratedState },
|
|
123
123
|
head: {
|
|
124
|
-
elements: new Set(
|
|
125
|
-
|
|
124
|
+
elements: new Set([
|
|
125
|
+
...hoofdLinks.map((props) => ({
|
|
126
|
+
props: Object.fromEntries(
|
|
127
|
+
Object.entries(props).map(([name, content]) => [
|
|
128
|
+
name,
|
|
129
|
+
String(content),
|
|
130
|
+
]),
|
|
131
|
+
),
|
|
132
|
+
type: "link",
|
|
133
|
+
})),
|
|
134
|
+
...metas.map((props) => ({
|
|
126
135
|
props: Object.fromEntries(
|
|
127
136
|
Object.entries(props).map(([name, content]) => [
|
|
128
137
|
name,
|
|
@@ -131,7 +140,7 @@ export async function ssr(App, data) {
|
|
|
131
140
|
),
|
|
132
141
|
type: "meta",
|
|
133
142
|
})),
|
|
134
|
-
),
|
|
143
|
+
]),
|
|
135
144
|
lang: i18n.getLocale(),
|
|
136
145
|
title,
|
|
137
146
|
},
|
|
@@ -156,6 +165,7 @@ export const del = HTMLElement("del");
|
|
|
156
165
|
export const details = HTMLElement("details");
|
|
157
166
|
export const dialog = HTMLElement("dialog");
|
|
158
167
|
export const div = HTMLElement("div");
|
|
168
|
+
export const ellipse = HTMLElement("ellipse");
|
|
159
169
|
export const em = HTMLElement("em");
|
|
160
170
|
export const fieldset = HTMLElement("fieldset");
|
|
161
171
|
export const figcaption = HTMLElement("figcaption");
|
|
@@ -186,7 +196,9 @@ export const option = HTMLElement("option");
|
|
|
186
196
|
export const p = HTMLElement("p");
|
|
187
197
|
export const path = HTMLElement("path");
|
|
188
198
|
export const picture = HTMLElement("picture");
|
|
199
|
+
export const polyline = HTMLElement("polyline");
|
|
189
200
|
export const pre = HTMLElement("pre");
|
|
201
|
+
export const rect = HTMLElement("rect");
|
|
190
202
|
export const section = HTMLElement("section");
|
|
191
203
|
export const select = HTMLElement("select");
|
|
192
204
|
export const small = HTMLElement("small");
|
|
@@ -200,7 +212,9 @@ export const svg = HTMLElement("svg");
|
|
|
200
212
|
export const table = HTMLElement("table");
|
|
201
213
|
export const tbody = HTMLElement("tbody");
|
|
202
214
|
export const td = HTMLElement("td");
|
|
215
|
+
export const text = HTMLElement("text");
|
|
203
216
|
export const textarea = HTMLElement("textarea");
|
|
217
|
+
export const th = HTMLElement("th");
|
|
204
218
|
export const thead = HTMLElement("thead");
|
|
205
219
|
export const title = HTMLElement("title");
|
|
206
220
|
export const tr = HTMLElement("tr");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evikit",
|
|
3
3
|
"description": "Preact/SQLite SSR framework for small web apps",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"@fastify/busboy": "^3.2.0",
|
|
25
25
|
"@types/gettext-parser": "^9.0.0",
|
|
26
26
|
"@types/gettext.js": "^2.0.0",
|
|
27
|
-
"@types/node": "^25",
|
|
28
27
|
"@valibot/to-json-schema": "^1.7.0",
|
|
29
28
|
"env-paths": "^4.0.0",
|
|
30
29
|
"gettext-parser": "^9.0.2",
|
|
@@ -41,6 +40,7 @@
|
|
|
41
40
|
"devDependencies": {
|
|
42
41
|
"@eslint/compat": "^2.1.0",
|
|
43
42
|
"@eslint/js": "^10.0.1",
|
|
43
|
+
"@types/node": "^24.13.3",
|
|
44
44
|
"eslint": "^10.4.0",
|
|
45
45
|
"eslint-config-prettier": "^10.1.8",
|
|
46
46
|
"eslint-plugin-perfectionist": "^5.9.0",
|
package/server.js
CHANGED
|
@@ -260,7 +260,11 @@ export function production({ html, prerender }) {
|
|
|
260
260
|
renderToString(h("title", null, rendered.head.title)),
|
|
261
261
|
)
|
|
262
262
|
.replace(/<!--app-head-->[\s\S]+?<body/, (match) =>
|
|
263
|
-
match
|
|
263
|
+
match
|
|
264
|
+
.replace(/<link[^>]+>/g, (link) =>
|
|
265
|
+
/data-hoofd/.test(link) ? "" : link,
|
|
266
|
+
)
|
|
267
|
+
.replace(/<meta[^>]+>/g, ""),
|
|
264
268
|
)
|
|
265
269
|
.replace(
|
|
266
270
|
`<!--app-head-->`,
|
package/sqlite.js
CHANGED
|
@@ -1,489 +1,496 @@
|
|
|
1
|
-
import envPaths from "env-paths";
|
|
2
|
-
import { fail, ok } from "node:assert/strict";
|
|
3
|
-
import { mkdirSync } from "node:fs";
|
|
4
|
-
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
import { getMetadata, parse } from "valibot";
|
|
6
|
-
|
|
7
|
-
import { flatten, relations } from "./db.js";
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
wantColumn.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
.join("
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
ok(
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
ok(
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
1
|
+
import envPaths from "env-paths";
|
|
2
|
+
import { fail, ok } from "node:assert/strict";
|
|
3
|
+
import { mkdirSync } from "node:fs";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import { getMetadata, parse } from "valibot";
|
|
6
|
+
|
|
7
|
+
import { flatten, relations } from "./db.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef Column
|
|
11
|
+
* @property {string} name
|
|
12
|
+
* @property {0 | 1} notnull
|
|
13
|
+
* @property {0 | 1} pk
|
|
14
|
+
* @property {"INTEGER" | "TEXT"} type
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** @type {typeof import("./sqlite.d.ts").sqlite} */
|
|
18
|
+
export function sqlite({ pkg, schema }) {
|
|
19
|
+
const data = pkg
|
|
20
|
+
? (() => {
|
|
21
|
+
const { data } = envPaths(pkg.name);
|
|
22
|
+
mkdirSync(data, { recursive: true });
|
|
23
|
+
return data;
|
|
24
|
+
})()
|
|
25
|
+
: undefined;
|
|
26
|
+
const database = new DatabaseSync(data ? `${data}/db.sqlite3` : ":memory:", {
|
|
27
|
+
timeout: 1000,
|
|
28
|
+
});
|
|
29
|
+
const flat = flatten(relations(schema));
|
|
30
|
+
|
|
31
|
+
const haveTables = /** @type {{ name: string }[]} */ (
|
|
32
|
+
database
|
|
33
|
+
.prepare(
|
|
34
|
+
"select * from sqlite_schema where type='table' and name not like 'sqlite_%'",
|
|
35
|
+
)
|
|
36
|
+
.all()
|
|
37
|
+
).map((table) => {
|
|
38
|
+
const columns = /** @type {Column[]} */ (
|
|
39
|
+
database.prepare(`pragma table_info(${table.name})`).all()
|
|
40
|
+
);
|
|
41
|
+
const foreignKeys =
|
|
42
|
+
/** @type {{ from: string, on_delete: "CASCADE" | "SET NULL", on_update: "CASCADE" | "SET NULL", table: string, to: string, }[]} */ (
|
|
43
|
+
database.prepare(`pragma foreign_key_list(${table.name})`).all()
|
|
44
|
+
);
|
|
45
|
+
return {
|
|
46
|
+
columns,
|
|
47
|
+
foreignKeys,
|
|
48
|
+
indices: /** @type {{ name: string }[]} */ (
|
|
49
|
+
database.prepare(`pragma index_list(${table.name})`).all()
|
|
50
|
+
)
|
|
51
|
+
.filter(({ name }) => !/^sqlite_/.test(name))
|
|
52
|
+
.map((index) => ({
|
|
53
|
+
...index,
|
|
54
|
+
info: /** @type {{ name: string; }} */ (
|
|
55
|
+
database.prepare(`pragma index_info(${index.name})`).get()
|
|
56
|
+
),
|
|
57
|
+
})),
|
|
58
|
+
table,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const wantTables = Object.entries(schema).map(([name, { entries }]) => ({
|
|
63
|
+
columns: Object.entries(entries).map(([name, maybeEntry]) => {
|
|
64
|
+
const entry =
|
|
65
|
+
/** @type {import("valibot").BaseSchema<undefined, undefined, any>} */ (
|
|
66
|
+
maybeEntry
|
|
67
|
+
);
|
|
68
|
+
const pk = /** @type {{ primary?: true }} */ (getMetadata(entry)).primary
|
|
69
|
+
? 1
|
|
70
|
+
: 0;
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
notnull: !pk && entry.expects.includes("undefined") ? 0 : 1,
|
|
74
|
+
pk,
|
|
75
|
+
type:
|
|
76
|
+
entry.expects == "string" || entry.expects == "(string | undefined)"
|
|
77
|
+
? "TEXT"
|
|
78
|
+
: entry.expects == "number" ||
|
|
79
|
+
entry.expects == "(number | undefined)"
|
|
80
|
+
? "INTEGER"
|
|
81
|
+
: entry.expects == "Uint8Array" ||
|
|
82
|
+
entry.expects == "(Uint8Array | undefined)"
|
|
83
|
+
? "BLOB"
|
|
84
|
+
: fail(name),
|
|
85
|
+
};
|
|
86
|
+
}),
|
|
87
|
+
foreignKeys: Object.entries(entries)
|
|
88
|
+
.map(([name, maybeEntry]) => {
|
|
89
|
+
const entry =
|
|
90
|
+
/** @type {import("valibot").BaseSchema<undefined, undefined, any>} */ (
|
|
91
|
+
maybeEntry
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const metadata =
|
|
95
|
+
/** @type {{ key?: string, references?: any } | undefined} */ (
|
|
96
|
+
getMetadata(entry)
|
|
97
|
+
);
|
|
98
|
+
const table = metadata
|
|
99
|
+
? Object.entries(schema)
|
|
100
|
+
.filter(([, value]) => value == metadata.references)
|
|
101
|
+
.find(Boolean)
|
|
102
|
+
: undefined;
|
|
103
|
+
if (!metadata?.references) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
ok(table);
|
|
107
|
+
const to = Object.entries(table[1].entries).find(
|
|
108
|
+
([, value]) => value == metadata.key,
|
|
109
|
+
);
|
|
110
|
+
ok(to);
|
|
111
|
+
return {
|
|
112
|
+
from: name,
|
|
113
|
+
on_delete: /undefined/.test(entry.expects) ? "SET NULL" : "CASCADE",
|
|
114
|
+
on_update: /undefined/.test(entry.expects) ? "SET NULL" : "CASCADE",
|
|
115
|
+
table: table[0],
|
|
116
|
+
to: to[0],
|
|
117
|
+
};
|
|
118
|
+
})
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.map((x) => {
|
|
121
|
+
ok(x);
|
|
122
|
+
return x;
|
|
123
|
+
}),
|
|
124
|
+
table: { name },
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
const migration = [];
|
|
128
|
+
|
|
129
|
+
for (const wantTable of wantTables) {
|
|
130
|
+
const haveTable = haveTables.find(
|
|
131
|
+
({ table }) => table.name == wantTable.table.name,
|
|
132
|
+
);
|
|
133
|
+
if (haveTable) {
|
|
134
|
+
for (const { info, name } of haveTable.indices) {
|
|
135
|
+
if (!wantTable.foreignKeys.find(({ from }) => from == info.name)) {
|
|
136
|
+
migration.push(`drop index ${name}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const haveColumns = haveTable.columns;
|
|
140
|
+
const columnsNowMissing = haveColumns.filter(
|
|
141
|
+
(haveColumn) =>
|
|
142
|
+
!wantTable.columns.find(({ name }) => name == haveColumn.name),
|
|
143
|
+
);
|
|
144
|
+
for (const { name, pk } of columnsNowMissing) {
|
|
145
|
+
if (pk) {
|
|
146
|
+
fail(`${haveTable.table.name}.${name}: Can't drop primary key`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const wantColumn of wantTable.columns) {
|
|
150
|
+
const haveColumn = haveColumns.find(
|
|
151
|
+
({ name }) => name == wantColumn.name,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
/** @param {Column} column */
|
|
155
|
+
function checkConstraint(column) {
|
|
156
|
+
ok(column);
|
|
157
|
+
ok(haveTable);
|
|
158
|
+
if (column.pk != wantColumn.pk) {
|
|
159
|
+
fail(
|
|
160
|
+
`${haveTable.table.name}.${column.name}: Can't toggle primary key`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (
|
|
164
|
+
!wantColumn.pk && // Without this condition I get an error with real database created by drizzle-kit.
|
|
165
|
+
column.notnull != wantColumn.notnull
|
|
166
|
+
) {
|
|
167
|
+
fail(
|
|
168
|
+
`${haveTable.table.name}.${column.name}: Can't toggle optional`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (haveColumn) {
|
|
174
|
+
checkConstraint(haveColumn);
|
|
175
|
+
} else {
|
|
176
|
+
const renamedColumn = columnsNowMissing.find(
|
|
177
|
+
({ notnull, type }) =>
|
|
178
|
+
wantColumn.type == type && wantColumn.notnull == notnull,
|
|
179
|
+
);
|
|
180
|
+
if (renamedColumn) {
|
|
181
|
+
checkConstraint(renamedColumn);
|
|
182
|
+
columnsNowMissing.splice(
|
|
183
|
+
columnsNowMissing.indexOf(renamedColumn),
|
|
184
|
+
1,
|
|
185
|
+
);
|
|
186
|
+
migration.push(
|
|
187
|
+
`alter table ${wantTable.table.name} rename column ${renamedColumn.name} to ${wantColumn.name}`,
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
if (wantColumn.pk) {
|
|
191
|
+
fail(
|
|
192
|
+
`${wantTable.table.name}.${wantColumn.name}: Can't add primary key`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
migration.push(
|
|
196
|
+
[
|
|
197
|
+
`alter table ${wantTable.table.name} add column`,
|
|
198
|
+
wantColumn.name,
|
|
199
|
+
wantColumn.type,
|
|
200
|
+
wantColumn.notnull ? "not null" : "",
|
|
201
|
+
...wantTable.foreignKeys
|
|
202
|
+
.map(({ from, on_delete, on_update, table, to }) =>
|
|
203
|
+
from == wantColumn.name
|
|
204
|
+
? `references ${table}(${to}) on delete ${on_delete} on update ${on_update}`
|
|
205
|
+
: "",
|
|
206
|
+
)
|
|
207
|
+
.filter(Boolean),
|
|
208
|
+
]
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.join(" "),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
for (const { from } of wantTable.foreignKeys) {
|
|
216
|
+
if (
|
|
217
|
+
from == wantColumn.name &&
|
|
218
|
+
!haveTable.indices.find(({ info }) => info.name == from)
|
|
219
|
+
) {
|
|
220
|
+
migration.push(
|
|
221
|
+
`create index ${wantTable.table.name}_${from} on ${wantTable.table.name}(${from})`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const missingColumn of columnsNowMissing) {
|
|
227
|
+
migration.push(
|
|
228
|
+
`alter table ${wantTable.table.name} drop column ${missingColumn.name}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (!wantTable.columns.some(({ pk }) => pk)) {
|
|
233
|
+
fail(`${wantTable.table.name} has no primary key`);
|
|
234
|
+
}
|
|
235
|
+
migration.push(
|
|
236
|
+
`create table ${wantTable.table.name} (\n` +
|
|
237
|
+
wantTable.columns
|
|
238
|
+
.map(({ name, notnull, pk, type }) =>
|
|
239
|
+
[
|
|
240
|
+
name,
|
|
241
|
+
type,
|
|
242
|
+
pk ? "primary key" : "",
|
|
243
|
+
!pk && notnull ? "not null" : "",
|
|
244
|
+
...wantTable.foreignKeys
|
|
245
|
+
.map(({ from, on_delete, on_update, table, to }) =>
|
|
246
|
+
from == name
|
|
247
|
+
? `references ${table}(${to}) on delete ${on_delete} on update ${on_update}`
|
|
248
|
+
: "",
|
|
249
|
+
)
|
|
250
|
+
.filter(Boolean),
|
|
251
|
+
]
|
|
252
|
+
.filter(Boolean)
|
|
253
|
+
.join(" "),
|
|
254
|
+
)
|
|
255
|
+
.join(",\n") +
|
|
256
|
+
`)`,
|
|
257
|
+
);
|
|
258
|
+
for (const { from } of wantTable.foreignKeys) {
|
|
259
|
+
migration.push(
|
|
260
|
+
`create index ${wantTable.table.name}_${from} on ${wantTable.table.name}(${from})`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const haveTable of haveTables) {
|
|
267
|
+
const wantTable = wantTables.find(
|
|
268
|
+
({ table }) => table.name == haveTable.table.name,
|
|
269
|
+
);
|
|
270
|
+
if (!wantTable) {
|
|
271
|
+
migration.push(`drop table ${haveTable.table.name}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const sql of migration) {
|
|
276
|
+
database.exec(sql);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return /** @type {any} */ (
|
|
280
|
+
Object.fromEntries(
|
|
281
|
+
Object.entries(schema).map(([maybeTable]) => {
|
|
282
|
+
const table = /** @type {keyof typeof schema & string} */ (maybeTable);
|
|
283
|
+
|
|
284
|
+
/** @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 */
|
|
285
|
+
function ensureArrayRecord(where) {
|
|
286
|
+
for (const key of Object.keys(where)) {
|
|
287
|
+
if (!Array.isArray(where[key])) {
|
|
288
|
+
Object.assign(where, { [key]: [where[key]] });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return [
|
|
294
|
+
table,
|
|
295
|
+
{
|
|
296
|
+
/** @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 */
|
|
297
|
+
delete(where) {
|
|
298
|
+
ensureArrayRecord(where);
|
|
299
|
+
database
|
|
300
|
+
.prepare(
|
|
301
|
+
`delete from ${table} where true\n` +
|
|
302
|
+
Object.entries(where)
|
|
303
|
+
.map(
|
|
304
|
+
([name, values]) =>
|
|
305
|
+
`and ${name} in (${values.map(() => "?").join(", ")})`,
|
|
306
|
+
)
|
|
307
|
+
.join("\n"),
|
|
308
|
+
)
|
|
309
|
+
.run(
|
|
310
|
+
...Object.values(where)
|
|
311
|
+
.flatMap((values) => values)
|
|
312
|
+
.map((value) => value ?? null),
|
|
313
|
+
);
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/** @param {any} join */
|
|
317
|
+
select(join) {
|
|
318
|
+
return select({ database, flat, join, schema, table });
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
/** @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 */
|
|
322
|
+
update({ set, where }) {
|
|
323
|
+
ensureArrayRecord(where);
|
|
324
|
+
database
|
|
325
|
+
.prepare(
|
|
326
|
+
`update ${table} set\n` +
|
|
327
|
+
Object.keys(set)
|
|
328
|
+
.map((name) => `${name} = ?`)
|
|
329
|
+
.join(",\n") +
|
|
330
|
+
`\nwhere true\n` +
|
|
331
|
+
Object.entries(where)
|
|
332
|
+
.map(
|
|
333
|
+
([name, values]) =>
|
|
334
|
+
`and ${name} in (${values.map(() => "?").join(", ")})`,
|
|
335
|
+
)
|
|
336
|
+
.join("\n"),
|
|
337
|
+
)
|
|
338
|
+
.run(
|
|
339
|
+
...[
|
|
340
|
+
...Object.values(set).map((value) => value ?? null),
|
|
341
|
+
...Object.values(where)
|
|
342
|
+
.flatMap((values) => values)
|
|
343
|
+
.map((value) => value ?? null),
|
|
344
|
+
],
|
|
345
|
+
);
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
/** @param {any[]} values */
|
|
349
|
+
upsert(...values) {
|
|
350
|
+
ok(schema[table]);
|
|
351
|
+
const columns = Object.entries(schema[table].entries);
|
|
352
|
+
const primaryKey = columns.find(
|
|
353
|
+
([, column]) =>
|
|
354
|
+
/** @type {{ primary?: true }} */ (getMetadata(column))
|
|
355
|
+
.primary,
|
|
356
|
+
);
|
|
357
|
+
ok(primaryKey);
|
|
358
|
+
const query = database.prepare(
|
|
359
|
+
`insert into ${table} (${columns.map(([name]) => name).join(", ")}) values\n` +
|
|
360
|
+
`(${columns.map(() => "?").join(", ")})\n` +
|
|
361
|
+
`on conflict (${primaryKey[0]}) do update set\n` +
|
|
362
|
+
columns.map(([name]) => `${name} = ?`).join(",\n"),
|
|
363
|
+
);
|
|
364
|
+
const outputs = [];
|
|
365
|
+
for (const row of values) {
|
|
366
|
+
const output = parse(schema[table], row);
|
|
367
|
+
const values = columns.map(([name]) => output[name] ?? null);
|
|
368
|
+
query.run(...values, ...values);
|
|
369
|
+
outputs.push(output);
|
|
370
|
+
}
|
|
371
|
+
return outputs;
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
}),
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** @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>; schema: Schema; table: Table; }) => any[]} */
|
|
381
|
+
function select(props) {
|
|
382
|
+
ok(typeof props.table == "string");
|
|
383
|
+
let alias = `${props.table}_0`;
|
|
384
|
+
let sql = `from ${props.table} ${alias}`;
|
|
385
|
+
|
|
386
|
+
let columns = Object.keys(
|
|
387
|
+
/** @type {any} */ (props.schema)[props.table].entries,
|
|
388
|
+
).map((name) => `${alias}.${name} ${alias}_${name}`);
|
|
389
|
+
|
|
390
|
+
/** @type {Record<string, { entities: any[]; table: string }>} */
|
|
391
|
+
const aliases = {};
|
|
392
|
+
/**
|
|
393
|
+
* @param {number} unique
|
|
394
|
+
* @param {string} table
|
|
395
|
+
* @param {string} alias
|
|
396
|
+
* @param {{ with: any }} ownJoin // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
397
|
+
*/
|
|
398
|
+
function join(unique, table, alias, ownJoin) {
|
|
399
|
+
if (!aliases[alias]) {
|
|
400
|
+
aliases[alias] = { entities: [], table };
|
|
401
|
+
}
|
|
402
|
+
for (const [name, itsJoin] of ownJoin ? Object.entries(ownJoin.with) : []) {
|
|
403
|
+
const otherFlat = props.flat[table];
|
|
404
|
+
ok(otherFlat);
|
|
405
|
+
const relation = otherFlat[/** @type {keyof typeof otherFlat} */ (name)];
|
|
406
|
+
ok(relation);
|
|
407
|
+
|
|
408
|
+
const { Its, Other, Own } =
|
|
409
|
+
/** @type {{ Its: string, Other: string, Own: string }} */ (
|
|
410
|
+
/** @type {any} */ (relation)
|
|
411
|
+
);
|
|
412
|
+
const otherAlias = `${name}_${unique + 1}`;
|
|
413
|
+
if (!aliases[otherAlias]) {
|
|
414
|
+
aliases[otherAlias] = { entities: [], table: Other };
|
|
415
|
+
}
|
|
416
|
+
columns = [
|
|
417
|
+
...columns,
|
|
418
|
+
|
|
419
|
+
...Object.keys(/** @type {any} */ (props.schema)[Other].entries).map(
|
|
420
|
+
(name) => `${otherAlias}.${name} ${otherAlias}_${name}`,
|
|
421
|
+
),
|
|
422
|
+
];
|
|
423
|
+
sql += `\n left join ${Other} ${otherAlias} on ${otherAlias}.${Its} = ${alias}.${Own}`;
|
|
424
|
+
if (typeof itsJoin == "object") {
|
|
425
|
+
ok(itsJoin);
|
|
426
|
+
join(unique + 1, Other, otherAlias, itsJoin);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
join(0, props.table, alias, /** @type {any} */ (props.join));
|
|
432
|
+
sql = `select ${columns.join(",\n")} ${sql}`;
|
|
433
|
+
|
|
434
|
+
const rows = props.database.prepare(sql).all();
|
|
435
|
+
for (const [alias, state] of Object.entries(aliases)) {
|
|
436
|
+
/** @type {Record<string, (typeof state.entities)[0]>} */
|
|
437
|
+
const unique = {};
|
|
438
|
+
for (const row of rows) {
|
|
439
|
+
/** @type {Record<string, any>} */
|
|
440
|
+
const unwrapped = {};
|
|
441
|
+
|
|
442
|
+
let empty = true;
|
|
443
|
+
for (const name of Object.keys(
|
|
444
|
+
/** @type {any} */ (props.schema)[state.table].entries,
|
|
445
|
+
)) {
|
|
446
|
+
unwrapped[name] = row[`${alias}_${name}`] ?? undefined;
|
|
447
|
+
if (unwrapped[name] !== undefined) {
|
|
448
|
+
empty = false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (!empty) {
|
|
452
|
+
unique[JSON.stringify(unwrapped)] = unwrapped;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
state.entities = Object.values(unique);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
alias = `${props.table}_0`;
|
|
459
|
+
/**
|
|
460
|
+
* @param {number} unique
|
|
461
|
+
* @param {string} table
|
|
462
|
+
* @param {string} alias
|
|
463
|
+
* @param {{ with: any }} ownJoin // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
464
|
+
*/
|
|
465
|
+
function merge(unique, table, alias, ownJoin) {
|
|
466
|
+
ok(aliases[alias]);
|
|
467
|
+
for (const [name, itsJoin] of ownJoin ? Object.entries(ownJoin.with) : []) {
|
|
468
|
+
const otherFlat = props.flat[table];
|
|
469
|
+
ok(otherFlat);
|
|
470
|
+
const relation = otherFlat[/** @type {keyof typeof otherFlat} */ (name)];
|
|
471
|
+
ok(relation);
|
|
472
|
+
|
|
473
|
+
const { Its, Other, Own, type } =
|
|
474
|
+
/** @type {{ Its: string, Other: string, Own: string, type: "referencedBy" | "references" }} */ (
|
|
475
|
+
/** @type {any} */ (relation)
|
|
476
|
+
);
|
|
477
|
+
const otherAlias = `${name}_${unique + 1}`;
|
|
478
|
+
ok(aliases[otherAlias]?.entities);
|
|
479
|
+
for (const self of aliases[alias].entities) {
|
|
480
|
+
const matchingOthers = aliases[otherAlias].entities.filter(
|
|
481
|
+
(other) => other[Its] == self[Own],
|
|
482
|
+
);
|
|
483
|
+
self[name] = type == "references" ? matchingOthers[0] : matchingOthers;
|
|
484
|
+
}
|
|
485
|
+
if (typeof itsJoin == "object") {
|
|
486
|
+
ok(itsJoin);
|
|
487
|
+
merge(unique + 1, Other, otherAlias, itsJoin);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
merge(0, props.table, alias, /** @type {any} */ (props.join));
|
|
493
|
+
const state = aliases[alias];
|
|
494
|
+
ok(state);
|
|
495
|
+
return state.entities;
|
|
496
|
+
}
|