edinburgh 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +691 -212
- package/build/src/datapack.d.ts +22 -3
- package/build/src/datapack.js +105 -41
- package/build/src/datapack.js.map +1 -1
- package/build/src/edinburgh.d.ts +31 -13
- package/build/src/edinburgh.js +149 -62
- package/build/src/edinburgh.js.map +1 -1
- package/build/src/indexes.d.ts +78 -56
- package/build/src/indexes.js +519 -284
- package/build/src/indexes.js.map +1 -1
- package/build/src/migrate-cli.d.ts +20 -0
- package/build/src/migrate-cli.js +122 -0
- package/build/src/migrate-cli.js.map +1 -0
- package/build/src/migrate.d.ts +33 -0
- package/build/src/migrate.js +225 -0
- package/build/src/migrate.js.map +1 -0
- package/build/src/models.d.ts +130 -25
- package/build/src/models.js +271 -169
- package/build/src/models.js.map +1 -1
- package/build/src/types.d.ts +24 -7
- package/build/src/types.js +49 -15
- package/build/src/types.js.map +1 -1
- package/build/src/utils.d.ts +6 -10
- package/build/src/utils.js +26 -32
- package/build/src/utils.js.map +1 -1
- package/package.json +12 -10
- package/skill/SKILL.md +1349 -0
- package/src/datapack.ts +117 -46
- package/src/edinburgh.ts +156 -64
- package/src/indexes.ts +550 -287
- package/src/migrate-cli.ts +138 -0
- package/src/migrate.ts +267 -0
- package/src/models.ts +352 -184
- package/src/types.ts +59 -16
- package/src/utils.ts +32 -32
package/src/types.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import DataPack from "./datapack.js";
|
|
2
|
+
import { DatabaseError } from "olmdb/lowlevel";
|
|
3
|
+
import { Model, modelRegistry, FieldConfig, currentTxn } from "./models.js";
|
|
4
|
+
import { assert, addErrorPath, dbGet } from "./utils.js";
|
|
5
|
+
import { PrimaryIndex, BaseIndex, IndexRangeIterator } from "./indexes.js";
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -70,6 +70,10 @@ export abstract class TypeWrapper<const T> {
|
|
|
70
70
|
equals(value1: T, value2: T): boolean {
|
|
71
71
|
return value1 === value2;
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
getLinkedModel(): (typeof Model<unknown>) | undefined {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
|
|
@@ -226,6 +230,10 @@ class ArrayType<T> extends TypeWrapper<T[]> {
|
|
|
226
230
|
return new ArrayType(inner);
|
|
227
231
|
}
|
|
228
232
|
|
|
233
|
+
default(): T[] {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
229
237
|
clone(value: T[]): T[] {
|
|
230
238
|
return value.map(this.inner.clone.bind(this.inner));
|
|
231
239
|
}
|
|
@@ -238,6 +246,11 @@ class ArrayType<T> extends TypeWrapper<T[]> {
|
|
|
238
246
|
return true;
|
|
239
247
|
}
|
|
240
248
|
|
|
249
|
+
toString() { return `array<${this.inner}>`; }
|
|
250
|
+
|
|
251
|
+
getLinkedModel() {
|
|
252
|
+
return this.inner.getLinkedModel();
|
|
253
|
+
}
|
|
241
254
|
}
|
|
242
255
|
|
|
243
256
|
/**
|
|
@@ -320,6 +333,12 @@ export class SetType<T> extends TypeWrapper<Set<T>> {
|
|
|
320
333
|
}
|
|
321
334
|
return true;
|
|
322
335
|
}
|
|
336
|
+
|
|
337
|
+
toString() { return `set<${this.inner}>`; }
|
|
338
|
+
|
|
339
|
+
getLinkedModel() {
|
|
340
|
+
return this.inner.getLinkedModel();
|
|
341
|
+
}
|
|
323
342
|
}
|
|
324
343
|
|
|
325
344
|
|
|
@@ -398,6 +417,20 @@ class OrType<const T> extends TypeWrapper<T> {
|
|
|
398
417
|
const cb = this._getChoiceIndex(b);
|
|
399
418
|
return ca === cb && this.choices[ca].equals(a, b);
|
|
400
419
|
}
|
|
420
|
+
|
|
421
|
+
toString() { return `or<${this.choices.join('|')}>`; }
|
|
422
|
+
|
|
423
|
+
getLinkedModel() {
|
|
424
|
+
let model;
|
|
425
|
+
for (const choice of this.choices) {
|
|
426
|
+
const m = choice.getLinkedModel();
|
|
427
|
+
if (m) {
|
|
428
|
+
if (model && model !== m) throw new DatabaseError(`Union type has multiple linked models, unsupported by getLinkedModel()`, 'INVALID_TYPE');
|
|
429
|
+
model = m;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return model;
|
|
433
|
+
}
|
|
401
434
|
}
|
|
402
435
|
|
|
403
436
|
/**
|
|
@@ -443,6 +476,8 @@ class LiteralType<const T> extends TypeWrapper<T> {
|
|
|
443
476
|
return new LiteralType(value);
|
|
444
477
|
}
|
|
445
478
|
|
|
479
|
+
toString() { return `literal<${JSON.stringify(this.value)}>`; }
|
|
480
|
+
|
|
446
481
|
default(): T {
|
|
447
482
|
return this.value;
|
|
448
483
|
}
|
|
@@ -481,7 +516,7 @@ class IdentifierType extends TypeWrapper<string> {
|
|
|
481
516
|
let id: string;
|
|
482
517
|
do {
|
|
483
518
|
id = DataPack.generateIdentifier();
|
|
484
|
-
} while (
|
|
519
|
+
} while (dbGet(model._txn.id, new DataPack().write(model.constructor._primary!._indexId!).writeIdentifier(id).toUint8Array()));
|
|
485
520
|
return id;
|
|
486
521
|
}
|
|
487
522
|
}
|
|
@@ -492,33 +527,33 @@ class IdentifierType extends TypeWrapper<string> {
|
|
|
492
527
|
*/
|
|
493
528
|
export class LinkType<T extends typeof Model<unknown>> extends TypeWrapper<InstanceType<T>> {
|
|
494
529
|
kind = 'link';
|
|
495
|
-
|
|
496
|
-
|
|
530
|
+
tableName: string;
|
|
531
|
+
|
|
497
532
|
/**
|
|
498
533
|
* Create a new LinkType.
|
|
499
534
|
* @param TargetModel - The model class this link points to.
|
|
500
535
|
*/
|
|
501
536
|
constructor(TargetModel: T) {
|
|
502
537
|
super();
|
|
503
|
-
this.
|
|
538
|
+
this.tableName = (TargetModel as any).tableName || TargetModel.name;
|
|
504
539
|
}
|
|
505
540
|
|
|
506
541
|
serialize(model: InstanceType<T>, pack: DataPack) {
|
|
507
|
-
pack.write(model.
|
|
542
|
+
pack.write(model.getPrimaryKey());
|
|
508
543
|
}
|
|
509
544
|
|
|
510
545
|
deserialize(pack: DataPack) {
|
|
511
|
-
return this.
|
|
546
|
+
return modelRegistry[this.tableName]._primary!._get(currentTxn(), pack.readUint8Array(), false);
|
|
512
547
|
}
|
|
513
548
|
|
|
514
549
|
getError(value: InstanceType<T>) {
|
|
515
|
-
if (!(value instanceof this.
|
|
516
|
-
return new DatabaseError(`Expected instance of ${this.
|
|
550
|
+
if (!(value instanceof modelRegistry[this.tableName])) {
|
|
551
|
+
return new DatabaseError(`Expected instance of ${this.tableName}, got ${typeof value}`, 'VALUE_ERROR');
|
|
517
552
|
}
|
|
518
553
|
}
|
|
519
554
|
|
|
520
555
|
serializeType(pack: DataPack): void {
|
|
521
|
-
pack.write(this.
|
|
556
|
+
pack.write(this.tableName);
|
|
522
557
|
}
|
|
523
558
|
|
|
524
559
|
static deserializeType(pack: DataPack, featureFlags: number): LinkType<any> {
|
|
@@ -527,6 +562,12 @@ export class LinkType<T extends typeof Model<unknown>> extends TypeWrapper<Insta
|
|
|
527
562
|
if (!targetModel) throw new DatabaseError(`Could not deserialize undefined model ${tableName}`, 'DESERIALIZATION_ERROR');
|
|
528
563
|
return new LinkType(targetModel);
|
|
529
564
|
}
|
|
565
|
+
|
|
566
|
+
toString() { return `link<${this.tableName}>`; }
|
|
567
|
+
|
|
568
|
+
getLinkedModel(): T {
|
|
569
|
+
return modelRegistry[this.tableName] as T;
|
|
570
|
+
}
|
|
530
571
|
}
|
|
531
572
|
|
|
532
573
|
/** Type wrapper instance for the string type. */
|
|
@@ -685,13 +726,14 @@ export function serializeType(arg: TypeWrapper<any>, pack: DataPack) {
|
|
|
685
726
|
const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (pack: DataPack, featureFlags: number) => TypeWrapper<any>}> = {
|
|
686
727
|
string: string,
|
|
687
728
|
number: number,
|
|
729
|
+
dateTime: dateTime,
|
|
730
|
+
boolean: boolean,
|
|
688
731
|
array: ArrayType,
|
|
732
|
+
set: SetType,
|
|
689
733
|
or: OrType,
|
|
690
734
|
literal: LiteralType,
|
|
691
|
-
boolean: boolean,
|
|
692
735
|
id: identifier,
|
|
693
736
|
link: LinkType,
|
|
694
|
-
set: SetType
|
|
695
737
|
};
|
|
696
738
|
|
|
697
739
|
/**
|
|
@@ -703,6 +745,7 @@ const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (pack:
|
|
|
703
745
|
export function deserializeType(pack: DataPack, featureFlags: number): TypeWrapper<any> {
|
|
704
746
|
const kind = pack.readString();
|
|
705
747
|
const TypeWrapper = TYPE_WRAPPERS[kind];
|
|
748
|
+
if (!TypeWrapper) throw new DatabaseError(`Unknown field type in database: ${kind}`, 'CONSISTENCY_ERROR');
|
|
706
749
|
if ('deserializeType' in TypeWrapper) {
|
|
707
750
|
return TypeWrapper.deserializeType(pack, featureFlags);
|
|
708
751
|
} else {
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as lowlevel from "olmdb/lowlevel";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Assert function for runtime checks with TypeScript assertion support.
|
|
3
5
|
* @param cond - Condition to check.
|
|
@@ -10,6 +12,33 @@ export function assert(cond: any, message?: string): asserts cond {
|
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
export function toBuffer(data: Uint8Array): ArrayBufferLike {
|
|
16
|
+
const b = data.buffer;
|
|
17
|
+
return b.byteLength === data.byteLength ? b : b.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hashBytes(data: Uint8Array): number {
|
|
21
|
+
let a = 0x811C9DC5, b = 0x811C9DC5;
|
|
22
|
+
for (const ch of data) {
|
|
23
|
+
a = Math.imul(a ^ ch, 0x517CC1B7) >>> 0;
|
|
24
|
+
b = Math.imul(b ^ ch, 0x27220A95) >>> 0;
|
|
25
|
+
}
|
|
26
|
+
return a + ((b & 0x1FFFFF) * 0x100000000);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function dbGet(txnId: number, key: Uint8Array): Uint8Array | undefined {
|
|
30
|
+
const result = lowlevel.get(txnId, toBuffer(key));
|
|
31
|
+
return result ? new Uint8Array(result) : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function dbPut(txnId: number, key: Uint8Array, value: Uint8Array): void {
|
|
35
|
+
lowlevel.put(txnId, toBuffer(key), toBuffer(value));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function dbDel(txnId: number, key: Uint8Array): void {
|
|
39
|
+
lowlevel.del(txnId, toBuffer(key));
|
|
40
|
+
}
|
|
41
|
+
|
|
13
42
|
/**
|
|
14
43
|
* Regular expression for parsing error messages with paths.
|
|
15
44
|
*/
|
|
@@ -29,39 +58,10 @@ export function addErrorPath<T>(error: T, path: string | number): T {
|
|
|
29
58
|
return error;
|
|
30
59
|
}
|
|
31
60
|
|
|
32
|
-
// Get
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Global log level for debugging output.
|
|
37
|
-
* 0 = no logging, 1 = model-level logs, 2 = update logs, 3 = read logs.
|
|
38
|
-
*/
|
|
39
|
-
export function setLogLevel(level: number) {
|
|
40
|
-
logLevel = level;
|
|
41
|
-
}
|
|
61
|
+
// Get log level from environment variable
|
|
62
|
+
// 0: no logging (default), 1: model-level logs, 2: add update logs, 3: add read logs
|
|
63
|
+
export const logLevel = parseInt(process.env.EDINBURGH_LOG_LEVEL || "0") || 0;
|
|
42
64
|
|
|
43
65
|
/** @internal Symbol used to access the underlying model from a proxy */
|
|
44
66
|
export declare const TARGET_SYMBOL: unique symbol;
|
|
45
67
|
|
|
46
|
-
export const delayedInits = new Set<{_delayedInit: () => boolean}>();
|
|
47
|
-
let tryingDelayedInits = false;
|
|
48
|
-
|
|
49
|
-
export function tryDelayedInits() {
|
|
50
|
-
if (tryingDelayedInits) return;
|
|
51
|
-
tryingDelayedInits = true;
|
|
52
|
-
let progress = true;
|
|
53
|
-
while(progress) {
|
|
54
|
-
progress = false;
|
|
55
|
-
for(const target of delayedInits) {
|
|
56
|
-
try {
|
|
57
|
-
if (target._delayedInit()) {
|
|
58
|
-
delayedInits.delete(target);
|
|
59
|
-
progress = true;
|
|
60
|
-
}
|
|
61
|
-
} catch(e) {
|
|
62
|
-
console.error("Error during delayed init:", e);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
tryingDelayedInits = false;
|
|
67
|
-
}
|