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