edinburgh 0.1.2 → 0.3.0

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 CHANGED
@@ -1,593 +1,668 @@
1
- import { Bytes } from "./bytes.js";
1
+ import { DataPack } from "./datapack.js";
2
2
  import * as olmdb from "olmdb";
3
3
  import { DatabaseError } from "olmdb";
4
4
  import { Model, modelRegistry, getMockModel } from "./models.js";
5
5
  import { assert, addErrorPath } from "./utils.js";
6
6
 
7
+
7
8
  /**
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
- */
9
+ * @internal Abstract base class for all type wrappers in the Edinburgh ORM system.
10
+ *
11
+ * This is an implementation detail and should not be referenced directly in user code.
12
+ * Type wrappers define how values are serialized to/from the database and how they are validated.
13
+ * Each type wrapper must implement serialization, deserialization, and validation logic.
14
+ *
15
+ * @template T - The TypeScript type this wrapper represents.
16
+ */
16
17
  export abstract class TypeWrapper<const T> {
17
18
  /** @internal Used for TypeScript type inference - this field is required for the type system */
18
19
  _T!: T;
19
20
 
20
21
  /** A string identifier for this type, used during serialization */
21
22
  abstract kind: string;
22
-
23
- constructor() {}
24
23
 
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;
24
+ constructor() {}
33
25
 
34
26
  /**
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
-
27
+ * Serialize a value from an object property to a Pack.
28
+ * @param value - The value to serialize.
29
+ * @param pack - The Pack instance to write to.
30
+ */
31
+ abstract serialize(value: T, pack: DataPack): void;
32
+
43
33
  /**
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
-
34
+ * Deserialize a value from a Pack into an object property.
35
+ * @param pack - The Pack instance to read from.
36
+ */
37
+ abstract deserialize(pack: DataPack): T;
38
+
51
39
  /**
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
- }
40
+ * Validate a value.
41
+ * @param value - The value to validate.
42
+ * @returns - A DatabaseError if validation fails.
43
+ */
44
+ abstract getError(value: T): DatabaseError | void;
64
45
 
65
46
  /**
66
- * Serialize type metadata to bytes (for schema serialization).
67
- * @param bytes - The Bytes instance to write to.
68
- */
69
- serializeType(bytes: Bytes) {}
47
+ * Serialize type metadata to a Pack (for schema serialization).
48
+ * @param pack - The Pack instance to write to.
49
+ */
50
+ serializeType(pack: DataPack) {}
70
51
 
71
52
  /**
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 {
53
+ * Check if indexing should be skipped for this field value.
54
+ * @param obj - The object containing the value.
55
+ * @param prop - The property name or index.
56
+ * @returns true if indexing should be skipped.
57
+ */
58
+ containsNull(value: T): boolean {
78
59
  return false;
79
60
  }
80
-
61
+
81
62
  toString(): string {
82
63
  return `${this.kind}`;
83
64
  }
65
+
66
+ clone(value: T): T {
67
+ return value;
68
+ }
69
+
70
+ equals(value1: T, value2: T): boolean {
71
+ return value1 === value2;
72
+ }
84
73
  }
85
74
 
86
- /**
87
- * Optional interface for type wrappers that can provide default values.
88
- * @template T - The TypeScript type this wrapper represents.
89
- */
75
+
90
76
  export interface TypeWrapper<T> {
91
77
  /**
92
- * Generate a default value for this type.
93
- * @param model - The model instance.
94
- * @returns The default value.
95
- */
78
+ * Generate a default value for this type.
79
+ * @param model - The model instance.
80
+ * @returns The default value.
81
+ */
96
82
  default?(model: any): T;
97
83
  }
98
84
 
99
- /**
100
- * @internal Type wrapper for string values.
101
- */
85
+
102
86
  class StringType extends TypeWrapper<string> {
103
87
  kind = 'string';
104
88
 
105
- serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
106
- bytes.writeString(obj[prop]);
89
+ serialize(value: string, pack: DataPack) {
90
+ pack.write(value);
107
91
  }
108
92
 
109
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
110
- obj[prop] = bytes.readString();
93
+ deserialize(pack: DataPack): string {
94
+ return pack.readString();
111
95
  }
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')];
96
+
97
+ getError(value: string) {
98
+ if (typeof value !== 'string') {
99
+ return new DatabaseError(`Expected string, got ${typeof value}`, 'INVALID_TYPE');
116
100
  }
117
- return [];
118
101
  }
119
102
  }
120
103
 
121
- /**
122
- * @internal Type wrapper for number values.
123
- */
104
+
105
+ class OrderedStringType extends StringType {
106
+ serialize(value: string, pack: DataPack) {
107
+ pack.writeOrderedString(value);
108
+ }
109
+ }
110
+
111
+
124
112
  class NumberType extends TypeWrapper<number> {
125
113
  kind = 'number';
126
-
127
- serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
128
- bytes.writeNumber(obj[prop]);
114
+
115
+ serialize(value: number, pack: DataPack) {
116
+ pack.write(value);
129
117
  }
130
-
131
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
132
- obj[prop] = bytes.readNumber();
118
+
119
+ deserialize(pack: DataPack): number {
120
+ return pack.readNumber();
133
121
  }
134
-
135
- getErrors(obj: any, prop: string | number): DatabaseError[] {
136
- const value = obj[prop];
122
+
123
+ getError(value: number) {
137
124
  if (typeof value !== 'number' || isNaN(value)) {
138
- return [new DatabaseError(`Expected number, got ${typeof value}`, 'INVALID_TYPE')];
125
+ return new DatabaseError(`Expected number, got ${typeof value}`, 'INVALID_TYPE');
139
126
  }
140
- return [];
141
127
  }
142
128
  }
143
129
 
144
- /**
145
- * @internal Type wrapper for boolean values.
146
- */
130
+ class DateTimeType extends TypeWrapper<Date> {
131
+ kind = 'dateTime';
132
+
133
+ serialize(value: Date, pack: DataPack) {
134
+ pack.write(value);
135
+ }
136
+
137
+ deserialize(pack: DataPack): Date {
138
+ return pack.readDate();;
139
+ }
140
+
141
+ getError(value: Date) {
142
+ if (!(value instanceof Date)) {
143
+ return new DatabaseError(`Expected Date, got ${typeof value}`, 'INVALID_TYPE');
144
+ }
145
+ }
146
+
147
+ default(): Date {
148
+ return new Date();
149
+ }
150
+
151
+ }
152
+
147
153
  class BooleanType extends TypeWrapper<boolean> {
148
154
  kind = 'boolean';
149
-
150
- serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
151
- bytes.writeBits(obj[prop] ? 1 : 0, 1);
155
+
156
+ serialize(value: boolean, pack: DataPack) {
157
+ pack.write(value);
152
158
  }
153
-
154
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
155
- obj[prop] = bytes.readBits(1) === 1;
159
+
160
+ deserialize(pack: DataPack): boolean {
161
+ return pack.readBoolean();
156
162
  }
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')];
163
+
164
+ getError(value: boolean) {
165
+ if (typeof value !== 'boolean') {
166
+ return new DatabaseError(`Expected boolean, got ${typeof value}`, 'INVALID_TYPE');
161
167
  }
162
- return [];
163
168
  }
164
169
  }
165
170
 
166
171
  /**
167
- * @internal Type wrapper for array values with optional length constraints.
168
- * @template T - The type of array elements.
169
- */
172
+ * @internal Type wrapper for array values with optional length constraints.
173
+ * @template T - The type of array elements.
174
+ */
170
175
  class ArrayType<T> extends TypeWrapper<T[]> {
171
176
  kind = 'array';
172
177
 
173
178
  /**
174
- * Create a new ArrayType.
175
- * @param inner - Type wrapper for array elements.
176
- * @param opts - Array constraints (min/max length).
177
- */
179
+ * Create a new ArrayType.
180
+ * @param inner - Type wrapper for array elements.
181
+ * @param opts - Array constraints (min/max length).
182
+ */
178
183
  constructor(public inner: TypeWrapper<T>, public opts: {min?: number, max?: number} = {}) {
179
184
  super();
180
185
  }
181
-
182
- serialize(obj: any, prop: string, bytes: Bytes, model?: any) {
183
- const value = obj[prop] as T[];
184
- bytes.writeNumber(value.length);
186
+
187
+ serialize(value: T[], pack: DataPack) {
188
+ pack.write(value.length);
185
189
  for(let i=0; i<value.length; i++) {
186
- this.inner.serialize(value, i, bytes, model);
190
+ this.inner.serialize(value[i], pack);
187
191
  }
188
192
  }
189
193
 
190
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
191
- const length = bytes.readNumber();
194
+ deserialize(pack: DataPack): T[] {
195
+ const length = pack.readNumber();
192
196
  const result: T[] = [];
193
197
  for (let i = 0; i < length; i++) {
194
- this.inner.deserialize(result, i, bytes, model);
198
+ result.push(this.inner.deserialize(pack));
195
199
  }
196
- obj[prop] = result;
200
+ return result;
197
201
  }
198
-
199
- getErrors(obj: any, prop: string | number): DatabaseError[] {
200
- const value = obj[prop];
202
+
203
+ getError(value: T[]) {
201
204
  if (!Array.isArray(value)) {
202
- return [new DatabaseError(`Expected array, got ${typeof value}`, 'INVALID_TYPE')];
205
+ return new DatabaseError(`Expected array, got ${typeof value}`, 'INVALID_TYPE');
203
206
  }
204
- const errors: DatabaseError[] = [];
207
+
205
208
  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'));
209
+ return new DatabaseError(`Array length ${value.length} is less than minimum ${this.opts.min}`, 'OUT_OF_BOUNDS');
207
210
  }
208
211
  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'));
212
+ return new DatabaseError(`Array length ${value.length} is greater than maximum ${this.opts.max}`, 'OUT_OF_BOUNDS');
210
213
  }
211
214
  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
+ let error = this.inner.getError(value[i]);
216
+ if (error) return addErrorPath(error, i);
215
217
  }
216
- return errors;
217
218
  }
218
219
 
219
- serializeType(bytes: Bytes): void {
220
- serializeType(this.inner, bytes);
220
+ serializeType(pack: DataPack): void {
221
+ serializeType(this.inner, pack);
221
222
  }
222
223
 
223
- static deserializeType(bytes: Bytes, featureFlags: number): ArrayType<any> {
224
- const inner = deserializeType(bytes, featureFlags);
224
+ static deserializeType(pack: DataPack, featureFlags: number): ArrayType<any> {
225
+ const inner = deserializeType(pack, featureFlags);
225
226
  return new ArrayType(inner);
226
227
  }
228
+
229
+ clone(value: T[]): T[] {
230
+ return value.map(this.inner.clone.bind(this.inner));
231
+ }
232
+
233
+ equals(a: T[], b: T[]): boolean {
234
+ if (a.length !== b.length) return false;
235
+ for (let i = 0; i < a.length; i++) {
236
+ if (!this.inner.equals(a[i], b[i])) return false;
237
+ }
238
+ return true;
239
+ }
240
+
227
241
  }
228
242
 
229
243
  /**
230
- * @internal Type wrapper for union/discriminated union types.
231
- * @template T - The union type this wrapper represents.
232
- */
244
+ * @internal Type wrapper for array values with optional length constraints.
245
+ * @template T - The type of array elements.
246
+ */
247
+ export class SetType<T> extends TypeWrapper<Set<T>> {
248
+ kind = 'set';
249
+
250
+ /**
251
+ * Create a new SetType.
252
+ * @param inner - Type wrapper for set elements.
253
+ */
254
+ constructor(public inner: TypeWrapper<T>, public opts: {min?: number, max?: number} = {}) {
255
+ super();
256
+ }
257
+
258
+ serialize(value: Set<T>, pack: DataPack) {
259
+ pack.write(value.size);
260
+ for (const item of value) {
261
+ this.inner.serialize(item, pack);
262
+ }
263
+ }
264
+
265
+ deserialize(pack: DataPack): Set<T> {
266
+ const length = pack.readNumber();
267
+ const result = new Set<T>();
268
+ for (let i = 0; i < length; i++) {
269
+ result.add(this.inner.deserialize(pack));
270
+ }
271
+ return result;
272
+ }
273
+
274
+ getError(value: Set<T>) {
275
+ if (!(value instanceof Set)) {
276
+ return new DatabaseError(`Expected Set, got ${typeof value}`, 'INVALID_TYPE');
277
+ }
278
+
279
+ if (this.opts.min !== undefined && value.size < this.opts.min) {
280
+ return new DatabaseError(`Set size ${value.size} is less than minimum ${this.opts.min}`, 'OUT_OF_BOUNDS');
281
+ }
282
+ if (this.opts.max !== undefined && value.size > this.opts.max) {
283
+ return new DatabaseError(`Set size ${value.size} is greater than maximum ${this.opts.max}`, 'OUT_OF_BOUNDS');
284
+ }
285
+
286
+ try {
287
+ for (const item of value) {
288
+ this.inner.getError(item);
289
+ }
290
+ } catch (err) {
291
+ throw addErrorPath(err, 'item');
292
+ }
293
+ }
294
+
295
+ serializeType(pack: DataPack): void {
296
+ serializeType(this.inner, pack);
297
+ }
298
+
299
+ static deserializeType(pack: DataPack, featureFlags: number): SetType<any> {
300
+ const inner = deserializeType(pack, featureFlags);
301
+ return new SetType(inner);
302
+ }
303
+
304
+ default(): Set<T> {
305
+ return new Set<T>();
306
+ }
307
+
308
+ clone(value: Set<T>): Set<T> {
309
+ const cloned = new Set<T>();
310
+ for (const item of value) {
311
+ cloned.add(this.inner.clone(item));
312
+ }
313
+ return cloned;
314
+ }
315
+
316
+ equals(a: Set<T>, b: Set<T>): boolean {
317
+ if (a.size !== b.size) return false;
318
+ for(const v of a) {
319
+ if (!b.has(v)) return false;
320
+ }
321
+ return true;
322
+ }
323
+ }
324
+
325
+
326
+ /**
327
+ * @internal Type wrapper for union/discriminated union types.
328
+ * @template T - The union type this wrapper represents.
329
+ */
233
330
  class OrType<const T> extends TypeWrapper<T> {
234
331
  kind = 'or';
235
332
 
236
333
  /**
237
- * Create a new OrType.
238
- * @param choices - Array of type wrappers representing the union choices.
239
- */
334
+ * Create a new OrType.
335
+ * @param choices - Array of type wrappers representing the union choices.
336
+ */
240
337
  constructor(public choices: TypeWrapper<T>[]) {
241
338
  super();
242
339
  }
243
340
 
244
- _getChoiceIndex(obj: any, prop: string | number): number {
341
+ _getChoiceIndex(value: any): number {
245
342
  for (const [i, choice] of this.choices.entries()) {
246
- if (choice.getErrors(obj, prop).length === 0) {
247
- return i;
248
- }
343
+ if (!choice.getError(value)) return i;
249
344
  }
250
- throw new DatabaseError(`Value does not match any union type: ${obj[prop]}`, 'INVALID_TYPE');
345
+ throw new DatabaseError(`Value does not match any union type: ${value}`, 'INVALID_TYPE');
251
346
  }
252
347
 
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);
348
+ serialize(value: T, pack: DataPack) {
349
+ const choiceIndex = this._getChoiceIndex(value);
350
+ pack.write(choiceIndex);
351
+ this.choices[choiceIndex].serialize(value, pack);
257
352
  }
258
353
 
259
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
260
- const index = bytes.readUIntN(this.choices.length-1);
354
+ deserialize(pack: DataPack) {
355
+ const index = pack.readNumber();
261
356
  if (index < 0 || index >= this.choices.length) {
262
357
  throw new DatabaseError(`Could not deserialize invalid union index ${index}`, 'DESERIALIZATION_ERROR');
263
358
  }
264
359
  const type = this.choices[index];
265
- type.deserialize(obj, prop, bytes, model);
360
+ return type.deserialize(pack);
266
361
  }
267
362
 
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
- }
363
+ getError(value: any) {
364
+ for (const choice of this.choices.values()) {
365
+ if (!choice.getError(value)) return;
277
366
  }
278
- return errors;
367
+ return new DatabaseError(`Value does not match any union type: ${value}`, 'INVALID_TYPE');
279
368
  }
280
369
 
281
- checkSkipIndex(obj: any, prop: string | number): boolean {
282
- const choiceIndex = this._getChoiceIndex(obj, prop);
283
- return this.choices[choiceIndex].checkSkipIndex(obj, prop);
370
+ containsNull(value: T): boolean {
371
+ const choiceIndex = this._getChoiceIndex(value);
372
+ return this.choices[choiceIndex].containsNull(value);
284
373
  }
285
374
 
286
- serializeType(bytes: Bytes): void {
287
- bytes.writeNumber(this.choices.length);
375
+ serializeType(pack: DataPack): void {
376
+ pack.write(this.choices.length);
288
377
  for (const choice of this.choices) {
289
- serializeType(choice, bytes);
378
+ serializeType(choice, pack);
290
379
  }
291
380
  }
292
381
 
293
- static deserializeType(bytes: Bytes, featureFlags: number): OrType<any> {
294
- const count = bytes.readNumber();
382
+ static deserializeType(pack: DataPack, featureFlags: number): OrType<any> {
383
+ const count = pack.readNumber();
295
384
  const choices: TypeWrapper<unknown>[] = [];
296
385
  for (let i = 0; i < count; i++) {
297
- choices.push(deserializeType(bytes, featureFlags));
386
+ choices.push(deserializeType(pack, featureFlags));
298
387
  }
299
388
  return new OrType(choices);
300
389
  }
390
+
391
+ clone(value: T): T {
392
+ const choiceIndex = this._getChoiceIndex(value);
393
+ return this.choices[choiceIndex].clone(value);
394
+ }
395
+
396
+ equals(a: T, b: T): boolean {
397
+ const ca = this._getChoiceIndex(a);
398
+ const cb = this._getChoiceIndex(b);
399
+ return ca === cb && this.choices[ca].equals(a, b);
400
+ }
301
401
  }
302
402
 
303
403
  /**
304
- * @internal Type wrapper for literal values (constants).
305
- * @template T - The literal type this wrapper represents.
306
- */
404
+ * @internal Type wrapper for literal values (constants).
405
+ * @template T - The literal type this wrapper represents.
406
+ */
307
407
  class LiteralType<const T> extends TypeWrapper<T> {
308
408
  kind = 'literal';
309
409
 
310
410
  /**
311
- * Create a new LiteralType.
312
- * @param value - The literal value this type represents.
313
- */
411
+ * Create a new LiteralType.
412
+ * @param value - The literal value this type represents.
413
+ */
314
414
  constructor(public value: T) {
315
415
  super();
316
416
  }
317
417
 
318
- serialize(obj: any, prop: string | number, bytes: Bytes, model?: any) {
418
+ serialize(value: T, pack: DataPack) {
319
419
  // Literal values don't need to be serialized since they're constants
320
420
  }
321
421
 
322
- deserialize(obj: any, prop: string | number, bytes: Bytes, model?: any): void {
323
- obj[prop] = this.value;
422
+ deserialize(pack: DataPack) {
423
+ return this.value;
324
424
  }
325
425
 
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')];
426
+ getError(value: any) {
427
+ if (this.value!==value) {
428
+ return new DatabaseError(`Invalid literal value ${value} instead of ${this.value}`, 'INVALID_TYPE');
429
+ }
328
430
  }
329
431
 
330
- serializeType(bytes: Bytes): void {
331
- bytes.writeString(this.value===undefined ? "" : JSON.stringify(this.value));
432
+ serializeType(pack: DataPack): void {
433
+ pack.write(this.value===undefined ? "" : JSON.stringify(this.value));
332
434
  }
333
-
334
- checkSkipIndex(obj: any, prop: string | number): boolean {
335
- return obj[prop] == null;
435
+
436
+ containsNull(value: T): boolean {
437
+ return value == null;
336
438
  }
337
439
 
338
- static deserializeType(bytes: Bytes, featureFlags: number): LiteralType<any> {
339
- const json = bytes.readString();
440
+ static deserializeType(pack: DataPack, featureFlags: number): LiteralType<any> {
441
+ const json = pack.readString();
340
442
  const value = json==="" ? undefined : JSON.parse(json);
341
443
  return new LiteralType(value);
342
444
  }
343
-
344
- default(model: any): T {
445
+
446
+ default(): T {
345
447
  return this.value;
346
448
  }
347
449
  }
348
450
 
349
- const ID_SIZE = 7;
451
+ const ID_SIZE = 8;
350
452
 
351
453
  /**
352
- * @internal Type wrapper for auto-generated unique identifier strings.
353
- */
454
+ * @internal Type wrapper for auto-generated unique identifier strings.
455
+ */
354
456
  class IdentifierType extends TypeWrapper<string> {
355
457
  kind = 'id';
356
-
357
- serialize(obj: any, prop: string|number, bytes: Bytes): void {
358
- const value = obj[prop];
458
+
459
+ serialize(value: string, pack: DataPack): void {
359
460
  assert(value.length === ID_SIZE);
360
- bytes.writeBase64(value);
461
+ pack.writeIdentifier(value);
361
462
  }
362
-
363
- deserialize(obj: any, prop: string | number, bytes: Bytes): void {
364
- obj[prop] = bytes.readBase64(ID_SIZE);
463
+
464
+ deserialize(pack: DataPack) {
465
+ return pack.readIdentifier();
365
466
  }
366
467
 
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 [];
468
+ getError(value: any) {
469
+ if (typeof value !== 'string' || value.length !== ID_SIZE) return new DatabaseError(`Invalid ID format: ${value}`, 'VALUE_ERROR');
371
470
  }
372
471
 
373
- serializeType(bytes: Bytes): void {
472
+ serializeType(pack: DataPack): void {
374
473
  }
375
474
 
376
- static deserializeType(bytes: Bytes, featureFlags: number): IdentifierType {
475
+ static deserializeType(pack: DataPack, featureFlags: number): IdentifierType {
377
476
  return new IdentifierType();
378
477
  }
379
-
478
+
380
479
  default(model: Model<any>): string {
381
480
  // Generate a random ID, and if it already exists in the database, retry.
382
481
  let id: string;
383
482
  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()));
483
+ id = DataPack.generateIdentifier();
484
+ } while (olmdb.get(new DataPack().write(model.constructor._primary!._cachedIndexId!).writeIdentifier(id).toUint8Array()));
395
485
  return id;
396
486
  }
397
487
  }
398
488
 
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
489
  /**
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>> {
490
+ * @internal Type wrapper for model relationships (foreign keys).
491
+ * @template T - The target model class type.
492
+ */
493
+ export class LinkType<T extends typeof Model<unknown>> extends TypeWrapper<InstanceType<T>> {
408
494
  kind = 'link';
409
495
  TargetModel: T;
410
-
496
+
411
497
  /**
412
- * Create a new LinkType.
413
- * @param TargetModel - The model class this link points to.
414
- */
498
+ * Create a new LinkType.
499
+ * @param TargetModel - The model class this link points to.
500
+ */
415
501
  constructor(TargetModel: T) {
416
502
  super();
417
503
  this.TargetModel = getMockModel(TargetModel);
418
504
  }
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
- }
505
+
506
+ serialize(model: InstanceType<T>, pack: DataPack) {
507
+ pack.write(model._getCreatePrimaryKey());
433
508
  }
434
509
 
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')];
510
+ deserialize(pack: DataPack) {
511
+ return this.TargetModel._primary!.getLazy(pack.readUint8Array());
512
+ }
513
+
514
+ getError(value: InstanceType<T>) {
515
+ if (!(value instanceof this.TargetModel)) {
516
+ return new DatabaseError(`Expected instance of ${this.TargetModel.tableName}, got ${typeof value}`, 'VALUE_ERROR');
469
517
  }
470
- return [];
471
518
  }
472
519
 
473
- serializeType(bytes: Bytes): void {
474
- bytes.writeString(this.TargetModel.tableName);
520
+ serializeType(pack: DataPack): void {
521
+ pack.write(this.TargetModel.tableName);
475
522
  }
476
523
 
477
- static deserializeType(bytes: Bytes, featureFlags: number): LinkType<any> {
478
- const tableName = bytes.readString();
524
+ static deserializeType(pack: DataPack, featureFlags: number): LinkType<any> {
525
+ const tableName = pack.readString();
479
526
  const targetModel = modelRegistry[tableName];
480
527
  if (!targetModel) throw new DatabaseError(`Could not deserialize undefined model ${tableName}`, 'DESERIALIZATION_ERROR');
481
528
  return new LinkType(targetModel);
482
529
  }
483
530
  }
484
531
 
485
- /** Constant representing the string type. */
486
- export const string = new StringType();
532
+ /** Type wrapper instance for the string type. */
533
+ export const string = new StringType() as TypeWrapper<string>;
534
+
535
+ /** Type wrapper instance for the ordered string type, which is just like a string
536
+ * except that it sorts lexicographically in the database (instead of by incrementing
537
+ * length first), making it suitable for index fields that want lexicographic range
538
+ * scans. Ordered strings are implemented as null-terminated UTF-8 strings, so they
539
+ * may not contain null characters.
540
+ */
541
+ export const orderedString = new OrderedStringType() as TypeWrapper<string>;
542
+
543
+ /** Type wrapper instance for the number type. */
544
+ export const number = new NumberType() as TypeWrapper<number>;
545
+
546
+ /** Type wrapper instance for the date/time type. */
547
+ export const dateTime = new DateTimeType() as TypeWrapper<Date>;
487
548
 
488
- /** Constant representing the number type. */
489
- export const number = new NumberType();
549
+ /** Type wrapper instance for the boolean type. */
550
+ export const boolean = new BooleanType() as TypeWrapper<boolean>;
490
551
 
491
- /** Constant representing the boolean type. */
492
- export const boolean = new BooleanType();
552
+ /** Type wrapper instance for the identifier type. */
553
+ export const identifier = new IdentifierType() as TypeWrapper<string>;
493
554
 
494
- /** Constant representing the identifier type. */
495
- export const identifier = new IdentifierType();
555
+ /** Type wrapper instance for the 'undefined' type. */
556
+ export const undef = new LiteralType(undefined) as TypeWrapper<undefined>;
496
557
 
497
558
  /**
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);
559
+ * Create a literal type wrapper for a constant value.
560
+ * @template T - The literal type.
561
+ * @param value - The literal value.
562
+ * @returns A literal type instance.
563
+ *
564
+ * @example
565
+ * ```typescript
566
+ * const statusType = E.literal("active");
567
+ * const countType = E.literal(42);
568
+ * ```
569
+ */
570
+ export function literal<const T>(value: T): TypeWrapper<T> {
571
+ return new LiteralType(value);
511
572
  }
512
573
 
513
574
  /**
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);
575
+ * Create a union type wrapper from multiple type choices.
576
+ * @template T - Array of type wrapper or basic types.
577
+ * @param choices - The type choices for the union.
578
+ * @returns A union type instance.
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * const stringOrNumber = E.or(E.string, E.number);
583
+ * const status = E.or("active", "inactive", "pending");
584
+ * ```
585
+ */
586
+ export function or<const T extends (TypeWrapper<unknown>|BasicType)[]>(...choices: T): TypeWrapper<UnwrapTypes<T>> {
587
+ return new OrType(choices.map(wrapIfLiteral));
527
588
  }
528
589
 
529
- /** Constant representing the 'undefined' type. */
530
- export const undef = new LiteralType(undefined);
531
-
532
590
  /**
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) {
591
+ * Create an optional type wrapper (allows undefined).
592
+ * @template T - Type wrapper or basic type to make optional.
593
+ * @param inner - The inner type to make optional.
594
+ * @returns A union type that accepts the inner type or undefined.
595
+ *
596
+ * @example
597
+ * ```typescript
598
+ * const optionalString = E.opt(E.string);
599
+ * const optionalNumber = E.opt(E.number);
600
+ * ```
601
+ */
602
+ export function opt<const T extends TypeWrapper<unknown>|BasicType>(inner: T): TypeWrapper<UnwrapTypes<[T, typeof undef]>> {
545
603
  return or(undef, inner);
546
604
  }
547
605
 
548
606
  /**
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);
607
+ * Create an array type wrapper with optional length constraints.
608
+ * @template T - The element type.
609
+ * @param inner - Type wrapper for array elements.
610
+ * @param opts - Optional constraints (min/max length).
611
+ * @returns An array type instance.
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * const stringArray = E.array(E.string);
616
+ * const boundedArray = E.array(E.number, {min: 1, max: 10});
617
+ * ```
618
+ */
619
+ export function array<const T>(inner: TypeWrapper<T>, opts: {min?: number, max?: number} = {}): TypeWrapper<T[]> {
620
+ return new ArrayType(wrapIfLiteral(inner), opts);
563
621
  }
564
622
 
565
623
  /**
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);
624
+ * Create a Set type wrapper with optional length constraints.
625
+ * @template T - The element type.
626
+ * @param inner - Type wrapper for set elements.
627
+ * @param opts - Optional constraints (min/max length).
628
+ * @returns A set type instance.
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * const stringSet = E.set(E.string);
633
+ * const boundedSet = E.set(E.number, {min: 1, max: 10});
634
+ * ```
635
+ */
636
+ export function set<const T>(inner: TypeWrapper<T>, opts: {min?: number, max?: number} = {}): TypeWrapper<Set<T>> {
637
+ return new SetType(wrapIfLiteral(inner), opts);
638
+ }
639
+
640
+ /**
641
+ * Create a link type wrapper for model relationships.
642
+ * @template T - The target model class.
643
+ * @param TargetModel - The model class this link points to.
644
+ * @returns A link type instance.
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * class User extends E.Model<User> {
649
+ * posts = E.field(E.array(E.link(Post, 'author')));
650
+ * }
651
+ *
652
+ * class Post extends E.Model<Post> {
653
+ * author = E.field(E.link(User));
654
+ * }
655
+ * ```
656
+ */
657
+ export function link<const T extends typeof Model<any>>(TargetModel: T): TypeWrapper<InstanceType<T>> {
658
+ return new LinkType(TargetModel);
584
659
  }
585
660
 
586
661
 
587
662
  // Utility types and functions
588
- export type BasicType = TypeWrapper<any> | string | number | boolean | undefined | null;
663
+ export type BasicType = string | number | boolean | undefined | null; // TypeWrapper<any>
589
664
 
590
- export type UnwrapTypes<T extends BasicType[]> = {
665
+ export type UnwrapTypes<T extends (TypeWrapper<unknown> | BasicType)[]> = {
591
666
  [K in keyof T]: T[K] extends TypeWrapper<infer U> ? U : T[K];
592
667
  }[number];
593
668
 
@@ -598,16 +673,16 @@ function wrapIfLiteral(type: any) {
598
673
  }
599
674
 
600
675
  /**
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);
676
+ * Serialize a type wrapper to a Pack for schema persistence.
677
+ * @param arg - The type wrapper to serialize.
678
+ * @param pack - The Pack instance to write to.
679
+ */
680
+ export function serializeType(arg: TypeWrapper<any>, pack: DataPack) {
681
+ pack.write(arg.kind);
682
+ arg.serializeType(pack);
608
683
  }
609
684
 
610
- const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (bytes: Bytes, featureFlags: number) => TypeWrapper<any>}> = {
685
+ const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (pack: DataPack, featureFlags: number) => TypeWrapper<any>}> = {
611
686
  string: string,
612
687
  number: number,
613
688
  array: ArrayType,
@@ -616,19 +691,20 @@ const TYPE_WRAPPERS: Record<string, TypeWrapper<any> | {deserializeType: (bytes:
616
691
  boolean: boolean,
617
692
  id: identifier,
618
693
  link: LinkType,
694
+ set: SetType
619
695
  };
620
696
 
621
697
  /**
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();
698
+ * Deserialize a type wrapper from a Pack.
699
+ * @param pack - The Pack instance to read from.
700
+ * @param featureFlags - Feature flags for version compatibility.
701
+ * @returns The deserialized type wrapper.
702
+ */
703
+ export function deserializeType(pack: DataPack, featureFlags: number): TypeWrapper<any> {
704
+ const kind = pack.readString();
629
705
  const TypeWrapper = TYPE_WRAPPERS[kind];
630
706
  if ('deserializeType' in TypeWrapper) {
631
- return TypeWrapper.deserializeType(bytes, featureFlags);
707
+ return TypeWrapper.deserializeType(pack, featureFlags);
632
708
  } else {
633
709
  return TypeWrapper;
634
710
  }