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