tauri-kargo-tools 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/schema/base.ts +497 -0
- package/src/schema/client.ts +62 -0
- package/src/schema/server.ts +44 -0
- package/src/test/data-model.ts +13 -0
- package/src/test/index.ts +92 -1
- package/src/test/worker.ts +27 -0
package/package.json
CHANGED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
export type Type<T> =
|
|
2
|
+
|
|
3
|
+
| "string"
|
|
4
|
+
|
|
5
|
+
| "number"
|
|
6
|
+
|
|
7
|
+
| "boolean"
|
|
8
|
+
|
|
9
|
+
| { union: readonly string[]; }
|
|
10
|
+
|
|
11
|
+
| { ref: readonly (keyof T)[]; }
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
| { arrayOf: Type<T>; }
|
|
16
|
+
|
|
17
|
+
| { optional: Type<T>; };
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export type Structure<T> = { [name: string]: Type<T>; };
|
|
22
|
+
export type EntityFieldValue = number | string | boolean | { ref: string } | EntityFieldValue[]
|
|
23
|
+
export type Entity = { [name: string]: EntityFieldValue }
|
|
24
|
+
export type EntityMap = { [id: string]: Entity }
|
|
25
|
+
|
|
26
|
+
export function createModel<T extends { [name: string]: Structure<T>; }>(def: T): T {
|
|
27
|
+
return def;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export class DataModel<T extends { [name: string]: Structure<T>; }> {
|
|
33
|
+
|
|
34
|
+
def: T;
|
|
35
|
+
|
|
36
|
+
map: { [id: string]: ModelElement<T>; } = {};
|
|
37
|
+
types: { [id: string]: keyof T } = {}
|
|
38
|
+
idx = 0;
|
|
39
|
+
idForModelElement: Map<ModelElement<T>, string>;
|
|
40
|
+
constructor(def: T) {
|
|
41
|
+
this.idForModelElement = new Map()
|
|
42
|
+
this.def = def;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
getValues(): ModelElement<T>[] {
|
|
46
|
+
return Object.values(this.map)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getValue(ref: string): ModelElement<T> {
|
|
50
|
+
return this.map[ref];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
is<K extends keyof T>(m: any, type: K): m is Interfaces<T>[K] {
|
|
54
|
+
const id = this.idForModelElement.get(m)
|
|
55
|
+
if (!id) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return this.types[id] === type
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
isRef<K extends keyof T>(ref: any, type: K): ref is Ref<T, K> {
|
|
62
|
+
if (typeof ref.ref !== "string") {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
const value = this.getValue(ref.ref)
|
|
66
|
+
if (ref.ref && this.is(value, type)) {
|
|
67
|
+
ref.getValue = () => value
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
return false
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
initField(ref: string, name: string, value: EntityFieldValue) {
|
|
74
|
+
const entity = this.map[ref]
|
|
75
|
+
if (entity) {
|
|
76
|
+
const type = this.types[ref]
|
|
77
|
+
const typeField = this.def[type][name]
|
|
78
|
+
if (this.checkType(typeField, this.map, value, {})) {
|
|
79
|
+
(entity as any)[name] = value
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
init(value: EntityMap) {
|
|
87
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
88
|
+
|
|
89
|
+
const map: { [id: string]: keyof T } = {};
|
|
90
|
+
|
|
91
|
+
for (const id in value) {
|
|
92
|
+
let ok = false;
|
|
93
|
+
|
|
94
|
+
for (const t in this.def) {
|
|
95
|
+
if (this.check(id, value, t as keyof T, map)) {
|
|
96
|
+
ok = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!ok) return undefined; // ou throw new Error(`No type matches for ${id}`)
|
|
102
|
+
}
|
|
103
|
+
this.map = value as any
|
|
104
|
+
for (let e of Object.entries(this.map)) {
|
|
105
|
+
this.idForModelElement.set(e[1], e[0])
|
|
106
|
+
}
|
|
107
|
+
this.types = map
|
|
108
|
+
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
check<K extends keyof T>(id: string, value: any, type: K, map: { [id: string]: keyof T }): boolean {
|
|
114
|
+
if (map[id] === type) {
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
if (map[id] !== undefined) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
map[id] = type
|
|
123
|
+
const struct = this.def[type]
|
|
124
|
+
if (this.checkStructure(struct, value, value[id], map)) {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
delete map[id]
|
|
128
|
+
return false
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
checkStructure(
|
|
133
|
+
struct: Structure<T>,
|
|
134
|
+
value: any,
|
|
135
|
+
obj: any,
|
|
136
|
+
map: { [id: string]: keyof T }
|
|
137
|
+
): boolean {
|
|
138
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
|
|
139
|
+
|
|
140
|
+
// mode strict : pas de propriétés inconnues
|
|
141
|
+
for (const k of Object.keys(obj)) {
|
|
142
|
+
if (!(k in struct)) return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const [prop, t] of Object.entries(struct)) {
|
|
146
|
+
const v = (obj as any)[prop];
|
|
147
|
+
|
|
148
|
+
// optional
|
|
149
|
+
if (this.isOptionalType(t)) {
|
|
150
|
+
if (v === undefined) continue;
|
|
151
|
+
if (!this.checkType(t.optional, value, v, map)) return false;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// required
|
|
156
|
+
if (v === undefined) return false;
|
|
157
|
+
if (!this.checkType(t as Type<T>, value, v, map)) return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private checkType(
|
|
164
|
+
t: Type<T>,
|
|
165
|
+
root: any, // le "value" global (map d'objets)
|
|
166
|
+
v: any,
|
|
167
|
+
map: { [id: string]: keyof T }
|
|
168
|
+
): boolean {
|
|
169
|
+
if (t === "string") return typeof v === "string";
|
|
170
|
+
if (t === "number") return typeof v === "number";
|
|
171
|
+
if (t === "boolean") return typeof v === "boolean";
|
|
172
|
+
|
|
173
|
+
if (this.isUnionType(t)) {
|
|
174
|
+
return typeof v === "string" && t.union.includes(v);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (this.isArrayOfType(t)) {
|
|
178
|
+
return Array.isArray(v) && v.every(el => this.checkType(t.arrayOf, root, el, map));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (this.isOptionalType(t)) {
|
|
182
|
+
return v === undefined || this.checkType(t.optional, root, v, map);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.isRefType(t)) {
|
|
186
|
+
// runtime minimal : { ref: "$0" }
|
|
187
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
188
|
+
const rid = (v as any).ref;
|
|
189
|
+
|
|
190
|
+
if (typeof rid !== "string") return false;
|
|
191
|
+
if (!(rid in root)) return false;
|
|
192
|
+
|
|
193
|
+
// doit matcher AU MOINS un type autorisé
|
|
194
|
+
const allowed = t.ref as readonly (keyof T)[];
|
|
195
|
+
for (const candidate of allowed) {
|
|
196
|
+
if (this.check(rid, root, candidate, map)) {
|
|
197
|
+
(v as any).getValue = () => root[rid]
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private isUnionType(x: any): x is { union: readonly string[] } {
|
|
208
|
+
return typeof x === "object" && x !== null && Array.isArray(x.union);
|
|
209
|
+
}
|
|
210
|
+
private isRefType(x: any): x is { ref: readonly (keyof T)[] } {
|
|
211
|
+
return typeof x === "object" && x !== null && Array.isArray(x.ref);
|
|
212
|
+
}
|
|
213
|
+
private isArrayOfType(x: any): x is { arrayOf: Type<T> } {
|
|
214
|
+
return typeof x === "object" && x !== null && "arrayOf" in x;
|
|
215
|
+
}
|
|
216
|
+
private isOptionalType(x: any): x is { optional: Type<T> } {
|
|
217
|
+
return typeof x === "object" && x !== null && "optional" in x;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
createValue<K extends keyof T>(type: K, value: Interfaces<T>[K]): Ref<T, K> {
|
|
222
|
+
|
|
223
|
+
const ref = `$${this.idx}`;
|
|
224
|
+
|
|
225
|
+
this.idx++;
|
|
226
|
+
|
|
227
|
+
this.map[ref] = value as any;
|
|
228
|
+
this.types[ref] = type
|
|
229
|
+
this.idForModelElement.set(this.map[ref], ref)
|
|
230
|
+
|
|
231
|
+
return { ref: ref, getValue: () => value } as any;
|
|
232
|
+
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export type RefUnion<TDefs extends { [name: string]: Structure<TDefs>; }> = {
|
|
241
|
+
[K in keyof TDefs]: Ref<TDefs, K>;
|
|
242
|
+
}[keyof TDefs];
|
|
243
|
+
|
|
244
|
+
type UnwrapOptional<V> = V extends { optional: infer O; } ? O : V;
|
|
245
|
+
|
|
246
|
+
export type Ref<TDefs extends { [name: string]: Structure<TDefs>; }, K extends keyof TDefs> = { ref: string, getValue(): Interfaces<TDefs>[K] }
|
|
247
|
+
|
|
248
|
+
// Résout un champ du DSL vers un type TypeScript concret
|
|
249
|
+
|
|
250
|
+
type ToTsType<
|
|
251
|
+
|
|
252
|
+
TDefs extends { [name: string]: Structure<TDefs>; },
|
|
253
|
+
|
|
254
|
+
V
|
|
255
|
+
|
|
256
|
+
> =
|
|
257
|
+
|
|
258
|
+
V extends "string" ? string :
|
|
259
|
+
|
|
260
|
+
V extends "number" ? number :
|
|
261
|
+
|
|
262
|
+
V extends "boolean" ? boolean :
|
|
263
|
+
|
|
264
|
+
V extends { ref: infer K; }
|
|
265
|
+
|
|
266
|
+
? K extends readonly (keyof TDefs)[]
|
|
267
|
+
|
|
268
|
+
? { [U in K[number]]: Ref<TDefs, U> }[K[number]]
|
|
269
|
+
|
|
270
|
+
: never
|
|
271
|
+
|
|
272
|
+
: V extends { arrayOf: infer O; }
|
|
273
|
+
|
|
274
|
+
? ToTsType<TDefs, O>[]
|
|
275
|
+
|
|
276
|
+
: V extends { optional: infer O; }
|
|
277
|
+
|
|
278
|
+
? ToTsType<TDefs, O>
|
|
279
|
+
|
|
280
|
+
: V extends { union: infer L; }
|
|
281
|
+
|
|
282
|
+
? L extends readonly string[]
|
|
283
|
+
|
|
284
|
+
? L[number]
|
|
285
|
+
|
|
286
|
+
: never
|
|
287
|
+
|
|
288
|
+
: never;
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
// Matérialise une "entité" (clé de TDefs) en interface concrète
|
|
293
|
+
|
|
294
|
+
export type ToInterface<
|
|
295
|
+
|
|
296
|
+
TDefs extends { [name: string]: Structure<TDefs>; },
|
|
297
|
+
|
|
298
|
+
Name extends keyof TDefs
|
|
299
|
+
|
|
300
|
+
> =
|
|
301
|
+
|
|
302
|
+
// Propriétés requises (pas 'optional')
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
-readonly [P in keyof TDefs[Name]as TDefs[Name][P] extends { optional: any; } ? never : P]:
|
|
306
|
+
|
|
307
|
+
ToTsType<TDefs, TDefs[Name][P]>;
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
&
|
|
312
|
+
|
|
313
|
+
// Propriétés optionnelles ('optional')
|
|
314
|
+
|
|
315
|
+
{
|
|
316
|
+
|
|
317
|
+
-readonly [P in keyof TDefs[Name]as TDefs[Name][P] extends { optional: any; } ? P : never]?:
|
|
318
|
+
|
|
319
|
+
ToTsType<TDefs, UnwrapOptional<TDefs[Name][P]>>;
|
|
320
|
+
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Matérialise l’ensemble des interfaces
|
|
324
|
+
|
|
325
|
+
type Interfaces<TDefs extends { [name: string]: Structure<TDefs>; }> = {
|
|
326
|
+
|
|
327
|
+
[K in keyof TDefs]: ToInterface<TDefs, K>;
|
|
328
|
+
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
type ModelElement<TDefs extends { [name: string]: Structure<TDefs>; }> = Interfaces<TDefs>[keyof TDefs];
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
type Def<TDefs> = { [name: string]: Structure<TDefs> };
|
|
336
|
+
|
|
337
|
+
export class TypeImplicationChecker<TDefs extends Def<TDefs>> {
|
|
338
|
+
constructor(public readonly def: TDefs) { }
|
|
339
|
+
|
|
340
|
+
/** Calcule la matrice implies[A][B] (point fixe) et la liste des incohérences A⇒B (A≠B). */
|
|
341
|
+
analyze() {
|
|
342
|
+
const names = Object.keys(this.def) as (keyof TDefs & string)[];
|
|
343
|
+
const implies: Record<string, Record<string, boolean>> = {};
|
|
344
|
+
|
|
345
|
+
for (const a of names) {
|
|
346
|
+
implies[a] = {};
|
|
347
|
+
for (const b of names) implies[a][b] = (a === b);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let changed = true;
|
|
351
|
+
while (changed) {
|
|
352
|
+
changed = false;
|
|
353
|
+
|
|
354
|
+
for (const a of names) {
|
|
355
|
+
for (const b of names) {
|
|
356
|
+
if (a === b) continue;
|
|
357
|
+
if (implies[a][b]) continue;
|
|
358
|
+
|
|
359
|
+
if (this.impliesStructure(a, b, implies)) {
|
|
360
|
+
implies[a][b] = true;
|
|
361
|
+
changed = true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const problems: Array<{ A: string; B: string }> = [];
|
|
368
|
+
for (const a of names) {
|
|
369
|
+
for (const b of names) {
|
|
370
|
+
if (a !== b && implies[a][b]) problems.push({ A: a, B: b });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return { implies, problems };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Vrai si le système est cohérent au sens “aucun A⇒B avec A≠B”. */
|
|
378
|
+
isCoherent(): boolean {
|
|
379
|
+
return this.analyze().problems.length === 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Lève une erreur si incohérent. */
|
|
383
|
+
assertCoherent(): void {
|
|
384
|
+
const { problems } = this.analyze();
|
|
385
|
+
if (problems.length) {
|
|
386
|
+
const msg = problems.map(p => `${p.A} => ${p.B}`).join(", ");
|
|
387
|
+
throw new Error(`Incoherent type system (implications found): ${msg}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ----------------- Implémentation interne -----------------
|
|
392
|
+
|
|
393
|
+
private isUnion(x: any): x is { union: readonly string[] } {
|
|
394
|
+
return typeof x === "object" && x !== null && Array.isArray(x.union);
|
|
395
|
+
}
|
|
396
|
+
private isRef(x: any): x is { ref: readonly string[] } {
|
|
397
|
+
return typeof x === "object" && x !== null && Array.isArray(x.ref);
|
|
398
|
+
}
|
|
399
|
+
private isArrayOf(x: any): x is { arrayOf: any } {
|
|
400
|
+
return typeof x === "object" && x !== null && "arrayOf" in x;
|
|
401
|
+
}
|
|
402
|
+
private isOptional(x: any): x is { optional: any } {
|
|
403
|
+
return typeof x === "object" && x !== null && "optional" in x;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private impliesType(a: Type<TDefs>, b: Type<TDefs>, implies: Record<string, Record<string, boolean>>): boolean {
|
|
407
|
+
// primitives
|
|
408
|
+
if (a === "string") return b === "string";
|
|
409
|
+
if (a === "number") return b === "number";
|
|
410
|
+
if (a === "boolean") return b === "boolean";
|
|
411
|
+
|
|
412
|
+
// union
|
|
413
|
+
if (this.isUnion(a)) {
|
|
414
|
+
if (b === "string") return true; // union de literals ⊆ string
|
|
415
|
+
if (!this.isUnion(b)) return false;
|
|
416
|
+
const A = new Set(a.union);
|
|
417
|
+
return b.union.every(x => A.has(x)); // A ⊆ B => union(A) ⇒ union(B)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// optional
|
|
421
|
+
if (this.isOptional(a)) {
|
|
422
|
+
if (!this.isOptional(b)) return false; // optional(X) ne peut pas impliquer Y (undefined possible)
|
|
423
|
+
return this.impliesType(a.optional, b.optional, implies);
|
|
424
|
+
}
|
|
425
|
+
if (this.isOptional(b)) {
|
|
426
|
+
// X ⇒ optional(Y) si X⇒Y
|
|
427
|
+
return this.impliesType(a, b.optional, implies);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// arrayOf
|
|
431
|
+
if (this.isArrayOf(a)) {
|
|
432
|
+
if (!this.isArrayOf(b)) return false;
|
|
433
|
+
return this.impliesType(a.arrayOf, b.arrayOf, implies);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ref
|
|
437
|
+
if (this.isRef(a)) {
|
|
438
|
+
if (!this.isRef(b)) return false;
|
|
439
|
+
// {ref:[A1..]} ⇒ {ref:[B1..]} si ∀Ai ∃Bj : Ai ⇒ Bj
|
|
440
|
+
return a.ref.every(ai => b.ref.some(bj => implies[String(ai)]?.[String(bj)] === true));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Structure-level implication A⇒B en mode STRICT (comme votre checkStructure):
|
|
448
|
+
* - un objet A ne doit pas avoir de propriété inconnue pour B
|
|
449
|
+
* - les requis de B doivent être requis dans A
|
|
450
|
+
* - les types doivent impliquer
|
|
451
|
+
*/
|
|
452
|
+
private impliesStructure(
|
|
453
|
+
A: keyof TDefs & string,
|
|
454
|
+
B: keyof TDefs & string,
|
|
455
|
+
implies: Record<string, Record<string, boolean>>
|
|
456
|
+
): boolean {
|
|
457
|
+
const SA = this.def[A];
|
|
458
|
+
const SB = this.def[B];
|
|
459
|
+
|
|
460
|
+
// STRICT: toutes les props de A doivent exister dans B (sinon un objet A échoue chez B)
|
|
461
|
+
for (const p in SA) {
|
|
462
|
+
if (!(p in SB)) return false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
for (const p in SB) {
|
|
466
|
+
const tb = SB[p];
|
|
467
|
+
const ta = SA[p];
|
|
468
|
+
|
|
469
|
+
const bOpt = this.isOptional(tb);
|
|
470
|
+
const aOpt = ta !== undefined ? this.isOptional(ta) : false;
|
|
471
|
+
|
|
472
|
+
if (!bOpt) {
|
|
473
|
+
// B requiert p => A doit le requérir aussi
|
|
474
|
+
if (ta === undefined) return false;
|
|
475
|
+
if (aOpt) return false;
|
|
476
|
+
if (!this.impliesType(ta, tb, implies)) return false;
|
|
477
|
+
} else {
|
|
478
|
+
// B optionnel : A peut ne pas avoir p ; si A l'a, il doit impliquer le noyau de B
|
|
479
|
+
if (ta === undefined) continue;
|
|
480
|
+
const taCore = aOpt ? (ta as any).optional : ta;
|
|
481
|
+
const tbCore = (tb as any).optional;
|
|
482
|
+
if (!this.impliesType(taCore, tbCore, implies)) return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export interface DataModelReponse {
|
|
491
|
+
type: 'dataModelReponse';
|
|
492
|
+
value: any;
|
|
493
|
+
}
|
|
494
|
+
export interface RefReponse {
|
|
495
|
+
type: 'refReponse';
|
|
496
|
+
ref: string;
|
|
497
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Ref, Structure, ToInterface, DataModel, RefUnion } from "./base";
|
|
2
|
+
export type KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
|
|
3
|
+
export type Value = string | number | boolean | Value[]
|
|
4
|
+
export interface DataModelProp<T extends { [name: string]: Structure<T>; }, K extends keyof T, F extends keyof ToInterface<T, K>> {
|
|
5
|
+
ref: Ref<T, K>
|
|
6
|
+
field: F
|
|
7
|
+
value: ToInterface<T, K>[F]
|
|
8
|
+
}
|
|
9
|
+
export interface SetDataModelProp<T extends { [name: string]: Structure<T>; }, K extends keyof T, F extends keyof ToInterface<T, K>> extends DataModelProp<T, K, F> {
|
|
10
|
+
type: "setDataModelProp"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class DataModelClient<T extends { [name: string]: Structure<T>; }> {
|
|
14
|
+
def: T;
|
|
15
|
+
resolveDataModel: (dm: DataModel<T>) => void = () => { };
|
|
16
|
+
resolveRefUnion: (ref: RefUnion<T>) => void = () => { };
|
|
17
|
+
|
|
18
|
+
async setProp<K extends keyof T, F extends KeysOfType<ToInterface<T, K>, Value>>(dvp: DataModelProp<T, K, F>): Promise<DataModel<T>> {
|
|
19
|
+
const setDataModelProp: SetDataModelProp<T, K, F> = { ...dvp, type: "setDataModelProp" }
|
|
20
|
+
self.postMessage(JSON.parse(JSON.stringify(setDataModelProp)))
|
|
21
|
+
|
|
22
|
+
const r = new Promise<DataModel<T>>((resolve) => {
|
|
23
|
+
this.resolveDataModel = resolve;
|
|
24
|
+
})
|
|
25
|
+
return r;
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
async getDataModel(): Promise<DataModel<T>> {
|
|
29
|
+
self.postMessage({ type: "getDataModel" })
|
|
30
|
+
const r = new Promise<DataModel<T>>((resolve) => {
|
|
31
|
+
this.resolveDataModel = resolve;
|
|
32
|
+
})
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
async getSelf() {
|
|
36
|
+
self.postMessage({ type: "getSelf" })
|
|
37
|
+
const r = new Promise<RefUnion<T>>((resolve) => {
|
|
38
|
+
this.resolveRefUnion = resolve;
|
|
39
|
+
})
|
|
40
|
+
return r;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
constructor(def: T) {
|
|
45
|
+
this.def = def;
|
|
46
|
+
self.addEventListener("message", (event) => {
|
|
47
|
+
const data = event.data;
|
|
48
|
+
if (data.type === "refReponse") {
|
|
49
|
+
const refReponse = data as { type: 'refReponse', ref: string };
|
|
50
|
+
const r = { ref: refReponse.ref } as RefUnion<T>;
|
|
51
|
+
this.resolveRefUnion(r);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (data.type === "dataModelReponse") {
|
|
55
|
+
const r = new DataModel<T>(this.def)
|
|
56
|
+
r.init(data.value)
|
|
57
|
+
this.resolveDataModel(r);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { set } from "../container";
|
|
2
|
+
import { Ref, Structure, ToInterface, DataModel, DataModelReponse, RefUnion } from "./base";
|
|
3
|
+
import { Value } from "./client";
|
|
4
|
+
|
|
5
|
+
export interface SetDataModelProp {
|
|
6
|
+
type: "setDataModelProp",
|
|
7
|
+
ref: { ref: string }
|
|
8
|
+
field: string
|
|
9
|
+
value: Value
|
|
10
|
+
}
|
|
11
|
+
export interface SimpleRef {
|
|
12
|
+
ref: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DataModelServer<T extends { [name: string]: Structure<T>; }> extends DataModel<T> {
|
|
16
|
+
|
|
17
|
+
constructor(def: T) {
|
|
18
|
+
super(def)
|
|
19
|
+
}
|
|
20
|
+
process(worker: Worker, check: (setDataModelProp: SetDataModelProp) => boolean, ref:SimpleRef) {
|
|
21
|
+
worker.addEventListener("message", async (event) => {
|
|
22
|
+
const data = event.data;
|
|
23
|
+
if (data.type === "setDataModelProp") {
|
|
24
|
+
const setDataModelProp = data as SetDataModelProp;
|
|
25
|
+
if (check(setDataModelProp)) {
|
|
26
|
+
this.initField(setDataModelProp.ref.ref, setDataModelProp.field, setDataModelProp.value);
|
|
27
|
+
worker.postMessage(this.cloneMap());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (data.type === "getDataModel") {
|
|
31
|
+
worker.postMessage(this.cloneMap());
|
|
32
|
+
}
|
|
33
|
+
if (data.type === "getSelf") {
|
|
34
|
+
const refReponse = { type: 'refReponse', ref: ref.ref };
|
|
35
|
+
worker.postMessage(refReponse);
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
cloneMap(): DataModelReponse {
|
|
41
|
+
return { type: 'dataModelReponse', value: JSON.parse(JSON.stringify(this.map)) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
package/src/test/index.ts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import * as test from "../test"
|
|
2
2
|
import * as api from '../api'
|
|
3
|
+
import * as schema from "../schema/base"
|
|
4
|
+
import { DataModelServer, SetDataModelProp } from "../schema/server"
|
|
5
|
+
import { model } from "./data-model"
|
|
6
|
+
test.test("Test schema client server", async () => {
|
|
7
|
+
|
|
8
|
+
const server = new DataModelServer(model)
|
|
9
|
+
const state = server.createValue("Cell", { nom: "A", state: false })
|
|
10
|
+
const groupe = server.createValue("Groupe", { membres: [state], state: false })
|
|
11
|
+
let resolve: (b: SetDataModelProp[]) => void = () => { }
|
|
12
|
+
const p = new Promise<SetDataModelProp[]>((r) => {
|
|
13
|
+
resolve = r
|
|
14
|
+
})
|
|
15
|
+
const m: SetDataModelProp[] = []
|
|
16
|
+
const worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module" });
|
|
17
|
+
server.process(worker, (op) => {
|
|
18
|
+
m.push(op)
|
|
19
|
+
if (m.length >= 2) {
|
|
20
|
+
resolve(m)
|
|
21
|
+
}
|
|
22
|
+
return true
|
|
23
|
+
}, groupe)
|
|
24
|
+
const v = await p
|
|
25
|
+
test.assertEquals(v[0].ref.ref === (state.ref as any), true)
|
|
26
|
+
test.assertEquals(v[1].ref.ref === (groupe.ref as any), true)
|
|
27
|
+
worker.terminate()
|
|
28
|
+
|
|
29
|
+
})
|
|
30
|
+
test.test("Test schema simple un type", async () => {
|
|
31
|
+
const vue = new schema.DataModel({
|
|
32
|
+
Point: {
|
|
33
|
+
x: "number",
|
|
34
|
+
y: "number"
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
const p = vue.createValue("Point", { x: 10, y: 15 })
|
|
38
|
+
test.assertEquals(p.getValue().x, 10)
|
|
39
|
+
test.assertEquals(p.getValue().y, 15)
|
|
40
|
+
const anotherVue = new schema.DataModel({
|
|
41
|
+
Point: {
|
|
42
|
+
x: "number",
|
|
43
|
+
y: "number"
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
const copy = JSON.parse(JSON.stringify(vue.map))
|
|
47
|
+
const map = anotherVue.init(copy)
|
|
48
|
+
test.assertEquals(map !== undefined, true)
|
|
49
|
+
test.assertEquals(map![p.ref], "Point")
|
|
50
|
+
const p2 = anotherVue.getValue(p.ref)
|
|
51
|
+
test.assertEquals(p2.x, 10)
|
|
52
|
+
test.assertEquals(p2.y, 15)
|
|
53
|
+
console.log(map)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test.test("Test schema simple avec deux type", async () => {
|
|
60
|
+
const vue = new schema.DataModel({
|
|
61
|
+
Point: {
|
|
62
|
+
x: "number",
|
|
63
|
+
y: "number"
|
|
64
|
+
},
|
|
65
|
+
Personne: {
|
|
66
|
+
nom: "string",
|
|
67
|
+
prenom: "string"
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
const point = vue.createValue("Point", { x: 10, y: 15 })
|
|
71
|
+
const personne = vue.createValue("Personne", { nom: "Toto", prenom: "Lili" })
|
|
72
|
+
let testPoint = false
|
|
73
|
+
let testPersonne = false
|
|
74
|
+
for (const p of vue.getValues()) {
|
|
75
|
+
if (vue.is(p, "Point")) {
|
|
76
|
+
test.assertEquals(p.x, 10)
|
|
77
|
+
test.assertEquals(p.y, 15)
|
|
78
|
+
testPoint = true
|
|
79
|
+
}
|
|
80
|
+
if (vue.is(p, "Personne")) {
|
|
81
|
+
test.assertEquals(p.nom, "Toto")
|
|
82
|
+
test.assertEquals(p.prenom, "Lili")
|
|
83
|
+
testPersonne = true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
test.assertEquals(testPoint, true)
|
|
87
|
+
test.assertEquals(testPersonne, true)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
})
|
|
3
94
|
test.test("Test read file", async () => {
|
|
4
95
|
|
|
5
96
|
const client = api.createClient();
|
|
@@ -35,7 +126,7 @@ test.test("Test read file", async () => {
|
|
|
35
126
|
|
|
36
127
|
}), false, "pas dans rep")
|
|
37
128
|
}
|
|
38
|
-
rep
|
|
129
|
+
rep = await client.explorer({ type: "array", path: "C:/Users/david/Documents/GitHub/tauriKargoExamples/examples/test-api-file-typescript" })
|
|
39
130
|
console.log(rep)
|
|
40
131
|
|
|
41
132
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DataModelClient } from "../schema/client"
|
|
2
|
+
import { model } from "./data-model"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const client = new DataModelClient(model);
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
(async () => {
|
|
9
|
+
const dm = await client.getDataModel();
|
|
10
|
+
for (const o of dm.getValues()) {
|
|
11
|
+
if (dm.is(o, "Groupe")) {
|
|
12
|
+
for (const ref of o.membres) {
|
|
13
|
+
console.log(dm.map)
|
|
14
|
+
const tmp = await client.setProp({ ref: ref, field: "state", value: true });
|
|
15
|
+
console.log(tmp.map)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const selfRef = await client.getSelf();
|
|
22
|
+
if (dm.isRef(selfRef, "Groupe")) {
|
|
23
|
+
await client.setProp({ ref: selfRef, field: "state", value: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
})()
|