edinburgh 0.1.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/src/types.ts ADDED
@@ -0,0 +1,635 @@
1
+ import { Bytes } from "./bytes.js";
2
+ import * as olmdb from "olmdb";
3
+ import { DatabaseError } from "olmdb";
4
+ import { Model, modelRegistry, getMockModel } from "./models.js";
5
+ import { assert, addErrorPath } from "./utils.js";
6
+
7
+ /**
8
+ * @internal Abstract base class for all type wrappers in the Edinburgh ORM system.
9
+ *
10
+ * This is an implementation detail and should not be referenced directly in user code.
11
+ * Type wrappers define how values are serialized to/from the database and how they are validated.
12
+ * Each type wrapper must implement serialization, deserialization, and validation logic.
13
+ *
14
+ * @template T - The TypeScript type this wrapper represents.
15
+ */
16
+ export abstract class TypeWrapper<const T> {
17
+ /** @internal Used for TypeScript type inference - this field is required for the type system */
18
+ _T!: T;
19
+
20
+ /** A string identifier for this type, used during serialization */
21
+ abstract kind: string;
22
+
23
+ constructor() {}
24
+
25
+ /**
26
+ * Serialize a value from an object property to bytes.
27
+ * @param obj - The object containing the value.
28
+ * @param prop - The property name or index.
29
+ * @param bytes - The Bytes instance to write to.
30
+ * @param model - Optional model instance for context.
31
+ */
32
+ abstract serialize(obj: any, prop: string|number, bytes: Bytes, model?: any): void;
33
+
34
+ /**
35
+ * Deserialize a value from bytes into an object property.
36
+ * @param obj - The object to set the value on.
37
+ * @param prop - The property name or index.
38
+ * @param bytes - The Bytes instance to read from.
39
+ * @param model - Optional model instance for context.
40
+ */
41
+ abstract deserialize(obj: any, prop: string|number, bytes: Bytes, model?: any): void;
42
+
43
+ /**
44
+ * Validate a value and return any validation errors.
45
+ * @param obj - The object containing the value.
46
+ * @param prop - The property name or index.
47
+ * @returns Array of validation errors (empty if valid).
48
+ */
49
+ abstract getErrors(obj: any, prop: string | number): DatabaseError[];
50
+
51
+ /**
52
+ * Validate a value and serialize it, throwing on validation errors.
53
+ * @param obj - The object containing the value.
54
+ * @param prop - The property name or index.
55
+ * @param bytes - The Bytes instance to write to.
56
+ * @param model - Optional model instance for context.
57
+ * @throws {DatabaseError} If validation fails.
58
+ */
59
+ validateAndSerialize(obj: any, prop: string|number, bytes: Bytes, model?: any): void {
60
+ const errors = this.getErrors(obj, prop);
61
+ if (errors.length) throw errors[0];
62
+ this.serialize(obj, prop, bytes, model);
63
+ }
64
+
65
+ /**
66
+ * Serialize type metadata to bytes (for schema serialization).
67
+ * @param bytes - The Bytes instance to write to.
68
+ */
69
+ serializeType(bytes: Bytes) {}
70
+
71
+ /**
72
+ * Check if indexing should be skipped for this field value.
73
+ * @param obj - The object containing the value.
74
+ * @param prop - The property name or index.
75
+ * @returns true if indexing should be skipped.
76
+ */
77
+ checkSkipIndex(obj: any, prop: string | number): boolean {
78
+ return false;
79
+ }
80
+
81
+ toString(): string {
82
+ return `${this.kind}`;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Optional interface for type wrappers that can provide default values.
88
+ * @template T - The TypeScript type this wrapper represents.
89
+ */
90
+ export interface TypeWrapper<T> {
91
+ /**
92
+ * Generate a default value for this type.
93
+ * @param model - The model instance.
94
+ * @returns The default value.
95
+ */
96
+ default?(model: any): T;
97
+ }
98
+
99
+ /**
100
+ * @internal Type wrapper for string values.
101
+ */
102
+ class StringType extends TypeWrapper<string> {
103
+ kind = 'string';
104
+
105
+ serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
106
+ bytes.writeString(obj[prop]);
107
+ }
108
+
109
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
110
+ obj[prop] = bytes.readString();
111
+ }
112
+
113
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
114
+ if (typeof obj[prop] !== 'string') {
115
+ return [new DatabaseError(`Expected string, got ${typeof obj[prop]}`, 'INVALID_TYPE')];
116
+ }
117
+ return [];
118
+ }
119
+ }
120
+
121
+ /**
122
+ * @internal Type wrapper for number values.
123
+ */
124
+ class NumberType extends TypeWrapper<number> {
125
+ kind = 'number';
126
+
127
+ serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
128
+ bytes.writeNumber(obj[prop]);
129
+ }
130
+
131
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
132
+ obj[prop] = bytes.readNumber();
133
+ }
134
+
135
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
136
+ const value = obj[prop];
137
+ if (typeof value !== 'number' || isNaN(value)) {
138
+ return [new DatabaseError(`Expected number, got ${typeof value}`, 'INVALID_TYPE')];
139
+ }
140
+ return [];
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @internal Type wrapper for boolean values.
146
+ */
147
+ class BooleanType extends TypeWrapper<boolean> {
148
+ kind = 'boolean';
149
+
150
+ serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
151
+ bytes.writeBits(obj[prop] ? 1 : 0, 1);
152
+ }
153
+
154
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
155
+ obj[prop] = bytes.readBits(1) === 1;
156
+ }
157
+
158
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
159
+ if (typeof obj[prop] !== 'boolean') {
160
+ return [new DatabaseError(`Expected boolean, got ${typeof obj[prop]}`, 'INVALID_TYPE')];
161
+ }
162
+ return [];
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @internal Type wrapper for array values with optional length constraints.
168
+ * @template T - The type of array elements.
169
+ */
170
+ class ArrayType<T> extends TypeWrapper<T[]> {
171
+ kind = 'array';
172
+
173
+ /**
174
+ * Create a new ArrayType.
175
+ * @param inner - Type wrapper for array elements.
176
+ * @param opts - Array constraints (min/max length).
177
+ */
178
+ constructor(public inner: TypeWrapper<T>, public opts: {min?: number, max?: number} = {}) {
179
+ super();
180
+ }
181
+
182
+ serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
183
+ const value = obj[prop] as T[];
184
+ bytes.writeNumber(value.length);
185
+ for(let i=0; i<value.length; i++) {
186
+ this.inner.serialize(value, i, bytes, model);
187
+ }
188
+ }
189
+
190
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
191
+ const length = bytes.readNumber();
192
+ const result: T[] = [];
193
+ for (let i = 0; i < length; i++) {
194
+ this.inner.deserialize(result, i, bytes, model);
195
+ }
196
+ obj[prop] = result;
197
+ }
198
+
199
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
200
+ const value = obj[prop];
201
+ if (!Array.isArray(value)) {
202
+ return [new DatabaseError(`Expected array, got ${typeof value}`, 'INVALID_TYPE')];
203
+ }
204
+ const errors: DatabaseError[] = [];
205
+ if (this.opts.min !== undefined && value.length < this.opts.min) {
206
+ errors.push(new DatabaseError(`Array length ${value.length} is less than minimum ${this.opts.min}`, 'OUT_OF_BOUNDS'));
207
+ }
208
+ if (this.opts.max !== undefined && value.length > this.opts.max) {
209
+ errors.push(new DatabaseError(`Array length ${value.length} is greater than maximum ${this.opts.max}`, 'OUT_OF_BOUNDS'));
210
+ }
211
+ for (let i = 0; i < value.length; i++) {
212
+ for(let itemError of this.inner.getErrors(value, i)) {
213
+ errors.push(addErrorPath(itemError, i));
214
+ }
215
+ }
216
+ return errors;
217
+ }
218
+
219
+ serializeType(bytes: Bytes): void {
220
+ serializeType(this.inner, bytes);
221
+ }
222
+
223
+ static deserializeType(bytes: Bytes, featureFlags: number): ArrayType<any> {
224
+ const inner = deserializeType(bytes, featureFlags);
225
+ return new ArrayType(inner);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * @internal Type wrapper for union/discriminated union types.
231
+ * @template T - The union type this wrapper represents.
232
+ */
233
+ class OrType<const T> extends TypeWrapper<T> {
234
+ kind = 'or';
235
+
236
+ /**
237
+ * Create a new OrType.
238
+ * @param choices - Array of type wrappers representing the union choices.
239
+ */
240
+ constructor(public choices: TypeWrapper<T>[]) {
241
+ super();
242
+ }
243
+
244
+ _getChoiceIndex(obj: any, prop: string | number): number {
245
+ for (const [i, choice] of this.choices.entries()) {
246
+ if (choice.getErrors(obj, prop).length === 0) {
247
+ return i;
248
+ }
249
+ }
250
+ throw new DatabaseError(`Value does not match any union type: ${obj[prop]}`, 'INVALID_TYPE');
251
+ }
252
+
253
+ serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
254
+ const choiceIndex = this._getChoiceIndex(obj, prop);
255
+ bytes.writeUIntN(choiceIndex, this.choices.length-1);
256
+ this.choices[choiceIndex].serialize(obj, prop, bytes, model);
257
+ }
258
+
259
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
260
+ const index = bytes.readUIntN(this.choices.length-1);
261
+ if (index < 0 || index >= this.choices.length) {
262
+ throw new DatabaseError(`Could not deserialize invalid union index ${index}`, 'DESERIALIZATION_ERROR');
263
+ }
264
+ const type = this.choices[index];
265
+ type.deserialize(obj, prop, bytes, model);
266
+ }
267
+
268
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
269
+ const errors: DatabaseError[] = [];
270
+ for (let i = 0; i < this.choices.length; i++) {
271
+ const type = this.choices[i];
272
+ const subErrors = type.getErrors(obj, prop);
273
+ if (subErrors.length === 0) return [];
274
+ for (let err of subErrors) {
275
+ errors.push(addErrorPath(err, `opt${i+1}`));
276
+ }
277
+ }
278
+ return errors;
279
+ }
280
+
281
+ checkSkipIndex(obj: any, prop: string | number): boolean {
282
+ const choiceIndex = this._getChoiceIndex(obj, prop);
283
+ return this.choices[choiceIndex].checkSkipIndex(obj, prop);
284
+ }
285
+
286
+ serializeType(bytes: Bytes): void {
287
+ bytes.writeNumber(this.choices.length);
288
+ for (const choice of this.choices) {
289
+ serializeType(choice, bytes);
290
+ }
291
+ }
292
+
293
+ static deserializeType(bytes: Bytes, featureFlags: number): OrType<any> {
294
+ const count = bytes.readNumber();
295
+ const choices: TypeWrapper<unknown>[] = [];
296
+ for (let i = 0; i < count; i++) {
297
+ choices.push(deserializeType(bytes, featureFlags));
298
+ }
299
+ return new OrType(choices);
300
+ }
301
+ }
302
+
303
+ /**
304
+ * @internal Type wrapper for literal values (constants).
305
+ * @template T - The literal type this wrapper represents.
306
+ */
307
+ class LiteralType<const T> extends TypeWrapper<T> {
308
+ kind = 'literal';
309
+
310
+ /**
311
+ * Create a new LiteralType.
312
+ * @param value - The literal value this type represents.
313
+ */
314
+ constructor(public value: T) {
315
+ super();
316
+ }
317
+
318
+ serialize(obj: any, prop: string | number, bytes: Bytes, model?: any) {
319
+ // Literal values don't need to be serialized since they're constants
320
+ }
321
+
322
+ deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
323
+ obj[prop] = this.value;
324
+ }
325
+
326
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
327
+ return this.value===obj[prop] ? [] : [new DatabaseError(`Invalid literal value ${obj[prop]} instead of ${this.value}`, 'INVALID_TYPE')];
328
+ }
329
+
330
+ serializeType(bytes: Bytes): void {
331
+ bytes.writeString(this.value===undefined ? "" : JSON.stringify(this.value));
332
+ }
333
+
334
+ checkSkipIndex(obj: any, prop: string | number): boolean {
335
+ return obj[prop] == null;
336
+ }
337
+
338
+ static deserializeType(bytes: Bytes, featureFlags: number): LiteralType<any> {
339
+ const json = bytes.readString();
340
+ const value = json==="" ? undefined : JSON.parse(json);
341
+ return new LiteralType(value);
342
+ }
343
+
344
+ default(model: any): T {
345
+ return this.value;
346
+ }
347
+ }
348
+
349
+ const ID_SIZE = 7;
350
+
351
+ /**
352
+ * @internal Type wrapper for auto-generated unique identifier strings.
353
+ */
354
+ class IdentifierType extends TypeWrapper<string> {
355
+ kind = 'id';
356
+
357
+ serialize(obj: any, prop: string|number, bytes: Bytes): void {
358
+ const value = obj[prop];
359
+ assert(value.length === ID_SIZE);
360
+ bytes.writeBase64(value);
361
+ }
362
+
363
+ deserialize(obj: any, prop: string | number, bytes: Bytes): void {
364
+ obj[prop] = bytes.readBase64(ID_SIZE);
365
+ }
366
+
367
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
368
+ const value = obj[prop];
369
+ if (typeof value !== 'string' || value.length !== ID_SIZE) return [new DatabaseError(`Invalid ID format: ${value}`, 'VALUE_ERROR')];
370
+ return [];
371
+ }
372
+
373
+ serializeType(bytes: Bytes): void {
374
+ }
375
+
376
+ static deserializeType(bytes: Bytes, featureFlags: number): IdentifierType {
377
+ return new IdentifierType();
378
+ }
379
+
380
+ default(model: Model<any>): string {
381
+ // Generate a random ID, and if it already exists in the database, retry.
382
+ let id: string;
383
+ do {
384
+ // Combine a timestamp with randomness, to create locality of reference as well as a high chance of uniqueness.
385
+ // Bits 9...42 are the date (wrapping about four times a year)
386
+ // Bit 0...14 are random bits (partly overlapping with the date, adding up to 31ms of jitter)
387
+ let num = Math.floor(+new Date() * (1<<9) + Math.random() * (1<<14));
388
+
389
+ id = '';
390
+ for(let i = 0; i < 7; i++) {
391
+ id = Bytes.BASE64_CHARS[num & 0x3f] + id;
392
+ num = Math.floor(num / 64);
393
+ }
394
+ } while (olmdb.get(new Bytes().writeNumber(model.constructor._pk!._cachedIndexId!).writeBase64(id).getBuffer()));
395
+ return id;
396
+ }
397
+ }
398
+
399
+ const WANT_PK_ARRAY = {};
400
+
401
+ type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp? P : never}[keyof T];
402
+
403
+ /**
404
+ * @internal Type wrapper for model relationships (foreign keys).
405
+ * @template T - The target model class type.
406
+ */
407
+ export class LinkType<T extends typeof Model<any>> extends TypeWrapper<InstanceType<T>> {
408
+ kind = 'link';
409
+ TargetModel: T;
410
+
411
+ /**
412
+ * Create a new LinkType.
413
+ * @param TargetModel - The model class this link points to.
414
+ */
415
+ constructor(TargetModel: T) {
416
+ super();
417
+ this.TargetModel = getMockModel(TargetModel);
418
+ }
419
+
420
+ serialize(obj: any, prop: string | number, bytes: Bytes, model: Model<InstanceType<T>>): void {
421
+ let pkArray;
422
+ const pk = this.TargetModel._pk!;
423
+ // If obj[prop] is getter(), it will return the primary key array (based on WANT_PK_ARRAY
424
+ // being the receiver). Otherwise, it will just return the value, which is a model instance.
425
+ let value = Reflect.get(obj, prop, WANT_PK_ARRAY) as any[] | Model<InstanceType<T>>;
426
+ if (value instanceof Array) {
427
+ // It's a pk array, and the object has not been loaded. We can just serialize it.
428
+ pk._serializeArgs(value, bytes);
429
+ } else {
430
+ // It's a model instance that has been loaded
431
+ pk._serializeModel(value, bytes);
432
+ }
433
+ }
434
+
435
+ deserialize(obj: any, prop: string, bytes: Bytes, sourceModel: Model<unknown>) {
436
+ const pk = this.TargetModel._pk!;
437
+ const pkArray = pk._deserializeKey(bytes);
438
+ const TargetModel = this.TargetModel;
439
+
440
+ // Define a getter to load the model on first access
441
+ Object.defineProperty(obj, prop, {
442
+ get: function() {
443
+ // Special case to return the primary key array instead of load the model, used by serialize.
444
+ if (this === WANT_PK_ARRAY) return pkArray;
445
+ const targetModel = TargetModel._pk!.get(...pkArray); // load by primary key Uint8Array
446
+ if (!targetModel) {
447
+ throw new DatabaseError(`Linked ${TargetModel.tableName} instance ${pkArray.join(', ')} not found`, 'BROKEN_LINK');
448
+ }
449
+ this[prop] = targetModel; // Cause set() to be called, so our property will be come a regular value
450
+ return targetModel;
451
+ },
452
+ set: function(newValue) {
453
+ // Convert back to a regular value property
454
+ Object.defineProperty(this, prop, {
455
+ value: newValue,
456
+ writable: true,
457
+ enumerable: true,
458
+ configurable: true
459
+ });
460
+ },
461
+ enumerable: true,
462
+ configurable: true
463
+ });
464
+ }
465
+
466
+ getErrors(obj: any, prop: string | number): DatabaseError[] {
467
+ if (!(obj[prop] instanceof this.TargetModel)) {
468
+ return [new DatabaseError(`Expected instance of ${this.TargetModel.tableName}, got ${typeof obj[prop]}`, 'VALUE_ERROR')];
469
+ }
470
+ return [];
471
+ }
472
+
473
+ serializeType(bytes: Bytes): void {
474
+ bytes.writeString(this.TargetModel.tableName);
475
+ }
476
+
477
+ static deserializeType(bytes: Bytes, featureFlags: number): LinkType<any> {
478
+ const tableName = bytes.readString();
479
+ const targetModel = modelRegistry[tableName];
480
+ if (!targetModel) throw new DatabaseError(`Could not deserialize undefined model ${tableName}`, 'DESERIALIZATION_ERROR');
481
+ return new LinkType(targetModel);
482
+ }
483
+ }
484
+
485
+ /** Constant representing the string type. */
486
+ export const string = new StringType();
487
+
488
+ /** Constant representing the number type. */
489
+ export const number = new NumberType();
490
+
491
+ /** Constant representing the boolean type. */
492
+ export const boolean = new BooleanType();
493
+
494
+ /** Constant representing the identifier type. */
495
+ export const identifier = new IdentifierType();
496
+
497
+ /**
498
+ * Create a literal type wrapper for a constant value.
499
+ * @template T - The literal type.
500
+ * @param value - The literal value.
501
+ * @returns A literal type instance.
502
+ *
503
+ * @example
504
+ * ```typescript
505
+ * const statusType = E.literal("active");
506
+ * const countType = E.literal(42);
507
+ * ```
508
+ */
509
+ export function literal<const T>(value: T) {
510
+ return new LiteralType<T>(value);
511
+ }
512
+
513
+ /**
514
+ * Create a union type wrapper from multiple type choices.
515
+ * @template T - Array of type wrapper or basic types.
516
+ * @param choices - The type choices for the union.
517
+ * @returns A union type instance.
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * const stringOrNumber = E.or(E.string, E.number);
522
+ * const status = E.or("active", "inactive", "pending");
523
+ * ```
524
+ */
525
+ export function or<const T extends (TypeWrapper<unknown>|BasicType)[]>(...choices: T) {
526
+ return new OrType<UnwrapTypes<T>>(choices.map(wrapIfLiteral) as any);
527
+ }
528
+
529
+ /** Constant representing the 'undefined' type. */
530
+ export const undef = new LiteralType(undefined);
531
+
532
+ /**
533
+ * Create an optional type wrapper (allows undefined).
534
+ * @template T - Type wrapper or basic type to make optional.
535
+ * @param inner - The inner type to make optional.
536
+ * @returns A union type that accepts the inner type or undefined.
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * const optionalString = E.opt(E.string);
541
+ * const optionalNumber = E.opt(E.number);
542
+ * ```
543
+ */
544
+ export function opt<const T extends TypeWrapper<unknown>|BasicType>(inner: T) {
545
+ return or(undef, inner);
546
+ }
547
+
548
+ /**
549
+ * Create an array type wrapper with optional length constraints.
550
+ * @template T - The element type.
551
+ * @param inner - Type wrapper for array elements.
552
+ * @param opts - Optional constraints (min/max length).
553
+ * @returns An array type instance.
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * const stringArray = E.array(E.string);
558
+ * const boundedArray = E.array(E.number, {min: 1, max: 10});
559
+ * ```
560
+ */
561
+ export function array<const T>(inner: TypeWrapper<T>, opts: {min?: number, max?: number} = {}) {
562
+ return new ArrayType<T>(wrapIfLiteral(inner), opts);
563
+ }
564
+
565
+ /**
566
+ * Create a link type wrapper for model relationships.
567
+ * @template T - The target model class.
568
+ * @param TargetModel - The model class this link points to.
569
+ * @returns A link type instance.
570
+ *
571
+ * @example
572
+ * ```typescript
573
+ * class User extends E.Model<User> {
574
+ * posts = E.field(E.array(E.link(Post, 'author')));
575
+ * }
576
+ *
577
+ * class Post extends E.Model<Post> {
578
+ * author = E.field(E.link(User));
579
+ * }
580
+ * ```
581
+ */
582
+ export function link<const T extends typeof Model<any>>(TargetModel: T) {
583
+ return new LinkType<T>(TargetModel);
584
+ }
585
+
586
+
587
+ // Utility types and functions
588
+ export type BasicType = TypeWrapper<any> | string | number | boolean | undefined | null;
589
+
590
+ export type UnwrapTypes<T extends BasicType[]> = {
591
+ [K in keyof T]: T[K] extends TypeWrapper<infer U> ? U : T[K];
592
+ }[number];
593
+
594
+ function wrapIfLiteral<const T>(type: TypeWrapper<T>): TypeWrapper<T>;
595
+ function wrapIfLiteral<const T>(type: T): LiteralType<T>;
596
+ function wrapIfLiteral(type: any) {
597
+ return type instanceof TypeWrapper ? type : new LiteralType(type);
598
+ }
599
+
600
+ /**
601
+ * Serialize a type wrapper to bytes for schema persistence.
602
+ * @param arg - The type wrapper to serialize.
603
+ * @param bytes - The Bytes instance to write to.
604
+ */
605
+ export function serializeType(arg: TypeWrapper<any>, bytes: Bytes) {
606
+ bytes.writeString(arg.kind);
607
+ arg.serializeType(bytes);
608
+ }
609
+
610
+ const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (bytes: Bytes, featureFlags: number) => TypeWrapper<any>}> = {
611
+ string: string,
612
+ number: number,
613
+ array: ArrayType,
614
+ or: OrType,
615
+ literal: LiteralType,
616
+ boolean: boolean,
617
+ id: identifier,
618
+ link: LinkType,
619
+ };
620
+
621
+ /**
622
+ * Deserialize a type wrapper from bytes.
623
+ * @param bytes - The Bytes instance to read from.
624
+ * @param featureFlags - Feature flags for version compatibility.
625
+ * @returns The deserialized type wrapper.
626
+ */
627
+ export function deserializeType(bytes: Bytes, featureFlags: number): TypeWrapper<any> {
628
+ const kind = bytes.readString();
629
+ const TypeWrapper = TYPE_WRAPPERS[kind];
630
+ if ('deserializeType' in TypeWrapper) {
631
+ return TypeWrapper.deserializeType(bytes, featureFlags);
632
+ } else {
633
+ return TypeWrapper;
634
+ }
635
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { DatabaseError } from "olmdb";
2
+
3
+ /**
4
+ * Assert function for runtime checks with TypeScript assertion support.
5
+ * @param cond - Condition to check.
6
+ * @param message - Optional error message.
7
+ * @throws {Error} If condition is false.
8
+ */
9
+ export function assert(cond: any, message?: string): asserts cond {
10
+ if (!cond) {
11
+ throw new Error(message || "Assertion failed");
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Regular expression for parsing error messages with paths.
17
+ */
18
+ export const ERROR_AT = /^(.*) at ([a-zA-Z0-9_.]+)$/;
19
+
20
+ /**
21
+ * Add a path segment to a DatabaseError message for better error reporting.
22
+ * @param error - The DatabaseError to modify.
23
+ * @param path - The path segment to add (string or number).
24
+ * @returns The modified DatabaseError.
25
+ */
26
+ export function addErrorPath(error: DatabaseError, path: string | number): DatabaseError {
27
+ const m = error.message.match(ERROR_AT);
28
+ error.message = m ? `${m[1]} at ${path}.${m[2]}` : `${error.message} at ${path}`;
29
+ return error;
30
+ }
31
+
32
+ /**
33
+ * Global log level for debugging output.
34
+ * 0 = no logging, 1 = model-level logs, 2 = update logs, 3 = read logs.
35
+ */
36
+ export let logLevel = 0;
37
+
38
+ /** @internal Symbol used to access the underlying model from a proxy */
39
+ export declare const TARGET_SYMBOL: unique symbol;