@arcote.tech/arc 0.1.8 → 0.1.10
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/context/context.d.ts +1 -0
- package/dist/context/element.d.ts +21 -1
- package/dist/context/event.d.ts +2 -1
- package/dist/context/query-builders.d.ts +1 -0
- package/dist/data-storage/data-storage-master.d.ts +2 -0
- package/dist/database/database-mappers.d.ts +39 -0
- package/dist/database/database-store.d.ts +58 -0
- package/dist/database/index.d.ts +3 -0
- package/dist/database/schema-extraction.d.ts +12 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/interface.d.ts +1 -0
- package/dist/db/postgresAdapter.d.ts +90 -0
- package/dist/db/sqliteAdapter.d.ts +47 -3
- package/dist/elements/abstract.d.ts +28 -0
- package/dist/elements/any.d.ts +2 -0
- package/dist/elements/array.d.ts +3 -0
- package/dist/elements/blob.d.ts +2 -0
- package/dist/elements/boolean.d.ts +3 -0
- package/dist/elements/branded.d.ts +2 -0
- package/dist/elements/date.d.ts +3 -0
- package/dist/elements/default.d.ts +2 -0
- package/dist/elements/file.d.ts +2 -0
- package/dist/elements/number.d.ts +3 -0
- package/dist/elements/object.d.ts +5 -0
- package/dist/elements/optional.d.ts +2 -0
- package/dist/elements/or.d.ts +2 -0
- package/dist/elements/record.d.ts +2 -0
- package/dist/elements/string-enum.d.ts +2 -0
- package/dist/elements/string.d.ts +3 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1574 -658
- package/dist/telemetry/context.d.ts +65 -0
- package/dist/telemetry/index.d.ts +47 -0
- package/dist/telemetry/interfaces.d.ts +84 -0
- package/dist/telemetry/logger.d.ts +67 -0
- package/dist/telemetry/no-op.d.ts +54 -0
- package/dist/telemetry/tracer.d.ts +85 -0
- package/dist/utils.d.ts +0 -19
- package/dist/view/view.d.ts +5 -3
- package/package.json +1 -1
- package/dist/collection/collection.d.ts +0 -81
- package/dist/collection/index.d.ts +0 -4
- package/dist/collection/queries/abstract-collection-query.d.ts +0 -14
- package/dist/collection/queries/find.d.ts +0 -29
- package/dist/collection/queries/one-item.d.ts +0 -2
- package/dist/collection/queries/util.d.ts +0 -3
- package/dist/collection/query-builders/find-by-id.d.ts +0 -2
- package/dist/collection/query-builders/find-one.d.ts +0 -2
- package/dist/collection/query-builders/find.d.ts +0 -13
- package/dist/context/simple-query.d.ts +0 -33
- package/dist/data-storage/data-storage-builder.d.ts +0 -16
- package/dist/data-storage/query-processor.d.ts +0 -22
- package/dist/data-storage/store-state-authorized.d.ts +0 -26
- package/dist/utils/arcObjectToStoreSchema.d.ts +0 -4
- package/dist/utils/index.d.ts +0 -2
package/dist/index.js
CHANGED
|
@@ -43,6 +43,19 @@ class ArcOptional {
|
|
|
43
43
|
anyOf: [parentSchema, { type: "null" }]
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
getColumnData() {
|
|
47
|
+
const parentColumnData = this.parent.getColumnData?.();
|
|
48
|
+
if (!parentColumnData) {
|
|
49
|
+
throw new Error(`Parent element does not implement getColumnData()`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...parentColumnData,
|
|
53
|
+
storeData: {
|
|
54
|
+
...parentColumnData.storeData,
|
|
55
|
+
isNullable: true
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
// elements/branded.ts
|
|
@@ -71,6 +84,13 @@ class ArcBranded {
|
|
|
71
84
|
toJsonSchema() {
|
|
72
85
|
return this.parent.toJsonSchema?.() ?? {};
|
|
73
86
|
}
|
|
87
|
+
getColumnData() {
|
|
88
|
+
const parentColumnData = this.parent.getColumnData?.();
|
|
89
|
+
if (!parentColumnData) {
|
|
90
|
+
throw new Error(`Parent element does not implement getColumnData()`);
|
|
91
|
+
}
|
|
92
|
+
return parentColumnData;
|
|
93
|
+
}
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
// elements/default.ts
|
|
@@ -111,12 +131,24 @@ class ArcDefault {
|
|
|
111
131
|
default: defaultValue
|
|
112
132
|
};
|
|
113
133
|
}
|
|
134
|
+
getColumnData() {
|
|
135
|
+
const parentColumnData = this.parent.getColumnData?.();
|
|
136
|
+
if (!parentColumnData) {
|
|
137
|
+
throw new Error(`Parent element does not implement getColumnData()`);
|
|
138
|
+
}
|
|
139
|
+
const defaultValue = typeof this.defaultValueOrCallback === "function" ? this.defaultValueOrCallback() : this.defaultValueOrCallback;
|
|
140
|
+
return {
|
|
141
|
+
...parentColumnData,
|
|
142
|
+
defaultValue
|
|
143
|
+
};
|
|
144
|
+
}
|
|
114
145
|
}
|
|
115
146
|
|
|
116
147
|
// elements/abstract.ts
|
|
117
148
|
class ArcAbstract {
|
|
118
149
|
validations;
|
|
119
150
|
_description;
|
|
151
|
+
_storeData;
|
|
120
152
|
constructor(validations = []) {
|
|
121
153
|
this.validations = validations;
|
|
122
154
|
}
|
|
@@ -138,6 +170,7 @@ class ArcAbstract {
|
|
|
138
170
|
const Constructor = this.constructor;
|
|
139
171
|
const newInstance = Object.assign(new Constructor, this);
|
|
140
172
|
newInstance._description = this._description;
|
|
173
|
+
newInstance._storeData = this._storeData ? { ...this._storeData } : undefined;
|
|
141
174
|
return newInstance;
|
|
142
175
|
}
|
|
143
176
|
validate(value) {
|
|
@@ -169,194 +202,54 @@ class ArcAbstract {
|
|
|
169
202
|
getValidations() {
|
|
170
203
|
return this.validations;
|
|
171
204
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
serialize(value) {
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
parse(value) {
|
|
180
|
-
return value;
|
|
181
|
-
}
|
|
182
|
-
deserialize(value) {
|
|
183
|
-
return value;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// elements/utils/type-validator-builder.ts
|
|
188
|
-
function primitiveTypeComparatorStrategyFactory(type) {
|
|
189
|
-
return (value) => typeof value === type;
|
|
190
|
-
}
|
|
191
|
-
function typeValidatorBuilder(typeName, comparatorStrategy) {
|
|
192
|
-
const comparator = comparatorStrategy || primitiveTypeComparatorStrategyFactory(typeName);
|
|
193
|
-
return {
|
|
194
|
-
name: "type",
|
|
195
|
-
validator: (value) => {
|
|
196
|
-
const valueType = typeof value;
|
|
197
|
-
if (!comparator(value))
|
|
198
|
-
return { current: valueType, expected: typeName };
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// elements/string.ts
|
|
205
|
-
var stringValidator = typeValidatorBuilder("string");
|
|
206
|
-
|
|
207
|
-
class ArcString extends ArcPrimitive {
|
|
208
|
-
constructor() {
|
|
209
|
-
super([stringValidator]);
|
|
210
|
-
}
|
|
211
|
-
minLength(min) {
|
|
212
|
-
return this.validation("minLength", (value) => {
|
|
213
|
-
if (value.length < min)
|
|
214
|
-
return {
|
|
215
|
-
currentLength: value.length,
|
|
216
|
-
minLength: min
|
|
217
|
-
};
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
maxLength(max) {
|
|
221
|
-
return this.validation("maxLength", (value) => {
|
|
222
|
-
if (value.length > max)
|
|
223
|
-
return {
|
|
224
|
-
currentLength: value.length,
|
|
225
|
-
maxLength: max
|
|
226
|
-
};
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
length(number) {
|
|
230
|
-
return this.validation("length", (value) => {
|
|
231
|
-
if (value.length !== number) {
|
|
232
|
-
return {
|
|
233
|
-
currentLength: value.length,
|
|
234
|
-
length: number
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
includes(str) {
|
|
240
|
-
return this.validation("includes", (value) => {
|
|
241
|
-
if (!value.includes(str)) {
|
|
242
|
-
return {
|
|
243
|
-
currentValue: value,
|
|
244
|
-
includes: str
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
startsWith(str) {
|
|
250
|
-
return this.validation("startsWith", (value) => {
|
|
251
|
-
if (!value.startsWith(str)) {
|
|
252
|
-
return {
|
|
253
|
-
currentValue: value,
|
|
254
|
-
startsWith: str
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
endsWith(str) {
|
|
260
|
-
return this.validation("endsWith", (value) => {
|
|
261
|
-
if (!value.endsWith(str)) {
|
|
262
|
-
return {
|
|
263
|
-
currentValue: value,
|
|
264
|
-
endsWith: str
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
regex(regex) {
|
|
270
|
-
return this.validation("regex", (value) => {
|
|
271
|
-
if (!regex.test(value)) {
|
|
272
|
-
return {
|
|
273
|
-
currentValue: value,
|
|
274
|
-
regex
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
email() {
|
|
280
|
-
const regex = /^(?:(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+)*)|(?:"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f])*"))@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$|(?:\[(?:\d{1,3}\.){3}\d{1,3}\])$/;
|
|
281
|
-
return this.validation("email", (value) => {
|
|
282
|
-
if (!regex.test(value)) {
|
|
283
|
-
return {
|
|
284
|
-
currentEmail: value
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
toJsonSchema() {
|
|
290
|
-
const schema = { type: "string" };
|
|
291
|
-
if (this._description) {
|
|
292
|
-
schema.description = this._description;
|
|
293
|
-
}
|
|
294
|
-
return schema;
|
|
205
|
+
primaryKey() {
|
|
206
|
+
const clone = this.clone();
|
|
207
|
+
clone._storeData = { ...clone._storeData, isPrimaryKey: true };
|
|
208
|
+
return clone;
|
|
295
209
|
}
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
currentUrl: value
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
});
|
|
210
|
+
autoIncrement() {
|
|
211
|
+
const clone = this.clone();
|
|
212
|
+
clone._storeData = { ...clone._storeData, isAutoIncrement: true };
|
|
213
|
+
return clone;
|
|
305
214
|
}
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
return
|
|
310
|
-
if (!(IPv4regex.test(value) || IPv6regex.test(value))) {
|
|
311
|
-
return {
|
|
312
|
-
currentIP: value
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
});
|
|
215
|
+
unique() {
|
|
216
|
+
const clone = this.clone();
|
|
217
|
+
clone._storeData = { ...clone._storeData, isUnique: true };
|
|
218
|
+
return clone;
|
|
316
219
|
}
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
220
|
+
index() {
|
|
221
|
+
const clone = this.clone();
|
|
222
|
+
clone._storeData = { ...clone._storeData, hasIndex: true };
|
|
223
|
+
return clone;
|
|
320
224
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
createFn;
|
|
329
|
-
constructor(name, createFn) {
|
|
330
|
-
super(string(), name);
|
|
331
|
-
this.createFn = createFn;
|
|
225
|
+
foreignKey(table, column, options) {
|
|
226
|
+
const clone = this.clone();
|
|
227
|
+
clone._storeData = {
|
|
228
|
+
...clone._storeData,
|
|
229
|
+
foreignKey: { table, column, ...options }
|
|
230
|
+
};
|
|
231
|
+
return clone;
|
|
332
232
|
}
|
|
333
|
-
|
|
334
|
-
|
|
233
|
+
databaseType(overrides) {
|
|
234
|
+
const clone = this.clone();
|
|
235
|
+
clone._storeData = {
|
|
236
|
+
...clone._storeData,
|
|
237
|
+
databaseType: { ...clone._storeData?.databaseType, ...overrides }
|
|
238
|
+
};
|
|
239
|
+
return clone;
|
|
335
240
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
241
|
+
columnOptions(options) {
|
|
242
|
+
const clone = this.clone();
|
|
243
|
+
clone._storeData = {
|
|
244
|
+
...clone._storeData,
|
|
245
|
+
columnOptions: { ...clone._storeData?.columnOptions, ...options }
|
|
246
|
+
};
|
|
247
|
+
return clone;
|
|
343
248
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return this.generateFn();
|
|
347
|
-
}
|
|
348
|
-
var timestamp = (new Date().getTime() / 1000 | 0).toString(16);
|
|
349
|
-
return timestamp + "xxxxxxxxxxxxxxxx".replace(/[x]/g, function() {
|
|
350
|
-
return (Math.random() * 16 | 0).toString(16);
|
|
351
|
-
}).toLowerCase();
|
|
249
|
+
getStoreData() {
|
|
250
|
+
return this._storeData;
|
|
352
251
|
}
|
|
353
252
|
}
|
|
354
|
-
function id(name, generateFn) {
|
|
355
|
-
return new ArcId(name, generateFn);
|
|
356
|
-
}
|
|
357
|
-
function customId(name, createFn) {
|
|
358
|
-
return new ArcCustomId(name, createFn);
|
|
359
|
-
}
|
|
360
253
|
|
|
361
254
|
// elements/any.ts
|
|
362
255
|
class ArcAny extends ArcAbstract {
|
|
@@ -379,10 +272,37 @@ class ArcAny extends ArcAbstract {
|
|
|
379
272
|
}
|
|
380
273
|
return schema;
|
|
381
274
|
}
|
|
275
|
+
getColumnData() {
|
|
276
|
+
const storeData = this.getStoreData();
|
|
277
|
+
return {
|
|
278
|
+
type: "object",
|
|
279
|
+
storeData: {
|
|
280
|
+
...storeData,
|
|
281
|
+
isNullable: false
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
382
285
|
}
|
|
383
286
|
function any() {
|
|
384
287
|
return new ArcAny;
|
|
385
288
|
}
|
|
289
|
+
// elements/utils/type-validator-builder.ts
|
|
290
|
+
function primitiveTypeComparatorStrategyFactory(type) {
|
|
291
|
+
return (value) => typeof value === type;
|
|
292
|
+
}
|
|
293
|
+
function typeValidatorBuilder(typeName, comparatorStrategy) {
|
|
294
|
+
const comparator = comparatorStrategy || primitiveTypeComparatorStrategyFactory(typeName);
|
|
295
|
+
return {
|
|
296
|
+
name: "type",
|
|
297
|
+
validator: (value) => {
|
|
298
|
+
const valueType = typeof value;
|
|
299
|
+
if (!comparator(value))
|
|
300
|
+
return { current: valueType, expected: typeName };
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
386
306
|
// elements/object.ts
|
|
387
307
|
var objectValidator = typeValidatorBuilder("object");
|
|
388
308
|
|
|
@@ -498,8 +418,10 @@ class ArcObject extends ArcAbstract {
|
|
|
498
418
|
const properties = {};
|
|
499
419
|
const required = [];
|
|
500
420
|
for (const [key, element] of Object.entries(this.rawShape)) {
|
|
501
|
-
|
|
502
|
-
|
|
421
|
+
if (element instanceof ArcOptional) {
|
|
422
|
+
properties[key] = element.parent.toJsonSchema?.() ?? {};
|
|
423
|
+
} else {
|
|
424
|
+
properties[key] = element.toJsonSchema?.() ?? {};
|
|
503
425
|
required.push(key);
|
|
504
426
|
}
|
|
505
427
|
}
|
|
@@ -514,6 +436,20 @@ class ArcObject extends ArcAbstract {
|
|
|
514
436
|
}
|
|
515
437
|
return schema;
|
|
516
438
|
}
|
|
439
|
+
getColumnData() {
|
|
440
|
+
const storeData = this.getStoreData();
|
|
441
|
+
return {
|
|
442
|
+
type: "object",
|
|
443
|
+
storeData: {
|
|
444
|
+
...storeData,
|
|
445
|
+
isNullable: false
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
merge(otherShape) {
|
|
450
|
+
const mergedShape = { ...this.rawShape, ...otherShape };
|
|
451
|
+
return new ArcObject(mergedShape);
|
|
452
|
+
}
|
|
517
453
|
}
|
|
518
454
|
function object(element) {
|
|
519
455
|
return new ArcObject(element);
|
|
@@ -613,10 +549,33 @@ class ArcArray extends ArcAbstract {
|
|
|
613
549
|
}
|
|
614
550
|
return schema;
|
|
615
551
|
}
|
|
552
|
+
getColumnData() {
|
|
553
|
+
const storeData = this.getStoreData();
|
|
554
|
+
return {
|
|
555
|
+
type: "array",
|
|
556
|
+
storeData: {
|
|
557
|
+
...storeData,
|
|
558
|
+
isNullable: false
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
616
562
|
}
|
|
617
563
|
function array(element) {
|
|
618
564
|
return new ArcArray(element);
|
|
619
565
|
}
|
|
566
|
+
// elements/abstract-primitive.ts
|
|
567
|
+
class ArcPrimitive extends ArcAbstract {
|
|
568
|
+
serialize(value) {
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
parse(value) {
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
deserialize(value) {
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
620
579
|
// elements/blob.ts
|
|
621
580
|
var blobValidator = {
|
|
622
581
|
name: "blob",
|
|
@@ -715,7 +674,17 @@ class ArcBlob extends ArcPrimitive {
|
|
|
715
674
|
}
|
|
716
675
|
return schema;
|
|
717
676
|
}
|
|
718
|
-
|
|
677
|
+
getColumnData() {
|
|
678
|
+
const storeData = this.getStoreData();
|
|
679
|
+
return {
|
|
680
|
+
type: "blob",
|
|
681
|
+
storeData: {
|
|
682
|
+
...storeData,
|
|
683
|
+
isNullable: false
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
719
688
|
function blob() {
|
|
720
689
|
return new ArcBlob;
|
|
721
690
|
}
|
|
@@ -740,6 +709,16 @@ class ArcBoolean extends ArcPrimitive {
|
|
|
740
709
|
}
|
|
741
710
|
return schema;
|
|
742
711
|
}
|
|
712
|
+
getColumnData() {
|
|
713
|
+
const storeData = this.getStoreData();
|
|
714
|
+
return {
|
|
715
|
+
type: "boolean",
|
|
716
|
+
storeData: {
|
|
717
|
+
...storeData,
|
|
718
|
+
isNullable: false
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
}
|
|
743
722
|
}
|
|
744
723
|
function boolean() {
|
|
745
724
|
return new ArcBoolean;
|
|
@@ -789,6 +768,16 @@ class ArcDate extends ArcAbstract {
|
|
|
789
768
|
}
|
|
790
769
|
return schema;
|
|
791
770
|
}
|
|
771
|
+
getColumnData() {
|
|
772
|
+
const storeData = this.getStoreData();
|
|
773
|
+
return {
|
|
774
|
+
type: "date",
|
|
775
|
+
storeData: {
|
|
776
|
+
...storeData,
|
|
777
|
+
isNullable: false
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
792
781
|
}
|
|
793
782
|
function date() {
|
|
794
783
|
return new ArcDate;
|
|
@@ -967,10 +956,207 @@ class ArcFile extends ArcPrimitive {
|
|
|
967
956
|
}
|
|
968
957
|
return schema;
|
|
969
958
|
}
|
|
959
|
+
getColumnData() {
|
|
960
|
+
const storeData = this.getStoreData();
|
|
961
|
+
return {
|
|
962
|
+
type: "blob",
|
|
963
|
+
storeData: {
|
|
964
|
+
...storeData,
|
|
965
|
+
isNullable: false
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
970
969
|
}
|
|
971
970
|
function file() {
|
|
972
971
|
return new ArcFile;
|
|
973
972
|
}
|
|
973
|
+
// elements/string.ts
|
|
974
|
+
var stringValidator = typeValidatorBuilder("string");
|
|
975
|
+
|
|
976
|
+
class ArcString extends ArcPrimitive {
|
|
977
|
+
constructor() {
|
|
978
|
+
super([stringValidator]);
|
|
979
|
+
}
|
|
980
|
+
minLength(min) {
|
|
981
|
+
return this.validation("minLength", (value) => {
|
|
982
|
+
if (value.length < min)
|
|
983
|
+
return {
|
|
984
|
+
currentLength: value.length,
|
|
985
|
+
minLength: min
|
|
986
|
+
};
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
maxLength(max) {
|
|
990
|
+
return this.validation("maxLength", (value) => {
|
|
991
|
+
if (value.length > max)
|
|
992
|
+
return {
|
|
993
|
+
currentLength: value.length,
|
|
994
|
+
maxLength: max
|
|
995
|
+
};
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
length(number) {
|
|
999
|
+
return this.validation("length", (value) => {
|
|
1000
|
+
if (value.length !== number) {
|
|
1001
|
+
return {
|
|
1002
|
+
currentLength: value.length,
|
|
1003
|
+
length: number
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
includes(str) {
|
|
1009
|
+
return this.validation("includes", (value) => {
|
|
1010
|
+
if (!value.includes(str)) {
|
|
1011
|
+
return {
|
|
1012
|
+
currentValue: value,
|
|
1013
|
+
includes: str
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
startsWith(str) {
|
|
1019
|
+
return this.validation("startsWith", (value) => {
|
|
1020
|
+
if (!value.startsWith(str)) {
|
|
1021
|
+
return {
|
|
1022
|
+
currentValue: value,
|
|
1023
|
+
startsWith: str
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
endsWith(str) {
|
|
1029
|
+
return this.validation("endsWith", (value) => {
|
|
1030
|
+
if (!value.endsWith(str)) {
|
|
1031
|
+
return {
|
|
1032
|
+
currentValue: value,
|
|
1033
|
+
endsWith: str
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
regex(regex) {
|
|
1039
|
+
return this.validation("regex", (value) => {
|
|
1040
|
+
if (!regex.test(value)) {
|
|
1041
|
+
return {
|
|
1042
|
+
currentValue: value,
|
|
1043
|
+
regex
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
email() {
|
|
1049
|
+
const regex = /^(?:(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+)*)|(?:"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f])*"))@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$|(?:\[(?:\d{1,3}\.){3}\d{1,3}\])$/;
|
|
1050
|
+
return this.validation("email", (value) => {
|
|
1051
|
+
if (!regex.test(value)) {
|
|
1052
|
+
return {
|
|
1053
|
+
currentEmail: value
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
toJsonSchema() {
|
|
1059
|
+
const schema = { type: "string" };
|
|
1060
|
+
if (this._description) {
|
|
1061
|
+
schema.description = this._description;
|
|
1062
|
+
}
|
|
1063
|
+
return schema;
|
|
1064
|
+
}
|
|
1065
|
+
url() {
|
|
1066
|
+
const regex = /^(https?):\/\/(?![-0-9])(?!www\.[0-9])(?:[a-zA-Z0-9-]+(?::[a-zA-Z0-9-]+)?@)?(?:localhost|\b(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b|[a-zA-Z0-9-]+\.)?[a-zA-Z0-9-]+\.(?:[a-zA-Z]{2,}|[a-zA-Z]{2}\.[a-zA-Z]{2})(?::\d{1,5})?(?!\/\/)(?:\/[a-zA-Z0-9-._~:?#\[\]@!$&'()*+,;=]*)*(?!\/{2})(?:;[a-zA-Z0-9-._~:?#\[\]@!$&'()*+,;=]*)*(\?(?!=)[a-zA-Z0-9&=;]*)?(\#[a-zA-Z0-9-]*)?$|^(https?):\/\/(localhost|\b(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)(?::\d{1,5})?(?!\/\/)(?:\/[a-zA-Z0-9-._~:?#\[\]@!$&'()*+,;=]*)*(?!\/{2})(?:;[a-zA-Z0-9-._~:?#\[\]@!$&'()*+,;=]*)*(\?(?!=)[a-zA-Z0-9&=;]*)?(\#[a-zA-Z0-9-]*)?$/;
|
|
1067
|
+
return this.validation("url", (value) => {
|
|
1068
|
+
if (!regex.test(value)) {
|
|
1069
|
+
return {
|
|
1070
|
+
currentUrl: value
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
ip() {
|
|
1076
|
+
const IPv4regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
1077
|
+
const IPv6regex = /^(?:([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|:(:[0-9a-fA-F]{1,4}){1,7}|([0-9a-fA-F]{1,4}:){1,6}:([0-9a-fA-F]{1,4}:){1,6}[0-9a-fA-F]{1,4})$/;
|
|
1078
|
+
return this.validation("ip", (value) => {
|
|
1079
|
+
if (!(IPv4regex.test(value) || IPv6regex.test(value))) {
|
|
1080
|
+
return {
|
|
1081
|
+
currentIP: value
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
validation(name, validator) {
|
|
1087
|
+
const instance = this.pipeValidation(name, validator);
|
|
1088
|
+
return instance;
|
|
1089
|
+
}
|
|
1090
|
+
getColumnData() {
|
|
1091
|
+
const storeData = this.getStoreData();
|
|
1092
|
+
const validationInfo = {};
|
|
1093
|
+
for (const validation of this.validations) {
|
|
1094
|
+
switch (validation.name) {
|
|
1095
|
+
case "minLength":
|
|
1096
|
+
break;
|
|
1097
|
+
case "maxLength":
|
|
1098
|
+
break;
|
|
1099
|
+
case "regex":
|
|
1100
|
+
break;
|
|
1101
|
+
case "email":
|
|
1102
|
+
validationInfo.pattern = "email";
|
|
1103
|
+
break;
|
|
1104
|
+
case "url":
|
|
1105
|
+
validationInfo.pattern = "url";
|
|
1106
|
+
break;
|
|
1107
|
+
case "ip":
|
|
1108
|
+
validationInfo.pattern = "ip";
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
type: "string",
|
|
1114
|
+
storeData: {
|
|
1115
|
+
...storeData,
|
|
1116
|
+
isNullable: false
|
|
1117
|
+
},
|
|
1118
|
+
validationInfo
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function string() {
|
|
1123
|
+
return new ArcString;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// elements/id.ts
|
|
1127
|
+
class ArcCustomId extends ArcBranded {
|
|
1128
|
+
createFn;
|
|
1129
|
+
constructor(name, createFn) {
|
|
1130
|
+
super(string(), name);
|
|
1131
|
+
this.createFn = createFn;
|
|
1132
|
+
}
|
|
1133
|
+
get(...args) {
|
|
1134
|
+
return this.createFn(...args);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
class ArcId extends ArcBranded {
|
|
1139
|
+
generateFn;
|
|
1140
|
+
constructor(name, generateFn) {
|
|
1141
|
+
super(string(), name);
|
|
1142
|
+
this.generateFn = generateFn;
|
|
1143
|
+
}
|
|
1144
|
+
generate() {
|
|
1145
|
+
if (this.generateFn) {
|
|
1146
|
+
return this.generateFn();
|
|
1147
|
+
}
|
|
1148
|
+
var timestamp = (new Date().getTime() / 1000 | 0).toString(16);
|
|
1149
|
+
return timestamp + "xxxxxxxxxxxxxxxx".replace(/[x]/g, function() {
|
|
1150
|
+
return (Math.random() * 16 | 0).toString(16);
|
|
1151
|
+
}).toLowerCase();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function id(name, generateFn) {
|
|
1155
|
+
return new ArcId(name, generateFn);
|
|
1156
|
+
}
|
|
1157
|
+
function customId(name, createFn) {
|
|
1158
|
+
return new ArcCustomId(name, createFn);
|
|
1159
|
+
}
|
|
974
1160
|
// elements/number.ts
|
|
975
1161
|
var numberValidator = typeValidatorBuilder("number");
|
|
976
1162
|
|
|
@@ -1001,6 +1187,26 @@ class ArcNumber extends ArcPrimitive {
|
|
|
1001
1187
|
}
|
|
1002
1188
|
return schema;
|
|
1003
1189
|
}
|
|
1190
|
+
getColumnData() {
|
|
1191
|
+
const storeData = this.getStoreData();
|
|
1192
|
+
const validationInfo = {};
|
|
1193
|
+
for (const validation of this.validations) {
|
|
1194
|
+
switch (validation.name) {
|
|
1195
|
+
case "min":
|
|
1196
|
+
break;
|
|
1197
|
+
case "max":
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
type: "number",
|
|
1203
|
+
storeData: {
|
|
1204
|
+
...storeData,
|
|
1205
|
+
isNullable: false
|
|
1206
|
+
},
|
|
1207
|
+
validationInfo
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1004
1210
|
}
|
|
1005
1211
|
function number() {
|
|
1006
1212
|
return new ArcNumber;
|
|
@@ -1039,6 +1245,23 @@ class ArcOr {
|
|
|
1039
1245
|
anyOf: this.elements.map((el) => el.toJsonSchema?.() ?? {})
|
|
1040
1246
|
};
|
|
1041
1247
|
}
|
|
1248
|
+
getColumnData() {
|
|
1249
|
+
return {
|
|
1250
|
+
type: "object",
|
|
1251
|
+
storeData: {
|
|
1252
|
+
isNullable: false
|
|
1253
|
+
},
|
|
1254
|
+
validationInfo: {
|
|
1255
|
+
unionTypes: this.elements.map((el) => {
|
|
1256
|
+
try {
|
|
1257
|
+
return el.getColumnData?.()?.type || "unknown";
|
|
1258
|
+
} catch {
|
|
1259
|
+
return "unknown";
|
|
1260
|
+
}
|
|
1261
|
+
})
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1042
1265
|
pickElement(value) {
|
|
1043
1266
|
for (const element of this.elements) {
|
|
1044
1267
|
if (this.isTypeOf(element, value))
|
|
@@ -1118,6 +1341,16 @@ class ArcRecord extends ArcAbstract {
|
|
|
1118
1341
|
}
|
|
1119
1342
|
return schema;
|
|
1120
1343
|
}
|
|
1344
|
+
getColumnData() {
|
|
1345
|
+
const storeData = this.getStoreData();
|
|
1346
|
+
return {
|
|
1347
|
+
type: "record",
|
|
1348
|
+
storeData: {
|
|
1349
|
+
...storeData,
|
|
1350
|
+
isNullable: false
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1121
1354
|
}
|
|
1122
1355
|
function record(key, element) {
|
|
1123
1356
|
return new ArcRecord(key, element);
|
|
@@ -1160,368 +1393,50 @@ class ArcStringEnum extends ArcAbstract {
|
|
|
1160
1393
|
getEnumerators() {
|
|
1161
1394
|
return this.values;
|
|
1162
1395
|
}
|
|
1396
|
+
getColumnData() {
|
|
1397
|
+
const storeData = this.getStoreData();
|
|
1398
|
+
return {
|
|
1399
|
+
type: "stringEnum",
|
|
1400
|
+
storeData: {
|
|
1401
|
+
...storeData,
|
|
1402
|
+
isNullable: false
|
|
1403
|
+
},
|
|
1404
|
+
validationInfo: {
|
|
1405
|
+
enumValues: this.values
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1163
1409
|
}
|
|
1164
1410
|
function stringEnum(...values) {
|
|
1165
1411
|
return new ArcStringEnum(values);
|
|
1166
1412
|
}
|
|
1167
|
-
//
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
return "NUMBER";
|
|
1180
|
-
}
|
|
1181
|
-
return "TEXT";
|
|
1182
|
-
}
|
|
1183
|
-
function arcObjectToStoreSchema(tableName, schema) {
|
|
1184
|
-
const columns = schema.entries().map(([name, element2]) => ({
|
|
1185
|
-
name,
|
|
1186
|
-
type: getSQLiteType(element2),
|
|
1187
|
-
isOptional: element2 instanceof ArcOptional
|
|
1188
|
-
}));
|
|
1189
|
-
columns.unshift({
|
|
1190
|
-
name: "_id",
|
|
1191
|
-
type: "TEXT",
|
|
1192
|
-
isOptional: false
|
|
1193
|
-
}, {
|
|
1194
|
-
name: "deleted",
|
|
1195
|
-
type: "INTEGER",
|
|
1196
|
-
isOptional: false,
|
|
1197
|
-
default: 0
|
|
1198
|
-
}, {
|
|
1199
|
-
name: "lastUpdate",
|
|
1200
|
-
type: "TEXT",
|
|
1201
|
-
isOptional: false
|
|
1202
|
-
});
|
|
1203
|
-
return {
|
|
1204
|
-
tables: [
|
|
1205
|
-
{
|
|
1206
|
-
name: tableName,
|
|
1207
|
-
columns,
|
|
1208
|
-
primaryKey: "_id"
|
|
1209
|
-
}
|
|
1210
|
-
]
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// context/query.ts
|
|
1215
|
-
class ArcQuery {
|
|
1216
|
-
lastResult;
|
|
1217
|
-
listeners = new Set;
|
|
1218
|
-
subscribe(listener) {
|
|
1219
|
-
this.listeners.add(listener);
|
|
1413
|
+
// command/command.ts
|
|
1414
|
+
class ArcCommand extends ArcContextElement {
|
|
1415
|
+
name;
|
|
1416
|
+
_description;
|
|
1417
|
+
_params;
|
|
1418
|
+
_results;
|
|
1419
|
+
_elements;
|
|
1420
|
+
_handler;
|
|
1421
|
+
_isPublic = false;
|
|
1422
|
+
constructor(name) {
|
|
1423
|
+
super();
|
|
1424
|
+
this.name = name;
|
|
1220
1425
|
}
|
|
1221
|
-
|
|
1222
|
-
this.
|
|
1426
|
+
use(elements) {
|
|
1427
|
+
const clone = this.clone();
|
|
1428
|
+
clone._elements = elements;
|
|
1429
|
+
return clone;
|
|
1223
1430
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1431
|
+
description(description) {
|
|
1432
|
+
const clone = this.clone();
|
|
1433
|
+
clone._description = description;
|
|
1434
|
+
return clone;
|
|
1227
1435
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
params;
|
|
1233
|
-
static key;
|
|
1234
|
-
constructor(params) {
|
|
1235
|
-
super();
|
|
1236
|
-
this.params = params;
|
|
1237
|
-
}
|
|
1238
|
-
serialize() {
|
|
1239
|
-
return {
|
|
1240
|
-
key: this.constructor.key,
|
|
1241
|
-
params: this.params
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// collection/queries/abstract-collection-query.ts
|
|
1247
|
-
class ArcCollectionQuery extends ArcSerializableQuery {
|
|
1248
|
-
collection;
|
|
1249
|
-
bindedChangeHandler = this.changeHandler.bind(this);
|
|
1250
|
-
store;
|
|
1251
|
-
constructor(collection, params) {
|
|
1252
|
-
super(params);
|
|
1253
|
-
this.collection = collection;
|
|
1254
|
-
}
|
|
1255
|
-
async run(dataStorage) {
|
|
1256
|
-
const store = dataStorage.getStore(this.collection.name);
|
|
1257
|
-
this.store = store;
|
|
1258
|
-
const result = await this.fetch(store);
|
|
1259
|
-
this.lastResult = result;
|
|
1260
|
-
return result;
|
|
1261
|
-
}
|
|
1262
|
-
changeHandler(changes) {
|
|
1263
|
-
let resultChanged = false;
|
|
1264
|
-
for (const change of changes) {
|
|
1265
|
-
const response = this.onChange(change);
|
|
1266
|
-
if (response !== false) {
|
|
1267
|
-
this.lastResult = response;
|
|
1268
|
-
resultChanged = true;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
if (resultChanged)
|
|
1272
|
-
this.nextResult(this.lastResult);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// collection/queries/find.ts
|
|
1277
|
-
class QueryCollectionResult {
|
|
1278
|
-
result;
|
|
1279
|
-
constructor(result) {
|
|
1280
|
-
this.result = result;
|
|
1281
|
-
}
|
|
1282
|
-
get(id3) {
|
|
1283
|
-
return id3 ? this.result.find((r) => r._id === id3) : undefined;
|
|
1284
|
-
}
|
|
1285
|
-
map(callbackfn) {
|
|
1286
|
-
return this.result.map(callbackfn);
|
|
1287
|
-
}
|
|
1288
|
-
toArray() {
|
|
1289
|
-
return this.result;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
class ArcFindQuery extends ArcCollectionQuery {
|
|
1294
|
-
params;
|
|
1295
|
-
constructor(collection, params) {
|
|
1296
|
-
super(collection, params);
|
|
1297
|
-
this.params = params;
|
|
1298
|
-
}
|
|
1299
|
-
async fetch(store) {
|
|
1300
|
-
const results = await store.find(this.params, this.bindedChangeHandler);
|
|
1301
|
-
return this.createResult(results);
|
|
1302
|
-
}
|
|
1303
|
-
checkItem(item) {
|
|
1304
|
-
if (!this.params.where)
|
|
1305
|
-
return true;
|
|
1306
|
-
return Object.entries(this.params.where).every(([key, condition]) => {
|
|
1307
|
-
const value = item[key];
|
|
1308
|
-
if (typeof condition !== "object" || condition === null) {
|
|
1309
|
-
return value === condition;
|
|
1310
|
-
}
|
|
1311
|
-
return Object.entries(condition).every(([operator, operatorValue]) => {
|
|
1312
|
-
switch (operator) {
|
|
1313
|
-
case "$eq":
|
|
1314
|
-
return value === operatorValue;
|
|
1315
|
-
case "$ne":
|
|
1316
|
-
return value !== operatorValue;
|
|
1317
|
-
case "$gt":
|
|
1318
|
-
return typeof value === "number" && typeof operatorValue === "number" && value > operatorValue;
|
|
1319
|
-
case "$gte":
|
|
1320
|
-
return typeof value === "number" && typeof operatorValue === "number" && value >= operatorValue;
|
|
1321
|
-
case "$lt":
|
|
1322
|
-
return typeof value === "number" && typeof operatorValue === "number" && value < operatorValue;
|
|
1323
|
-
case "$lte":
|
|
1324
|
-
return typeof value === "number" && typeof operatorValue === "number" && value <= operatorValue;
|
|
1325
|
-
case "$in":
|
|
1326
|
-
return Array.isArray(operatorValue) && operatorValue.includes(value);
|
|
1327
|
-
case "$nin":
|
|
1328
|
-
return Array.isArray(operatorValue) && !operatorValue.includes(value);
|
|
1329
|
-
case "$exists":
|
|
1330
|
-
return typeof operatorValue === "boolean" ? operatorValue ? value !== undefined : value === undefined : true;
|
|
1331
|
-
default:
|
|
1332
|
-
return true;
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
onChange(change) {
|
|
1338
|
-
const lastResult = this.lastResult;
|
|
1339
|
-
const lastResultAsArray = lastResult || [];
|
|
1340
|
-
const index = lastResultAsArray.findIndex((e) => e._id === (change.type === "delete" ? change.id : change.id));
|
|
1341
|
-
const isInLastResult = index !== -1;
|
|
1342
|
-
const shouldBeInTheResult = change.type !== "delete" && this.checkItem(change.item);
|
|
1343
|
-
if (isInLastResult && shouldBeInTheResult)
|
|
1344
|
-
return this.createResult(lastResultAsArray.toSpliced(index, 1, change.item));
|
|
1345
|
-
else if (isInLastResult && (change.type === "delete" || !shouldBeInTheResult))
|
|
1346
|
-
return this.createResult(lastResultAsArray.toSpliced(index, 1));
|
|
1347
|
-
else if (!isInLastResult && shouldBeInTheResult)
|
|
1348
|
-
return this.createResult(lastResultAsArray.concat(change.item));
|
|
1349
|
-
return false;
|
|
1350
|
-
}
|
|
1351
|
-
createResult(result) {
|
|
1352
|
-
return new QueryCollectionResult(result);
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// collection/query-builders/find.ts
|
|
1357
|
-
class ArcFindQueryBuilder {
|
|
1358
|
-
collection;
|
|
1359
|
-
queryContext;
|
|
1360
|
-
options;
|
|
1361
|
-
constructor(collection, queryContext, options) {
|
|
1362
|
-
this.collection = collection;
|
|
1363
|
-
this.queryContext = queryContext;
|
|
1364
|
-
this.options = options;
|
|
1365
|
-
}
|
|
1366
|
-
toQuery() {
|
|
1367
|
-
return this.queryContext.cacheQuery(ArcFindQuery, [
|
|
1368
|
-
this.collection,
|
|
1369
|
-
this.options
|
|
1370
|
-
]);
|
|
1371
|
-
}
|
|
1372
|
-
run() {
|
|
1373
|
-
return this.queryContext.runQuery(this.toQuery());
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
// collection/collection.ts
|
|
1378
|
-
class ArcCollection extends ArcContextElementWithStore {
|
|
1379
|
-
name;
|
|
1380
|
-
id;
|
|
1381
|
-
schema;
|
|
1382
|
-
options;
|
|
1383
|
-
_restrictions;
|
|
1384
|
-
constructor(name, id3, schema, options) {
|
|
1385
|
-
super();
|
|
1386
|
-
this.name = name;
|
|
1387
|
-
this.id = id3;
|
|
1388
|
-
this.schema = schema;
|
|
1389
|
-
this.options = options;
|
|
1390
|
-
}
|
|
1391
|
-
storeSchema() {
|
|
1392
|
-
return arcObjectToStoreSchema(this.name, this.schema);
|
|
1393
|
-
}
|
|
1394
|
-
restrictions(authContext) {
|
|
1395
|
-
if (this._restrictions) {
|
|
1396
|
-
return this._restrictions(authContext);
|
|
1397
|
-
}
|
|
1398
|
-
return {
|
|
1399
|
-
read: false,
|
|
1400
|
-
write: false,
|
|
1401
|
-
modify: false,
|
|
1402
|
-
delete: false
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
auth(restrictionsFn) {
|
|
1406
|
-
const collection = new ArcCollection(this.name, this.id, this.schema, this.options);
|
|
1407
|
-
collection._restrictions = restrictionsFn;
|
|
1408
|
-
return collection;
|
|
1409
|
-
}
|
|
1410
|
-
serialize(data) {
|
|
1411
|
-
return {
|
|
1412
|
-
_id: this.id.serialize(data._id),
|
|
1413
|
-
...this.schema.serialize(data)
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
deserialize(data) {
|
|
1417
|
-
return {
|
|
1418
|
-
_id: this.id.deserialize(data._id),
|
|
1419
|
-
...this.schema.deserialize(data)
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
queryBuilder = (context) => {
|
|
1423
|
-
return {
|
|
1424
|
-
find: (options) => new ArcFindQueryBuilder(this, context, options || {})
|
|
1425
|
-
};
|
|
1426
|
-
};
|
|
1427
|
-
commandContext = (dataStorage, publishEvent) => {
|
|
1428
|
-
const store = dataStorage.getStore(this.name);
|
|
1429
|
-
return {
|
|
1430
|
-
add: async (data) => {
|
|
1431
|
-
if (this.id instanceof ArcCustomId)
|
|
1432
|
-
throw new Error("Collection with custom id not support 'add' method");
|
|
1433
|
-
const id3 = this.id.generate();
|
|
1434
|
-
const parsed = this.schema.parse(data);
|
|
1435
|
-
const body = {
|
|
1436
|
-
_id: id3,
|
|
1437
|
-
lastUpdate: new Date().toISOString(),
|
|
1438
|
-
...parsed
|
|
1439
|
-
};
|
|
1440
|
-
await store.set(body);
|
|
1441
|
-
await publishEvent({
|
|
1442
|
-
type: "set",
|
|
1443
|
-
to: body
|
|
1444
|
-
});
|
|
1445
|
-
return { id: id3 };
|
|
1446
|
-
},
|
|
1447
|
-
remove: async (id3) => {
|
|
1448
|
-
await store.remove(id3);
|
|
1449
|
-
return { success: true };
|
|
1450
|
-
},
|
|
1451
|
-
set: async (id3, data) => {
|
|
1452
|
-
const parsed = this.schema.parse(data);
|
|
1453
|
-
const body = {
|
|
1454
|
-
_id: id3,
|
|
1455
|
-
...parsed
|
|
1456
|
-
};
|
|
1457
|
-
await store.set(body);
|
|
1458
|
-
await publishEvent({
|
|
1459
|
-
type: "set",
|
|
1460
|
-
to: body
|
|
1461
|
-
});
|
|
1462
|
-
return { success: true };
|
|
1463
|
-
},
|
|
1464
|
-
find: async (options) => {
|
|
1465
|
-
return store.find(options);
|
|
1466
|
-
},
|
|
1467
|
-
findOne: async (where) => {
|
|
1468
|
-
const result = await store.find({
|
|
1469
|
-
where,
|
|
1470
|
-
limit: 1
|
|
1471
|
-
});
|
|
1472
|
-
return result[0];
|
|
1473
|
-
},
|
|
1474
|
-
modify: async (id3, data) => {
|
|
1475
|
-
const deserialized = this.schema.serializePartial(data);
|
|
1476
|
-
const { from, to } = await store.modify(id3, deserialized);
|
|
1477
|
-
await publishEvent({
|
|
1478
|
-
type: "modify",
|
|
1479
|
-
changes: deserialized,
|
|
1480
|
-
from,
|
|
1481
|
-
to
|
|
1482
|
-
});
|
|
1483
|
-
},
|
|
1484
|
-
edit: async (id3, editCallback) => {
|
|
1485
|
-
const { from, to } = await store.mutate(id3, editCallback);
|
|
1486
|
-
await publishEvent({
|
|
1487
|
-
type: "mutate",
|
|
1488
|
-
from,
|
|
1489
|
-
to
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
};
|
|
1493
|
-
};
|
|
1494
|
-
}
|
|
1495
|
-
function collection(name, id3, schema, options = {}) {
|
|
1496
|
-
return new ArcCollection(name, id3, schema, options);
|
|
1497
|
-
}
|
|
1498
|
-
// command/command.ts
|
|
1499
|
-
class ArcCommand extends ArcContextElement {
|
|
1500
|
-
name;
|
|
1501
|
-
_description;
|
|
1502
|
-
_params;
|
|
1503
|
-
_results;
|
|
1504
|
-
_elements;
|
|
1505
|
-
_handler;
|
|
1506
|
-
_isPublic = false;
|
|
1507
|
-
constructor(name) {
|
|
1508
|
-
super();
|
|
1509
|
-
this.name = name;
|
|
1510
|
-
}
|
|
1511
|
-
use(elements) {
|
|
1512
|
-
const clone = this.clone();
|
|
1513
|
-
clone._elements = elements;
|
|
1514
|
-
return clone;
|
|
1515
|
-
}
|
|
1516
|
-
description(description) {
|
|
1517
|
-
const clone = this.clone();
|
|
1518
|
-
clone._description = description;
|
|
1519
|
-
return clone;
|
|
1520
|
-
}
|
|
1521
|
-
public() {
|
|
1522
|
-
const clone = this.clone();
|
|
1523
|
-
clone._isPublic = true;
|
|
1524
|
-
return clone;
|
|
1436
|
+
public() {
|
|
1437
|
+
const clone = this.clone();
|
|
1438
|
+
clone._isPublic = true;
|
|
1439
|
+
return clone;
|
|
1525
1440
|
}
|
|
1526
1441
|
get isPublic() {
|
|
1527
1442
|
return this._isPublic;
|
|
@@ -1590,15 +1505,19 @@ function command(name) {
|
|
|
1590
1505
|
// context/context.ts
|
|
1591
1506
|
class ArcContext {
|
|
1592
1507
|
elements;
|
|
1508
|
+
elementsSet = new Set;
|
|
1593
1509
|
constructor(elements) {
|
|
1594
1510
|
this.elements = elements;
|
|
1511
|
+
this.elements.forEach((element2) => {
|
|
1512
|
+
this.elementsSet.add(element2);
|
|
1513
|
+
});
|
|
1595
1514
|
}
|
|
1596
1515
|
get(name) {
|
|
1597
1516
|
return this.elements.find((element2) => element2.name === name);
|
|
1598
1517
|
}
|
|
1599
1518
|
getSyncListeners(eventType, authContext) {
|
|
1600
1519
|
const listeners = [];
|
|
1601
|
-
this.
|
|
1520
|
+
this.elementsSet.forEach((element2) => {
|
|
1602
1521
|
if (element2.observer) {
|
|
1603
1522
|
const handlers = element2.observer(authContext);
|
|
1604
1523
|
const config = handlers[eventType];
|
|
@@ -1611,7 +1530,7 @@ class ArcContext {
|
|
|
1611
1530
|
}
|
|
1612
1531
|
getAsyncListeners(eventType, authContext) {
|
|
1613
1532
|
const listeners = [];
|
|
1614
|
-
this.
|
|
1533
|
+
this.elementsSet.forEach((element2) => {
|
|
1615
1534
|
if (element2.observer) {
|
|
1616
1535
|
const handlers = element2.observer(authContext);
|
|
1617
1536
|
const config = handlers[eventType];
|
|
@@ -1682,6 +1601,28 @@ function contextMerge(...contexts) {
|
|
|
1682
1601
|
}
|
|
1683
1602
|
// context/event.ts
|
|
1684
1603
|
var eventValidator = typeValidatorBuilder("object");
|
|
1604
|
+
var eventSchema = new ArcObject({
|
|
1605
|
+
id: string().primaryKey(),
|
|
1606
|
+
type: string(),
|
|
1607
|
+
payload: any(),
|
|
1608
|
+
createdAt: date()
|
|
1609
|
+
});
|
|
1610
|
+
var sharedEventDatabaseSchema = null;
|
|
1611
|
+
function getSharedEventDatabaseSchema() {
|
|
1612
|
+
if (!sharedEventDatabaseSchema) {
|
|
1613
|
+
sharedEventDatabaseSchema = {
|
|
1614
|
+
tables: [{
|
|
1615
|
+
name: "events",
|
|
1616
|
+
schema: eventSchema,
|
|
1617
|
+
options: {
|
|
1618
|
+
softDelete: false,
|
|
1619
|
+
versioning: true
|
|
1620
|
+
}
|
|
1621
|
+
}]
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
return sharedEventDatabaseSchema;
|
|
1625
|
+
}
|
|
1685
1626
|
var eventStoreSchema = {
|
|
1686
1627
|
tables: [
|
|
1687
1628
|
{
|
|
@@ -1715,6 +1656,7 @@ class ArcEvent extends ArcContextElementWithStore {
|
|
|
1715
1656
|
payload;
|
|
1716
1657
|
_restrictions;
|
|
1717
1658
|
storeSchema = () => eventStoreSchema;
|
|
1659
|
+
databaseStoreSchema = () => getSharedEventDatabaseSchema();
|
|
1718
1660
|
constructor(name, payload) {
|
|
1719
1661
|
super();
|
|
1720
1662
|
this.name = name;
|
|
@@ -1760,6 +1702,21 @@ class ArcEvent extends ArcContextElementWithStore {
|
|
|
1760
1702
|
function event(name, payload) {
|
|
1761
1703
|
return new ArcEvent(name, payload ? payload instanceof ArcObject ? payload : new ArcObject(payload) : undefined);
|
|
1762
1704
|
}
|
|
1705
|
+
// context/query.ts
|
|
1706
|
+
class ArcQuery {
|
|
1707
|
+
lastResult;
|
|
1708
|
+
listeners = new Set;
|
|
1709
|
+
subscribe(listener) {
|
|
1710
|
+
this.listeners.add(listener);
|
|
1711
|
+
}
|
|
1712
|
+
unsubscribe(listener) {
|
|
1713
|
+
this.listeners.delete(listener);
|
|
1714
|
+
}
|
|
1715
|
+
nextResult(result) {
|
|
1716
|
+
this.lastResult = result;
|
|
1717
|
+
this.listeners.forEach((listener) => listener(result));
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1763
1720
|
// context/query-builder-context.ts
|
|
1764
1721
|
class QueryBuilderContext {
|
|
1765
1722
|
queryCache;
|
|
@@ -2228,107 +2185,729 @@ class MasterStoreState extends StoreState {
|
|
|
2228
2185
|
item,
|
|
2229
2186
|
id: change.id
|
|
2230
2187
|
}
|
|
2231
|
-
};
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
throw new Error("Unknown change type");
|
|
2191
|
+
}
|
|
2192
|
+
async applyChange(change) {
|
|
2193
|
+
const transaction = await this.dataStorage.getReadWriteTransaction();
|
|
2194
|
+
const { event: event3, from, to } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2195
|
+
await transaction.commit();
|
|
2196
|
+
this.notifyListeners([event3]);
|
|
2197
|
+
return { from, to };
|
|
2198
|
+
}
|
|
2199
|
+
async applyChanges(changes) {
|
|
2200
|
+
const transaction = await this.dataStorage.getReadWriteTransaction();
|
|
2201
|
+
const events = [];
|
|
2202
|
+
for (const change of changes) {
|
|
2203
|
+
const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2204
|
+
if (event3)
|
|
2205
|
+
events.push(event3);
|
|
2206
|
+
}
|
|
2207
|
+
await transaction.commit();
|
|
2208
|
+
if (events.length > 0) {
|
|
2209
|
+
this.notifyListeners(events);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
async find(options, listener) {
|
|
2213
|
+
if (listener) {
|
|
2214
|
+
this.listeners.set(listener, { callback: listener });
|
|
2215
|
+
}
|
|
2216
|
+
const transaction = await this.dataStorage.getReadTransaction();
|
|
2217
|
+
const results = await transaction.find(this.storeName, options);
|
|
2218
|
+
return results.map((item) => this.deserialize ? this.deserialize(item) : item);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// data-storage/data-storage-master.ts
|
|
2223
|
+
class MasterDataStorage extends DataStorage {
|
|
2224
|
+
dbAdapter;
|
|
2225
|
+
arcContext;
|
|
2226
|
+
stores = new Map;
|
|
2227
|
+
rtcAdapter;
|
|
2228
|
+
reinitTablesExecuted = false;
|
|
2229
|
+
constructor(dbAdapter, rtcAdapterFactory, arcContext) {
|
|
2230
|
+
super();
|
|
2231
|
+
this.dbAdapter = dbAdapter;
|
|
2232
|
+
this.arcContext = arcContext;
|
|
2233
|
+
this.rtcAdapter = rtcAdapterFactory(this);
|
|
2234
|
+
this.executeReinitTablesOnReady();
|
|
2235
|
+
}
|
|
2236
|
+
async executeReinitTablesOnReady() {
|
|
2237
|
+
try {
|
|
2238
|
+
const adapter = await this.dbAdapter;
|
|
2239
|
+
if (!this.reinitTablesExecuted) {
|
|
2240
|
+
await adapter.executeReinitTables(this);
|
|
2241
|
+
this.reinitTablesExecuted = true;
|
|
2242
|
+
}
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
console.error("Failed to execute reinitTable functions:", error);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
async getReadTransaction() {
|
|
2248
|
+
return (await this.dbAdapter).readTransaction();
|
|
2249
|
+
}
|
|
2250
|
+
async getReadWriteTransaction() {
|
|
2251
|
+
return (await this.dbAdapter).readWriteTransaction();
|
|
2252
|
+
}
|
|
2253
|
+
getStore(storeName) {
|
|
2254
|
+
if (!this.stores.has(storeName)) {
|
|
2255
|
+
this.stores.set(storeName, new MasterStoreState(storeName, this));
|
|
2256
|
+
}
|
|
2257
|
+
return this.stores.get(storeName);
|
|
2258
|
+
}
|
|
2259
|
+
async applyChanges(changes) {
|
|
2260
|
+
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
2261
|
+
}
|
|
2262
|
+
applySerializedChanges(changes) {
|
|
2263
|
+
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
|
|
2264
|
+
}
|
|
2265
|
+
async commitChanges(changes) {
|
|
2266
|
+
await Promise.all([
|
|
2267
|
+
this.applyChanges(changes),
|
|
2268
|
+
this.rtcAdapter.commitChanges(changes)
|
|
2269
|
+
]);
|
|
2270
|
+
}
|
|
2271
|
+
fork() {
|
|
2272
|
+
return new ForkedDataStorage(this);
|
|
2273
|
+
}
|
|
2274
|
+
async sync(progressCallback) {
|
|
2275
|
+
await this.rtcAdapter.sync(progressCallback);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
// database/schema-extraction.ts
|
|
2279
|
+
function extractDatabaseAgnosticSchema(arcObject, tableName) {
|
|
2280
|
+
const columns = [];
|
|
2281
|
+
for (const [fieldName, fieldSchema] of arcObject.entries()) {
|
|
2282
|
+
const arcElement = fieldSchema;
|
|
2283
|
+
if (typeof arcElement.getColumnData !== "function") {
|
|
2284
|
+
throw new Error(`Element for field '${fieldName}' does not implement getColumnData() method. Element type: ${arcElement.constructor.name}`);
|
|
2285
|
+
}
|
|
2286
|
+
const columnInfo = arcElement.getColumnData();
|
|
2287
|
+
const fullColumnInfo = {
|
|
2288
|
+
name: fieldName,
|
|
2289
|
+
...columnInfo
|
|
2290
|
+
};
|
|
2291
|
+
columns.push(fullColumnInfo);
|
|
2292
|
+
}
|
|
2293
|
+
return {
|
|
2294
|
+
tableName,
|
|
2295
|
+
columns
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// db/postgresAdapter.ts
|
|
2300
|
+
class PostgreSQLReadTransaction {
|
|
2301
|
+
db;
|
|
2302
|
+
tables;
|
|
2303
|
+
adapter;
|
|
2304
|
+
constructor(db, tables, adapter) {
|
|
2305
|
+
this.db = db;
|
|
2306
|
+
this.tables = tables;
|
|
2307
|
+
this.adapter = adapter;
|
|
2308
|
+
}
|
|
2309
|
+
hasSoftDelete(tableName) {
|
|
2310
|
+
if (this.adapter) {
|
|
2311
|
+
return this.adapter.hasSoftDelete(tableName);
|
|
2312
|
+
}
|
|
2313
|
+
const table = this.tables.get(tableName);
|
|
2314
|
+
if (!table)
|
|
2315
|
+
return false;
|
|
2316
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2317
|
+
}
|
|
2318
|
+
deserializeValue(value, column) {
|
|
2319
|
+
if (value === null || value === undefined)
|
|
2320
|
+
return null;
|
|
2321
|
+
switch (column.type.toLowerCase()) {
|
|
2322
|
+
case "json":
|
|
2323
|
+
case "jsonb":
|
|
2324
|
+
if (typeof value === "string") {
|
|
2325
|
+
try {
|
|
2326
|
+
return JSON.parse(value);
|
|
2327
|
+
} catch {
|
|
2328
|
+
return value;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return value;
|
|
2332
|
+
case "text":
|
|
2333
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
2334
|
+
try {
|
|
2335
|
+
const parsed = JSON.parse(value);
|
|
2336
|
+
if (typeof parsed === "object" || Array.isArray(parsed)) {
|
|
2337
|
+
return parsed;
|
|
2338
|
+
}
|
|
2339
|
+
} catch {}
|
|
2340
|
+
}
|
|
2341
|
+
return value;
|
|
2342
|
+
case "datetime":
|
|
2343
|
+
case "timestamp":
|
|
2344
|
+
return new Date(value);
|
|
2345
|
+
default:
|
|
2346
|
+
return value;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
deserializeRow(row, table) {
|
|
2350
|
+
const result = {};
|
|
2351
|
+
for (const column of table.columns) {
|
|
2352
|
+
const value = row[column.name];
|
|
2353
|
+
result[column.name] = this.deserializeValue(value, column);
|
|
2354
|
+
}
|
|
2355
|
+
return result;
|
|
2356
|
+
}
|
|
2357
|
+
getId(store, id3) {
|
|
2358
|
+
return id3;
|
|
2359
|
+
}
|
|
2360
|
+
buildWhereClause(where, tableName) {
|
|
2361
|
+
const conditions = [];
|
|
2362
|
+
const params = [];
|
|
2363
|
+
let paramIndex = 1;
|
|
2364
|
+
if (tableName && this.hasSoftDelete(tableName)) {
|
|
2365
|
+
conditions.push('"deleted" = $1');
|
|
2366
|
+
params.push(false);
|
|
2367
|
+
paramIndex = 2;
|
|
2368
|
+
}
|
|
2369
|
+
if (!where) {
|
|
2370
|
+
return {
|
|
2371
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
2372
|
+
params
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
Object.entries(where).forEach(([key, value]) => {
|
|
2376
|
+
if (typeof value === "object" && value !== null) {
|
|
2377
|
+
Object.entries(value).forEach(([operator, operand]) => {
|
|
2378
|
+
switch (operator) {
|
|
2379
|
+
case "$eq":
|
|
2380
|
+
case "$ne":
|
|
2381
|
+
case "$gt":
|
|
2382
|
+
case "$gte":
|
|
2383
|
+
case "$lt":
|
|
2384
|
+
case "$lte":
|
|
2385
|
+
conditions.push(`"${key}" ${this.getOperatorSymbol(operator)} $${paramIndex}`);
|
|
2386
|
+
params.push(operand);
|
|
2387
|
+
paramIndex++;
|
|
2388
|
+
break;
|
|
2389
|
+
case "$in":
|
|
2390
|
+
case "$nin":
|
|
2391
|
+
if (Array.isArray(operand)) {
|
|
2392
|
+
const placeholders = operand.map(() => `$${paramIndex++}`).join(", ");
|
|
2393
|
+
conditions.push(`"${key}" ${operator === "$in" ? "IN" : "NOT IN"} (${placeholders})`);
|
|
2394
|
+
params.push(...operand);
|
|
2395
|
+
}
|
|
2396
|
+
break;
|
|
2397
|
+
case "$exists":
|
|
2398
|
+
if (typeof operand === "boolean") {
|
|
2399
|
+
conditions.push(operand ? `"${key}" IS NOT NULL` : `"${key}" IS NULL`);
|
|
2400
|
+
}
|
|
2401
|
+
break;
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
} else {
|
|
2405
|
+
conditions.push(`"${key}" = $${paramIndex}`);
|
|
2406
|
+
params.push(value);
|
|
2407
|
+
paramIndex++;
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
return {
|
|
2411
|
+
sql: conditions.join(" AND "),
|
|
2412
|
+
params
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
getOperatorSymbol(operator) {
|
|
2416
|
+
const operators = {
|
|
2417
|
+
$eq: "=",
|
|
2418
|
+
$ne: "!=",
|
|
2419
|
+
$gt: ">",
|
|
2420
|
+
$gte: ">=",
|
|
2421
|
+
$lt: "<",
|
|
2422
|
+
$lte: "<="
|
|
2423
|
+
};
|
|
2424
|
+
return operators[operator] || "=";
|
|
2425
|
+
}
|
|
2426
|
+
buildOrderByClause(orderBy) {
|
|
2427
|
+
if (!orderBy)
|
|
2428
|
+
return "";
|
|
2429
|
+
const orderClauses = Object.entries(orderBy).map(([key, direction]) => `"${key}" ${direction.toUpperCase()}`).join(", ");
|
|
2430
|
+
return orderClauses ? `ORDER BY ${orderClauses}` : "";
|
|
2431
|
+
}
|
|
2432
|
+
async find(store, options) {
|
|
2433
|
+
const { where, limit, offset, orderBy } = options || {};
|
|
2434
|
+
const whereClause = this.buildWhereClause(where, store);
|
|
2435
|
+
const orderByClause = this.buildOrderByClause(orderBy);
|
|
2436
|
+
const table = this.tables.get(store);
|
|
2437
|
+
if (!table) {
|
|
2438
|
+
throw new Error(`Store ${store} not found`);
|
|
2439
|
+
}
|
|
2440
|
+
let query2 = `
|
|
2441
|
+
SELECT *
|
|
2442
|
+
FROM "${store}"
|
|
2443
|
+
WHERE ${whereClause.sql}
|
|
2444
|
+
${orderByClause}
|
|
2445
|
+
`;
|
|
2446
|
+
let params = whereClause.params;
|
|
2447
|
+
let paramIndex = params.length + 1;
|
|
2448
|
+
if (limit) {
|
|
2449
|
+
query2 += ` LIMIT $${paramIndex}`;
|
|
2450
|
+
params.push(limit);
|
|
2451
|
+
paramIndex++;
|
|
2452
|
+
}
|
|
2453
|
+
if (offset) {
|
|
2454
|
+
query2 += ` OFFSET $${paramIndex}`;
|
|
2455
|
+
params.push(offset);
|
|
2456
|
+
}
|
|
2457
|
+
const rows = await this.db.exec(query2, params);
|
|
2458
|
+
return rows.map((row) => this.deserializeRow(row, table));
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
class PostgreSQLReadWriteTransaction extends PostgreSQLReadTransaction {
|
|
2463
|
+
adapter;
|
|
2464
|
+
queries = [];
|
|
2465
|
+
constructor(db, tables, adapter) {
|
|
2466
|
+
super(db, tables);
|
|
2467
|
+
this.adapter = adapter;
|
|
2468
|
+
}
|
|
2469
|
+
async remove(store, id3) {
|
|
2470
|
+
const table = this.tables.get(store);
|
|
2471
|
+
if (!table) {
|
|
2472
|
+
throw new Error(`Store ${store} not found`);
|
|
2473
|
+
}
|
|
2474
|
+
const query2 = `UPDATE "${store}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
|
|
2475
|
+
this.queries.push({
|
|
2476
|
+
sql: query2,
|
|
2477
|
+
params: [true, new Date().toISOString(), id3]
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
async set(store, item) {
|
|
2481
|
+
const table = this.tables.get(store);
|
|
2482
|
+
if (!table) {
|
|
2483
|
+
throw new Error(`Store ${store} not found`);
|
|
2484
|
+
}
|
|
2485
|
+
const hasVersioning = this.adapter.hasVersioning(store);
|
|
2486
|
+
if (hasVersioning) {
|
|
2487
|
+
await this.setWithVersioning(store, item, table);
|
|
2488
|
+
} else {
|
|
2489
|
+
await this.setWithoutVersioning(store, item, table);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
async setWithoutVersioning(store, item, table) {
|
|
2493
|
+
const columnNames = table.columns.map((col) => col.name);
|
|
2494
|
+
const values = table.columns.map((column) => {
|
|
2495
|
+
let value = item[column.name];
|
|
2496
|
+
if (value === undefined && column.default !== undefined) {
|
|
2497
|
+
value = column.default;
|
|
2498
|
+
}
|
|
2499
|
+
return this.serializeValue(value, column);
|
|
2500
|
+
});
|
|
2501
|
+
const placeholders = columnNames.map((_, i) => `$${i + 1}`).join(", ");
|
|
2502
|
+
const updateColumns = columnNames.filter((col) => col !== table.primaryKey).map((col) => `"${col}" = EXCLUDED."${col}"`).join(", ");
|
|
2503
|
+
if (store === "events") {
|
|
2504
|
+
const simpleInsertSql = `
|
|
2505
|
+
INSERT INTO "${table.name}"
|
|
2506
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2507
|
+
VALUES (${placeholders})
|
|
2508
|
+
`;
|
|
2509
|
+
this.queries.push({
|
|
2510
|
+
sql: simpleInsertSql,
|
|
2511
|
+
params: values
|
|
2512
|
+
});
|
|
2513
|
+
} else {
|
|
2514
|
+
const sql = `
|
|
2515
|
+
INSERT INTO "${table.name}"
|
|
2516
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2517
|
+
VALUES (${placeholders})
|
|
2518
|
+
ON CONFLICT ("${table.primaryKey}")
|
|
2519
|
+
DO UPDATE SET ${updateColumns}
|
|
2520
|
+
`;
|
|
2521
|
+
this.queries.push({
|
|
2522
|
+
sql,
|
|
2523
|
+
params: values
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
async setWithVersioning(store, item, table) {
|
|
2528
|
+
const regularColumns = table.columns.filter((col) => col.name !== "__version");
|
|
2529
|
+
const columnNames = regularColumns.map((col) => col.name);
|
|
2530
|
+
const values = regularColumns.map((column) => {
|
|
2531
|
+
let value = item[column.name];
|
|
2532
|
+
if (value === undefined && column.default !== undefined) {
|
|
2533
|
+
value = column.default;
|
|
2534
|
+
}
|
|
2535
|
+
return this.serializeValue(value, column);
|
|
2536
|
+
});
|
|
2537
|
+
columnNames.push("__version");
|
|
2538
|
+
const placeholders = regularColumns.map((_, i) => `$${i + 1}`).join(", ");
|
|
2539
|
+
const updateColumns = regularColumns.filter((col) => col.name !== table.primaryKey).map((col) => `"${col.name}" = EXCLUDED."${col.name}"`).join(", ");
|
|
2540
|
+
const sql = `
|
|
2541
|
+
WITH next_version AS (
|
|
2542
|
+
INSERT INTO __arc_version_counters (table_name, last_version)
|
|
2543
|
+
VALUES ($${values.length + 1}, 1)
|
|
2544
|
+
ON CONFLICT(table_name)
|
|
2545
|
+
DO UPDATE SET last_version = __arc_version_counters.last_version + 1
|
|
2546
|
+
RETURNING last_version
|
|
2547
|
+
),
|
|
2548
|
+
upsert AS (
|
|
2549
|
+
INSERT INTO "${table.name}"
|
|
2550
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2551
|
+
VALUES (${placeholders}, (SELECT last_version FROM next_version))
|
|
2552
|
+
ON CONFLICT ("${table.primaryKey}")
|
|
2553
|
+
DO UPDATE SET ${updateColumns}${updateColumns ? ", " : ""}"__version" = (SELECT last_version FROM next_version)
|
|
2554
|
+
RETURNING *
|
|
2555
|
+
)
|
|
2556
|
+
SELECT * FROM upsert
|
|
2557
|
+
`;
|
|
2558
|
+
this.queries.push({
|
|
2559
|
+
sql,
|
|
2560
|
+
params: [...values, store]
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
async commit() {
|
|
2564
|
+
if (this.queries.length === 0) {
|
|
2565
|
+
return Promise.resolve();
|
|
2566
|
+
}
|
|
2567
|
+
try {
|
|
2568
|
+
await this.db.execBatch(this.queries);
|
|
2569
|
+
this.queries = [];
|
|
2570
|
+
} catch (error) {
|
|
2571
|
+
this.queries = [];
|
|
2572
|
+
throw error;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
serializeValue(value, column) {
|
|
2576
|
+
if (value === null || value === undefined)
|
|
2577
|
+
return null;
|
|
2578
|
+
switch (column.type.toLowerCase()) {
|
|
2579
|
+
case "timestamp":
|
|
2580
|
+
case "datetime":
|
|
2581
|
+
if (value instanceof Date) {
|
|
2582
|
+
return value.toISOString();
|
|
2583
|
+
}
|
|
2584
|
+
if (typeof value === "number") {
|
|
2585
|
+
const date3 = value > 10000000000 ? new Date(value) : new Date(value * 1000);
|
|
2586
|
+
return date3.toISOString();
|
|
2587
|
+
}
|
|
2588
|
+
if (typeof value === "string") {
|
|
2589
|
+
const date3 = new Date(value);
|
|
2590
|
+
if (!isNaN(date3.getTime())) {
|
|
2591
|
+
return date3.toISOString();
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return value;
|
|
2595
|
+
case "json":
|
|
2596
|
+
case "jsonb":
|
|
2597
|
+
return JSON.stringify(value);
|
|
2598
|
+
default:
|
|
2599
|
+
if (value instanceof Date) {
|
|
2600
|
+
return value.toISOString();
|
|
2601
|
+
}
|
|
2602
|
+
if (Array.isArray(value) || typeof value === "object") {
|
|
2603
|
+
return JSON.stringify(value);
|
|
2604
|
+
}
|
|
2605
|
+
return value;
|
|
2232
2606
|
}
|
|
2233
|
-
throw new Error("Unknown change type");
|
|
2234
2607
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
class PostgreSQLAdapter {
|
|
2611
|
+
db;
|
|
2612
|
+
context;
|
|
2613
|
+
tables = new Map;
|
|
2614
|
+
tableSchemas = new Map;
|
|
2615
|
+
pendingReinitTables = [];
|
|
2616
|
+
mapType(arcType, storeData) {
|
|
2617
|
+
if (storeData?.databaseType?.postgresql) {
|
|
2618
|
+
return storeData.databaseType.postgresql;
|
|
2619
|
+
}
|
|
2620
|
+
switch (arcType) {
|
|
2621
|
+
case "string":
|
|
2622
|
+
case "stringEnum":
|
|
2623
|
+
return "TEXT";
|
|
2624
|
+
case "id":
|
|
2625
|
+
case "customId":
|
|
2626
|
+
return storeData?.databaseType?.postgresql || "TEXT";
|
|
2627
|
+
case "number":
|
|
2628
|
+
return storeData?.isAutoIncrement ? "SERIAL" : "INTEGER";
|
|
2629
|
+
case "boolean":
|
|
2630
|
+
return "BOOLEAN";
|
|
2631
|
+
case "date":
|
|
2632
|
+
return "TIMESTAMP";
|
|
2633
|
+
case "object":
|
|
2634
|
+
case "array":
|
|
2635
|
+
case "record":
|
|
2636
|
+
return "JSONB";
|
|
2637
|
+
case "blob":
|
|
2638
|
+
return "BYTEA";
|
|
2639
|
+
default:
|
|
2640
|
+
return "TEXT";
|
|
2641
|
+
}
|
|
2241
2642
|
}
|
|
2242
|
-
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2247
|
-
if (event3)
|
|
2248
|
-
events.push(event3);
|
|
2643
|
+
buildConstraints(storeData) {
|
|
2644
|
+
const constraints = [];
|
|
2645
|
+
if (storeData?.isPrimaryKey) {
|
|
2646
|
+
constraints.push("PRIMARY KEY");
|
|
2249
2647
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2648
|
+
if (storeData?.isUnique) {
|
|
2649
|
+
constraints.push("UNIQUE");
|
|
2650
|
+
}
|
|
2651
|
+
if (storeData?.foreignKey) {
|
|
2652
|
+
const { table, column, onDelete, onUpdate } = storeData.foreignKey;
|
|
2653
|
+
let fkConstraint = `REFERENCES ${table}(${column})`;
|
|
2654
|
+
if (onDelete)
|
|
2655
|
+
fkConstraint += ` ON DELETE ${onDelete}`;
|
|
2656
|
+
if (onUpdate)
|
|
2657
|
+
fkConstraint += ` ON UPDATE ${onUpdate}`;
|
|
2658
|
+
constraints.push(fkConstraint);
|
|
2253
2659
|
}
|
|
2660
|
+
return constraints;
|
|
2254
2661
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2662
|
+
generateColumnSQL(columnInfo) {
|
|
2663
|
+
const type = this.mapType(columnInfo.type, columnInfo.storeData);
|
|
2664
|
+
const constraints = [];
|
|
2665
|
+
if (!columnInfo.storeData?.isNullable) {
|
|
2666
|
+
constraints.push("NOT NULL");
|
|
2258
2667
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2668
|
+
constraints.push(...this.buildConstraints(columnInfo.storeData));
|
|
2669
|
+
if (columnInfo.defaultValue !== undefined) {
|
|
2670
|
+
if (type === "UUID" && columnInfo.defaultValue === "auto") {
|
|
2671
|
+
constraints.push("DEFAULT gen_random_uuid()");
|
|
2672
|
+
} else if (typeof columnInfo.defaultValue === "string") {
|
|
2673
|
+
constraints.push(`DEFAULT '${columnInfo.defaultValue}'`);
|
|
2674
|
+
} else {
|
|
2675
|
+
constraints.push(`DEFAULT ${columnInfo.defaultValue}`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
return `"${columnInfo.name}" ${type} ${constraints.join(" ")}`.trim();
|
|
2679
|
+
}
|
|
2680
|
+
generateCreateTableSQL(tableName, columns) {
|
|
2681
|
+
const columnDefinitions = columns.map((col) => this.generateColumnSQL(col));
|
|
2682
|
+
const indexes = columns.filter((col) => col.storeData?.hasIndex && !col.storeData?.isPrimaryKey).map((col) => `CREATE INDEX IF NOT EXISTS idx_${tableName}_${col.name} ON "${tableName}"("${col.name}");`);
|
|
2683
|
+
let sql = `CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
2684
|
+
${columnDefinitions.join(`,
|
|
2685
|
+
`)}
|
|
2686
|
+
)`;
|
|
2687
|
+
if (indexes.length > 0) {
|
|
2688
|
+
sql += `;
|
|
2689
|
+
` + indexes.join(`
|
|
2690
|
+
`);
|
|
2691
|
+
}
|
|
2692
|
+
return sql;
|
|
2262
2693
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2694
|
+
constructor(db, context3) {
|
|
2695
|
+
this.db = db;
|
|
2696
|
+
this.context = context3;
|
|
2697
|
+
this.context.elements.forEach((element3) => {
|
|
2698
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
2699
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
2700
|
+
databaseSchema.tables.forEach((dbTable) => {
|
|
2701
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
2702
|
+
const columns = agnosticSchema.columns.map((columnInfo) => ({
|
|
2703
|
+
name: columnInfo.name,
|
|
2704
|
+
type: this.mapType(columnInfo.type, columnInfo.storeData),
|
|
2705
|
+
constraints: this.buildConstraints(columnInfo.storeData),
|
|
2706
|
+
isNullable: columnInfo.storeData?.isNullable || false,
|
|
2707
|
+
defaultValue: columnInfo.defaultValue,
|
|
2708
|
+
isPrimaryKey: columnInfo.storeData?.isPrimaryKey || false,
|
|
2709
|
+
isAutoIncrement: columnInfo.storeData?.isAutoIncrement || false,
|
|
2710
|
+
isUnique: columnInfo.storeData?.isUnique || false,
|
|
2711
|
+
hasIndex: columnInfo.storeData?.hasIndex || false,
|
|
2712
|
+
foreignKey: columnInfo.storeData?.foreignKey
|
|
2713
|
+
}));
|
|
2714
|
+
this.tableSchemas.set(dbTable.name, columns);
|
|
2715
|
+
const legacyTable = {
|
|
2716
|
+
name: dbTable.name,
|
|
2717
|
+
primaryKey: columns.find((col) => col.isPrimaryKey)?.name || "_id",
|
|
2718
|
+
columns: columns.map((col) => ({
|
|
2719
|
+
name: col.name,
|
|
2720
|
+
type: col.type,
|
|
2721
|
+
isOptional: col.isNullable,
|
|
2722
|
+
default: col.defaultValue
|
|
2723
|
+
}))
|
|
2724
|
+
};
|
|
2725
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2276
2729
|
}
|
|
2277
|
-
async
|
|
2278
|
-
|
|
2730
|
+
async initialize() {
|
|
2731
|
+
await this.createVersionCounterTable();
|
|
2732
|
+
await this.createTableVersionsTable();
|
|
2733
|
+
const processedSchemas = new Set;
|
|
2734
|
+
const processedTables = new Set;
|
|
2735
|
+
for (const element3 of this.context.elements) {
|
|
2736
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
2737
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
2738
|
+
if (processedSchemas.has(databaseSchema)) {
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
processedSchemas.add(databaseSchema);
|
|
2742
|
+
for (const dbTable of databaseSchema.tables) {
|
|
2743
|
+
const tableKey = dbTable.version ? `${dbTable.name}_v${dbTable.version}` : dbTable.name;
|
|
2744
|
+
if (!processedTables.has(tableKey)) {
|
|
2745
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
2746
|
+
let allColumns = [...agnosticSchema.columns];
|
|
2747
|
+
if (dbTable.options?.versioning) {
|
|
2748
|
+
allColumns.push({
|
|
2749
|
+
name: "__version",
|
|
2750
|
+
type: "number",
|
|
2751
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
2752
|
+
defaultValue: 1
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
if (dbTable.options?.softDelete) {
|
|
2756
|
+
allColumns.push({
|
|
2757
|
+
name: "deleted",
|
|
2758
|
+
type: "boolean",
|
|
2759
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
2760
|
+
defaultValue: false
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
2764
|
+
if (dbTable.version && await this.checkVersionedTableExists(dbTable.name, dbTable.version)) {
|
|
2765
|
+
console.log(`Versioned table ${physicalTableName} already exists, skipping creation`);
|
|
2766
|
+
} else {
|
|
2767
|
+
await this.createTableIfNotExistsNew(physicalTableName, allColumns);
|
|
2768
|
+
if (dbTable.version) {
|
|
2769
|
+
await this.registerTableVersion(dbTable.name, dbTable.version, physicalTableName);
|
|
2770
|
+
if (databaseSchema.reinitTable) {
|
|
2771
|
+
this.pendingReinitTables.push({
|
|
2772
|
+
tableName: physicalTableName,
|
|
2773
|
+
reinitFn: databaseSchema.reinitTable
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
const legacyTable = {
|
|
2779
|
+
name: physicalTableName,
|
|
2780
|
+
primaryKey: allColumns.find((col) => col.storeData?.isPrimaryKey)?.name || "_id",
|
|
2781
|
+
columns: allColumns.map((col) => ({
|
|
2782
|
+
name: col.name,
|
|
2783
|
+
type: this.mapType(col.type, col.storeData),
|
|
2784
|
+
isOptional: col.storeData?.isNullable || false,
|
|
2785
|
+
default: col.defaultValue
|
|
2786
|
+
}))
|
|
2787
|
+
};
|
|
2788
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2789
|
+
processedTables.add(tableKey);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2279
2794
|
}
|
|
2280
|
-
async
|
|
2281
|
-
|
|
2795
|
+
async createTableIfNotExistsNew(tableName, columns) {
|
|
2796
|
+
const createTableSQL = this.generateCreateTableSQL(tableName, columns);
|
|
2797
|
+
await this.db.exec(createTableSQL);
|
|
2282
2798
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2799
|
+
async createVersionCounterTable() {
|
|
2800
|
+
const sql = `
|
|
2801
|
+
CREATE TABLE IF NOT EXISTS __arc_version_counters (
|
|
2802
|
+
table_name TEXT PRIMARY KEY,
|
|
2803
|
+
last_version BIGINT NOT NULL DEFAULT 0
|
|
2804
|
+
)
|
|
2805
|
+
`;
|
|
2806
|
+
await this.db.exec(sql);
|
|
2288
2807
|
}
|
|
2289
|
-
async
|
|
2290
|
-
|
|
2808
|
+
async createTableVersionsTable() {
|
|
2809
|
+
const sql = `
|
|
2810
|
+
CREATE TABLE IF NOT EXISTS __arc_table_versions (
|
|
2811
|
+
table_name TEXT NOT NULL,
|
|
2812
|
+
version INTEGER NOT NULL,
|
|
2813
|
+
physical_table_name TEXT NOT NULL,
|
|
2814
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
2815
|
+
is_active BOOLEAN DEFAULT FALSE,
|
|
2816
|
+
PRIMARY KEY (table_name, version)
|
|
2817
|
+
)
|
|
2818
|
+
`;
|
|
2819
|
+
await this.db.exec(sql);
|
|
2291
2820
|
}
|
|
2292
|
-
|
|
2293
|
-
return
|
|
2821
|
+
getPhysicalTableName(logicalName, version) {
|
|
2822
|
+
return version ? `${logicalName}_v${version}` : logicalName;
|
|
2294
2823
|
}
|
|
2295
|
-
async
|
|
2296
|
-
await
|
|
2297
|
-
|
|
2298
|
-
this.rtcAdapter.commitChanges(changes)
|
|
2299
|
-
]);
|
|
2824
|
+
async checkVersionedTableExists(logicalName, version) {
|
|
2825
|
+
const result = await this.db.exec("SELECT COUNT(*) as count FROM __arc_table_versions WHERE table_name = $1 AND version = $2", [logicalName, version]);
|
|
2826
|
+
return result[0]?.count > 0;
|
|
2300
2827
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2828
|
+
async registerTableVersion(logicalName, version, physicalName) {
|
|
2829
|
+
await this.db.exec("INSERT INTO __arc_table_versions (table_name, version, physical_table_name, is_active) VALUES ($1, $2, $3, $4)", [logicalName, version, physicalName, true]);
|
|
2303
2830
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2831
|
+
hasVersioning(tableName) {
|
|
2832
|
+
const table = this.tables.get(tableName);
|
|
2833
|
+
if (!table)
|
|
2834
|
+
return false;
|
|
2835
|
+
return table.columns.some((col) => col.name === "__version");
|
|
2836
|
+
}
|
|
2837
|
+
hasSoftDelete(tableName) {
|
|
2838
|
+
const table = this.tables.get(tableName);
|
|
2839
|
+
if (!table)
|
|
2840
|
+
return false;
|
|
2841
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2842
|
+
}
|
|
2843
|
+
async executeReinitTables(dataStorage) {
|
|
2844
|
+
for (const { tableName, reinitFn } of this.pendingReinitTables) {
|
|
2845
|
+
try {
|
|
2846
|
+
await reinitFn(tableName, dataStorage);
|
|
2847
|
+
console.log(`Successfully reinitialized table: ${tableName}`);
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
console.error(`Failed to reinitialize table ${tableName}:`, error);
|
|
2850
|
+
throw error;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
this.pendingReinitTables = [];
|
|
2854
|
+
}
|
|
2855
|
+
readWriteTransaction(stores) {
|
|
2856
|
+
return new PostgreSQLReadWriteTransaction(this.db, this.tables, this);
|
|
2857
|
+
}
|
|
2858
|
+
readTransaction(stores) {
|
|
2859
|
+
return new PostgreSQLReadTransaction(this.db, this.tables, this);
|
|
2306
2860
|
}
|
|
2307
2861
|
}
|
|
2862
|
+
var createPostgreSQLAdapterFactory = (db) => {
|
|
2863
|
+
return async (context3) => {
|
|
2864
|
+
const adapter = new PostgreSQLAdapter(db, context3);
|
|
2865
|
+
await adapter.initialize();
|
|
2866
|
+
return adapter;
|
|
2867
|
+
};
|
|
2868
|
+
};
|
|
2308
2869
|
// db/sqliteAdapter.ts
|
|
2309
2870
|
class SQLiteReadTransaction {
|
|
2310
2871
|
db;
|
|
2311
2872
|
tables;
|
|
2312
|
-
|
|
2873
|
+
adapter;
|
|
2874
|
+
constructor(db, tables, adapter) {
|
|
2313
2875
|
this.db = db;
|
|
2314
2876
|
this.tables = tables;
|
|
2877
|
+
this.adapter = adapter;
|
|
2878
|
+
}
|
|
2879
|
+
hasSoftDelete(tableName) {
|
|
2880
|
+
if (this.adapter) {
|
|
2881
|
+
return this.adapter.hasSoftDelete(tableName);
|
|
2882
|
+
}
|
|
2883
|
+
const table = this.tables.get(tableName);
|
|
2884
|
+
if (!table)
|
|
2885
|
+
return false;
|
|
2886
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2315
2887
|
}
|
|
2316
2888
|
deserializeValue(value, column) {
|
|
2317
2889
|
if (value === null || value === undefined)
|
|
2318
2890
|
return null;
|
|
2319
2891
|
switch (column.type.toLowerCase()) {
|
|
2320
2892
|
case "json":
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
return
|
|
2893
|
+
if (typeof value === "string") {
|
|
2894
|
+
try {
|
|
2895
|
+
return JSON.parse(value);
|
|
2896
|
+
} catch {
|
|
2897
|
+
return value;
|
|
2326
2898
|
}
|
|
2327
|
-
return value;
|
|
2328
|
-
} catch {
|
|
2329
|
-
return value;
|
|
2330
2899
|
}
|
|
2331
|
-
|
|
2900
|
+
return value;
|
|
2901
|
+
case "text":
|
|
2902
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
2903
|
+
try {
|
|
2904
|
+
const parsed = JSON.parse(value);
|
|
2905
|
+
if (typeof parsed === "object" || Array.isArray(parsed)) {
|
|
2906
|
+
return parsed;
|
|
2907
|
+
}
|
|
2908
|
+
} catch {}
|
|
2909
|
+
}
|
|
2910
|
+
return value;
|
|
2332
2911
|
case "datetime":
|
|
2333
2912
|
case "timestamp":
|
|
2334
2913
|
return new Date(value);
|
|
@@ -2347,12 +2926,18 @@ class SQLiteReadTransaction {
|
|
|
2347
2926
|
getId(store, id3) {
|
|
2348
2927
|
return id3;
|
|
2349
2928
|
}
|
|
2350
|
-
buildWhereClause(where) {
|
|
2929
|
+
buildWhereClause(where, tableName) {
|
|
2930
|
+
const conditions = [];
|
|
2931
|
+
const params = [];
|
|
2932
|
+
if (tableName && this.hasSoftDelete(tableName)) {
|
|
2933
|
+
conditions.push("deleted = 0");
|
|
2934
|
+
}
|
|
2351
2935
|
if (!where) {
|
|
2352
|
-
return {
|
|
2936
|
+
return {
|
|
2937
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
2938
|
+
params
|
|
2939
|
+
};
|
|
2353
2940
|
}
|
|
2354
|
-
const conditions = ["deleted != 1"];
|
|
2355
|
-
const params = [];
|
|
2356
2941
|
Object.entries(where).forEach(([key, value]) => {
|
|
2357
2942
|
if (typeof value === "object" && value !== null) {
|
|
2358
2943
|
Object.entries(value).forEach(([operator, operand]) => {
|
|
@@ -2409,7 +2994,7 @@ class SQLiteReadTransaction {
|
|
|
2409
2994
|
}
|
|
2410
2995
|
async find(store, options) {
|
|
2411
2996
|
const { where, limit, offset, orderBy } = options || {};
|
|
2412
|
-
const whereClause = this.buildWhereClause(where);
|
|
2997
|
+
const whereClause = this.buildWhereClause(where, store);
|
|
2413
2998
|
const orderByClause = this.buildOrderByClause(orderBy);
|
|
2414
2999
|
const table = this.tables.get(store);
|
|
2415
3000
|
if (!table) {
|
|
@@ -2429,6 +3014,11 @@ class SQLiteReadTransaction {
|
|
|
2429
3014
|
}
|
|
2430
3015
|
|
|
2431
3016
|
class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
3017
|
+
adapter;
|
|
3018
|
+
constructor(db, tables, adapter) {
|
|
3019
|
+
super(db, tables, adapter);
|
|
3020
|
+
this.adapter = adapter;
|
|
3021
|
+
}
|
|
2432
3022
|
async remove(store, id3) {
|
|
2433
3023
|
const table = this.tables.get(store);
|
|
2434
3024
|
if (!table) {
|
|
@@ -2442,7 +3032,14 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2442
3032
|
if (!table) {
|
|
2443
3033
|
throw new Error(`Store ${store} not found`);
|
|
2444
3034
|
}
|
|
2445
|
-
const
|
|
3035
|
+
const hasVersioning = this.adapter.hasVersioning(store);
|
|
3036
|
+
if (hasVersioning) {
|
|
3037
|
+
await this.setWithVersioning(store, item, table);
|
|
3038
|
+
} else {
|
|
3039
|
+
await this.setWithoutVersioning(store, item, table);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
async setWithoutVersioning(store, item, table) {
|
|
2446
3043
|
const columnNames = table.columns.map((col) => col.name);
|
|
2447
3044
|
const values = table.columns.map((column) => {
|
|
2448
3045
|
let value = item[column.name];
|
|
@@ -2459,6 +3056,32 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2459
3056
|
`;
|
|
2460
3057
|
await this.db.exec(sql, values);
|
|
2461
3058
|
}
|
|
3059
|
+
async setWithVersioning(store, item, table) {
|
|
3060
|
+
const regularColumns = table.columns.filter((col) => col.name !== "__version");
|
|
3061
|
+
const columnNames = regularColumns.map((col) => col.name);
|
|
3062
|
+
const values = regularColumns.map((column) => {
|
|
3063
|
+
let value = item[column.name];
|
|
3064
|
+
if (value === undefined && column.default !== undefined) {
|
|
3065
|
+
value = column.default;
|
|
3066
|
+
}
|
|
3067
|
+
return this.serializeValue(value, column);
|
|
3068
|
+
});
|
|
3069
|
+
columnNames.push("__version");
|
|
3070
|
+
const placeholders = regularColumns.map(() => "?").join(", ");
|
|
3071
|
+
const sql = `
|
|
3072
|
+
WITH next_version AS (
|
|
3073
|
+
INSERT INTO __arc_version_counters (table_name, last_version)
|
|
3074
|
+
VALUES (?, 1)
|
|
3075
|
+
ON CONFLICT(table_name)
|
|
3076
|
+
DO UPDATE SET last_version = last_version + 1
|
|
3077
|
+
RETURNING last_version
|
|
3078
|
+
)
|
|
3079
|
+
INSERT OR REPLACE INTO ${table.name}
|
|
3080
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
3081
|
+
VALUES (${placeholders}, (SELECT last_version FROM next_version))
|
|
3082
|
+
`;
|
|
3083
|
+
await this.db.exec(sql, [...values, store]);
|
|
3084
|
+
}
|
|
2462
3085
|
async commit() {
|
|
2463
3086
|
return Promise.resolve();
|
|
2464
3087
|
}
|
|
@@ -2466,6 +3089,22 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2466
3089
|
if (value === null || value === undefined)
|
|
2467
3090
|
return null;
|
|
2468
3091
|
switch (column.type.toLowerCase()) {
|
|
3092
|
+
case "timestamp":
|
|
3093
|
+
case "datetime":
|
|
3094
|
+
if (value instanceof Date) {
|
|
3095
|
+
return value.toISOString();
|
|
3096
|
+
}
|
|
3097
|
+
if (typeof value === "number") {
|
|
3098
|
+
const date3 = value > 10000000000 ? new Date(value) : new Date(value * 1000);
|
|
3099
|
+
return date3.toISOString();
|
|
3100
|
+
}
|
|
3101
|
+
if (typeof value === "string") {
|
|
3102
|
+
const date3 = new Date(value);
|
|
3103
|
+
if (!isNaN(date3.getTime())) {
|
|
3104
|
+
return date3.toISOString();
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
return value;
|
|
2469
3108
|
case "json":
|
|
2470
3109
|
return JSON.stringify(value);
|
|
2471
3110
|
default:
|
|
@@ -2484,58 +3123,238 @@ class SQLiteAdapter {
|
|
|
2484
3123
|
db;
|
|
2485
3124
|
context;
|
|
2486
3125
|
tables = new Map;
|
|
3126
|
+
tableSchemas = new Map;
|
|
3127
|
+
pendingReinitTables = [];
|
|
3128
|
+
mapType(arcType, storeData) {
|
|
3129
|
+
if (storeData?.databaseType?.sqlite) {
|
|
3130
|
+
return storeData.databaseType.sqlite;
|
|
3131
|
+
}
|
|
3132
|
+
switch (arcType) {
|
|
3133
|
+
case "string":
|
|
3134
|
+
case "id":
|
|
3135
|
+
case "customId":
|
|
3136
|
+
case "stringEnum":
|
|
3137
|
+
return "TEXT";
|
|
3138
|
+
case "number":
|
|
3139
|
+
return "INTEGER";
|
|
3140
|
+
case "boolean":
|
|
3141
|
+
return "INTEGER";
|
|
3142
|
+
case "date":
|
|
3143
|
+
return "TIMESTAMP";
|
|
3144
|
+
case "object":
|
|
3145
|
+
case "array":
|
|
3146
|
+
case "record":
|
|
3147
|
+
return "JSON";
|
|
3148
|
+
case "blob":
|
|
3149
|
+
return "BLOB";
|
|
3150
|
+
default:
|
|
3151
|
+
return "TEXT";
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
buildConstraints(storeData) {
|
|
3155
|
+
const constraints = [];
|
|
3156
|
+
if (storeData?.isPrimaryKey) {
|
|
3157
|
+
constraints.push("PRIMARY KEY");
|
|
3158
|
+
}
|
|
3159
|
+
if (storeData?.isAutoIncrement) {
|
|
3160
|
+
constraints.push("AUTOINCREMENT");
|
|
3161
|
+
}
|
|
3162
|
+
if (storeData?.isUnique) {
|
|
3163
|
+
constraints.push("UNIQUE");
|
|
3164
|
+
}
|
|
3165
|
+
if (storeData?.foreignKey) {
|
|
3166
|
+
const { table, column, onDelete, onUpdate } = storeData.foreignKey;
|
|
3167
|
+
let fkConstraint = `REFERENCES ${table}(${column})`;
|
|
3168
|
+
if (onDelete)
|
|
3169
|
+
fkConstraint += ` ON DELETE ${onDelete}`;
|
|
3170
|
+
if (onUpdate)
|
|
3171
|
+
fkConstraint += ` ON UPDATE ${onUpdate}`;
|
|
3172
|
+
constraints.push(fkConstraint);
|
|
3173
|
+
}
|
|
3174
|
+
return constraints;
|
|
3175
|
+
}
|
|
3176
|
+
generateColumnSQL(columnInfo) {
|
|
3177
|
+
const type = this.mapType(columnInfo.type, columnInfo.storeData);
|
|
3178
|
+
const constraints = [];
|
|
3179
|
+
if (!columnInfo.storeData?.isNullable) {
|
|
3180
|
+
constraints.push("NOT NULL");
|
|
3181
|
+
}
|
|
3182
|
+
constraints.push(...this.buildConstraints(columnInfo.storeData));
|
|
3183
|
+
if (columnInfo.defaultValue !== undefined) {
|
|
3184
|
+
constraints.push(`DEFAULT ${JSON.stringify(columnInfo.defaultValue)}`);
|
|
3185
|
+
}
|
|
3186
|
+
return `"${columnInfo.name}" ${type} ${constraints.join(" ")}`.trim();
|
|
3187
|
+
}
|
|
3188
|
+
generateCreateTableSQL(tableName, columns) {
|
|
3189
|
+
const columnDefinitions = columns.map((col) => this.generateColumnSQL(col));
|
|
3190
|
+
const indexes = columns.filter((col) => col.storeData?.hasIndex && !col.storeData?.isPrimaryKey).map((col) => `CREATE INDEX IF NOT EXISTS idx_${tableName}_${col.name} ON ${tableName}(${col.name});`);
|
|
3191
|
+
let sql = `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3192
|
+
${columnDefinitions.join(`,
|
|
3193
|
+
`)}
|
|
3194
|
+
)`;
|
|
3195
|
+
if (indexes.length > 0) {
|
|
3196
|
+
sql += `;
|
|
3197
|
+
` + indexes.join(`
|
|
3198
|
+
`);
|
|
3199
|
+
}
|
|
3200
|
+
return sql;
|
|
3201
|
+
}
|
|
2487
3202
|
constructor(db, context3) {
|
|
2488
3203
|
this.db = db;
|
|
2489
3204
|
this.context = context3;
|
|
2490
3205
|
this.context.elements.forEach((element3) => {
|
|
2491
|
-
if ("
|
|
2492
|
-
element3.
|
|
2493
|
-
|
|
3206
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
3207
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
3208
|
+
databaseSchema.tables.forEach((dbTable) => {
|
|
3209
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
3210
|
+
const columns = agnosticSchema.columns.map((columnInfo) => ({
|
|
3211
|
+
name: columnInfo.name,
|
|
3212
|
+
type: this.mapType(columnInfo.type, columnInfo.storeData),
|
|
3213
|
+
constraints: this.buildConstraints(columnInfo.storeData),
|
|
3214
|
+
isNullable: columnInfo.storeData?.isNullable || false,
|
|
3215
|
+
defaultValue: columnInfo.defaultValue,
|
|
3216
|
+
isPrimaryKey: columnInfo.storeData?.isPrimaryKey || false,
|
|
3217
|
+
isAutoIncrement: columnInfo.storeData?.isAutoIncrement || false,
|
|
3218
|
+
isUnique: columnInfo.storeData?.isUnique || false,
|
|
3219
|
+
hasIndex: columnInfo.storeData?.hasIndex || false,
|
|
3220
|
+
foreignKey: columnInfo.storeData?.foreignKey
|
|
3221
|
+
}));
|
|
3222
|
+
this.tableSchemas.set(dbTable.name, columns);
|
|
3223
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
3224
|
+
const legacyTable = {
|
|
3225
|
+
name: physicalTableName,
|
|
3226
|
+
primaryKey: columns.find((col) => col.isPrimaryKey)?.name || "_id",
|
|
3227
|
+
columns: columns.map((col) => ({
|
|
3228
|
+
name: col.name,
|
|
3229
|
+
type: col.type,
|
|
3230
|
+
isOptional: col.isNullable,
|
|
3231
|
+
default: col.defaultValue
|
|
3232
|
+
}))
|
|
3233
|
+
};
|
|
3234
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2494
3235
|
});
|
|
2495
3236
|
}
|
|
2496
3237
|
});
|
|
2497
3238
|
}
|
|
2498
3239
|
async initialize() {
|
|
2499
|
-
|
|
3240
|
+
await this.createVersionCounterTable();
|
|
3241
|
+
await this.createTableVersionsTable();
|
|
3242
|
+
const processedSchemas = new Set;
|
|
3243
|
+
const processedTables = new Set;
|
|
2500
3244
|
for (const element3 of this.context.elements) {
|
|
2501
|
-
if ("
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
3245
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
3246
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
3247
|
+
if (processedSchemas.has(databaseSchema)) {
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
processedSchemas.add(databaseSchema);
|
|
3251
|
+
for (const dbTable of databaseSchema.tables) {
|
|
3252
|
+
const tableKey = dbTable.version ? `${dbTable.name}_v${dbTable.version}` : dbTable.name;
|
|
3253
|
+
if (!processedTables.has(tableKey)) {
|
|
3254
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
3255
|
+
let allColumns = [...agnosticSchema.columns];
|
|
3256
|
+
if (dbTable.options?.versioning) {
|
|
3257
|
+
allColumns.push({
|
|
3258
|
+
name: "__version",
|
|
3259
|
+
type: "number",
|
|
3260
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
3261
|
+
defaultValue: 1
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
if (dbTable.options?.softDelete) {
|
|
3265
|
+
allColumns.push({
|
|
3266
|
+
name: "deleted",
|
|
3267
|
+
type: "boolean",
|
|
3268
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
3269
|
+
defaultValue: false
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
3273
|
+
if (dbTable.version && await this.checkVersionedTableExists(dbTable.name, dbTable.version)) {
|
|
3274
|
+
console.log(`Versioned table ${physicalTableName} already exists, skipping creation`);
|
|
3275
|
+
} else {
|
|
3276
|
+
await this.createTableIfNotExistsNew(physicalTableName, allColumns);
|
|
3277
|
+
if (dbTable.version) {
|
|
3278
|
+
await this.registerTableVersion(dbTable.name, dbTable.version, physicalTableName);
|
|
3279
|
+
if (databaseSchema.reinitTable) {
|
|
3280
|
+
this.pendingReinitTables.push({
|
|
3281
|
+
tableName: physicalTableName,
|
|
3282
|
+
reinitFn: databaseSchema.reinitTable
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
processedTables.add(tableKey);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
2509
3290
|
}
|
|
2510
3291
|
}
|
|
2511
3292
|
}
|
|
2512
|
-
async
|
|
2513
|
-
const
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
if (column.name === table.primaryKey) {
|
|
2522
|
-
constraints.push("PRIMARY KEY");
|
|
2523
|
-
}
|
|
2524
|
-
return `"${column.name}" ${column.type} ${constraints.join(" ")}`.trim();
|
|
2525
|
-
});
|
|
2526
|
-
const createTableSQL = `
|
|
2527
|
-
CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
2528
|
-
${columns.join(`,
|
|
2529
|
-
`)}
|
|
3293
|
+
async createTableIfNotExistsNew(tableName, columns) {
|
|
3294
|
+
const createTableSQL = this.generateCreateTableSQL(tableName, columns);
|
|
3295
|
+
await this.db.exec(createTableSQL);
|
|
3296
|
+
}
|
|
3297
|
+
async createVersionCounterTable() {
|
|
3298
|
+
const sql = `
|
|
3299
|
+
CREATE TABLE IF NOT EXISTS __arc_version_counters (
|
|
3300
|
+
table_name TEXT PRIMARY KEY,
|
|
3301
|
+
last_version INTEGER NOT NULL DEFAULT 0
|
|
2530
3302
|
)
|
|
2531
3303
|
`;
|
|
2532
|
-
await this.db.exec(
|
|
3304
|
+
await this.db.exec(sql);
|
|
3305
|
+
}
|
|
3306
|
+
async createTableVersionsTable() {
|
|
3307
|
+
const sql = `
|
|
3308
|
+
CREATE TABLE IF NOT EXISTS __arc_table_versions (
|
|
3309
|
+
table_name TEXT NOT NULL,
|
|
3310
|
+
version INTEGER NOT NULL,
|
|
3311
|
+
physical_table_name TEXT NOT NULL,
|
|
3312
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
3313
|
+
is_active INTEGER DEFAULT 0,
|
|
3314
|
+
PRIMARY KEY (table_name, version)
|
|
3315
|
+
)
|
|
3316
|
+
`;
|
|
3317
|
+
await this.db.exec(sql);
|
|
3318
|
+
}
|
|
3319
|
+
getPhysicalTableName(logicalName, version) {
|
|
3320
|
+
return version ? `${logicalName}_v${version}` : logicalName;
|
|
3321
|
+
}
|
|
3322
|
+
async checkVersionedTableExists(logicalName, version) {
|
|
3323
|
+
const result = await this.db.exec("SELECT COUNT(*) as count FROM __arc_table_versions WHERE table_name = ? AND version = ?", [logicalName, version]);
|
|
3324
|
+
return result[0]?.count > 0;
|
|
3325
|
+
}
|
|
3326
|
+
async registerTableVersion(logicalName, version, physicalName) {
|
|
3327
|
+
await this.db.exec("INSERT INTO __arc_table_versions (table_name, version, physical_table_name, is_active) VALUES (?, ?, ?, ?)", [logicalName, version, physicalName, 1]);
|
|
3328
|
+
}
|
|
3329
|
+
hasVersioning(tableName) {
|
|
3330
|
+
const table = this.tables.get(tableName);
|
|
3331
|
+
if (!table)
|
|
3332
|
+
return false;
|
|
3333
|
+
return table.columns.some((col) => col.name === "__version");
|
|
3334
|
+
}
|
|
3335
|
+
hasSoftDelete(tableName) {
|
|
3336
|
+
const table = this.tables.get(tableName);
|
|
3337
|
+
if (!table)
|
|
3338
|
+
return false;
|
|
3339
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
3340
|
+
}
|
|
3341
|
+
async executeReinitTables(dataStorage) {
|
|
3342
|
+
for (const { tableName, reinitFn } of this.pendingReinitTables) {
|
|
3343
|
+
try {
|
|
3344
|
+
await reinitFn(tableName, dataStorage);
|
|
3345
|
+
console.log(`Successfully reinitialized table: ${tableName}`);
|
|
3346
|
+
} catch (error) {
|
|
3347
|
+
console.error(`Failed to reinitialize table ${tableName}:`, error);
|
|
3348
|
+
throw error;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
this.pendingReinitTables = [];
|
|
2533
3352
|
}
|
|
2534
3353
|
readWriteTransaction(stores) {
|
|
2535
|
-
return new SQLiteReadWriteTransaction(this.db, this.tables);
|
|
3354
|
+
return new SQLiteReadWriteTransaction(this.db, this.tables, this);
|
|
2536
3355
|
}
|
|
2537
3356
|
readTransaction(stores) {
|
|
2538
|
-
return new SQLiteReadTransaction(this.db, this.tables);
|
|
3357
|
+
return new SQLiteReadTransaction(this.db, this.tables, this);
|
|
2539
3358
|
}
|
|
2540
3359
|
}
|
|
2541
3360
|
var createSQLiteAdapterFactory = (db) => {
|
|
@@ -2599,17 +3418,17 @@ class ArcListener extends ArcContextElement {
|
|
|
2599
3418
|
return (e) => {
|
|
2600
3419
|
const element4 = this._elements.find((element5) => element5.name === e.name);
|
|
2601
3420
|
if (!element4) {
|
|
2602
|
-
throw new Error(`Element "${String(name)}" not found in listener "${this.name}"`);
|
|
3421
|
+
throw new Error(`Element "${String(e.name)}" not found in listener "${this.name}"`);
|
|
2603
3422
|
}
|
|
2604
3423
|
if (!element4.commandContext) {
|
|
2605
|
-
throw new Error(`Element "${String(name)}" does not have a command context`);
|
|
3424
|
+
throw new Error(`Element "${String(e.name)}" does not have a command context`);
|
|
2606
3425
|
}
|
|
2607
3426
|
return element4.commandContext(dataStorage, publishEvent, authContext);
|
|
2608
3427
|
};
|
|
2609
3428
|
}
|
|
2610
3429
|
const element3 = this._elements.find((element4) => element4.name === name);
|
|
2611
3430
|
if (!element3) {
|
|
2612
|
-
throw new Error(`Element "${
|
|
3431
|
+
throw new Error(`Element "${element3}" not found in listener "${this.name}"`);
|
|
2613
3432
|
}
|
|
2614
3433
|
if (!element3.commandContext) {
|
|
2615
3434
|
throw new Error(`Element "${String(name)}" does not have a command context`);
|
|
@@ -2673,14 +3492,14 @@ class EventPublisher {
|
|
|
2673
3492
|
async runAsyncListeners() {
|
|
2674
3493
|
const allAsyncTasks = this.asyncEvents.flatMap(({ event: event3, listeners, authContext }) => listeners.map(async (listener3) => {
|
|
2675
3494
|
const listenerFork = this.dataStorage.fork();
|
|
3495
|
+
const childPublisher = new EventPublisher(this.context, this.dataStorage, authContext);
|
|
2676
3496
|
const asyncPublishEvent = async (childEvent) => {
|
|
2677
|
-
const childPublisher = new EventPublisher(this.context, listenerFork, authContext);
|
|
2678
3497
|
await childPublisher.publishEvent(childEvent, listenerFork);
|
|
2679
|
-
await childPublisher.runAsyncListeners();
|
|
2680
3498
|
};
|
|
2681
3499
|
try {
|
|
2682
3500
|
await listener3(event3, listenerFork, asyncPublishEvent);
|
|
2683
3501
|
await listenerFork.merge();
|
|
3502
|
+
childPublisher.runAsyncListeners();
|
|
2684
3503
|
} catch (error) {
|
|
2685
3504
|
console.error("Async listener failed:", error);
|
|
2686
3505
|
}
|
|
@@ -3154,6 +3973,22 @@ class RTCClient {
|
|
|
3154
3973
|
var rtcClientFactory = (token) => (storage) => {
|
|
3155
3974
|
return new RTCClient(storage, token);
|
|
3156
3975
|
};
|
|
3976
|
+
// context/serializable-query.ts
|
|
3977
|
+
class ArcSerializableQuery extends ArcQuery {
|
|
3978
|
+
params;
|
|
3979
|
+
static key;
|
|
3980
|
+
constructor(params) {
|
|
3981
|
+
super();
|
|
3982
|
+
this.params = params;
|
|
3983
|
+
}
|
|
3984
|
+
serialize() {
|
|
3985
|
+
return {
|
|
3986
|
+
key: this.constructor.key,
|
|
3987
|
+
params: this.params
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3157
3992
|
// view/queries/abstract-view-query.ts
|
|
3158
3993
|
class ArcViewQuery extends ArcSerializableQuery {
|
|
3159
3994
|
view;
|
|
@@ -3457,6 +4292,7 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3457
4292
|
_elements;
|
|
3458
4293
|
_handler;
|
|
3459
4294
|
_isAsync = false;
|
|
4295
|
+
_version;
|
|
3460
4296
|
restrictions;
|
|
3461
4297
|
constructor(name, id3, schema) {
|
|
3462
4298
|
super();
|
|
@@ -3464,8 +4300,84 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3464
4300
|
this.id = id3;
|
|
3465
4301
|
this.schema = schema;
|
|
3466
4302
|
}
|
|
3467
|
-
|
|
3468
|
-
|
|
4303
|
+
databaseStoreSchema = () => {
|
|
4304
|
+
const systemFields = {
|
|
4305
|
+
_id: string().primaryKey()
|
|
4306
|
+
};
|
|
4307
|
+
const completeSchema = this.schema.merge(systemFields);
|
|
4308
|
+
return {
|
|
4309
|
+
tables: [
|
|
4310
|
+
{
|
|
4311
|
+
name: this.name,
|
|
4312
|
+
schema: completeSchema,
|
|
4313
|
+
version: this._version,
|
|
4314
|
+
options: {
|
|
4315
|
+
softDelete: true,
|
|
4316
|
+
versioning: true
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
],
|
|
4320
|
+
reinitTable: async (tableName, dataStorage) => {
|
|
4321
|
+
console.log(`Reinitializing view table: ${tableName} for view: ${this.name}`);
|
|
4322
|
+
if (this._handler && this._elements) {
|
|
4323
|
+
try {
|
|
4324
|
+
const eventStore = dataStorage.getStore("events");
|
|
4325
|
+
const viewStore = dataStorage.getStore(this.name);
|
|
4326
|
+
const events = await eventStore.find({
|
|
4327
|
+
where: {
|
|
4328
|
+
type: {
|
|
4329
|
+
$in: this._elements.filter((element3) => element3 instanceof ArcEvent).map((element3) => element3.name)
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
});
|
|
4333
|
+
console.log(`Found ${events.length} events to replay for view ${this.name}`);
|
|
4334
|
+
const reinitContext = {
|
|
4335
|
+
set: async (id3, data) => {
|
|
4336
|
+
const parsed = this.schema.parse(data);
|
|
4337
|
+
const body = {
|
|
4338
|
+
_id: id3,
|
|
4339
|
+
lastUpdate: new Date().toISOString(),
|
|
4340
|
+
...parsed
|
|
4341
|
+
};
|
|
4342
|
+
await viewStore.set(body);
|
|
4343
|
+
},
|
|
4344
|
+
modify: async (id3, data) => {
|
|
4345
|
+
await viewStore.modify(id3, {
|
|
4346
|
+
...data,
|
|
4347
|
+
lastUpdate: new Date().toISOString()
|
|
4348
|
+
});
|
|
4349
|
+
},
|
|
4350
|
+
remove: async (id3) => {
|
|
4351
|
+
await viewStore.remove(id3);
|
|
4352
|
+
},
|
|
4353
|
+
find: async (options) => {
|
|
4354
|
+
return viewStore.find(options);
|
|
4355
|
+
},
|
|
4356
|
+
findOne: async (where) => {
|
|
4357
|
+
const result = await viewStore.find({ where, limit: 1 });
|
|
4358
|
+
return result[0];
|
|
4359
|
+
},
|
|
4360
|
+
$auth: {}
|
|
4361
|
+
};
|
|
4362
|
+
for (const event3 of events) {
|
|
4363
|
+
if (this._handler && this._handler[event3.type]) {
|
|
4364
|
+
try {
|
|
4365
|
+
await this._handler[event3.type](reinitContext, event3);
|
|
4366
|
+
} catch (error) {
|
|
4367
|
+
console.error(`Error processing event ${event3.type} for view ${this.name}:`, error);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
console.log(`Successfully reinitialized view ${this.name} with ${events.length} events`);
|
|
4372
|
+
} catch (error) {
|
|
4373
|
+
console.error(`Failed to reinitialize view ${this.name}:`, error);
|
|
4374
|
+
throw error;
|
|
4375
|
+
}
|
|
4376
|
+
} else {
|
|
4377
|
+
console.log(`No handlers defined for view ${this.name}, skipping event replay`);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
3469
4381
|
};
|
|
3470
4382
|
auth(restrictionsFn) {
|
|
3471
4383
|
const clone = this.clone();
|
|
@@ -3487,6 +4399,11 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3487
4399
|
clone._isAsync = true;
|
|
3488
4400
|
return clone;
|
|
3489
4401
|
}
|
|
4402
|
+
version(version) {
|
|
4403
|
+
const clone = this.clone();
|
|
4404
|
+
clone._version = version;
|
|
4405
|
+
return clone;
|
|
4406
|
+
}
|
|
3490
4407
|
handle(handler) {
|
|
3491
4408
|
const clone = this.clone();
|
|
3492
4409
|
clone._handler = handler;
|
|
@@ -3607,6 +4524,7 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3607
4524
|
clone._elements = this._elements;
|
|
3608
4525
|
clone._handler = this._handler;
|
|
3609
4526
|
clone._isAsync = this._isAsync;
|
|
4527
|
+
clone._version = this._version;
|
|
3610
4528
|
clone.restrictions = this.restrictions;
|
|
3611
4529
|
return clone;
|
|
3612
4530
|
}
|
|
@@ -3634,14 +4552,13 @@ export {
|
|
|
3634
4552
|
date,
|
|
3635
4553
|
customId,
|
|
3636
4554
|
createSQLiteAdapterFactory,
|
|
4555
|
+
createPostgreSQLAdapterFactory,
|
|
3637
4556
|
contextMerge,
|
|
3638
4557
|
context,
|
|
3639
4558
|
command,
|
|
3640
|
-
collection,
|
|
3641
4559
|
boolean,
|
|
3642
4560
|
blob,
|
|
3643
4561
|
array,
|
|
3644
|
-
arcObjectToStoreSchema,
|
|
3645
4562
|
any,
|
|
3646
4563
|
StoreState,
|
|
3647
4564
|
SQLiteAdapter,
|
|
@@ -3649,6 +4566,7 @@ export {
|
|
|
3649
4566
|
QueryViewResult,
|
|
3650
4567
|
QueryCache,
|
|
3651
4568
|
QueryBuilderContext,
|
|
4569
|
+
PostgreSQLAdapter,
|
|
3652
4570
|
ModelBase,
|
|
3653
4571
|
Model,
|
|
3654
4572
|
MasterStoreState,
|
|
@@ -3681,8 +4599,6 @@ export {
|
|
|
3681
4599
|
ArcContextElement,
|
|
3682
4600
|
ArcContext,
|
|
3683
4601
|
ArcCommand,
|
|
3684
|
-
ArcCollectionQuery,
|
|
3685
|
-
ArcCollection,
|
|
3686
4602
|
ArcBranded,
|
|
3687
4603
|
ArcBoolean,
|
|
3688
4604
|
ArcBlob,
|