@delta-comic/db 3.0.0-next.10 → 3.0.0-next.12
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/dist/index.d.mts +578 -138
- package/dist/index.mjs +326 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -9
package/dist/index.mjs
CHANGED
|
@@ -4,9 +4,11 @@ import { isTauri } from "@tauri-apps/api/core";
|
|
|
4
4
|
import { CamelCasePlugin, Kysely } from "kysely";
|
|
5
5
|
import { SerializePlugin } from "kysely-plugin-serialize";
|
|
6
6
|
import { defineMutation, useMutation, useQuery, useQueryCache } from "@pinia/colada";
|
|
7
|
+
import { ref, toValue, watch } from "vue";
|
|
7
8
|
import { SourcedValue, Struct, UniContentPage } from "@delta-comic/model";
|
|
8
9
|
import { fromPairs } from "es-toolkit/compat";
|
|
9
|
-
import {
|
|
10
|
+
import { Compile } from "typebox/compile";
|
|
11
|
+
import { Value } from "typebox/value";
|
|
10
12
|
//#region lib/utils.ts
|
|
11
13
|
var utils_exports = /* @__PURE__ */ __exportAll({
|
|
12
14
|
CommonQueryKey: () => CommonQueryKey,
|
|
@@ -43,7 +45,6 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
43
45
|
removeByNames: () => removeByNames,
|
|
44
46
|
useQuery: () => useQuery$4,
|
|
45
47
|
useRemove: () => useRemove$3,
|
|
46
|
-
useSetKind: () => useSetKind,
|
|
47
48
|
useToggleEnable: () => useToggleEnable,
|
|
48
49
|
useUpsert: () => useUpsert$4
|
|
49
50
|
});
|
|
@@ -115,29 +116,6 @@ const useToggleEnable = defineMutation(() => {
|
|
|
115
116
|
key
|
|
116
117
|
};
|
|
117
118
|
});
|
|
118
|
-
const useSetKind = defineMutation(() => {
|
|
119
|
-
const queryCache = useQueryCache();
|
|
120
|
-
const key = ["db", "db:plugin:"];
|
|
121
|
-
const { mutateAsync, ...mutation } = useMutation({
|
|
122
|
-
mutation: async ({ pluginName, kind }) => withTransition(async (trx) => {
|
|
123
|
-
const plugin = await trx.selectFrom("plugin").select(["loaderName", "meta"]).where("pluginName", "=", pluginName).executeTakeFirstOrThrow();
|
|
124
|
-
if (plugin.loaderName === "builtin") throw new Error("built-in plugin kind cannot be changed");
|
|
125
|
-
await trx.updateTable("plugin").set({ meta: JSON.stringify({
|
|
126
|
-
...plugin.meta,
|
|
127
|
-
kind
|
|
128
|
-
}) }).where("pluginName", "=", pluginName).execute();
|
|
129
|
-
}),
|
|
130
|
-
onSettled: () => {
|
|
131
|
-
queryCache.invalidateQueries({ key });
|
|
132
|
-
},
|
|
133
|
-
key
|
|
134
|
-
});
|
|
135
|
-
return {
|
|
136
|
-
...mutation,
|
|
137
|
-
setKind: mutateAsync,
|
|
138
|
-
key
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
119
|
const useQuery$4 = (query, otherKeys = [], initialData) => useQuery({
|
|
142
120
|
query: async () => {
|
|
143
121
|
const { db } = await import("./index.mjs");
|
|
@@ -146,8 +124,9 @@ const useQuery$4 = (query, otherKeys = [], initialData) => useQuery({
|
|
|
146
124
|
key: () => [
|
|
147
125
|
"db",
|
|
148
126
|
"db:plugin:",
|
|
149
|
-
query
|
|
150
|
-
|
|
127
|
+
query,
|
|
128
|
+
...otherKeys
|
|
129
|
+
],
|
|
151
130
|
staleTime: 15e3,
|
|
152
131
|
refetchOnMount: "always",
|
|
153
132
|
initialData,
|
|
@@ -283,8 +262,9 @@ const useQueryItem = (query, otherKeys = [], initialData) => useQuery({
|
|
|
283
262
|
key: () => [
|
|
284
263
|
"db:favouriteItem:",
|
|
285
264
|
"db:favouriteCard:",
|
|
286
|
-
query
|
|
287
|
-
|
|
265
|
+
query,
|
|
266
|
+
...toValue(otherKeys)
|
|
267
|
+
],
|
|
288
268
|
staleTime: 15e3,
|
|
289
269
|
initialData,
|
|
290
270
|
initialDataUpdatedAt: 0
|
|
@@ -294,7 +274,11 @@ const useQueryCard = (query, otherKeys = [], initialData) => useQuery({
|
|
|
294
274
|
const { db } = await import("./index.mjs");
|
|
295
275
|
return await query(db.selectFrom("favouriteCard"));
|
|
296
276
|
},
|
|
297
|
-
key: () => [
|
|
277
|
+
key: () => [
|
|
278
|
+
"db:favouriteCard:",
|
|
279
|
+
query,
|
|
280
|
+
...toValue(otherKeys)
|
|
281
|
+
],
|
|
298
282
|
staleTime: 15e3,
|
|
299
283
|
refetchOnMount: "always",
|
|
300
284
|
initialData,
|
|
@@ -329,7 +313,7 @@ const useUpsert$2 = defineMutation(() => {
|
|
|
329
313
|
await trx.replaceInto("history").values({
|
|
330
314
|
itemKey,
|
|
331
315
|
timestamp: Date.now(),
|
|
332
|
-
ep: Struct.toRaw(item)
|
|
316
|
+
ep: Struct.toRaw(item.thisEp)
|
|
333
317
|
}).execute();
|
|
334
318
|
}, trx),
|
|
335
319
|
onSettled: () => {
|
|
@@ -369,8 +353,9 @@ const useQuery$3 = (query, otherKeys = [], initialData) => useQuery({
|
|
|
369
353
|
key: () => [
|
|
370
354
|
"db",
|
|
371
355
|
"db:history:",
|
|
372
|
-
query
|
|
373
|
-
|
|
356
|
+
query,
|
|
357
|
+
...otherKeys
|
|
358
|
+
],
|
|
374
359
|
staleTime: 15e3,
|
|
375
360
|
refetchOnMount: "always",
|
|
376
361
|
initialData,
|
|
@@ -437,8 +422,9 @@ const useQuery$2 = (query, otherKeys = [], initialData) => useQuery({
|
|
|
437
422
|
key: () => [
|
|
438
423
|
"db",
|
|
439
424
|
"db:subscribe:",
|
|
440
|
-
query
|
|
441
|
-
|
|
425
|
+
query,
|
|
426
|
+
...otherKeys
|
|
427
|
+
],
|
|
442
428
|
staleTime: 15e3,
|
|
443
429
|
refetchOnMount: "always",
|
|
444
430
|
initialData,
|
|
@@ -513,8 +499,9 @@ const useQuery$1 = (query, otherKeys = [], initialData) => useQuery({
|
|
|
513
499
|
key: () => [
|
|
514
500
|
"db",
|
|
515
501
|
"db:recentView:",
|
|
516
|
-
query
|
|
517
|
-
|
|
502
|
+
query,
|
|
503
|
+
...otherKeys
|
|
504
|
+
],
|
|
518
505
|
staleTime: 15e3,
|
|
519
506
|
refetchOnMount: "always",
|
|
520
507
|
initialData,
|
|
@@ -588,6 +575,302 @@ const useConfig = (belongTo, form) => {
|
|
|
588
575
|
});
|
|
589
576
|
return Object.assign(data, { ready });
|
|
590
577
|
};
|
|
578
|
+
const clientCamelRowSchemas = {
|
|
579
|
+
itemStore: {
|
|
580
|
+
type: "object",
|
|
581
|
+
properties: {
|
|
582
|
+
key: { type: "string" },
|
|
583
|
+
item: { anyOf: [{ type: "object" }, { type: "string" }] }
|
|
584
|
+
},
|
|
585
|
+
required: ["key", "item"],
|
|
586
|
+
additionalProperties: false
|
|
587
|
+
},
|
|
588
|
+
favouriteCard: {
|
|
589
|
+
type: "object",
|
|
590
|
+
properties: {
|
|
591
|
+
createAt: { type: "integer" },
|
|
592
|
+
title: { type: "string" },
|
|
593
|
+
private: { anyOf: [
|
|
594
|
+
{ type: "boolean" },
|
|
595
|
+
{ const: 0 },
|
|
596
|
+
{ const: 1 }
|
|
597
|
+
] },
|
|
598
|
+
description: { type: "string" }
|
|
599
|
+
},
|
|
600
|
+
required: [
|
|
601
|
+
"createAt",
|
|
602
|
+
"title",
|
|
603
|
+
"private",
|
|
604
|
+
"description"
|
|
605
|
+
],
|
|
606
|
+
additionalProperties: false
|
|
607
|
+
},
|
|
608
|
+
favouriteItem: {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: {
|
|
611
|
+
addTime: { type: "integer" },
|
|
612
|
+
belongTo: { type: "integer" },
|
|
613
|
+
itemKey: { type: "string" }
|
|
614
|
+
},
|
|
615
|
+
required: [
|
|
616
|
+
"addTime",
|
|
617
|
+
"belongTo",
|
|
618
|
+
"itemKey"
|
|
619
|
+
],
|
|
620
|
+
additionalProperties: false
|
|
621
|
+
},
|
|
622
|
+
history: {
|
|
623
|
+
type: "object",
|
|
624
|
+
properties: {
|
|
625
|
+
ep: { anyOf: [{ type: "object" }, { type: "string" }] },
|
|
626
|
+
timestamp: { type: "integer" },
|
|
627
|
+
itemKey: { type: "string" }
|
|
628
|
+
},
|
|
629
|
+
required: [
|
|
630
|
+
"ep",
|
|
631
|
+
"timestamp",
|
|
632
|
+
"itemKey"
|
|
633
|
+
],
|
|
634
|
+
additionalProperties: false
|
|
635
|
+
},
|
|
636
|
+
recentView: {
|
|
637
|
+
type: "object",
|
|
638
|
+
properties: {
|
|
639
|
+
timestamp: { type: "integer" },
|
|
640
|
+
itemKey: { type: "string" },
|
|
641
|
+
isViewed: { anyOf: [
|
|
642
|
+
{ type: "boolean" },
|
|
643
|
+
{ const: 0 },
|
|
644
|
+
{ const: 1 }
|
|
645
|
+
] }
|
|
646
|
+
},
|
|
647
|
+
required: [
|
|
648
|
+
"timestamp",
|
|
649
|
+
"itemKey",
|
|
650
|
+
"isViewed"
|
|
651
|
+
],
|
|
652
|
+
additionalProperties: false
|
|
653
|
+
},
|
|
654
|
+
subscribe: {
|
|
655
|
+
type: "object",
|
|
656
|
+
properties: {
|
|
657
|
+
itemKey: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
658
|
+
author: { anyOf: [
|
|
659
|
+
{ type: "object" },
|
|
660
|
+
{ type: "string" },
|
|
661
|
+
{ type: "null" }
|
|
662
|
+
] },
|
|
663
|
+
type: { type: "string" },
|
|
664
|
+
key: { type: "string" },
|
|
665
|
+
plugin: { type: "string" }
|
|
666
|
+
},
|
|
667
|
+
required: [
|
|
668
|
+
"itemKey",
|
|
669
|
+
"author",
|
|
670
|
+
"type",
|
|
671
|
+
"key",
|
|
672
|
+
"plugin"
|
|
673
|
+
],
|
|
674
|
+
additionalProperties: false
|
|
675
|
+
},
|
|
676
|
+
plugin: {
|
|
677
|
+
type: "object",
|
|
678
|
+
properties: {
|
|
679
|
+
installerName: { type: "string" },
|
|
680
|
+
loaderName: { type: "string" },
|
|
681
|
+
pluginName: { type: "string" },
|
|
682
|
+
meta: { anyOf: [{ type: "object" }, { type: "string" }] },
|
|
683
|
+
enable: { anyOf: [
|
|
684
|
+
{ type: "boolean" },
|
|
685
|
+
{ const: 0 },
|
|
686
|
+
{ const: 1 }
|
|
687
|
+
] },
|
|
688
|
+
installInput: { type: "string" },
|
|
689
|
+
displayName: { anyOf: [{ type: "string" }, { type: "null" }] }
|
|
690
|
+
},
|
|
691
|
+
required: [
|
|
692
|
+
"installerName",
|
|
693
|
+
"loaderName",
|
|
694
|
+
"pluginName",
|
|
695
|
+
"meta",
|
|
696
|
+
"enable",
|
|
697
|
+
"installInput",
|
|
698
|
+
"displayName"
|
|
699
|
+
],
|
|
700
|
+
additionalProperties: false
|
|
701
|
+
},
|
|
702
|
+
nativeStore: {
|
|
703
|
+
type: "object",
|
|
704
|
+
properties: {
|
|
705
|
+
namespace: { type: "string" },
|
|
706
|
+
key: { type: "string" },
|
|
707
|
+
value: { type: "string" }
|
|
708
|
+
},
|
|
709
|
+
required: [
|
|
710
|
+
"namespace",
|
|
711
|
+
"key",
|
|
712
|
+
"value"
|
|
713
|
+
],
|
|
714
|
+
additionalProperties: false
|
|
715
|
+
},
|
|
716
|
+
config: {
|
|
717
|
+
type: "object",
|
|
718
|
+
properties: {
|
|
719
|
+
belongTo: { type: "string" },
|
|
720
|
+
form: { type: "string" },
|
|
721
|
+
data: { type: "string" }
|
|
722
|
+
},
|
|
723
|
+
required: [
|
|
724
|
+
"belongTo",
|
|
725
|
+
"form",
|
|
726
|
+
"data"
|
|
727
|
+
],
|
|
728
|
+
additionalProperties: false
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region lib/validation.ts
|
|
733
|
+
const validationLogger = logger.scoped("db:validation");
|
|
734
|
+
const validators = /* @__PURE__ */ new WeakMap();
|
|
735
|
+
const validatorFor = (schema) => {
|
|
736
|
+
const cached = validators.get(schema);
|
|
737
|
+
if (cached) return cached;
|
|
738
|
+
const validator = Compile(schema);
|
|
739
|
+
validators.set(schema, validator);
|
|
740
|
+
return validator;
|
|
741
|
+
};
|
|
742
|
+
const validationDetails = (schema, value) => [...Value.Errors(schema, value)].slice(0, 5).map((error) => ({
|
|
743
|
+
instancePath: error.instancePath,
|
|
744
|
+
message: error.message
|
|
745
|
+
}));
|
|
746
|
+
const tableSchema = (table) => clientCamelRowSchemas[table];
|
|
747
|
+
/** Validates a row before writing; throws to block invalid data from persisting. */
|
|
748
|
+
const assertWrite = (schema, table, value) => {
|
|
749
|
+
if (!validatorFor(schema).Check(value)) {
|
|
750
|
+
validationLogger.error("database write validation failed", {
|
|
751
|
+
table,
|
|
752
|
+
errors: validationDetails(schema, value)
|
|
753
|
+
});
|
|
754
|
+
throw new Error(`database write validation failed for ${table}: ${JSON.stringify(validationDetails(schema, value))}`);
|
|
755
|
+
}
|
|
756
|
+
return value;
|
|
757
|
+
};
|
|
758
|
+
const assertWriteRow = (table, value) => assertWrite(tableSchema(table), table, value);
|
|
759
|
+
/** Validates a row on read; failures are non-intrusive: logged and the value returned as-is. */
|
|
760
|
+
const validateRead = (schema, table, value) => {
|
|
761
|
+
if (!validatorFor(schema).Check(value)) validationLogger.warn("database read validation failed", {
|
|
762
|
+
table,
|
|
763
|
+
errors: validationDetails(schema, value)
|
|
764
|
+
});
|
|
765
|
+
return value;
|
|
766
|
+
};
|
|
767
|
+
const validateReadRow = (table, value) => validateRead(tableSchema(table), table, value);
|
|
768
|
+
/** Filters invalid rows on read; failures are non-intrusive: logged and the row dropped. */
|
|
769
|
+
const filterValidRows = (schema, table, rows) => {
|
|
770
|
+
const valid = [];
|
|
771
|
+
for (const row of rows) {
|
|
772
|
+
if (validatorFor(schema).Check(row)) {
|
|
773
|
+
valid.push(row);
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
validationLogger.warn("database read validation failed, row dropped", {
|
|
777
|
+
table,
|
|
778
|
+
errors: validationDetails(schema, row)
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return valid;
|
|
782
|
+
};
|
|
783
|
+
const filterValidRowsFor = (table, rows) => filterValidRows(tableSchema(table), table, rows);
|
|
784
|
+
//#endregion
|
|
785
|
+
//#region lib/writeValidation.ts
|
|
786
|
+
const isTableNode = (node) => node?.kind === "TableNode";
|
|
787
|
+
const isValuesNode = (node) => node?.kind === "ValuesNode";
|
|
788
|
+
const isValueNode = (node) => node.kind === "ValueNode";
|
|
789
|
+
const isColumnNode = (node) => node.kind === "ColumnNode";
|
|
790
|
+
const tableNameOf = (node) => {
|
|
791
|
+
if (!isTableNode(node)) return null;
|
|
792
|
+
return node.table.identifier.name;
|
|
793
|
+
};
|
|
794
|
+
const rowObjectsOf = (node) => {
|
|
795
|
+
const columns = node.columns;
|
|
796
|
+
if (!columns || columns.length === 0) return null;
|
|
797
|
+
const names = columns.map((column) => column.column.name);
|
|
798
|
+
const values = node.values;
|
|
799
|
+
if (!isValuesNode(values)) return null;
|
|
800
|
+
const rows = [];
|
|
801
|
+
for (const list of values.values) {
|
|
802
|
+
let entries;
|
|
803
|
+
if (list.kind === "PrimitiveValueListNode") entries = [...list.values];
|
|
804
|
+
else if (list.kind === "ValueListNode") {
|
|
805
|
+
const wrapped = [];
|
|
806
|
+
for (const entry of list.values) {
|
|
807
|
+
if (!isValueNode(entry)) return null;
|
|
808
|
+
wrapped.push(entry.value);
|
|
809
|
+
}
|
|
810
|
+
entries = wrapped;
|
|
811
|
+
} else return null;
|
|
812
|
+
if (entries.length !== names.length) return null;
|
|
813
|
+
rows.push(Object.fromEntries(names.map((name, index) => [name, entries[index]])));
|
|
814
|
+
}
|
|
815
|
+
return rows;
|
|
816
|
+
};
|
|
817
|
+
const updatePatchOf = (node) => {
|
|
818
|
+
if (!node.updates || node.updates.length === 0) return null;
|
|
819
|
+
const patch = {};
|
|
820
|
+
for (const update of node.updates) {
|
|
821
|
+
if (!isColumnNode(update.column)) return null;
|
|
822
|
+
const value = update.value;
|
|
823
|
+
if (!isValueNode(value)) return null;
|
|
824
|
+
patch[update.column.column.name] = value.value;
|
|
825
|
+
}
|
|
826
|
+
return patch;
|
|
827
|
+
};
|
|
828
|
+
const partialSchemas = /* @__PURE__ */ new WeakMap();
|
|
829
|
+
const partialOf = (schema) => {
|
|
830
|
+
const cached = partialSchemas.get(schema);
|
|
831
|
+
if (cached) return cached;
|
|
832
|
+
const partial = {
|
|
833
|
+
...schema,
|
|
834
|
+
required: void 0
|
|
835
|
+
};
|
|
836
|
+
partialSchemas.set(schema, partial);
|
|
837
|
+
return partial;
|
|
838
|
+
};
|
|
839
|
+
const validateWriteNode = (node) => {
|
|
840
|
+
let tableName = null;
|
|
841
|
+
let rows = null;
|
|
842
|
+
let patch = null;
|
|
843
|
+
if (node.kind === "InsertQueryNode") {
|
|
844
|
+
tableName = tableNameOf(node.into);
|
|
845
|
+
rows = rowObjectsOf(node);
|
|
846
|
+
} else if (node.kind === "UpdateQueryNode") {
|
|
847
|
+
tableName = tableNameOf(node.table);
|
|
848
|
+
patch = updatePatchOf(node);
|
|
849
|
+
} else return;
|
|
850
|
+
if (tableName === null) return;
|
|
851
|
+
const schema = clientCamelRowSchemas[tableName];
|
|
852
|
+
if (!schema) return;
|
|
853
|
+
if (rows !== null) {
|
|
854
|
+
if (rows.length === 0) return;
|
|
855
|
+
for (const row of rows) assertWrite(schema, tableName, row);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (patch !== null) assertWrite(partialOf(schema), tableName, patch);
|
|
859
|
+
};
|
|
860
|
+
/**
|
|
861
|
+
* Centralized write gate: validates every insert/replace/update against the
|
|
862
|
+
* generated camelCase row schema before it reaches the dialect. Registered
|
|
863
|
+
* before CamelCasePlugin, so it observes camelCase identifiers and raw values.
|
|
864
|
+
*/
|
|
865
|
+
var WriteValidationPlugin = class {
|
|
866
|
+
transformQuery({ node }) {
|
|
867
|
+
validateWriteNode(node);
|
|
868
|
+
return node;
|
|
869
|
+
}
|
|
870
|
+
async transformResult({ result }) {
|
|
871
|
+
return result;
|
|
872
|
+
}
|
|
873
|
+
};
|
|
591
874
|
//#endregion
|
|
592
875
|
//#region lib/nativeStore/index.ts
|
|
593
876
|
const nativeStoreLogger = logger.scoped("db:native-store");
|
|
@@ -697,9 +980,13 @@ const createDialect = async () => {
|
|
|
697
980
|
};
|
|
698
981
|
const db = new Kysely({
|
|
699
982
|
dialect: await createDialect(),
|
|
700
|
-
plugins: [
|
|
983
|
+
plugins: [
|
|
984
|
+
new WriteValidationPlugin(),
|
|
985
|
+
new CamelCasePlugin(),
|
|
986
|
+
new SerializePlugin()
|
|
987
|
+
]
|
|
701
988
|
});
|
|
702
989
|
//#endregion
|
|
703
|
-
export { config_exports as ConfigDB, utils_exports as DBUtils, favourite_exports as FavouriteDB, history_exports as HistoryDB, itemStore_exports as ItemStoreDB, plugin_exports as PluginArchiveDB, recentView_exports as RecentDB, subscribe_exports as SubscribeDB, db, isTauriRuntime, useConfig, useNativeStore };
|
|
990
|
+
export { config_exports as ConfigDB, utils_exports as DBUtils, favourite_exports as FavouriteDB, history_exports as HistoryDB, itemStore_exports as ItemStoreDB, plugin_exports as PluginArchiveDB, recentView_exports as RecentDB, subscribe_exports as SubscribeDB, WriteValidationPlugin, assertWrite, assertWriteRow, db, filterValidRows, filterValidRowsFor, isTauriRuntime, tableSchema, useConfig, useNativeStore, validateRead, validateReadRow };
|
|
704
991
|
|
|
705
992
|
//# sourceMappingURL=index.mjs.map
|