@arcote.tech/arc 0.1.8 → 0.1.9
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 +1571 -657
- 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
|
|
|
@@ -514,6 +434,20 @@ class ArcObject extends ArcAbstract {
|
|
|
514
434
|
}
|
|
515
435
|
return schema;
|
|
516
436
|
}
|
|
437
|
+
getColumnData() {
|
|
438
|
+
const storeData = this.getStoreData();
|
|
439
|
+
return {
|
|
440
|
+
type: "object",
|
|
441
|
+
storeData: {
|
|
442
|
+
...storeData,
|
|
443
|
+
isNullable: false
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
merge(otherShape) {
|
|
448
|
+
const mergedShape = { ...this.rawShape, ...otherShape };
|
|
449
|
+
return new ArcObject(mergedShape);
|
|
450
|
+
}
|
|
517
451
|
}
|
|
518
452
|
function object(element) {
|
|
519
453
|
return new ArcObject(element);
|
|
@@ -613,10 +547,33 @@ class ArcArray extends ArcAbstract {
|
|
|
613
547
|
}
|
|
614
548
|
return schema;
|
|
615
549
|
}
|
|
550
|
+
getColumnData() {
|
|
551
|
+
const storeData = this.getStoreData();
|
|
552
|
+
return {
|
|
553
|
+
type: "array",
|
|
554
|
+
storeData: {
|
|
555
|
+
...storeData,
|
|
556
|
+
isNullable: false
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
616
560
|
}
|
|
617
561
|
function array(element) {
|
|
618
562
|
return new ArcArray(element);
|
|
619
563
|
}
|
|
564
|
+
// elements/abstract-primitive.ts
|
|
565
|
+
class ArcPrimitive extends ArcAbstract {
|
|
566
|
+
serialize(value) {
|
|
567
|
+
return value;
|
|
568
|
+
}
|
|
569
|
+
parse(value) {
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
deserialize(value) {
|
|
573
|
+
return value;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
620
577
|
// elements/blob.ts
|
|
621
578
|
var blobValidator = {
|
|
622
579
|
name: "blob",
|
|
@@ -715,8 +672,18 @@ class ArcBlob extends ArcPrimitive {
|
|
|
715
672
|
}
|
|
716
673
|
return schema;
|
|
717
674
|
}
|
|
718
|
-
|
|
719
|
-
|
|
675
|
+
getColumnData() {
|
|
676
|
+
const storeData = this.getStoreData();
|
|
677
|
+
return {
|
|
678
|
+
type: "blob",
|
|
679
|
+
storeData: {
|
|
680
|
+
...storeData,
|
|
681
|
+
isNullable: false
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function blob() {
|
|
720
687
|
return new ArcBlob;
|
|
721
688
|
}
|
|
722
689
|
// elements/boolean.ts
|
|
@@ -740,6 +707,16 @@ class ArcBoolean extends ArcPrimitive {
|
|
|
740
707
|
}
|
|
741
708
|
return schema;
|
|
742
709
|
}
|
|
710
|
+
getColumnData() {
|
|
711
|
+
const storeData = this.getStoreData();
|
|
712
|
+
return {
|
|
713
|
+
type: "boolean",
|
|
714
|
+
storeData: {
|
|
715
|
+
...storeData,
|
|
716
|
+
isNullable: false
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
743
720
|
}
|
|
744
721
|
function boolean() {
|
|
745
722
|
return new ArcBoolean;
|
|
@@ -789,6 +766,16 @@ class ArcDate extends ArcAbstract {
|
|
|
789
766
|
}
|
|
790
767
|
return schema;
|
|
791
768
|
}
|
|
769
|
+
getColumnData() {
|
|
770
|
+
const storeData = this.getStoreData();
|
|
771
|
+
return {
|
|
772
|
+
type: "date",
|
|
773
|
+
storeData: {
|
|
774
|
+
...storeData,
|
|
775
|
+
isNullable: false
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
792
779
|
}
|
|
793
780
|
function date() {
|
|
794
781
|
return new ArcDate;
|
|
@@ -967,10 +954,207 @@ class ArcFile extends ArcPrimitive {
|
|
|
967
954
|
}
|
|
968
955
|
return schema;
|
|
969
956
|
}
|
|
957
|
+
getColumnData() {
|
|
958
|
+
const storeData = this.getStoreData();
|
|
959
|
+
return {
|
|
960
|
+
type: "blob",
|
|
961
|
+
storeData: {
|
|
962
|
+
...storeData,
|
|
963
|
+
isNullable: false
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
970
967
|
}
|
|
971
968
|
function file() {
|
|
972
969
|
return new ArcFile;
|
|
973
970
|
}
|
|
971
|
+
// elements/string.ts
|
|
972
|
+
var stringValidator = typeValidatorBuilder("string");
|
|
973
|
+
|
|
974
|
+
class ArcString extends ArcPrimitive {
|
|
975
|
+
constructor() {
|
|
976
|
+
super([stringValidator]);
|
|
977
|
+
}
|
|
978
|
+
minLength(min) {
|
|
979
|
+
return this.validation("minLength", (value) => {
|
|
980
|
+
if (value.length < min)
|
|
981
|
+
return {
|
|
982
|
+
currentLength: value.length,
|
|
983
|
+
minLength: min
|
|
984
|
+
};
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
maxLength(max) {
|
|
988
|
+
return this.validation("maxLength", (value) => {
|
|
989
|
+
if (value.length > max)
|
|
990
|
+
return {
|
|
991
|
+
currentLength: value.length,
|
|
992
|
+
maxLength: max
|
|
993
|
+
};
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
length(number) {
|
|
997
|
+
return this.validation("length", (value) => {
|
|
998
|
+
if (value.length !== number) {
|
|
999
|
+
return {
|
|
1000
|
+
currentLength: value.length,
|
|
1001
|
+
length: number
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
includes(str) {
|
|
1007
|
+
return this.validation("includes", (value) => {
|
|
1008
|
+
if (!value.includes(str)) {
|
|
1009
|
+
return {
|
|
1010
|
+
currentValue: value,
|
|
1011
|
+
includes: str
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
startsWith(str) {
|
|
1017
|
+
return this.validation("startsWith", (value) => {
|
|
1018
|
+
if (!value.startsWith(str)) {
|
|
1019
|
+
return {
|
|
1020
|
+
currentValue: value,
|
|
1021
|
+
startsWith: str
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
endsWith(str) {
|
|
1027
|
+
return this.validation("endsWith", (value) => {
|
|
1028
|
+
if (!value.endsWith(str)) {
|
|
1029
|
+
return {
|
|
1030
|
+
currentValue: value,
|
|
1031
|
+
endsWith: str
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
regex(regex) {
|
|
1037
|
+
return this.validation("regex", (value) => {
|
|
1038
|
+
if (!regex.test(value)) {
|
|
1039
|
+
return {
|
|
1040
|
+
currentValue: value,
|
|
1041
|
+
regex
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
email() {
|
|
1047
|
+
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}\])$/;
|
|
1048
|
+
return this.validation("email", (value) => {
|
|
1049
|
+
if (!regex.test(value)) {
|
|
1050
|
+
return {
|
|
1051
|
+
currentEmail: value
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
toJsonSchema() {
|
|
1057
|
+
const schema = { type: "string" };
|
|
1058
|
+
if (this._description) {
|
|
1059
|
+
schema.description = this._description;
|
|
1060
|
+
}
|
|
1061
|
+
return schema;
|
|
1062
|
+
}
|
|
1063
|
+
url() {
|
|
1064
|
+
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-]*)?$/;
|
|
1065
|
+
return this.validation("url", (value) => {
|
|
1066
|
+
if (!regex.test(value)) {
|
|
1067
|
+
return {
|
|
1068
|
+
currentUrl: value
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
ip() {
|
|
1074
|
+
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]?)$/;
|
|
1075
|
+
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})$/;
|
|
1076
|
+
return this.validation("ip", (value) => {
|
|
1077
|
+
if (!(IPv4regex.test(value) || IPv6regex.test(value))) {
|
|
1078
|
+
return {
|
|
1079
|
+
currentIP: value
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
validation(name, validator) {
|
|
1085
|
+
const instance = this.pipeValidation(name, validator);
|
|
1086
|
+
return instance;
|
|
1087
|
+
}
|
|
1088
|
+
getColumnData() {
|
|
1089
|
+
const storeData = this.getStoreData();
|
|
1090
|
+
const validationInfo = {};
|
|
1091
|
+
for (const validation of this.validations) {
|
|
1092
|
+
switch (validation.name) {
|
|
1093
|
+
case "minLength":
|
|
1094
|
+
break;
|
|
1095
|
+
case "maxLength":
|
|
1096
|
+
break;
|
|
1097
|
+
case "regex":
|
|
1098
|
+
break;
|
|
1099
|
+
case "email":
|
|
1100
|
+
validationInfo.pattern = "email";
|
|
1101
|
+
break;
|
|
1102
|
+
case "url":
|
|
1103
|
+
validationInfo.pattern = "url";
|
|
1104
|
+
break;
|
|
1105
|
+
case "ip":
|
|
1106
|
+
validationInfo.pattern = "ip";
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
type: "string",
|
|
1112
|
+
storeData: {
|
|
1113
|
+
...storeData,
|
|
1114
|
+
isNullable: false
|
|
1115
|
+
},
|
|
1116
|
+
validationInfo
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function string() {
|
|
1121
|
+
return new ArcString;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// elements/id.ts
|
|
1125
|
+
class ArcCustomId extends ArcBranded {
|
|
1126
|
+
createFn;
|
|
1127
|
+
constructor(name, createFn) {
|
|
1128
|
+
super(string(), name);
|
|
1129
|
+
this.createFn = createFn;
|
|
1130
|
+
}
|
|
1131
|
+
get(...args) {
|
|
1132
|
+
return this.createFn(...args);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
class ArcId extends ArcBranded {
|
|
1137
|
+
generateFn;
|
|
1138
|
+
constructor(name, generateFn) {
|
|
1139
|
+
super(string(), name);
|
|
1140
|
+
this.generateFn = generateFn;
|
|
1141
|
+
}
|
|
1142
|
+
generate() {
|
|
1143
|
+
if (this.generateFn) {
|
|
1144
|
+
return this.generateFn();
|
|
1145
|
+
}
|
|
1146
|
+
var timestamp = (new Date().getTime() / 1000 | 0).toString(16);
|
|
1147
|
+
return timestamp + "xxxxxxxxxxxxxxxx".replace(/[x]/g, function() {
|
|
1148
|
+
return (Math.random() * 16 | 0).toString(16);
|
|
1149
|
+
}).toLowerCase();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function id(name, generateFn) {
|
|
1153
|
+
return new ArcId(name, generateFn);
|
|
1154
|
+
}
|
|
1155
|
+
function customId(name, createFn) {
|
|
1156
|
+
return new ArcCustomId(name, createFn);
|
|
1157
|
+
}
|
|
974
1158
|
// elements/number.ts
|
|
975
1159
|
var numberValidator = typeValidatorBuilder("number");
|
|
976
1160
|
|
|
@@ -1001,6 +1185,26 @@ class ArcNumber extends ArcPrimitive {
|
|
|
1001
1185
|
}
|
|
1002
1186
|
return schema;
|
|
1003
1187
|
}
|
|
1188
|
+
getColumnData() {
|
|
1189
|
+
const storeData = this.getStoreData();
|
|
1190
|
+
const validationInfo = {};
|
|
1191
|
+
for (const validation of this.validations) {
|
|
1192
|
+
switch (validation.name) {
|
|
1193
|
+
case "min":
|
|
1194
|
+
break;
|
|
1195
|
+
case "max":
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
type: "number",
|
|
1201
|
+
storeData: {
|
|
1202
|
+
...storeData,
|
|
1203
|
+
isNullable: false
|
|
1204
|
+
},
|
|
1205
|
+
validationInfo
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1004
1208
|
}
|
|
1005
1209
|
function number() {
|
|
1006
1210
|
return new ArcNumber;
|
|
@@ -1039,6 +1243,23 @@ class ArcOr {
|
|
|
1039
1243
|
anyOf: this.elements.map((el) => el.toJsonSchema?.() ?? {})
|
|
1040
1244
|
};
|
|
1041
1245
|
}
|
|
1246
|
+
getColumnData() {
|
|
1247
|
+
return {
|
|
1248
|
+
type: "object",
|
|
1249
|
+
storeData: {
|
|
1250
|
+
isNullable: false
|
|
1251
|
+
},
|
|
1252
|
+
validationInfo: {
|
|
1253
|
+
unionTypes: this.elements.map((el) => {
|
|
1254
|
+
try {
|
|
1255
|
+
return el.getColumnData?.()?.type || "unknown";
|
|
1256
|
+
} catch {
|
|
1257
|
+
return "unknown";
|
|
1258
|
+
}
|
|
1259
|
+
})
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1042
1263
|
pickElement(value) {
|
|
1043
1264
|
for (const element of this.elements) {
|
|
1044
1265
|
if (this.isTypeOf(element, value))
|
|
@@ -1118,6 +1339,16 @@ class ArcRecord extends ArcAbstract {
|
|
|
1118
1339
|
}
|
|
1119
1340
|
return schema;
|
|
1120
1341
|
}
|
|
1342
|
+
getColumnData() {
|
|
1343
|
+
const storeData = this.getStoreData();
|
|
1344
|
+
return {
|
|
1345
|
+
type: "record",
|
|
1346
|
+
storeData: {
|
|
1347
|
+
...storeData,
|
|
1348
|
+
isNullable: false
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1121
1352
|
}
|
|
1122
1353
|
function record(key, element) {
|
|
1123
1354
|
return new ArcRecord(key, element);
|
|
@@ -1160,368 +1391,50 @@ class ArcStringEnum extends ArcAbstract {
|
|
|
1160
1391
|
getEnumerators() {
|
|
1161
1392
|
return this.values;
|
|
1162
1393
|
}
|
|
1394
|
+
getColumnData() {
|
|
1395
|
+
const storeData = this.getStoreData();
|
|
1396
|
+
return {
|
|
1397
|
+
type: "stringEnum",
|
|
1398
|
+
storeData: {
|
|
1399
|
+
...storeData,
|
|
1400
|
+
isNullable: false
|
|
1401
|
+
},
|
|
1402
|
+
validationInfo: {
|
|
1403
|
+
enumValues: this.values
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1163
1407
|
}
|
|
1164
1408
|
function stringEnum(...values) {
|
|
1165
1409
|
return new ArcStringEnum(values);
|
|
1166
1410
|
}
|
|
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);
|
|
1411
|
+
// command/command.ts
|
|
1412
|
+
class ArcCommand extends ArcContextElement {
|
|
1413
|
+
name;
|
|
1414
|
+
_description;
|
|
1415
|
+
_params;
|
|
1416
|
+
_results;
|
|
1417
|
+
_elements;
|
|
1418
|
+
_handler;
|
|
1419
|
+
_isPublic = false;
|
|
1420
|
+
constructor(name) {
|
|
1421
|
+
super();
|
|
1422
|
+
this.name = name;
|
|
1220
1423
|
}
|
|
1221
|
-
|
|
1222
|
-
this.
|
|
1424
|
+
use(elements) {
|
|
1425
|
+
const clone = this.clone();
|
|
1426
|
+
clone._elements = elements;
|
|
1427
|
+
return clone;
|
|
1223
1428
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1429
|
+
description(description) {
|
|
1430
|
+
const clone = this.clone();
|
|
1431
|
+
clone._description = description;
|
|
1432
|
+
return clone;
|
|
1227
1433
|
}
|
|
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;
|
|
1434
|
+
public() {
|
|
1435
|
+
const clone = this.clone();
|
|
1436
|
+
clone._isPublic = true;
|
|
1437
|
+
return clone;
|
|
1525
1438
|
}
|
|
1526
1439
|
get isPublic() {
|
|
1527
1440
|
return this._isPublic;
|
|
@@ -1590,15 +1503,19 @@ function command(name) {
|
|
|
1590
1503
|
// context/context.ts
|
|
1591
1504
|
class ArcContext {
|
|
1592
1505
|
elements;
|
|
1506
|
+
elementsSet = new Set;
|
|
1593
1507
|
constructor(elements) {
|
|
1594
1508
|
this.elements = elements;
|
|
1509
|
+
this.elements.forEach((element2) => {
|
|
1510
|
+
this.elementsSet.add(element2);
|
|
1511
|
+
});
|
|
1595
1512
|
}
|
|
1596
1513
|
get(name) {
|
|
1597
1514
|
return this.elements.find((element2) => element2.name === name);
|
|
1598
1515
|
}
|
|
1599
1516
|
getSyncListeners(eventType, authContext) {
|
|
1600
1517
|
const listeners = [];
|
|
1601
|
-
this.
|
|
1518
|
+
this.elementsSet.forEach((element2) => {
|
|
1602
1519
|
if (element2.observer) {
|
|
1603
1520
|
const handlers = element2.observer(authContext);
|
|
1604
1521
|
const config = handlers[eventType];
|
|
@@ -1611,7 +1528,7 @@ class ArcContext {
|
|
|
1611
1528
|
}
|
|
1612
1529
|
getAsyncListeners(eventType, authContext) {
|
|
1613
1530
|
const listeners = [];
|
|
1614
|
-
this.
|
|
1531
|
+
this.elementsSet.forEach((element2) => {
|
|
1615
1532
|
if (element2.observer) {
|
|
1616
1533
|
const handlers = element2.observer(authContext);
|
|
1617
1534
|
const config = handlers[eventType];
|
|
@@ -1682,6 +1599,28 @@ function contextMerge(...contexts) {
|
|
|
1682
1599
|
}
|
|
1683
1600
|
// context/event.ts
|
|
1684
1601
|
var eventValidator = typeValidatorBuilder("object");
|
|
1602
|
+
var eventSchema = new ArcObject({
|
|
1603
|
+
id: string().primaryKey(),
|
|
1604
|
+
type: string(),
|
|
1605
|
+
payload: any(),
|
|
1606
|
+
createdAt: date()
|
|
1607
|
+
});
|
|
1608
|
+
var sharedEventDatabaseSchema = null;
|
|
1609
|
+
function getSharedEventDatabaseSchema() {
|
|
1610
|
+
if (!sharedEventDatabaseSchema) {
|
|
1611
|
+
sharedEventDatabaseSchema = {
|
|
1612
|
+
tables: [{
|
|
1613
|
+
name: "events",
|
|
1614
|
+
schema: eventSchema,
|
|
1615
|
+
options: {
|
|
1616
|
+
softDelete: false,
|
|
1617
|
+
versioning: true
|
|
1618
|
+
}
|
|
1619
|
+
}]
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
return sharedEventDatabaseSchema;
|
|
1623
|
+
}
|
|
1685
1624
|
var eventStoreSchema = {
|
|
1686
1625
|
tables: [
|
|
1687
1626
|
{
|
|
@@ -1715,6 +1654,7 @@ class ArcEvent extends ArcContextElementWithStore {
|
|
|
1715
1654
|
payload;
|
|
1716
1655
|
_restrictions;
|
|
1717
1656
|
storeSchema = () => eventStoreSchema;
|
|
1657
|
+
databaseStoreSchema = () => getSharedEventDatabaseSchema();
|
|
1718
1658
|
constructor(name, payload) {
|
|
1719
1659
|
super();
|
|
1720
1660
|
this.name = name;
|
|
@@ -1760,6 +1700,21 @@ class ArcEvent extends ArcContextElementWithStore {
|
|
|
1760
1700
|
function event(name, payload) {
|
|
1761
1701
|
return new ArcEvent(name, payload ? payload instanceof ArcObject ? payload : new ArcObject(payload) : undefined);
|
|
1762
1702
|
}
|
|
1703
|
+
// context/query.ts
|
|
1704
|
+
class ArcQuery {
|
|
1705
|
+
lastResult;
|
|
1706
|
+
listeners = new Set;
|
|
1707
|
+
subscribe(listener) {
|
|
1708
|
+
this.listeners.add(listener);
|
|
1709
|
+
}
|
|
1710
|
+
unsubscribe(listener) {
|
|
1711
|
+
this.listeners.delete(listener);
|
|
1712
|
+
}
|
|
1713
|
+
nextResult(result) {
|
|
1714
|
+
this.lastResult = result;
|
|
1715
|
+
this.listeners.forEach((listener) => listener(result));
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1763
1718
|
// context/query-builder-context.ts
|
|
1764
1719
|
class QueryBuilderContext {
|
|
1765
1720
|
queryCache;
|
|
@@ -2228,107 +2183,729 @@ class MasterStoreState extends StoreState {
|
|
|
2228
2183
|
item,
|
|
2229
2184
|
id: change.id
|
|
2230
2185
|
}
|
|
2231
|
-
};
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
throw new Error("Unknown change type");
|
|
2189
|
+
}
|
|
2190
|
+
async applyChange(change) {
|
|
2191
|
+
const transaction = await this.dataStorage.getReadWriteTransaction();
|
|
2192
|
+
const { event: event3, from, to } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2193
|
+
await transaction.commit();
|
|
2194
|
+
this.notifyListeners([event3]);
|
|
2195
|
+
return { from, to };
|
|
2196
|
+
}
|
|
2197
|
+
async applyChanges(changes) {
|
|
2198
|
+
const transaction = await this.dataStorage.getReadWriteTransaction();
|
|
2199
|
+
const events = [];
|
|
2200
|
+
for (const change of changes) {
|
|
2201
|
+
const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2202
|
+
if (event3)
|
|
2203
|
+
events.push(event3);
|
|
2204
|
+
}
|
|
2205
|
+
await transaction.commit();
|
|
2206
|
+
if (events.length > 0) {
|
|
2207
|
+
this.notifyListeners(events);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async find(options, listener) {
|
|
2211
|
+
if (listener) {
|
|
2212
|
+
this.listeners.set(listener, { callback: listener });
|
|
2213
|
+
}
|
|
2214
|
+
const transaction = await this.dataStorage.getReadTransaction();
|
|
2215
|
+
const results = await transaction.find(this.storeName, options);
|
|
2216
|
+
return results.map((item) => this.deserialize ? this.deserialize(item) : item);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// data-storage/data-storage-master.ts
|
|
2221
|
+
class MasterDataStorage extends DataStorage {
|
|
2222
|
+
dbAdapter;
|
|
2223
|
+
arcContext;
|
|
2224
|
+
stores = new Map;
|
|
2225
|
+
rtcAdapter;
|
|
2226
|
+
reinitTablesExecuted = false;
|
|
2227
|
+
constructor(dbAdapter, rtcAdapterFactory, arcContext) {
|
|
2228
|
+
super();
|
|
2229
|
+
this.dbAdapter = dbAdapter;
|
|
2230
|
+
this.arcContext = arcContext;
|
|
2231
|
+
this.rtcAdapter = rtcAdapterFactory(this);
|
|
2232
|
+
this.executeReinitTablesOnReady();
|
|
2233
|
+
}
|
|
2234
|
+
async executeReinitTablesOnReady() {
|
|
2235
|
+
try {
|
|
2236
|
+
const adapter = await this.dbAdapter;
|
|
2237
|
+
if (!this.reinitTablesExecuted) {
|
|
2238
|
+
await adapter.executeReinitTables(this);
|
|
2239
|
+
this.reinitTablesExecuted = true;
|
|
2240
|
+
}
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
console.error("Failed to execute reinitTable functions:", error);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
async getReadTransaction() {
|
|
2246
|
+
return (await this.dbAdapter).readTransaction();
|
|
2247
|
+
}
|
|
2248
|
+
async getReadWriteTransaction() {
|
|
2249
|
+
return (await this.dbAdapter).readWriteTransaction();
|
|
2250
|
+
}
|
|
2251
|
+
getStore(storeName) {
|
|
2252
|
+
if (!this.stores.has(storeName)) {
|
|
2253
|
+
this.stores.set(storeName, new MasterStoreState(storeName, this));
|
|
2254
|
+
}
|
|
2255
|
+
return this.stores.get(storeName);
|
|
2256
|
+
}
|
|
2257
|
+
async applyChanges(changes) {
|
|
2258
|
+
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
2259
|
+
}
|
|
2260
|
+
applySerializedChanges(changes) {
|
|
2261
|
+
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
|
|
2262
|
+
}
|
|
2263
|
+
async commitChanges(changes) {
|
|
2264
|
+
await Promise.all([
|
|
2265
|
+
this.applyChanges(changes),
|
|
2266
|
+
this.rtcAdapter.commitChanges(changes)
|
|
2267
|
+
]);
|
|
2268
|
+
}
|
|
2269
|
+
fork() {
|
|
2270
|
+
return new ForkedDataStorage(this);
|
|
2271
|
+
}
|
|
2272
|
+
async sync(progressCallback) {
|
|
2273
|
+
await this.rtcAdapter.sync(progressCallback);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
// database/schema-extraction.ts
|
|
2277
|
+
function extractDatabaseAgnosticSchema(arcObject, tableName) {
|
|
2278
|
+
const columns = [];
|
|
2279
|
+
for (const [fieldName, fieldSchema] of arcObject.entries()) {
|
|
2280
|
+
const arcElement = fieldSchema;
|
|
2281
|
+
if (typeof arcElement.getColumnData !== "function") {
|
|
2282
|
+
throw new Error(`Element for field '${fieldName}' does not implement getColumnData() method. Element type: ${arcElement.constructor.name}`);
|
|
2283
|
+
}
|
|
2284
|
+
const columnInfo = arcElement.getColumnData();
|
|
2285
|
+
const fullColumnInfo = {
|
|
2286
|
+
name: fieldName,
|
|
2287
|
+
...columnInfo
|
|
2288
|
+
};
|
|
2289
|
+
columns.push(fullColumnInfo);
|
|
2290
|
+
}
|
|
2291
|
+
return {
|
|
2292
|
+
tableName,
|
|
2293
|
+
columns
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// db/postgresAdapter.ts
|
|
2298
|
+
class PostgreSQLReadTransaction {
|
|
2299
|
+
db;
|
|
2300
|
+
tables;
|
|
2301
|
+
adapter;
|
|
2302
|
+
constructor(db, tables, adapter) {
|
|
2303
|
+
this.db = db;
|
|
2304
|
+
this.tables = tables;
|
|
2305
|
+
this.adapter = adapter;
|
|
2306
|
+
}
|
|
2307
|
+
hasSoftDelete(tableName) {
|
|
2308
|
+
if (this.adapter) {
|
|
2309
|
+
return this.adapter.hasSoftDelete(tableName);
|
|
2310
|
+
}
|
|
2311
|
+
const table = this.tables.get(tableName);
|
|
2312
|
+
if (!table)
|
|
2313
|
+
return false;
|
|
2314
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2315
|
+
}
|
|
2316
|
+
deserializeValue(value, column) {
|
|
2317
|
+
if (value === null || value === undefined)
|
|
2318
|
+
return null;
|
|
2319
|
+
switch (column.type.toLowerCase()) {
|
|
2320
|
+
case "json":
|
|
2321
|
+
case "jsonb":
|
|
2322
|
+
if (typeof value === "string") {
|
|
2323
|
+
try {
|
|
2324
|
+
return JSON.parse(value);
|
|
2325
|
+
} catch {
|
|
2326
|
+
return value;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
return value;
|
|
2330
|
+
case "text":
|
|
2331
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
2332
|
+
try {
|
|
2333
|
+
const parsed = JSON.parse(value);
|
|
2334
|
+
if (typeof parsed === "object" || Array.isArray(parsed)) {
|
|
2335
|
+
return parsed;
|
|
2336
|
+
}
|
|
2337
|
+
} catch {}
|
|
2338
|
+
}
|
|
2339
|
+
return value;
|
|
2340
|
+
case "datetime":
|
|
2341
|
+
case "timestamp":
|
|
2342
|
+
return new Date(value);
|
|
2343
|
+
default:
|
|
2344
|
+
return value;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
deserializeRow(row, table) {
|
|
2348
|
+
const result = {};
|
|
2349
|
+
for (const column of table.columns) {
|
|
2350
|
+
const value = row[column.name];
|
|
2351
|
+
result[column.name] = this.deserializeValue(value, column);
|
|
2352
|
+
}
|
|
2353
|
+
return result;
|
|
2354
|
+
}
|
|
2355
|
+
getId(store, id3) {
|
|
2356
|
+
return id3;
|
|
2357
|
+
}
|
|
2358
|
+
buildWhereClause(where, tableName) {
|
|
2359
|
+
const conditions = [];
|
|
2360
|
+
const params = [];
|
|
2361
|
+
let paramIndex = 1;
|
|
2362
|
+
if (tableName && this.hasSoftDelete(tableName)) {
|
|
2363
|
+
conditions.push('"deleted" = $1');
|
|
2364
|
+
params.push(false);
|
|
2365
|
+
paramIndex = 2;
|
|
2366
|
+
}
|
|
2367
|
+
if (!where) {
|
|
2368
|
+
return {
|
|
2369
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
2370
|
+
params
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
Object.entries(where).forEach(([key, value]) => {
|
|
2374
|
+
if (typeof value === "object" && value !== null) {
|
|
2375
|
+
Object.entries(value).forEach(([operator, operand]) => {
|
|
2376
|
+
switch (operator) {
|
|
2377
|
+
case "$eq":
|
|
2378
|
+
case "$ne":
|
|
2379
|
+
case "$gt":
|
|
2380
|
+
case "$gte":
|
|
2381
|
+
case "$lt":
|
|
2382
|
+
case "$lte":
|
|
2383
|
+
conditions.push(`"${key}" ${this.getOperatorSymbol(operator)} $${paramIndex}`);
|
|
2384
|
+
params.push(operand);
|
|
2385
|
+
paramIndex++;
|
|
2386
|
+
break;
|
|
2387
|
+
case "$in":
|
|
2388
|
+
case "$nin":
|
|
2389
|
+
if (Array.isArray(operand)) {
|
|
2390
|
+
const placeholders = operand.map(() => `$${paramIndex++}`).join(", ");
|
|
2391
|
+
conditions.push(`"${key}" ${operator === "$in" ? "IN" : "NOT IN"} (${placeholders})`);
|
|
2392
|
+
params.push(...operand);
|
|
2393
|
+
}
|
|
2394
|
+
break;
|
|
2395
|
+
case "$exists":
|
|
2396
|
+
if (typeof operand === "boolean") {
|
|
2397
|
+
conditions.push(operand ? `"${key}" IS NOT NULL` : `"${key}" IS NULL`);
|
|
2398
|
+
}
|
|
2399
|
+
break;
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
} else {
|
|
2403
|
+
conditions.push(`"${key}" = $${paramIndex}`);
|
|
2404
|
+
params.push(value);
|
|
2405
|
+
paramIndex++;
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
return {
|
|
2409
|
+
sql: conditions.join(" AND "),
|
|
2410
|
+
params
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
getOperatorSymbol(operator) {
|
|
2414
|
+
const operators = {
|
|
2415
|
+
$eq: "=",
|
|
2416
|
+
$ne: "!=",
|
|
2417
|
+
$gt: ">",
|
|
2418
|
+
$gte: ">=",
|
|
2419
|
+
$lt: "<",
|
|
2420
|
+
$lte: "<="
|
|
2421
|
+
};
|
|
2422
|
+
return operators[operator] || "=";
|
|
2423
|
+
}
|
|
2424
|
+
buildOrderByClause(orderBy) {
|
|
2425
|
+
if (!orderBy)
|
|
2426
|
+
return "";
|
|
2427
|
+
const orderClauses = Object.entries(orderBy).map(([key, direction]) => `"${key}" ${direction.toUpperCase()}`).join(", ");
|
|
2428
|
+
return orderClauses ? `ORDER BY ${orderClauses}` : "";
|
|
2429
|
+
}
|
|
2430
|
+
async find(store, options) {
|
|
2431
|
+
const { where, limit, offset, orderBy } = options || {};
|
|
2432
|
+
const whereClause = this.buildWhereClause(where, store);
|
|
2433
|
+
const orderByClause = this.buildOrderByClause(orderBy);
|
|
2434
|
+
const table = this.tables.get(store);
|
|
2435
|
+
if (!table) {
|
|
2436
|
+
throw new Error(`Store ${store} not found`);
|
|
2437
|
+
}
|
|
2438
|
+
let query2 = `
|
|
2439
|
+
SELECT *
|
|
2440
|
+
FROM "${store}"
|
|
2441
|
+
WHERE ${whereClause.sql}
|
|
2442
|
+
${orderByClause}
|
|
2443
|
+
`;
|
|
2444
|
+
let params = whereClause.params;
|
|
2445
|
+
let paramIndex = params.length + 1;
|
|
2446
|
+
if (limit) {
|
|
2447
|
+
query2 += ` LIMIT $${paramIndex}`;
|
|
2448
|
+
params.push(limit);
|
|
2449
|
+
paramIndex++;
|
|
2450
|
+
}
|
|
2451
|
+
if (offset) {
|
|
2452
|
+
query2 += ` OFFSET $${paramIndex}`;
|
|
2453
|
+
params.push(offset);
|
|
2454
|
+
}
|
|
2455
|
+
const rows = await this.db.exec(query2, params);
|
|
2456
|
+
return rows.map((row) => this.deserializeRow(row, table));
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
class PostgreSQLReadWriteTransaction extends PostgreSQLReadTransaction {
|
|
2461
|
+
adapter;
|
|
2462
|
+
queries = [];
|
|
2463
|
+
constructor(db, tables, adapter) {
|
|
2464
|
+
super(db, tables);
|
|
2465
|
+
this.adapter = adapter;
|
|
2466
|
+
}
|
|
2467
|
+
async remove(store, id3) {
|
|
2468
|
+
const table = this.tables.get(store);
|
|
2469
|
+
if (!table) {
|
|
2470
|
+
throw new Error(`Store ${store} not found`);
|
|
2471
|
+
}
|
|
2472
|
+
const query2 = `UPDATE "${store}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
|
|
2473
|
+
this.queries.push({
|
|
2474
|
+
sql: query2,
|
|
2475
|
+
params: [true, new Date().toISOString(), id3]
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
async set(store, item) {
|
|
2479
|
+
const table = this.tables.get(store);
|
|
2480
|
+
if (!table) {
|
|
2481
|
+
throw new Error(`Store ${store} not found`);
|
|
2482
|
+
}
|
|
2483
|
+
const hasVersioning = this.adapter.hasVersioning(store);
|
|
2484
|
+
if (hasVersioning) {
|
|
2485
|
+
await this.setWithVersioning(store, item, table);
|
|
2486
|
+
} else {
|
|
2487
|
+
await this.setWithoutVersioning(store, item, table);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
async setWithoutVersioning(store, item, table) {
|
|
2491
|
+
const columnNames = table.columns.map((col) => col.name);
|
|
2492
|
+
const values = table.columns.map((column) => {
|
|
2493
|
+
let value = item[column.name];
|
|
2494
|
+
if (value === undefined && column.default !== undefined) {
|
|
2495
|
+
value = column.default;
|
|
2496
|
+
}
|
|
2497
|
+
return this.serializeValue(value, column);
|
|
2498
|
+
});
|
|
2499
|
+
const placeholders = columnNames.map((_, i) => `$${i + 1}`).join(", ");
|
|
2500
|
+
const updateColumns = columnNames.filter((col) => col !== table.primaryKey).map((col) => `"${col}" = EXCLUDED."${col}"`).join(", ");
|
|
2501
|
+
if (store === "events") {
|
|
2502
|
+
const simpleInsertSql = `
|
|
2503
|
+
INSERT INTO "${table.name}"
|
|
2504
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2505
|
+
VALUES (${placeholders})
|
|
2506
|
+
`;
|
|
2507
|
+
this.queries.push({
|
|
2508
|
+
sql: simpleInsertSql,
|
|
2509
|
+
params: values
|
|
2510
|
+
});
|
|
2511
|
+
} else {
|
|
2512
|
+
const sql = `
|
|
2513
|
+
INSERT INTO "${table.name}"
|
|
2514
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2515
|
+
VALUES (${placeholders})
|
|
2516
|
+
ON CONFLICT ("${table.primaryKey}")
|
|
2517
|
+
DO UPDATE SET ${updateColumns}
|
|
2518
|
+
`;
|
|
2519
|
+
this.queries.push({
|
|
2520
|
+
sql,
|
|
2521
|
+
params: values
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
async setWithVersioning(store, item, table) {
|
|
2526
|
+
const regularColumns = table.columns.filter((col) => col.name !== "__version");
|
|
2527
|
+
const columnNames = regularColumns.map((col) => col.name);
|
|
2528
|
+
const values = regularColumns.map((column) => {
|
|
2529
|
+
let value = item[column.name];
|
|
2530
|
+
if (value === undefined && column.default !== undefined) {
|
|
2531
|
+
value = column.default;
|
|
2532
|
+
}
|
|
2533
|
+
return this.serializeValue(value, column);
|
|
2534
|
+
});
|
|
2535
|
+
columnNames.push("__version");
|
|
2536
|
+
const placeholders = regularColumns.map((_, i) => `$${i + 1}`).join(", ");
|
|
2537
|
+
const updateColumns = regularColumns.filter((col) => col.name !== table.primaryKey).map((col) => `"${col.name}" = EXCLUDED."${col.name}"`).join(", ");
|
|
2538
|
+
const sql = `
|
|
2539
|
+
WITH next_version AS (
|
|
2540
|
+
INSERT INTO __arc_version_counters (table_name, last_version)
|
|
2541
|
+
VALUES ($${values.length + 1}, 1)
|
|
2542
|
+
ON CONFLICT(table_name)
|
|
2543
|
+
DO UPDATE SET last_version = __arc_version_counters.last_version + 1
|
|
2544
|
+
RETURNING last_version
|
|
2545
|
+
),
|
|
2546
|
+
upsert AS (
|
|
2547
|
+
INSERT INTO "${table.name}"
|
|
2548
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
2549
|
+
VALUES (${placeholders}, (SELECT last_version FROM next_version))
|
|
2550
|
+
ON CONFLICT ("${table.primaryKey}")
|
|
2551
|
+
DO UPDATE SET ${updateColumns}${updateColumns ? ", " : ""}"__version" = (SELECT last_version FROM next_version)
|
|
2552
|
+
RETURNING *
|
|
2553
|
+
)
|
|
2554
|
+
SELECT * FROM upsert
|
|
2555
|
+
`;
|
|
2556
|
+
this.queries.push({
|
|
2557
|
+
sql,
|
|
2558
|
+
params: [...values, store]
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
async commit() {
|
|
2562
|
+
if (this.queries.length === 0) {
|
|
2563
|
+
return Promise.resolve();
|
|
2564
|
+
}
|
|
2565
|
+
try {
|
|
2566
|
+
await this.db.execBatch(this.queries);
|
|
2567
|
+
this.queries = [];
|
|
2568
|
+
} catch (error) {
|
|
2569
|
+
this.queries = [];
|
|
2570
|
+
throw error;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
serializeValue(value, column) {
|
|
2574
|
+
if (value === null || value === undefined)
|
|
2575
|
+
return null;
|
|
2576
|
+
switch (column.type.toLowerCase()) {
|
|
2577
|
+
case "timestamp":
|
|
2578
|
+
case "datetime":
|
|
2579
|
+
if (value instanceof Date) {
|
|
2580
|
+
return value.toISOString();
|
|
2581
|
+
}
|
|
2582
|
+
if (typeof value === "number") {
|
|
2583
|
+
const date3 = value > 10000000000 ? new Date(value) : new Date(value * 1000);
|
|
2584
|
+
return date3.toISOString();
|
|
2585
|
+
}
|
|
2586
|
+
if (typeof value === "string") {
|
|
2587
|
+
const date3 = new Date(value);
|
|
2588
|
+
if (!isNaN(date3.getTime())) {
|
|
2589
|
+
return date3.toISOString();
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return value;
|
|
2593
|
+
case "json":
|
|
2594
|
+
case "jsonb":
|
|
2595
|
+
return JSON.stringify(value);
|
|
2596
|
+
default:
|
|
2597
|
+
if (value instanceof Date) {
|
|
2598
|
+
return value.toISOString();
|
|
2599
|
+
}
|
|
2600
|
+
if (Array.isArray(value) || typeof value === "object") {
|
|
2601
|
+
return JSON.stringify(value);
|
|
2602
|
+
}
|
|
2603
|
+
return value;
|
|
2232
2604
|
}
|
|
2233
|
-
throw new Error("Unknown change type");
|
|
2234
2605
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
class PostgreSQLAdapter {
|
|
2609
|
+
db;
|
|
2610
|
+
context;
|
|
2611
|
+
tables = new Map;
|
|
2612
|
+
tableSchemas = new Map;
|
|
2613
|
+
pendingReinitTables = [];
|
|
2614
|
+
mapType(arcType, storeData) {
|
|
2615
|
+
if (storeData?.databaseType?.postgresql) {
|
|
2616
|
+
return storeData.databaseType.postgresql;
|
|
2617
|
+
}
|
|
2618
|
+
switch (arcType) {
|
|
2619
|
+
case "string":
|
|
2620
|
+
case "stringEnum":
|
|
2621
|
+
return "TEXT";
|
|
2622
|
+
case "id":
|
|
2623
|
+
case "customId":
|
|
2624
|
+
return storeData?.databaseType?.postgresql || "TEXT";
|
|
2625
|
+
case "number":
|
|
2626
|
+
return storeData?.isAutoIncrement ? "SERIAL" : "INTEGER";
|
|
2627
|
+
case "boolean":
|
|
2628
|
+
return "BOOLEAN";
|
|
2629
|
+
case "date":
|
|
2630
|
+
return "TIMESTAMP";
|
|
2631
|
+
case "object":
|
|
2632
|
+
case "array":
|
|
2633
|
+
case "record":
|
|
2634
|
+
return "JSONB";
|
|
2635
|
+
case "blob":
|
|
2636
|
+
return "BYTEA";
|
|
2637
|
+
default:
|
|
2638
|
+
return "TEXT";
|
|
2639
|
+
}
|
|
2241
2640
|
}
|
|
2242
|
-
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change);
|
|
2247
|
-
if (event3)
|
|
2248
|
-
events.push(event3);
|
|
2641
|
+
buildConstraints(storeData) {
|
|
2642
|
+
const constraints = [];
|
|
2643
|
+
if (storeData?.isPrimaryKey) {
|
|
2644
|
+
constraints.push("PRIMARY KEY");
|
|
2249
2645
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2646
|
+
if (storeData?.isUnique) {
|
|
2647
|
+
constraints.push("UNIQUE");
|
|
2648
|
+
}
|
|
2649
|
+
if (storeData?.foreignKey) {
|
|
2650
|
+
const { table, column, onDelete, onUpdate } = storeData.foreignKey;
|
|
2651
|
+
let fkConstraint = `REFERENCES ${table}(${column})`;
|
|
2652
|
+
if (onDelete)
|
|
2653
|
+
fkConstraint += ` ON DELETE ${onDelete}`;
|
|
2654
|
+
if (onUpdate)
|
|
2655
|
+
fkConstraint += ` ON UPDATE ${onUpdate}`;
|
|
2656
|
+
constraints.push(fkConstraint);
|
|
2253
2657
|
}
|
|
2658
|
+
return constraints;
|
|
2254
2659
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2660
|
+
generateColumnSQL(columnInfo) {
|
|
2661
|
+
const type = this.mapType(columnInfo.type, columnInfo.storeData);
|
|
2662
|
+
const constraints = [];
|
|
2663
|
+
if (!columnInfo.storeData?.isNullable) {
|
|
2664
|
+
constraints.push("NOT NULL");
|
|
2258
2665
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2666
|
+
constraints.push(...this.buildConstraints(columnInfo.storeData));
|
|
2667
|
+
if (columnInfo.defaultValue !== undefined) {
|
|
2668
|
+
if (type === "UUID" && columnInfo.defaultValue === "auto") {
|
|
2669
|
+
constraints.push("DEFAULT gen_random_uuid()");
|
|
2670
|
+
} else if (typeof columnInfo.defaultValue === "string") {
|
|
2671
|
+
constraints.push(`DEFAULT '${columnInfo.defaultValue}'`);
|
|
2672
|
+
} else {
|
|
2673
|
+
constraints.push(`DEFAULT ${columnInfo.defaultValue}`);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
return `"${columnInfo.name}" ${type} ${constraints.join(" ")}`.trim();
|
|
2677
|
+
}
|
|
2678
|
+
generateCreateTableSQL(tableName, columns) {
|
|
2679
|
+
const columnDefinitions = columns.map((col) => this.generateColumnSQL(col));
|
|
2680
|
+
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}");`);
|
|
2681
|
+
let sql = `CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
2682
|
+
${columnDefinitions.join(`,
|
|
2683
|
+
`)}
|
|
2684
|
+
)`;
|
|
2685
|
+
if (indexes.length > 0) {
|
|
2686
|
+
sql += `;
|
|
2687
|
+
` + indexes.join(`
|
|
2688
|
+
`);
|
|
2689
|
+
}
|
|
2690
|
+
return sql;
|
|
2262
2691
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2692
|
+
constructor(db, context3) {
|
|
2693
|
+
this.db = db;
|
|
2694
|
+
this.context = context3;
|
|
2695
|
+
this.context.elements.forEach((element3) => {
|
|
2696
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
2697
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
2698
|
+
databaseSchema.tables.forEach((dbTable) => {
|
|
2699
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
2700
|
+
const columns = agnosticSchema.columns.map((columnInfo) => ({
|
|
2701
|
+
name: columnInfo.name,
|
|
2702
|
+
type: this.mapType(columnInfo.type, columnInfo.storeData),
|
|
2703
|
+
constraints: this.buildConstraints(columnInfo.storeData),
|
|
2704
|
+
isNullable: columnInfo.storeData?.isNullable || false,
|
|
2705
|
+
defaultValue: columnInfo.defaultValue,
|
|
2706
|
+
isPrimaryKey: columnInfo.storeData?.isPrimaryKey || false,
|
|
2707
|
+
isAutoIncrement: columnInfo.storeData?.isAutoIncrement || false,
|
|
2708
|
+
isUnique: columnInfo.storeData?.isUnique || false,
|
|
2709
|
+
hasIndex: columnInfo.storeData?.hasIndex || false,
|
|
2710
|
+
foreignKey: columnInfo.storeData?.foreignKey
|
|
2711
|
+
}));
|
|
2712
|
+
this.tableSchemas.set(dbTable.name, columns);
|
|
2713
|
+
const legacyTable = {
|
|
2714
|
+
name: dbTable.name,
|
|
2715
|
+
primaryKey: columns.find((col) => col.isPrimaryKey)?.name || "_id",
|
|
2716
|
+
columns: columns.map((col) => ({
|
|
2717
|
+
name: col.name,
|
|
2718
|
+
type: col.type,
|
|
2719
|
+
isOptional: col.isNullable,
|
|
2720
|
+
default: col.defaultValue
|
|
2721
|
+
}))
|
|
2722
|
+
};
|
|
2723
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2276
2727
|
}
|
|
2277
|
-
async
|
|
2278
|
-
|
|
2728
|
+
async initialize() {
|
|
2729
|
+
await this.createVersionCounterTable();
|
|
2730
|
+
await this.createTableVersionsTable();
|
|
2731
|
+
const processedSchemas = new Set;
|
|
2732
|
+
const processedTables = new Set;
|
|
2733
|
+
for (const element3 of this.context.elements) {
|
|
2734
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
2735
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
2736
|
+
if (processedSchemas.has(databaseSchema)) {
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
processedSchemas.add(databaseSchema);
|
|
2740
|
+
for (const dbTable of databaseSchema.tables) {
|
|
2741
|
+
const tableKey = dbTable.version ? `${dbTable.name}_v${dbTable.version}` : dbTable.name;
|
|
2742
|
+
if (!processedTables.has(tableKey)) {
|
|
2743
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
2744
|
+
let allColumns = [...agnosticSchema.columns];
|
|
2745
|
+
if (dbTable.options?.versioning) {
|
|
2746
|
+
allColumns.push({
|
|
2747
|
+
name: "__version",
|
|
2748
|
+
type: "number",
|
|
2749
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
2750
|
+
defaultValue: 1
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
if (dbTable.options?.softDelete) {
|
|
2754
|
+
allColumns.push({
|
|
2755
|
+
name: "deleted",
|
|
2756
|
+
type: "boolean",
|
|
2757
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
2758
|
+
defaultValue: false
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
2762
|
+
if (dbTable.version && await this.checkVersionedTableExists(dbTable.name, dbTable.version)) {
|
|
2763
|
+
console.log(`Versioned table ${physicalTableName} already exists, skipping creation`);
|
|
2764
|
+
} else {
|
|
2765
|
+
await this.createTableIfNotExistsNew(physicalTableName, allColumns);
|
|
2766
|
+
if (dbTable.version) {
|
|
2767
|
+
await this.registerTableVersion(dbTable.name, dbTable.version, physicalTableName);
|
|
2768
|
+
if (databaseSchema.reinitTable) {
|
|
2769
|
+
this.pendingReinitTables.push({
|
|
2770
|
+
tableName: physicalTableName,
|
|
2771
|
+
reinitFn: databaseSchema.reinitTable
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
const legacyTable = {
|
|
2777
|
+
name: physicalTableName,
|
|
2778
|
+
primaryKey: allColumns.find((col) => col.storeData?.isPrimaryKey)?.name || "_id",
|
|
2779
|
+
columns: allColumns.map((col) => ({
|
|
2780
|
+
name: col.name,
|
|
2781
|
+
type: this.mapType(col.type, col.storeData),
|
|
2782
|
+
isOptional: col.storeData?.isNullable || false,
|
|
2783
|
+
default: col.defaultValue
|
|
2784
|
+
}))
|
|
2785
|
+
};
|
|
2786
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2787
|
+
processedTables.add(tableKey);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2279
2792
|
}
|
|
2280
|
-
async
|
|
2281
|
-
|
|
2793
|
+
async createTableIfNotExistsNew(tableName, columns) {
|
|
2794
|
+
const createTableSQL = this.generateCreateTableSQL(tableName, columns);
|
|
2795
|
+
await this.db.exec(createTableSQL);
|
|
2282
2796
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2797
|
+
async createVersionCounterTable() {
|
|
2798
|
+
const sql = `
|
|
2799
|
+
CREATE TABLE IF NOT EXISTS __arc_version_counters (
|
|
2800
|
+
table_name TEXT PRIMARY KEY,
|
|
2801
|
+
last_version BIGINT NOT NULL DEFAULT 0
|
|
2802
|
+
)
|
|
2803
|
+
`;
|
|
2804
|
+
await this.db.exec(sql);
|
|
2288
2805
|
}
|
|
2289
|
-
async
|
|
2290
|
-
|
|
2806
|
+
async createTableVersionsTable() {
|
|
2807
|
+
const sql = `
|
|
2808
|
+
CREATE TABLE IF NOT EXISTS __arc_table_versions (
|
|
2809
|
+
table_name TEXT NOT NULL,
|
|
2810
|
+
version INTEGER NOT NULL,
|
|
2811
|
+
physical_table_name TEXT NOT NULL,
|
|
2812
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
2813
|
+
is_active BOOLEAN DEFAULT FALSE,
|
|
2814
|
+
PRIMARY KEY (table_name, version)
|
|
2815
|
+
)
|
|
2816
|
+
`;
|
|
2817
|
+
await this.db.exec(sql);
|
|
2291
2818
|
}
|
|
2292
|
-
|
|
2293
|
-
return
|
|
2819
|
+
getPhysicalTableName(logicalName, version) {
|
|
2820
|
+
return version ? `${logicalName}_v${version}` : logicalName;
|
|
2294
2821
|
}
|
|
2295
|
-
async
|
|
2296
|
-
await
|
|
2297
|
-
|
|
2298
|
-
this.rtcAdapter.commitChanges(changes)
|
|
2299
|
-
]);
|
|
2822
|
+
async checkVersionedTableExists(logicalName, version) {
|
|
2823
|
+
const result = await this.db.exec("SELECT COUNT(*) as count FROM __arc_table_versions WHERE table_name = $1 AND version = $2", [logicalName, version]);
|
|
2824
|
+
return result[0]?.count > 0;
|
|
2300
2825
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2826
|
+
async registerTableVersion(logicalName, version, physicalName) {
|
|
2827
|
+
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
2828
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2829
|
+
hasVersioning(tableName) {
|
|
2830
|
+
const table = this.tables.get(tableName);
|
|
2831
|
+
if (!table)
|
|
2832
|
+
return false;
|
|
2833
|
+
return table.columns.some((col) => col.name === "__version");
|
|
2834
|
+
}
|
|
2835
|
+
hasSoftDelete(tableName) {
|
|
2836
|
+
const table = this.tables.get(tableName);
|
|
2837
|
+
if (!table)
|
|
2838
|
+
return false;
|
|
2839
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2840
|
+
}
|
|
2841
|
+
async executeReinitTables(dataStorage) {
|
|
2842
|
+
for (const { tableName, reinitFn } of this.pendingReinitTables) {
|
|
2843
|
+
try {
|
|
2844
|
+
await reinitFn(tableName, dataStorage);
|
|
2845
|
+
console.log(`Successfully reinitialized table: ${tableName}`);
|
|
2846
|
+
} catch (error) {
|
|
2847
|
+
console.error(`Failed to reinitialize table ${tableName}:`, error);
|
|
2848
|
+
throw error;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
this.pendingReinitTables = [];
|
|
2852
|
+
}
|
|
2853
|
+
readWriteTransaction(stores) {
|
|
2854
|
+
return new PostgreSQLReadWriteTransaction(this.db, this.tables, this);
|
|
2855
|
+
}
|
|
2856
|
+
readTransaction(stores) {
|
|
2857
|
+
return new PostgreSQLReadTransaction(this.db, this.tables, this);
|
|
2306
2858
|
}
|
|
2307
2859
|
}
|
|
2860
|
+
var createPostgreSQLAdapterFactory = (db) => {
|
|
2861
|
+
return async (context3) => {
|
|
2862
|
+
const adapter = new PostgreSQLAdapter(db, context3);
|
|
2863
|
+
await adapter.initialize();
|
|
2864
|
+
return adapter;
|
|
2865
|
+
};
|
|
2866
|
+
};
|
|
2308
2867
|
// db/sqliteAdapter.ts
|
|
2309
2868
|
class SQLiteReadTransaction {
|
|
2310
2869
|
db;
|
|
2311
2870
|
tables;
|
|
2312
|
-
|
|
2871
|
+
adapter;
|
|
2872
|
+
constructor(db, tables, adapter) {
|
|
2313
2873
|
this.db = db;
|
|
2314
2874
|
this.tables = tables;
|
|
2875
|
+
this.adapter = adapter;
|
|
2876
|
+
}
|
|
2877
|
+
hasSoftDelete(tableName) {
|
|
2878
|
+
if (this.adapter) {
|
|
2879
|
+
return this.adapter.hasSoftDelete(tableName);
|
|
2880
|
+
}
|
|
2881
|
+
const table = this.tables.get(tableName);
|
|
2882
|
+
if (!table)
|
|
2883
|
+
return false;
|
|
2884
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
2315
2885
|
}
|
|
2316
2886
|
deserializeValue(value, column) {
|
|
2317
2887
|
if (value === null || value === undefined)
|
|
2318
2888
|
return null;
|
|
2319
2889
|
switch (column.type.toLowerCase()) {
|
|
2320
2890
|
case "json":
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
return
|
|
2891
|
+
if (typeof value === "string") {
|
|
2892
|
+
try {
|
|
2893
|
+
return JSON.parse(value);
|
|
2894
|
+
} catch {
|
|
2895
|
+
return value;
|
|
2326
2896
|
}
|
|
2327
|
-
return value;
|
|
2328
|
-
} catch {
|
|
2329
|
-
return value;
|
|
2330
2897
|
}
|
|
2331
|
-
|
|
2898
|
+
return value;
|
|
2899
|
+
case "text":
|
|
2900
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
2901
|
+
try {
|
|
2902
|
+
const parsed = JSON.parse(value);
|
|
2903
|
+
if (typeof parsed === "object" || Array.isArray(parsed)) {
|
|
2904
|
+
return parsed;
|
|
2905
|
+
}
|
|
2906
|
+
} catch {}
|
|
2907
|
+
}
|
|
2908
|
+
return value;
|
|
2332
2909
|
case "datetime":
|
|
2333
2910
|
case "timestamp":
|
|
2334
2911
|
return new Date(value);
|
|
@@ -2347,12 +2924,18 @@ class SQLiteReadTransaction {
|
|
|
2347
2924
|
getId(store, id3) {
|
|
2348
2925
|
return id3;
|
|
2349
2926
|
}
|
|
2350
|
-
buildWhereClause(where) {
|
|
2927
|
+
buildWhereClause(where, tableName) {
|
|
2928
|
+
const conditions = [];
|
|
2929
|
+
const params = [];
|
|
2930
|
+
if (tableName && this.hasSoftDelete(tableName)) {
|
|
2931
|
+
conditions.push("deleted = 0");
|
|
2932
|
+
}
|
|
2351
2933
|
if (!where) {
|
|
2352
|
-
return {
|
|
2934
|
+
return {
|
|
2935
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
2936
|
+
params
|
|
2937
|
+
};
|
|
2353
2938
|
}
|
|
2354
|
-
const conditions = ["deleted != 1"];
|
|
2355
|
-
const params = [];
|
|
2356
2939
|
Object.entries(where).forEach(([key, value]) => {
|
|
2357
2940
|
if (typeof value === "object" && value !== null) {
|
|
2358
2941
|
Object.entries(value).forEach(([operator, operand]) => {
|
|
@@ -2409,7 +2992,7 @@ class SQLiteReadTransaction {
|
|
|
2409
2992
|
}
|
|
2410
2993
|
async find(store, options) {
|
|
2411
2994
|
const { where, limit, offset, orderBy } = options || {};
|
|
2412
|
-
const whereClause = this.buildWhereClause(where);
|
|
2995
|
+
const whereClause = this.buildWhereClause(where, store);
|
|
2413
2996
|
const orderByClause = this.buildOrderByClause(orderBy);
|
|
2414
2997
|
const table = this.tables.get(store);
|
|
2415
2998
|
if (!table) {
|
|
@@ -2429,6 +3012,11 @@ class SQLiteReadTransaction {
|
|
|
2429
3012
|
}
|
|
2430
3013
|
|
|
2431
3014
|
class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
3015
|
+
adapter;
|
|
3016
|
+
constructor(db, tables, adapter) {
|
|
3017
|
+
super(db, tables, adapter);
|
|
3018
|
+
this.adapter = adapter;
|
|
3019
|
+
}
|
|
2432
3020
|
async remove(store, id3) {
|
|
2433
3021
|
const table = this.tables.get(store);
|
|
2434
3022
|
if (!table) {
|
|
@@ -2442,7 +3030,14 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2442
3030
|
if (!table) {
|
|
2443
3031
|
throw new Error(`Store ${store} not found`);
|
|
2444
3032
|
}
|
|
2445
|
-
const
|
|
3033
|
+
const hasVersioning = this.adapter.hasVersioning(store);
|
|
3034
|
+
if (hasVersioning) {
|
|
3035
|
+
await this.setWithVersioning(store, item, table);
|
|
3036
|
+
} else {
|
|
3037
|
+
await this.setWithoutVersioning(store, item, table);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
async setWithoutVersioning(store, item, table) {
|
|
2446
3041
|
const columnNames = table.columns.map((col) => col.name);
|
|
2447
3042
|
const values = table.columns.map((column) => {
|
|
2448
3043
|
let value = item[column.name];
|
|
@@ -2459,6 +3054,32 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2459
3054
|
`;
|
|
2460
3055
|
await this.db.exec(sql, values);
|
|
2461
3056
|
}
|
|
3057
|
+
async setWithVersioning(store, item, table) {
|
|
3058
|
+
const regularColumns = table.columns.filter((col) => col.name !== "__version");
|
|
3059
|
+
const columnNames = regularColumns.map((col) => col.name);
|
|
3060
|
+
const values = regularColumns.map((column) => {
|
|
3061
|
+
let value = item[column.name];
|
|
3062
|
+
if (value === undefined && column.default !== undefined) {
|
|
3063
|
+
value = column.default;
|
|
3064
|
+
}
|
|
3065
|
+
return this.serializeValue(value, column);
|
|
3066
|
+
});
|
|
3067
|
+
columnNames.push("__version");
|
|
3068
|
+
const placeholders = regularColumns.map(() => "?").join(", ");
|
|
3069
|
+
const sql = `
|
|
3070
|
+
WITH next_version AS (
|
|
3071
|
+
INSERT INTO __arc_version_counters (table_name, last_version)
|
|
3072
|
+
VALUES (?, 1)
|
|
3073
|
+
ON CONFLICT(table_name)
|
|
3074
|
+
DO UPDATE SET last_version = last_version + 1
|
|
3075
|
+
RETURNING last_version
|
|
3076
|
+
)
|
|
3077
|
+
INSERT OR REPLACE INTO ${table.name}
|
|
3078
|
+
(${columnNames.map((c) => `"${c}"`).join(", ")})
|
|
3079
|
+
VALUES (${placeholders}, (SELECT last_version FROM next_version))
|
|
3080
|
+
`;
|
|
3081
|
+
await this.db.exec(sql, [...values, store]);
|
|
3082
|
+
}
|
|
2462
3083
|
async commit() {
|
|
2463
3084
|
return Promise.resolve();
|
|
2464
3085
|
}
|
|
@@ -2466,6 +3087,22 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
|
|
|
2466
3087
|
if (value === null || value === undefined)
|
|
2467
3088
|
return null;
|
|
2468
3089
|
switch (column.type.toLowerCase()) {
|
|
3090
|
+
case "timestamp":
|
|
3091
|
+
case "datetime":
|
|
3092
|
+
if (value instanceof Date) {
|
|
3093
|
+
return value.toISOString();
|
|
3094
|
+
}
|
|
3095
|
+
if (typeof value === "number") {
|
|
3096
|
+
const date3 = value > 10000000000 ? new Date(value) : new Date(value * 1000);
|
|
3097
|
+
return date3.toISOString();
|
|
3098
|
+
}
|
|
3099
|
+
if (typeof value === "string") {
|
|
3100
|
+
const date3 = new Date(value);
|
|
3101
|
+
if (!isNaN(date3.getTime())) {
|
|
3102
|
+
return date3.toISOString();
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
return value;
|
|
2469
3106
|
case "json":
|
|
2470
3107
|
return JSON.stringify(value);
|
|
2471
3108
|
default:
|
|
@@ -2484,58 +3121,238 @@ class SQLiteAdapter {
|
|
|
2484
3121
|
db;
|
|
2485
3122
|
context;
|
|
2486
3123
|
tables = new Map;
|
|
3124
|
+
tableSchemas = new Map;
|
|
3125
|
+
pendingReinitTables = [];
|
|
3126
|
+
mapType(arcType, storeData) {
|
|
3127
|
+
if (storeData?.databaseType?.sqlite) {
|
|
3128
|
+
return storeData.databaseType.sqlite;
|
|
3129
|
+
}
|
|
3130
|
+
switch (arcType) {
|
|
3131
|
+
case "string":
|
|
3132
|
+
case "id":
|
|
3133
|
+
case "customId":
|
|
3134
|
+
case "stringEnum":
|
|
3135
|
+
return "TEXT";
|
|
3136
|
+
case "number":
|
|
3137
|
+
return "INTEGER";
|
|
3138
|
+
case "boolean":
|
|
3139
|
+
return "INTEGER";
|
|
3140
|
+
case "date":
|
|
3141
|
+
return "TIMESTAMP";
|
|
3142
|
+
case "object":
|
|
3143
|
+
case "array":
|
|
3144
|
+
case "record":
|
|
3145
|
+
return "JSON";
|
|
3146
|
+
case "blob":
|
|
3147
|
+
return "BLOB";
|
|
3148
|
+
default:
|
|
3149
|
+
return "TEXT";
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
buildConstraints(storeData) {
|
|
3153
|
+
const constraints = [];
|
|
3154
|
+
if (storeData?.isPrimaryKey) {
|
|
3155
|
+
constraints.push("PRIMARY KEY");
|
|
3156
|
+
}
|
|
3157
|
+
if (storeData?.isAutoIncrement) {
|
|
3158
|
+
constraints.push("AUTOINCREMENT");
|
|
3159
|
+
}
|
|
3160
|
+
if (storeData?.isUnique) {
|
|
3161
|
+
constraints.push("UNIQUE");
|
|
3162
|
+
}
|
|
3163
|
+
if (storeData?.foreignKey) {
|
|
3164
|
+
const { table, column, onDelete, onUpdate } = storeData.foreignKey;
|
|
3165
|
+
let fkConstraint = `REFERENCES ${table}(${column})`;
|
|
3166
|
+
if (onDelete)
|
|
3167
|
+
fkConstraint += ` ON DELETE ${onDelete}`;
|
|
3168
|
+
if (onUpdate)
|
|
3169
|
+
fkConstraint += ` ON UPDATE ${onUpdate}`;
|
|
3170
|
+
constraints.push(fkConstraint);
|
|
3171
|
+
}
|
|
3172
|
+
return constraints;
|
|
3173
|
+
}
|
|
3174
|
+
generateColumnSQL(columnInfo) {
|
|
3175
|
+
const type = this.mapType(columnInfo.type, columnInfo.storeData);
|
|
3176
|
+
const constraints = [];
|
|
3177
|
+
if (!columnInfo.storeData?.isNullable) {
|
|
3178
|
+
constraints.push("NOT NULL");
|
|
3179
|
+
}
|
|
3180
|
+
constraints.push(...this.buildConstraints(columnInfo.storeData));
|
|
3181
|
+
if (columnInfo.defaultValue !== undefined) {
|
|
3182
|
+
constraints.push(`DEFAULT ${JSON.stringify(columnInfo.defaultValue)}`);
|
|
3183
|
+
}
|
|
3184
|
+
return `"${columnInfo.name}" ${type} ${constraints.join(" ")}`.trim();
|
|
3185
|
+
}
|
|
3186
|
+
generateCreateTableSQL(tableName, columns) {
|
|
3187
|
+
const columnDefinitions = columns.map((col) => this.generateColumnSQL(col));
|
|
3188
|
+
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});`);
|
|
3189
|
+
let sql = `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3190
|
+
${columnDefinitions.join(`,
|
|
3191
|
+
`)}
|
|
3192
|
+
)`;
|
|
3193
|
+
if (indexes.length > 0) {
|
|
3194
|
+
sql += `;
|
|
3195
|
+
` + indexes.join(`
|
|
3196
|
+
`);
|
|
3197
|
+
}
|
|
3198
|
+
return sql;
|
|
3199
|
+
}
|
|
2487
3200
|
constructor(db, context3) {
|
|
2488
3201
|
this.db = db;
|
|
2489
3202
|
this.context = context3;
|
|
2490
3203
|
this.context.elements.forEach((element3) => {
|
|
2491
|
-
if ("
|
|
2492
|
-
element3.
|
|
2493
|
-
|
|
3204
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
3205
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
3206
|
+
databaseSchema.tables.forEach((dbTable) => {
|
|
3207
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
3208
|
+
const columns = agnosticSchema.columns.map((columnInfo) => ({
|
|
3209
|
+
name: columnInfo.name,
|
|
3210
|
+
type: this.mapType(columnInfo.type, columnInfo.storeData),
|
|
3211
|
+
constraints: this.buildConstraints(columnInfo.storeData),
|
|
3212
|
+
isNullable: columnInfo.storeData?.isNullable || false,
|
|
3213
|
+
defaultValue: columnInfo.defaultValue,
|
|
3214
|
+
isPrimaryKey: columnInfo.storeData?.isPrimaryKey || false,
|
|
3215
|
+
isAutoIncrement: columnInfo.storeData?.isAutoIncrement || false,
|
|
3216
|
+
isUnique: columnInfo.storeData?.isUnique || false,
|
|
3217
|
+
hasIndex: columnInfo.storeData?.hasIndex || false,
|
|
3218
|
+
foreignKey: columnInfo.storeData?.foreignKey
|
|
3219
|
+
}));
|
|
3220
|
+
this.tableSchemas.set(dbTable.name, columns);
|
|
3221
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
3222
|
+
const legacyTable = {
|
|
3223
|
+
name: physicalTableName,
|
|
3224
|
+
primaryKey: columns.find((col) => col.isPrimaryKey)?.name || "_id",
|
|
3225
|
+
columns: columns.map((col) => ({
|
|
3226
|
+
name: col.name,
|
|
3227
|
+
type: col.type,
|
|
3228
|
+
isOptional: col.isNullable,
|
|
3229
|
+
default: col.defaultValue
|
|
3230
|
+
}))
|
|
3231
|
+
};
|
|
3232
|
+
this.tables.set(dbTable.name, legacyTable);
|
|
2494
3233
|
});
|
|
2495
3234
|
}
|
|
2496
3235
|
});
|
|
2497
3236
|
}
|
|
2498
3237
|
async initialize() {
|
|
2499
|
-
|
|
3238
|
+
await this.createVersionCounterTable();
|
|
3239
|
+
await this.createTableVersionsTable();
|
|
3240
|
+
const processedSchemas = new Set;
|
|
3241
|
+
const processedTables = new Set;
|
|
2500
3242
|
for (const element3 of this.context.elements) {
|
|
2501
|
-
if ("
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
3243
|
+
if ("databaseStoreSchema" in element3 && typeof element3.databaseStoreSchema === "function") {
|
|
3244
|
+
const databaseSchema = element3.databaseStoreSchema();
|
|
3245
|
+
if (processedSchemas.has(databaseSchema)) {
|
|
3246
|
+
continue;
|
|
3247
|
+
}
|
|
3248
|
+
processedSchemas.add(databaseSchema);
|
|
3249
|
+
for (const dbTable of databaseSchema.tables) {
|
|
3250
|
+
const tableKey = dbTable.version ? `${dbTable.name}_v${dbTable.version}` : dbTable.name;
|
|
3251
|
+
if (!processedTables.has(tableKey)) {
|
|
3252
|
+
const agnosticSchema = extractDatabaseAgnosticSchema(dbTable.schema, dbTable.name);
|
|
3253
|
+
let allColumns = [...agnosticSchema.columns];
|
|
3254
|
+
if (dbTable.options?.versioning) {
|
|
3255
|
+
allColumns.push({
|
|
3256
|
+
name: "__version",
|
|
3257
|
+
type: "number",
|
|
3258
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
3259
|
+
defaultValue: 1
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
if (dbTable.options?.softDelete) {
|
|
3263
|
+
allColumns.push({
|
|
3264
|
+
name: "deleted",
|
|
3265
|
+
type: "boolean",
|
|
3266
|
+
storeData: { isNullable: false, hasIndex: true },
|
|
3267
|
+
defaultValue: false
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
const physicalTableName = this.getPhysicalTableName(dbTable.name, dbTable.version);
|
|
3271
|
+
if (dbTable.version && await this.checkVersionedTableExists(dbTable.name, dbTable.version)) {
|
|
3272
|
+
console.log(`Versioned table ${physicalTableName} already exists, skipping creation`);
|
|
3273
|
+
} else {
|
|
3274
|
+
await this.createTableIfNotExistsNew(physicalTableName, allColumns);
|
|
3275
|
+
if (dbTable.version) {
|
|
3276
|
+
await this.registerTableVersion(dbTable.name, dbTable.version, physicalTableName);
|
|
3277
|
+
if (databaseSchema.reinitTable) {
|
|
3278
|
+
this.pendingReinitTables.push({
|
|
3279
|
+
tableName: physicalTableName,
|
|
3280
|
+
reinitFn: databaseSchema.reinitTable
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
processedTables.add(tableKey);
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
2509
3288
|
}
|
|
2510
3289
|
}
|
|
2511
3290
|
}
|
|
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
|
-
`)}
|
|
3291
|
+
async createTableIfNotExistsNew(tableName, columns) {
|
|
3292
|
+
const createTableSQL = this.generateCreateTableSQL(tableName, columns);
|
|
3293
|
+
await this.db.exec(createTableSQL);
|
|
3294
|
+
}
|
|
3295
|
+
async createVersionCounterTable() {
|
|
3296
|
+
const sql = `
|
|
3297
|
+
CREATE TABLE IF NOT EXISTS __arc_version_counters (
|
|
3298
|
+
table_name TEXT PRIMARY KEY,
|
|
3299
|
+
last_version INTEGER NOT NULL DEFAULT 0
|
|
2530
3300
|
)
|
|
2531
3301
|
`;
|
|
2532
|
-
await this.db.exec(
|
|
3302
|
+
await this.db.exec(sql);
|
|
3303
|
+
}
|
|
3304
|
+
async createTableVersionsTable() {
|
|
3305
|
+
const sql = `
|
|
3306
|
+
CREATE TABLE IF NOT EXISTS __arc_table_versions (
|
|
3307
|
+
table_name TEXT NOT NULL,
|
|
3308
|
+
version INTEGER NOT NULL,
|
|
3309
|
+
physical_table_name TEXT NOT NULL,
|
|
3310
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
3311
|
+
is_active INTEGER DEFAULT 0,
|
|
3312
|
+
PRIMARY KEY (table_name, version)
|
|
3313
|
+
)
|
|
3314
|
+
`;
|
|
3315
|
+
await this.db.exec(sql);
|
|
3316
|
+
}
|
|
3317
|
+
getPhysicalTableName(logicalName, version) {
|
|
3318
|
+
return version ? `${logicalName}_v${version}` : logicalName;
|
|
3319
|
+
}
|
|
3320
|
+
async checkVersionedTableExists(logicalName, version) {
|
|
3321
|
+
const result = await this.db.exec("SELECT COUNT(*) as count FROM __arc_table_versions WHERE table_name = ? AND version = ?", [logicalName, version]);
|
|
3322
|
+
return result[0]?.count > 0;
|
|
3323
|
+
}
|
|
3324
|
+
async registerTableVersion(logicalName, version, physicalName) {
|
|
3325
|
+
await this.db.exec("INSERT INTO __arc_table_versions (table_name, version, physical_table_name, is_active) VALUES (?, ?, ?, ?)", [logicalName, version, physicalName, 1]);
|
|
3326
|
+
}
|
|
3327
|
+
hasVersioning(tableName) {
|
|
3328
|
+
const table = this.tables.get(tableName);
|
|
3329
|
+
if (!table)
|
|
3330
|
+
return false;
|
|
3331
|
+
return table.columns.some((col) => col.name === "__version");
|
|
3332
|
+
}
|
|
3333
|
+
hasSoftDelete(tableName) {
|
|
3334
|
+
const table = this.tables.get(tableName);
|
|
3335
|
+
if (!table)
|
|
3336
|
+
return false;
|
|
3337
|
+
return table.columns.some((col) => col.name === "deleted");
|
|
3338
|
+
}
|
|
3339
|
+
async executeReinitTables(dataStorage) {
|
|
3340
|
+
for (const { tableName, reinitFn } of this.pendingReinitTables) {
|
|
3341
|
+
try {
|
|
3342
|
+
await reinitFn(tableName, dataStorage);
|
|
3343
|
+
console.log(`Successfully reinitialized table: ${tableName}`);
|
|
3344
|
+
} catch (error) {
|
|
3345
|
+
console.error(`Failed to reinitialize table ${tableName}:`, error);
|
|
3346
|
+
throw error;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
this.pendingReinitTables = [];
|
|
2533
3350
|
}
|
|
2534
3351
|
readWriteTransaction(stores) {
|
|
2535
|
-
return new SQLiteReadWriteTransaction(this.db, this.tables);
|
|
3352
|
+
return new SQLiteReadWriteTransaction(this.db, this.tables, this);
|
|
2536
3353
|
}
|
|
2537
3354
|
readTransaction(stores) {
|
|
2538
|
-
return new SQLiteReadTransaction(this.db, this.tables);
|
|
3355
|
+
return new SQLiteReadTransaction(this.db, this.tables, this);
|
|
2539
3356
|
}
|
|
2540
3357
|
}
|
|
2541
3358
|
var createSQLiteAdapterFactory = (db) => {
|
|
@@ -2599,17 +3416,17 @@ class ArcListener extends ArcContextElement {
|
|
|
2599
3416
|
return (e) => {
|
|
2600
3417
|
const element4 = this._elements.find((element5) => element5.name === e.name);
|
|
2601
3418
|
if (!element4) {
|
|
2602
|
-
throw new Error(`Element "${String(name)}" not found in listener "${this.name}"`);
|
|
3419
|
+
throw new Error(`Element "${String(e.name)}" not found in listener "${this.name}"`);
|
|
2603
3420
|
}
|
|
2604
3421
|
if (!element4.commandContext) {
|
|
2605
|
-
throw new Error(`Element "${String(name)}" does not have a command context`);
|
|
3422
|
+
throw new Error(`Element "${String(e.name)}" does not have a command context`);
|
|
2606
3423
|
}
|
|
2607
3424
|
return element4.commandContext(dataStorage, publishEvent, authContext);
|
|
2608
3425
|
};
|
|
2609
3426
|
}
|
|
2610
3427
|
const element3 = this._elements.find((element4) => element4.name === name);
|
|
2611
3428
|
if (!element3) {
|
|
2612
|
-
throw new Error(`Element "${
|
|
3429
|
+
throw new Error(`Element "${element3}" not found in listener "${this.name}"`);
|
|
2613
3430
|
}
|
|
2614
3431
|
if (!element3.commandContext) {
|
|
2615
3432
|
throw new Error(`Element "${String(name)}" does not have a command context`);
|
|
@@ -2673,14 +3490,14 @@ class EventPublisher {
|
|
|
2673
3490
|
async runAsyncListeners() {
|
|
2674
3491
|
const allAsyncTasks = this.asyncEvents.flatMap(({ event: event3, listeners, authContext }) => listeners.map(async (listener3) => {
|
|
2675
3492
|
const listenerFork = this.dataStorage.fork();
|
|
3493
|
+
const childPublisher = new EventPublisher(this.context, this.dataStorage, authContext);
|
|
2676
3494
|
const asyncPublishEvent = async (childEvent) => {
|
|
2677
|
-
const childPublisher = new EventPublisher(this.context, listenerFork, authContext);
|
|
2678
3495
|
await childPublisher.publishEvent(childEvent, listenerFork);
|
|
2679
|
-
await childPublisher.runAsyncListeners();
|
|
2680
3496
|
};
|
|
2681
3497
|
try {
|
|
2682
3498
|
await listener3(event3, listenerFork, asyncPublishEvent);
|
|
2683
3499
|
await listenerFork.merge();
|
|
3500
|
+
childPublisher.runAsyncListeners();
|
|
2684
3501
|
} catch (error) {
|
|
2685
3502
|
console.error("Async listener failed:", error);
|
|
2686
3503
|
}
|
|
@@ -3154,6 +3971,22 @@ class RTCClient {
|
|
|
3154
3971
|
var rtcClientFactory = (token) => (storage) => {
|
|
3155
3972
|
return new RTCClient(storage, token);
|
|
3156
3973
|
};
|
|
3974
|
+
// context/serializable-query.ts
|
|
3975
|
+
class ArcSerializableQuery extends ArcQuery {
|
|
3976
|
+
params;
|
|
3977
|
+
static key;
|
|
3978
|
+
constructor(params) {
|
|
3979
|
+
super();
|
|
3980
|
+
this.params = params;
|
|
3981
|
+
}
|
|
3982
|
+
serialize() {
|
|
3983
|
+
return {
|
|
3984
|
+
key: this.constructor.key,
|
|
3985
|
+
params: this.params
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3157
3990
|
// view/queries/abstract-view-query.ts
|
|
3158
3991
|
class ArcViewQuery extends ArcSerializableQuery {
|
|
3159
3992
|
view;
|
|
@@ -3457,6 +4290,7 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3457
4290
|
_elements;
|
|
3458
4291
|
_handler;
|
|
3459
4292
|
_isAsync = false;
|
|
4293
|
+
_version;
|
|
3460
4294
|
restrictions;
|
|
3461
4295
|
constructor(name, id3, schema) {
|
|
3462
4296
|
super();
|
|
@@ -3464,8 +4298,84 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3464
4298
|
this.id = id3;
|
|
3465
4299
|
this.schema = schema;
|
|
3466
4300
|
}
|
|
3467
|
-
|
|
3468
|
-
|
|
4301
|
+
databaseStoreSchema = () => {
|
|
4302
|
+
const systemFields = {
|
|
4303
|
+
_id: string().primaryKey()
|
|
4304
|
+
};
|
|
4305
|
+
const completeSchema = this.schema.merge(systemFields);
|
|
4306
|
+
return {
|
|
4307
|
+
tables: [
|
|
4308
|
+
{
|
|
4309
|
+
name: this.name,
|
|
4310
|
+
schema: completeSchema,
|
|
4311
|
+
version: this._version,
|
|
4312
|
+
options: {
|
|
4313
|
+
softDelete: true,
|
|
4314
|
+
versioning: true
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
],
|
|
4318
|
+
reinitTable: async (tableName, dataStorage) => {
|
|
4319
|
+
console.log(`Reinitializing view table: ${tableName} for view: ${this.name}`);
|
|
4320
|
+
if (this._handler && this._elements) {
|
|
4321
|
+
try {
|
|
4322
|
+
const eventStore = dataStorage.getStore("events");
|
|
4323
|
+
const viewStore = dataStorage.getStore(this.name);
|
|
4324
|
+
const events = await eventStore.find({
|
|
4325
|
+
where: {
|
|
4326
|
+
type: {
|
|
4327
|
+
$in: this._elements.filter((element3) => element3 instanceof ArcEvent).map((element3) => element3.name)
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
});
|
|
4331
|
+
console.log(`Found ${events.length} events to replay for view ${this.name}`);
|
|
4332
|
+
const reinitContext = {
|
|
4333
|
+
set: async (id3, data) => {
|
|
4334
|
+
const parsed = this.schema.parse(data);
|
|
4335
|
+
const body = {
|
|
4336
|
+
_id: id3,
|
|
4337
|
+
lastUpdate: new Date().toISOString(),
|
|
4338
|
+
...parsed
|
|
4339
|
+
};
|
|
4340
|
+
await viewStore.set(body);
|
|
4341
|
+
},
|
|
4342
|
+
modify: async (id3, data) => {
|
|
4343
|
+
await viewStore.modify(id3, {
|
|
4344
|
+
...data,
|
|
4345
|
+
lastUpdate: new Date().toISOString()
|
|
4346
|
+
});
|
|
4347
|
+
},
|
|
4348
|
+
remove: async (id3) => {
|
|
4349
|
+
await viewStore.remove(id3);
|
|
4350
|
+
},
|
|
4351
|
+
find: async (options) => {
|
|
4352
|
+
return viewStore.find(options);
|
|
4353
|
+
},
|
|
4354
|
+
findOne: async (where) => {
|
|
4355
|
+
const result = await viewStore.find({ where, limit: 1 });
|
|
4356
|
+
return result[0];
|
|
4357
|
+
},
|
|
4358
|
+
$auth: {}
|
|
4359
|
+
};
|
|
4360
|
+
for (const event3 of events) {
|
|
4361
|
+
if (this._handler && this._handler[event3.type]) {
|
|
4362
|
+
try {
|
|
4363
|
+
await this._handler[event3.type](reinitContext, event3);
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
console.error(`Error processing event ${event3.type} for view ${this.name}:`, error);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
console.log(`Successfully reinitialized view ${this.name} with ${events.length} events`);
|
|
4370
|
+
} catch (error) {
|
|
4371
|
+
console.error(`Failed to reinitialize view ${this.name}:`, error);
|
|
4372
|
+
throw error;
|
|
4373
|
+
}
|
|
4374
|
+
} else {
|
|
4375
|
+
console.log(`No handlers defined for view ${this.name}, skipping event replay`);
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
};
|
|
3469
4379
|
};
|
|
3470
4380
|
auth(restrictionsFn) {
|
|
3471
4381
|
const clone = this.clone();
|
|
@@ -3487,6 +4397,11 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3487
4397
|
clone._isAsync = true;
|
|
3488
4398
|
return clone;
|
|
3489
4399
|
}
|
|
4400
|
+
version(version) {
|
|
4401
|
+
const clone = this.clone();
|
|
4402
|
+
clone._version = version;
|
|
4403
|
+
return clone;
|
|
4404
|
+
}
|
|
3490
4405
|
handle(handler) {
|
|
3491
4406
|
const clone = this.clone();
|
|
3492
4407
|
clone._handler = handler;
|
|
@@ -3607,6 +4522,7 @@ class ArcView extends ArcContextElementWithStore {
|
|
|
3607
4522
|
clone._elements = this._elements;
|
|
3608
4523
|
clone._handler = this._handler;
|
|
3609
4524
|
clone._isAsync = this._isAsync;
|
|
4525
|
+
clone._version = this._version;
|
|
3610
4526
|
clone.restrictions = this.restrictions;
|
|
3611
4527
|
return clone;
|
|
3612
4528
|
}
|
|
@@ -3634,14 +4550,13 @@ export {
|
|
|
3634
4550
|
date,
|
|
3635
4551
|
customId,
|
|
3636
4552
|
createSQLiteAdapterFactory,
|
|
4553
|
+
createPostgreSQLAdapterFactory,
|
|
3637
4554
|
contextMerge,
|
|
3638
4555
|
context,
|
|
3639
4556
|
command,
|
|
3640
|
-
collection,
|
|
3641
4557
|
boolean,
|
|
3642
4558
|
blob,
|
|
3643
4559
|
array,
|
|
3644
|
-
arcObjectToStoreSchema,
|
|
3645
4560
|
any,
|
|
3646
4561
|
StoreState,
|
|
3647
4562
|
SQLiteAdapter,
|
|
@@ -3649,6 +4564,7 @@ export {
|
|
|
3649
4564
|
QueryViewResult,
|
|
3650
4565
|
QueryCache,
|
|
3651
4566
|
QueryBuilderContext,
|
|
4567
|
+
PostgreSQLAdapter,
|
|
3652
4568
|
ModelBase,
|
|
3653
4569
|
Model,
|
|
3654
4570
|
MasterStoreState,
|
|
@@ -3681,8 +4597,6 @@ export {
|
|
|
3681
4597
|
ArcContextElement,
|
|
3682
4598
|
ArcContext,
|
|
3683
4599
|
ArcCommand,
|
|
3684
|
-
ArcCollectionQuery,
|
|
3685
|
-
ArcCollection,
|
|
3686
4600
|
ArcBranded,
|
|
3687
4601
|
ArcBoolean,
|
|
3688
4602
|
ArcBlob,
|