@framesquared/data 0.1.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/LICENSE +21 -0
- package/dist/Operation-4K76GXM7.js +7 -0
- package/dist/Operation-4K76GXM7.js.map +1 -0
- package/dist/chunk-CJJTXTV2.js +30 -0
- package/dist/chunk-CJJTXTV2.js.map +1 -0
- package/dist/index.d.ts +1028 -0
- package/dist/index.js +2931 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2931 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Operation
|
|
3
|
+
} from "./chunk-CJJTXTV2.js";
|
|
4
|
+
|
|
5
|
+
// src/Model.ts
|
|
6
|
+
import { Base, Observable } from "@framesquared/core";
|
|
7
|
+
|
|
8
|
+
// src/field/Field.ts
|
|
9
|
+
var FieldType = /* @__PURE__ */ ((FieldType2) => {
|
|
10
|
+
FieldType2["STRING"] = "string";
|
|
11
|
+
FieldType2["INT"] = "int";
|
|
12
|
+
FieldType2["FLOAT"] = "float";
|
|
13
|
+
FieldType2["BOOLEAN"] = "boolean";
|
|
14
|
+
FieldType2["DATE"] = "date";
|
|
15
|
+
FieldType2["AUTO"] = "auto";
|
|
16
|
+
return FieldType2;
|
|
17
|
+
})(FieldType || {});
|
|
18
|
+
var Field = class {
|
|
19
|
+
name;
|
|
20
|
+
type;
|
|
21
|
+
mapping;
|
|
22
|
+
persist;
|
|
23
|
+
critical;
|
|
24
|
+
allowNull;
|
|
25
|
+
sortType;
|
|
26
|
+
validators;
|
|
27
|
+
customConvert;
|
|
28
|
+
customSerialize;
|
|
29
|
+
customDefault;
|
|
30
|
+
constructor(def) {
|
|
31
|
+
this.name = def.name;
|
|
32
|
+
this.type = def.type ?? "auto" /* AUTO */;
|
|
33
|
+
this.mapping = def.mapping;
|
|
34
|
+
this.persist = def.persist ?? true;
|
|
35
|
+
this.critical = def.critical ?? false;
|
|
36
|
+
this.allowNull = def.allowNull ?? false;
|
|
37
|
+
this.sortType = def.sortType;
|
|
38
|
+
this.validators = def.validators ?? [];
|
|
39
|
+
this.customConvert = def.convert;
|
|
40
|
+
this.customSerialize = def.serialize;
|
|
41
|
+
this.customDefault = def.defaultValue;
|
|
42
|
+
}
|
|
43
|
+
/** Type-specific default (overridden by subclasses). */
|
|
44
|
+
get defaultValue() {
|
|
45
|
+
return this.customDefault !== void 0 ? this.customDefault : this.getTypeDefault();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Converts a raw value to the field's type.
|
|
49
|
+
* If a custom `convert` was specified in the definition, it is used.
|
|
50
|
+
* Otherwise delegates to the subclass's `coerce()`.
|
|
51
|
+
*/
|
|
52
|
+
convert(value, record) {
|
|
53
|
+
if (this.customConvert) {
|
|
54
|
+
return this.customConvert(value, record);
|
|
55
|
+
}
|
|
56
|
+
if (value === null && this.allowNull) return null;
|
|
57
|
+
if (value === void 0) return this.defaultValue;
|
|
58
|
+
return this.coerce(value);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Serializes a value for transport.
|
|
62
|
+
*/
|
|
63
|
+
serialize(value, record) {
|
|
64
|
+
if (this.customSerialize) {
|
|
65
|
+
return this.customSerialize(value, record);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var StringField = class extends Field {
|
|
71
|
+
constructor(def) {
|
|
72
|
+
super({ ...def, type: "string" /* STRING */ });
|
|
73
|
+
}
|
|
74
|
+
getTypeDefault() {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
coerce(value) {
|
|
78
|
+
if (value === null) return "";
|
|
79
|
+
return String(value);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var IntField = class extends Field {
|
|
83
|
+
constructor(def) {
|
|
84
|
+
super({ ...def, type: "int" /* INT */ });
|
|
85
|
+
}
|
|
86
|
+
getTypeDefault() {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
coerce(value) {
|
|
90
|
+
if (value === null) return 0;
|
|
91
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
92
|
+
const n = parseInt(String(value), 10);
|
|
93
|
+
return Number.isNaN(n) ? 0 : n;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var FloatField = class extends Field {
|
|
97
|
+
constructor(def) {
|
|
98
|
+
super({ ...def, type: "float" /* FLOAT */ });
|
|
99
|
+
}
|
|
100
|
+
getTypeDefault() {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
coerce(value) {
|
|
104
|
+
if (value === null) return 0;
|
|
105
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
106
|
+
const n = parseFloat(String(value));
|
|
107
|
+
return Number.isNaN(n) ? 0 : n;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "0", "no", "off", ""]);
|
|
111
|
+
var BooleanField = class extends Field {
|
|
112
|
+
constructor(def) {
|
|
113
|
+
super({ ...def, type: "boolean" /* BOOLEAN */ });
|
|
114
|
+
}
|
|
115
|
+
getTypeDefault() {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
coerce(value) {
|
|
119
|
+
if (value === null || value === void 0) return false;
|
|
120
|
+
if (typeof value === "string") {
|
|
121
|
+
return !FALSE_STRINGS.has(value.toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
return Boolean(value);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var DateField = class extends Field {
|
|
127
|
+
constructor(def) {
|
|
128
|
+
super({ ...def, type: "date" /* DATE */ });
|
|
129
|
+
}
|
|
130
|
+
getTypeDefault() {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
coerce(value) {
|
|
134
|
+
if (value instanceof Date) return value;
|
|
135
|
+
if (typeof value === "number") return new Date(value);
|
|
136
|
+
if (typeof value === "string") {
|
|
137
|
+
const d = new Date(value);
|
|
138
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
serialize(value) {
|
|
143
|
+
if (value instanceof Date) return value.toISOString();
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var AutoField = class extends Field {
|
|
148
|
+
constructor(def) {
|
|
149
|
+
super({ ...def, type: "auto" /* AUTO */ });
|
|
150
|
+
}
|
|
151
|
+
getTypeDefault() {
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
coerce(value) {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var FIELD_CONSTRUCTORS = {
|
|
159
|
+
["string" /* STRING */]: StringField,
|
|
160
|
+
["int" /* INT */]: IntField,
|
|
161
|
+
["float" /* FLOAT */]: FloatField,
|
|
162
|
+
["boolean" /* BOOLEAN */]: BooleanField,
|
|
163
|
+
["date" /* DATE */]: DateField,
|
|
164
|
+
["auto" /* AUTO */]: AutoField
|
|
165
|
+
};
|
|
166
|
+
function createField(def) {
|
|
167
|
+
if (typeof def === "string") {
|
|
168
|
+
return new AutoField({ name: def });
|
|
169
|
+
}
|
|
170
|
+
const type = def.type ?? "auto" /* AUTO */;
|
|
171
|
+
const Ctor = FIELD_CONSTRUCTORS[type] ?? AutoField;
|
|
172
|
+
return new Ctor(def);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/association/Association.ts
|
|
176
|
+
var Association = class {
|
|
177
|
+
type;
|
|
178
|
+
config;
|
|
179
|
+
ownerModelName;
|
|
180
|
+
/** Resolved associated model class (may be null until forward ref resolves). */
|
|
181
|
+
associatedModel = null;
|
|
182
|
+
constructor(type, ownerModelName, config) {
|
|
183
|
+
this.type = type;
|
|
184
|
+
this.ownerModelName = ownerModelName;
|
|
185
|
+
this.config = config;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The property name used for nested JSON loading and as the
|
|
189
|
+
* default getter/collection accessor name.
|
|
190
|
+
*/
|
|
191
|
+
get associationName() {
|
|
192
|
+
if (!this.associatedModel) return "";
|
|
193
|
+
const raw = this.associatedModel.$className.split(".").pop();
|
|
194
|
+
const lower = raw[0].toLowerCase() + raw.slice(1);
|
|
195
|
+
if (this.type === "hasMany" || this.type === "manyToMany") {
|
|
196
|
+
return lower.endsWith("s") ? lower : lower + "s";
|
|
197
|
+
}
|
|
198
|
+
return lower;
|
|
199
|
+
}
|
|
200
|
+
get getterName() {
|
|
201
|
+
if (this.config.getterName) return this.config.getterName;
|
|
202
|
+
const name = this.associationName;
|
|
203
|
+
return `get${name[0].toUpperCase()}${name.slice(1)}`;
|
|
204
|
+
}
|
|
205
|
+
get setterName() {
|
|
206
|
+
if (this.config.setterName) return this.config.setterName;
|
|
207
|
+
const name = this.associationName;
|
|
208
|
+
return `set${name[0].toUpperCase()}${name.slice(1)}`;
|
|
209
|
+
}
|
|
210
|
+
get foreignKey() {
|
|
211
|
+
return this.config.foreignKey;
|
|
212
|
+
}
|
|
213
|
+
get primaryKey() {
|
|
214
|
+
return this.config.primaryKey ?? "id";
|
|
215
|
+
}
|
|
216
|
+
get cascade() {
|
|
217
|
+
return this.config.cascade ?? false;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/association/HasOne.ts
|
|
222
|
+
var HasOne = class extends Association {
|
|
223
|
+
constructor(ownerModelName, config) {
|
|
224
|
+
super("hasOne", ownerModelName, config);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/association/HasMany.ts
|
|
229
|
+
var HasMany = class extends Association {
|
|
230
|
+
constructor(ownerModelName, config) {
|
|
231
|
+
super("hasMany", ownerModelName, config);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/association/BelongsTo.ts
|
|
236
|
+
var BelongsTo = class extends Association {
|
|
237
|
+
constructor(ownerModelName, config) {
|
|
238
|
+
super("belongsTo", ownerModelName, config);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/association/ManyToMany.ts
|
|
243
|
+
var ManyToMany = class extends Association {
|
|
244
|
+
constructor(ownerModelName, config) {
|
|
245
|
+
super("manyToMany", ownerModelName, config);
|
|
246
|
+
}
|
|
247
|
+
get through() {
|
|
248
|
+
return this.config.through ?? "";
|
|
249
|
+
}
|
|
250
|
+
get otherKey() {
|
|
251
|
+
return this.config.otherKey ?? "";
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/Schema.ts
|
|
256
|
+
var models = /* @__PURE__ */ new Map();
|
|
257
|
+
var associations = /* @__PURE__ */ new Map();
|
|
258
|
+
var pendingReferences = /* @__PURE__ */ new Map();
|
|
259
|
+
var SchemaImpl = class {
|
|
260
|
+
/**
|
|
261
|
+
* Registers a Model class and processes its association declarations.
|
|
262
|
+
* If the model has `static hasOne`, `static hasMany`, `static belongsTo`,
|
|
263
|
+
* or `static manyToMany` arrays, creates Association instances for each.
|
|
264
|
+
*/
|
|
265
|
+
register(ModelClass) {
|
|
266
|
+
const name = ModelClass.$className;
|
|
267
|
+
models.set(name, ModelClass);
|
|
268
|
+
this.processAssociations(ModelClass, "hasOne", HasOne);
|
|
269
|
+
this.processAssociations(ModelClass, "hasMany", HasMany);
|
|
270
|
+
this.processAssociations(ModelClass, "belongsTo", BelongsTo);
|
|
271
|
+
this.processAssociations(ModelClass, "manyToMany", ManyToMany);
|
|
272
|
+
const pending = pendingReferences.get(name);
|
|
273
|
+
if (pending) {
|
|
274
|
+
for (const assoc of pending) {
|
|
275
|
+
assoc.associatedModel = ModelClass;
|
|
276
|
+
}
|
|
277
|
+
pendingReferences.delete(name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Retrieves a registered Model class by name.
|
|
282
|
+
*/
|
|
283
|
+
get(name) {
|
|
284
|
+
return models.get(name);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Returns all Association instances owned by the given model name.
|
|
288
|
+
*/
|
|
289
|
+
getAssociations(modelName) {
|
|
290
|
+
return associations.get(modelName) ?? [];
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Clears all registrations (for testing).
|
|
294
|
+
*/
|
|
295
|
+
clear() {
|
|
296
|
+
models.clear();
|
|
297
|
+
associations.clear();
|
|
298
|
+
pendingReferences.clear();
|
|
299
|
+
}
|
|
300
|
+
// -----------------------------------------------------------------------
|
|
301
|
+
// Internal: process static association arrays
|
|
302
|
+
// -----------------------------------------------------------------------
|
|
303
|
+
processAssociations(ModelClass, staticProp, AssocClass) {
|
|
304
|
+
const configs = ModelClass[staticProp];
|
|
305
|
+
if (!configs || !Array.isArray(configs)) return;
|
|
306
|
+
const ownerName = ModelClass.$className;
|
|
307
|
+
if (!associations.has(ownerName)) {
|
|
308
|
+
associations.set(ownerName, []);
|
|
309
|
+
}
|
|
310
|
+
const list = associations.get(ownerName);
|
|
311
|
+
for (const config of configs) {
|
|
312
|
+
const assoc = new AssocClass(ownerName, config);
|
|
313
|
+
const modelRef = config.model;
|
|
314
|
+
if (typeof modelRef === "string") {
|
|
315
|
+
const resolved = models.get(modelRef);
|
|
316
|
+
if (resolved) {
|
|
317
|
+
assoc.associatedModel = resolved;
|
|
318
|
+
} else {
|
|
319
|
+
if (!pendingReferences.has(modelRef)) {
|
|
320
|
+
pendingReferences.set(modelRef, []);
|
|
321
|
+
}
|
|
322
|
+
pendingReferences.get(modelRef).push(assoc);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
assoc.associatedModel = modelRef;
|
|
326
|
+
}
|
|
327
|
+
list.push(assoc);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var Schema = new SchemaImpl();
|
|
332
|
+
|
|
333
|
+
// src/ModelCollection.ts
|
|
334
|
+
var ModelCollection = class {
|
|
335
|
+
items = [];
|
|
336
|
+
getCount() {
|
|
337
|
+
return this.items.length;
|
|
338
|
+
}
|
|
339
|
+
getAll() {
|
|
340
|
+
return [...this.items];
|
|
341
|
+
}
|
|
342
|
+
add(record) {
|
|
343
|
+
if (!this.items.includes(record)) {
|
|
344
|
+
this.items.push(record);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
remove(record) {
|
|
348
|
+
const idx = this.items.indexOf(record);
|
|
349
|
+
if (idx !== -1) this.items.splice(idx, 1);
|
|
350
|
+
}
|
|
351
|
+
getById(id) {
|
|
352
|
+
return this.items.find((r) => r.getId() === id);
|
|
353
|
+
}
|
|
354
|
+
filter(fn) {
|
|
355
|
+
return this.items.filter(fn);
|
|
356
|
+
}
|
|
357
|
+
destroyAll() {
|
|
358
|
+
for (const item of this.items) {
|
|
359
|
+
if (!item.isDestroyed) item.destroy();
|
|
360
|
+
}
|
|
361
|
+
this.items.length = 0;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var ManyToManyCollection = class extends ModelCollection {
|
|
365
|
+
junctions = /* @__PURE__ */ new Map();
|
|
366
|
+
link(record, _junctionData) {
|
|
367
|
+
this.add(record);
|
|
368
|
+
this.junctions.set(record, _junctionData ?? {});
|
|
369
|
+
}
|
|
370
|
+
unlink(record) {
|
|
371
|
+
this.remove(record);
|
|
372
|
+
this.junctions.delete(record);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// src/Model.ts
|
|
377
|
+
function getNestedValue(data, path) {
|
|
378
|
+
const segments = path.split(".");
|
|
379
|
+
let current = data;
|
|
380
|
+
for (const seg of segments) {
|
|
381
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
current = current[seg];
|
|
385
|
+
}
|
|
386
|
+
return current;
|
|
387
|
+
}
|
|
388
|
+
var MODEL_PROPS = /* @__PURE__ */ new Set([
|
|
389
|
+
// Commonly accessed non-field properties/methods that should NOT
|
|
390
|
+
// be intercepted by the proxy's field get/set logic.
|
|
391
|
+
"constructor",
|
|
392
|
+
"prototype",
|
|
393
|
+
"__proto__",
|
|
394
|
+
"get",
|
|
395
|
+
"set",
|
|
396
|
+
"getData",
|
|
397
|
+
"getId",
|
|
398
|
+
"setId",
|
|
399
|
+
"isModified",
|
|
400
|
+
"getChanges",
|
|
401
|
+
"commit",
|
|
402
|
+
"reject",
|
|
403
|
+
"validate",
|
|
404
|
+
"isValid",
|
|
405
|
+
"on",
|
|
406
|
+
"un",
|
|
407
|
+
"fireEvent",
|
|
408
|
+
"fireEventArgs",
|
|
409
|
+
"addListener",
|
|
410
|
+
"removeListener",
|
|
411
|
+
"hasListener",
|
|
412
|
+
"clearListeners",
|
|
413
|
+
"suspendEvents",
|
|
414
|
+
"resumeEvents",
|
|
415
|
+
"isSuspended",
|
|
416
|
+
"mon",
|
|
417
|
+
"mun",
|
|
418
|
+
"relayEvents",
|
|
419
|
+
"fireEventedAction",
|
|
420
|
+
"destroy",
|
|
421
|
+
"isDestroyed",
|
|
422
|
+
"$destroyHooks",
|
|
423
|
+
"$className",
|
|
424
|
+
"self",
|
|
425
|
+
"$callStack",
|
|
426
|
+
"$configInitialized",
|
|
427
|
+
"$pendingConfig",
|
|
428
|
+
"hasMixin",
|
|
429
|
+
"initConfig",
|
|
430
|
+
"callParent",
|
|
431
|
+
"modified",
|
|
432
|
+
"phantom",
|
|
433
|
+
"$associations",
|
|
434
|
+
"$fieldMap",
|
|
435
|
+
"$data",
|
|
436
|
+
Symbol.dispose,
|
|
437
|
+
Symbol.toPrimitive,
|
|
438
|
+
Symbol.toStringTag
|
|
439
|
+
]);
|
|
440
|
+
function createModelProxy(model) {
|
|
441
|
+
return new Proxy(model, {
|
|
442
|
+
get(target, prop, receiver) {
|
|
443
|
+
if (MODEL_PROPS.has(prop) || typeof prop === "symbol") {
|
|
444
|
+
return Reflect.get(target, prop, receiver);
|
|
445
|
+
}
|
|
446
|
+
if (prop in target) {
|
|
447
|
+
const desc = Object.getOwnPropertyDescriptor(target, prop) ?? Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop);
|
|
448
|
+
if (desc?.get || typeof desc?.value === "function") {
|
|
449
|
+
return Reflect.get(target, prop, receiver);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (typeof prop === "string" && target.$fieldMap?.has(prop)) {
|
|
453
|
+
return target.get(prop);
|
|
454
|
+
}
|
|
455
|
+
return Reflect.get(target, prop, receiver);
|
|
456
|
+
},
|
|
457
|
+
set(target, prop, value, receiver) {
|
|
458
|
+
if (typeof prop === "string" && target.$fieldMap?.has(prop)) {
|
|
459
|
+
target.set(prop, value);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
return Reflect.set(target, prop, value, receiver);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
var ObservableMixin = Observable;
|
|
467
|
+
var Model = class extends Base {
|
|
468
|
+
static $className = "Ext.data.Model";
|
|
469
|
+
/** Field definitions — override in subclasses. */
|
|
470
|
+
static fields = [];
|
|
471
|
+
/** Name of the ID field. */
|
|
472
|
+
static idProperty = "id";
|
|
473
|
+
// -- Instance state (non-enumerable to keep proxy clean) --
|
|
474
|
+
/** Resolved Field instances keyed by name. */
|
|
475
|
+
$fieldMap;
|
|
476
|
+
/** Current field values. */
|
|
477
|
+
$data;
|
|
478
|
+
/** Previous values for dirty fields (fieldName → old value). */
|
|
479
|
+
modified = {};
|
|
480
|
+
/** Per-instance association storage: assocName → Model | ModelCollection */
|
|
481
|
+
$associations = /* @__PURE__ */ new Map();
|
|
482
|
+
// -----------------------------------------------------------------------
|
|
483
|
+
// Construction
|
|
484
|
+
// -----------------------------------------------------------------------
|
|
485
|
+
constructor(_data) {
|
|
486
|
+
super();
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Factory method that sets up fields, applies data, wraps in proxy.
|
|
490
|
+
*/
|
|
491
|
+
static create(data) {
|
|
492
|
+
const instance = new this();
|
|
493
|
+
instance.$fieldMap = /* @__PURE__ */ new Map();
|
|
494
|
+
instance.$data = {};
|
|
495
|
+
instance.modified = {};
|
|
496
|
+
instance.$associations = /* @__PURE__ */ new Map();
|
|
497
|
+
const fieldDefs = this.fields;
|
|
498
|
+
for (const def of fieldDefs) {
|
|
499
|
+
const field = createField(def);
|
|
500
|
+
instance.$fieldMap.set(field.name, field);
|
|
501
|
+
}
|
|
502
|
+
ensureObservable(instance);
|
|
503
|
+
instance.$loadData(data ?? {});
|
|
504
|
+
installAssociations(instance, data ?? {});
|
|
505
|
+
return createModelProxy(instance);
|
|
506
|
+
}
|
|
507
|
+
// -----------------------------------------------------------------------
|
|
508
|
+
// Data loading (initial — not dirty)
|
|
509
|
+
// -----------------------------------------------------------------------
|
|
510
|
+
/** @internal Loads initial data without marking as dirty. */
|
|
511
|
+
$loadData(rawData) {
|
|
512
|
+
for (const [key, value] of Object.entries(rawData)) {
|
|
513
|
+
if (!this.$fieldMap.has(key)) {
|
|
514
|
+
this.$data[key] = value;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
for (const [name, field] of this.$fieldMap) {
|
|
518
|
+
let value;
|
|
519
|
+
if (field.mapping) {
|
|
520
|
+
value = getNestedValue(rawData, field.mapping);
|
|
521
|
+
} else {
|
|
522
|
+
value = rawData[name];
|
|
523
|
+
}
|
|
524
|
+
if (value !== void 0) {
|
|
525
|
+
value = field.convert(value, this);
|
|
526
|
+
} else {
|
|
527
|
+
value = field.defaultValue;
|
|
528
|
+
if (value !== void 0) {
|
|
529
|
+
value = field.convert(value, this);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
this.$data[name] = value;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// -----------------------------------------------------------------------
|
|
536
|
+
// get / set
|
|
537
|
+
// -----------------------------------------------------------------------
|
|
538
|
+
get(fieldName) {
|
|
539
|
+
return this.$data[fieldName];
|
|
540
|
+
}
|
|
541
|
+
set(fieldNameOrData, value) {
|
|
542
|
+
const changes = typeof fieldNameOrData === "string" ? { [fieldNameOrData]: value } : fieldNameOrData;
|
|
543
|
+
const modifiedFields = [];
|
|
544
|
+
for (const [name, newValue] of Object.entries(changes)) {
|
|
545
|
+
const field = this.$fieldMap.get(name);
|
|
546
|
+
if (!field) {
|
|
547
|
+
const oldValue2 = this.$data[name];
|
|
548
|
+
if (newValue !== oldValue2) {
|
|
549
|
+
if (!(name in this.modified)) this.modified[name] = oldValue2;
|
|
550
|
+
this.$data[name] = newValue;
|
|
551
|
+
modifiedFields.push(name);
|
|
552
|
+
}
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const converted = field.convert(newValue, this);
|
|
556
|
+
const oldValue = this.$data[name];
|
|
557
|
+
if (converted === oldValue) continue;
|
|
558
|
+
if (!(name in this.modified)) {
|
|
559
|
+
this.modified[name] = oldValue;
|
|
560
|
+
}
|
|
561
|
+
this.$data[name] = converted;
|
|
562
|
+
modifiedFields.push(name);
|
|
563
|
+
const Ctor = this.constructor;
|
|
564
|
+
if (name === Ctor.idProperty && typeof this.fireEvent === "function") {
|
|
565
|
+
this.fireEvent("idchanged", this, converted, oldValue);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (modifiedFields.length > 0 && typeof this.fireEvent === "function") {
|
|
569
|
+
this.fireEvent("change", this, modifiedFields);
|
|
570
|
+
}
|
|
571
|
+
return modifiedFields;
|
|
572
|
+
}
|
|
573
|
+
getData(options) {
|
|
574
|
+
const result = {};
|
|
575
|
+
for (const [name, field] of this.$fieldMap) {
|
|
576
|
+
const value = this.$data[name];
|
|
577
|
+
result[name] = options?.serialize ? field.serialize(value, this) : value;
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
// -----------------------------------------------------------------------
|
|
582
|
+
// Dirty tracking
|
|
583
|
+
// -----------------------------------------------------------------------
|
|
584
|
+
isModified(fieldName) {
|
|
585
|
+
if (fieldName) {
|
|
586
|
+
return fieldName in this.modified;
|
|
587
|
+
}
|
|
588
|
+
return Object.keys(this.modified).length > 0;
|
|
589
|
+
}
|
|
590
|
+
getChanges() {
|
|
591
|
+
const result = {};
|
|
592
|
+
for (const name of Object.keys(this.modified)) {
|
|
593
|
+
result[name] = this.$data[name];
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
commit(silent) {
|
|
598
|
+
this.modified = {};
|
|
599
|
+
if (!silent && typeof this.fireEvent === "function") {
|
|
600
|
+
this.fireEvent("commit", this);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
reject(silent) {
|
|
604
|
+
for (const [name, oldValue] of Object.entries(this.modified)) {
|
|
605
|
+
this.$data[name] = oldValue;
|
|
606
|
+
}
|
|
607
|
+
this.modified = {};
|
|
608
|
+
if (!silent && typeof this.fireEvent === "function") {
|
|
609
|
+
this.fireEvent("reject", this);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// -----------------------------------------------------------------------
|
|
613
|
+
// Identification
|
|
614
|
+
// -----------------------------------------------------------------------
|
|
615
|
+
getId() {
|
|
616
|
+
const Ctor = this.constructor;
|
|
617
|
+
return this.$data[Ctor.idProperty];
|
|
618
|
+
}
|
|
619
|
+
setId(id) {
|
|
620
|
+
const Ctor = this.constructor;
|
|
621
|
+
this.set(Ctor.idProperty, id);
|
|
622
|
+
}
|
|
623
|
+
get phantom() {
|
|
624
|
+
const id = this.getId();
|
|
625
|
+
return id === void 0 || id === null || id === 0 || id === "";
|
|
626
|
+
}
|
|
627
|
+
// -----------------------------------------------------------------------
|
|
628
|
+
// Validation
|
|
629
|
+
// -----------------------------------------------------------------------
|
|
630
|
+
validate() {
|
|
631
|
+
const errors = [];
|
|
632
|
+
for (const [name, field] of this.$fieldMap) {
|
|
633
|
+
const value = this.$data[name];
|
|
634
|
+
for (const validator of field.validators) {
|
|
635
|
+
const msg = validator(value, this);
|
|
636
|
+
if (msg) {
|
|
637
|
+
errors.push({ field: name, message: msg });
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return { valid: errors.length === 0, errors };
|
|
642
|
+
}
|
|
643
|
+
isValid() {
|
|
644
|
+
return this.validate().valid;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
function ensureObservable(instance) {
|
|
648
|
+
const proto = ObservableMixin.prototype;
|
|
649
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
650
|
+
if (name === "constructor") continue;
|
|
651
|
+
if (name in instance) continue;
|
|
652
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
653
|
+
if (desc && typeof desc.value === "function") {
|
|
654
|
+
instance[name] = desc.value.bind(instance);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function installAssociations(instance, rawData) {
|
|
659
|
+
const modelName = instance.constructor.$className;
|
|
660
|
+
const assocs = Schema.getAssociations(modelName);
|
|
661
|
+
if (assocs.length === 0) return;
|
|
662
|
+
for (const assoc of assocs) {
|
|
663
|
+
if (!assoc.associatedModel) continue;
|
|
664
|
+
switch (assoc.type) {
|
|
665
|
+
case "hasOne":
|
|
666
|
+
installHasOne(instance, assoc, rawData);
|
|
667
|
+
break;
|
|
668
|
+
case "hasMany":
|
|
669
|
+
installHasMany(instance, assoc, rawData);
|
|
670
|
+
break;
|
|
671
|
+
case "belongsTo":
|
|
672
|
+
installBelongsTo(instance, assoc, rawData);
|
|
673
|
+
break;
|
|
674
|
+
case "manyToMany":
|
|
675
|
+
installManyToMany(instance, assoc, rawData);
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function installHasOne(instance, assoc, rawData) {
|
|
681
|
+
const AssocModel = assoc.associatedModel;
|
|
682
|
+
const name = assoc.associationName;
|
|
683
|
+
const getterName = assoc.getterName;
|
|
684
|
+
const setterName = assoc.setterName;
|
|
685
|
+
instance.$associations.set(name, null);
|
|
686
|
+
instance[getterName] = function() {
|
|
687
|
+
return instance.$associations.get(name) ?? null;
|
|
688
|
+
};
|
|
689
|
+
instance[setterName] = function(child) {
|
|
690
|
+
instance.$associations.set(name, child);
|
|
691
|
+
const parentId = instance.get(assoc.primaryKey);
|
|
692
|
+
if (parentId !== void 0 && child.$fieldMap?.has(assoc.foreignKey)) {
|
|
693
|
+
child.set(assoc.foreignKey, parentId);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
const nestedData = rawData[name];
|
|
697
|
+
if (nestedData && typeof nestedData === "object" && !Array.isArray(nestedData)) {
|
|
698
|
+
const childData = { ...nestedData };
|
|
699
|
+
const parentId = instance.get(assoc.primaryKey);
|
|
700
|
+
if (parentId !== void 0) {
|
|
701
|
+
childData[assoc.foreignKey] = parentId;
|
|
702
|
+
}
|
|
703
|
+
const child = AssocModel.create(childData);
|
|
704
|
+
instance.$associations.set(name, child);
|
|
705
|
+
}
|
|
706
|
+
if (assoc.cascade) {
|
|
707
|
+
instance.$destroyHooks.push(() => {
|
|
708
|
+
const child = instance.$associations.get(name);
|
|
709
|
+
if (child && !child.isDestroyed) child.destroy();
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function installHasMany(instance, assoc, rawData) {
|
|
714
|
+
const AssocModel = assoc.associatedModel;
|
|
715
|
+
const name = assoc.associationName;
|
|
716
|
+
const collection = new ModelCollection();
|
|
717
|
+
instance.$associations.set(name, collection);
|
|
718
|
+
const wrappedCollection = {
|
|
719
|
+
getCount: () => collection.getCount(),
|
|
720
|
+
getAll: () => collection.getAll(),
|
|
721
|
+
getById: (id) => collection.getById(id),
|
|
722
|
+
filter: (fn) => collection.filter(fn),
|
|
723
|
+
add(child) {
|
|
724
|
+
const parentId = instance.get(assoc.primaryKey);
|
|
725
|
+
if (parentId !== void 0 && child.$fieldMap?.has(assoc.foreignKey)) {
|
|
726
|
+
child.set(assoc.foreignKey, parentId);
|
|
727
|
+
}
|
|
728
|
+
collection.add(child);
|
|
729
|
+
},
|
|
730
|
+
remove(child) {
|
|
731
|
+
collection.remove(child);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
instance[name] = function() {
|
|
735
|
+
return wrappedCollection;
|
|
736
|
+
};
|
|
737
|
+
const nestedData = rawData[name];
|
|
738
|
+
if (Array.isArray(nestedData)) {
|
|
739
|
+
const parentId = instance.get(assoc.primaryKey);
|
|
740
|
+
for (const itemData of nestedData) {
|
|
741
|
+
if (typeof itemData === "object" && itemData !== null) {
|
|
742
|
+
const childData = { ...itemData };
|
|
743
|
+
if (parentId !== void 0) {
|
|
744
|
+
childData[assoc.foreignKey] = parentId;
|
|
745
|
+
}
|
|
746
|
+
const child = AssocModel.create(childData);
|
|
747
|
+
collection.add(child);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (assoc.cascade) {
|
|
752
|
+
instance.$destroyHooks.push(() => {
|
|
753
|
+
collection.destroyAll();
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function installBelongsTo(instance, assoc, rawData) {
|
|
758
|
+
const AssocModel = assoc.associatedModel;
|
|
759
|
+
const name = assoc.associationName;
|
|
760
|
+
const getterName = assoc.getterName;
|
|
761
|
+
const setterName = assoc.setterName;
|
|
762
|
+
instance.$associations.set(name, null);
|
|
763
|
+
instance[getterName] = function() {
|
|
764
|
+
return instance.$associations.get(name) ?? null;
|
|
765
|
+
};
|
|
766
|
+
instance[setterName] = function(parent) {
|
|
767
|
+
instance.$associations.set(name, parent);
|
|
768
|
+
const parentId = parent.get(assoc.primaryKey);
|
|
769
|
+
if (parentId !== void 0 && instance.$fieldMap?.has(assoc.foreignKey)) {
|
|
770
|
+
instance.set(assoc.foreignKey, parentId);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
const nestedData = rawData[name];
|
|
774
|
+
if (nestedData && typeof nestedData === "object" && !Array.isArray(nestedData)) {
|
|
775
|
+
const parent = AssocModel.create(nestedData);
|
|
776
|
+
instance.$associations.set(name, parent);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function installManyToMany(instance, assoc, rawData) {
|
|
780
|
+
const AssocModel = assoc.associatedModel;
|
|
781
|
+
const name = assoc.associationName;
|
|
782
|
+
const collection = new ManyToManyCollection();
|
|
783
|
+
instance.$associations.set(name, collection);
|
|
784
|
+
instance[name] = function() {
|
|
785
|
+
return collection;
|
|
786
|
+
};
|
|
787
|
+
const nestedData = rawData[name];
|
|
788
|
+
if (Array.isArray(nestedData)) {
|
|
789
|
+
for (const itemData of nestedData) {
|
|
790
|
+
if (typeof itemData === "object" && itemData !== null) {
|
|
791
|
+
const child = AssocModel.create(itemData);
|
|
792
|
+
collection.link(child);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/validators.ts
|
|
799
|
+
function presence(message = "is required") {
|
|
800
|
+
return (value) => {
|
|
801
|
+
if (value === null || value === void 0 || value === "") {
|
|
802
|
+
return message;
|
|
803
|
+
}
|
|
804
|
+
return null;
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function length(opts) {
|
|
808
|
+
return (value) => {
|
|
809
|
+
const str = String(value ?? "");
|
|
810
|
+
if (opts.min !== void 0 && str.length < opts.min) {
|
|
811
|
+
return opts.message ?? `must be at least ${opts.min} characters`;
|
|
812
|
+
}
|
|
813
|
+
if (opts.max !== void 0 && str.length > opts.max) {
|
|
814
|
+
return opts.message ?? `must be at most ${opts.max} characters`;
|
|
815
|
+
}
|
|
816
|
+
return null;
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function formatValidator(pattern, message) {
|
|
820
|
+
return (value) => {
|
|
821
|
+
if (!pattern.test(String(value ?? ""))) {
|
|
822
|
+
return message ?? `does not match the required format`;
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function inclusion(list, message) {
|
|
828
|
+
return (value) => {
|
|
829
|
+
if (!list.includes(value)) {
|
|
830
|
+
return message ?? `must be one of: ${list.join(", ")}`;
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function exclusion(list, message) {
|
|
836
|
+
return (value) => {
|
|
837
|
+
if (list.includes(value)) {
|
|
838
|
+
return message ?? `is not allowed`;
|
|
839
|
+
}
|
|
840
|
+
return null;
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function rangeValidator(opts) {
|
|
844
|
+
return (value) => {
|
|
845
|
+
const n = Number(value);
|
|
846
|
+
if (opts.min !== void 0 && n < opts.min) {
|
|
847
|
+
return opts.message ?? `must be at least ${opts.min}`;
|
|
848
|
+
}
|
|
849
|
+
if (opts.max !== void 0 && n > opts.max) {
|
|
850
|
+
return opts.message ?? `must be at most ${opts.max}`;
|
|
851
|
+
}
|
|
852
|
+
return null;
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
856
|
+
function email(message = "is not a valid email address") {
|
|
857
|
+
return (value) => {
|
|
858
|
+
if (!EMAIL_RE.test(String(value ?? ""))) {
|
|
859
|
+
return message;
|
|
860
|
+
}
|
|
861
|
+
return null;
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/store/Store.ts
|
|
866
|
+
import { Base as Base2, Observable as Observable2 } from "@framesquared/core";
|
|
867
|
+
|
|
868
|
+
// src/store/Collection.ts
|
|
869
|
+
var Collection = class {
|
|
870
|
+
items = [];
|
|
871
|
+
keyMap = null;
|
|
872
|
+
keyFn;
|
|
873
|
+
constructor(keyFn) {
|
|
874
|
+
this.keyFn = keyFn;
|
|
875
|
+
if (keyFn) {
|
|
876
|
+
this.keyMap = /* @__PURE__ */ new Map();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
getCount() {
|
|
880
|
+
return this.items.length;
|
|
881
|
+
}
|
|
882
|
+
getAt(index) {
|
|
883
|
+
return this.items[index];
|
|
884
|
+
}
|
|
885
|
+
getByKey(key) {
|
|
886
|
+
return this.keyMap?.get(key);
|
|
887
|
+
}
|
|
888
|
+
indexOf(item) {
|
|
889
|
+
return this.items.indexOf(item);
|
|
890
|
+
}
|
|
891
|
+
contains(item) {
|
|
892
|
+
return this.items.includes(item);
|
|
893
|
+
}
|
|
894
|
+
add(item) {
|
|
895
|
+
this.items.push(item);
|
|
896
|
+
if (this.keyFn && this.keyMap) {
|
|
897
|
+
this.keyMap.set(this.keyFn(item), item);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
insert(index, item) {
|
|
901
|
+
this.items.splice(index, 0, item);
|
|
902
|
+
if (this.keyFn && this.keyMap) {
|
|
903
|
+
this.keyMap.set(this.keyFn(item), item);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
remove(item) {
|
|
907
|
+
const idx = this.items.indexOf(item);
|
|
908
|
+
if (idx === -1) return void 0;
|
|
909
|
+
this.items.splice(idx, 1);
|
|
910
|
+
if (this.keyFn && this.keyMap) {
|
|
911
|
+
this.keyMap.delete(this.keyFn(item));
|
|
912
|
+
}
|
|
913
|
+
return item;
|
|
914
|
+
}
|
|
915
|
+
removeAt(index, count = 1) {
|
|
916
|
+
const removed = this.items.splice(index, count);
|
|
917
|
+
if (this.keyFn && this.keyMap) {
|
|
918
|
+
for (const item of removed) {
|
|
919
|
+
this.keyMap.delete(this.keyFn(item));
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return removed;
|
|
923
|
+
}
|
|
924
|
+
clear() {
|
|
925
|
+
this.items.length = 0;
|
|
926
|
+
this.keyMap?.clear();
|
|
927
|
+
}
|
|
928
|
+
toArray() {
|
|
929
|
+
return [...this.items];
|
|
930
|
+
}
|
|
931
|
+
each(fn) {
|
|
932
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
933
|
+
if (fn(this.items[i], i) === false) break;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
find(fn) {
|
|
937
|
+
return this.items.find(fn);
|
|
938
|
+
}
|
|
939
|
+
filter(fn) {
|
|
940
|
+
return this.items.filter(fn);
|
|
941
|
+
}
|
|
942
|
+
sort(compareFn) {
|
|
943
|
+
this.items.sort(compareFn);
|
|
944
|
+
}
|
|
945
|
+
first() {
|
|
946
|
+
return this.items[0];
|
|
947
|
+
}
|
|
948
|
+
last() {
|
|
949
|
+
return this.items[this.items.length - 1];
|
|
950
|
+
}
|
|
951
|
+
getRange(start = 0, end) {
|
|
952
|
+
return this.items.slice(start, end);
|
|
953
|
+
}
|
|
954
|
+
/** Rebuilds the key map (call after sort or external mutation). */
|
|
955
|
+
rekey() {
|
|
956
|
+
if (this.keyFn && this.keyMap) {
|
|
957
|
+
this.keyMap.clear();
|
|
958
|
+
for (const item of this.items) {
|
|
959
|
+
this.keyMap.set(this.keyFn(item), item);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/store/Store.ts
|
|
966
|
+
var ObservableMixin2 = Observable2;
|
|
967
|
+
function ensureObservable2(instance) {
|
|
968
|
+
const proto = ObservableMixin2.prototype;
|
|
969
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
970
|
+
if (name === "constructor") continue;
|
|
971
|
+
if (name in instance) continue;
|
|
972
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
973
|
+
if (desc && typeof desc.value === "function") {
|
|
974
|
+
instance[name] = desc.value.bind(instance);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function matchFilter(record, f) {
|
|
979
|
+
if (f.filterFn) return f.filterFn(record);
|
|
980
|
+
const fieldValue = record.get(f.property);
|
|
981
|
+
const filterValue = f.value;
|
|
982
|
+
const op = f.operator ?? "=";
|
|
983
|
+
const ci = !(f.caseSensitive ?? false);
|
|
984
|
+
switch (op) {
|
|
985
|
+
case "=":
|
|
986
|
+
return fieldValue === filterValue;
|
|
987
|
+
case "!=":
|
|
988
|
+
return fieldValue !== filterValue;
|
|
989
|
+
case "<":
|
|
990
|
+
return fieldValue < filterValue;
|
|
991
|
+
case "<=":
|
|
992
|
+
return fieldValue <= filterValue;
|
|
993
|
+
case ">":
|
|
994
|
+
return fieldValue > filterValue;
|
|
995
|
+
case ">=":
|
|
996
|
+
return fieldValue >= filterValue;
|
|
997
|
+
case "like": {
|
|
998
|
+
const sv = ci ? String(fieldValue).toLowerCase() : String(fieldValue);
|
|
999
|
+
const fv = ci ? String(filterValue).toLowerCase() : String(filterValue);
|
|
1000
|
+
return sv.includes(fv);
|
|
1001
|
+
}
|
|
1002
|
+
case "in":
|
|
1003
|
+
return Array.isArray(filterValue) && filterValue.includes(fieldValue);
|
|
1004
|
+
case "notin":
|
|
1005
|
+
return Array.isArray(filterValue) && !filterValue.includes(fieldValue);
|
|
1006
|
+
default:
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function buildComparator(sorters) {
|
|
1011
|
+
return (a, b) => {
|
|
1012
|
+
for (const sorter of sorters) {
|
|
1013
|
+
if (sorter.sorterFn) {
|
|
1014
|
+
const result = sorter.sorterFn(a, b);
|
|
1015
|
+
if (result !== 0) return result;
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
const va = a.get(sorter.property);
|
|
1019
|
+
const vb = b.get(sorter.property);
|
|
1020
|
+
const dir = sorter.direction === "DESC" ? -1 : 1;
|
|
1021
|
+
if (va < vb) return -1 * dir;
|
|
1022
|
+
if (va > vb) return 1 * dir;
|
|
1023
|
+
}
|
|
1024
|
+
return 0;
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
var Store = class extends Base2 {
|
|
1028
|
+
static $className = "Ext.data.Store";
|
|
1029
|
+
ModelClass;
|
|
1030
|
+
data;
|
|
1031
|
+
allData;
|
|
1032
|
+
// unfiltered snapshot
|
|
1033
|
+
currentSorters = [];
|
|
1034
|
+
currentFilters = [];
|
|
1035
|
+
groupField = null;
|
|
1036
|
+
groupDirection = "ASC";
|
|
1037
|
+
cachedGroups = null;
|
|
1038
|
+
removedRecords = [];
|
|
1039
|
+
// Observable state (populated by ensureObservable)
|
|
1040
|
+
$destroyHooks = [];
|
|
1041
|
+
constructor(config) {
|
|
1042
|
+
super();
|
|
1043
|
+
ensureObservable2(this);
|
|
1044
|
+
this.ModelClass = config.model;
|
|
1045
|
+
this.data = new Collection((r) => r.getId());
|
|
1046
|
+
this.allData = new Collection((r) => r.getId());
|
|
1047
|
+
if (config.data) {
|
|
1048
|
+
this.loadData(config.data);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// -----------------------------------------------------------------------
|
|
1052
|
+
// Data loading
|
|
1053
|
+
// -----------------------------------------------------------------------
|
|
1054
|
+
loadData(rawRecords) {
|
|
1055
|
+
for (const raw of rawRecords) {
|
|
1056
|
+
const record = this.ModelClass.create(raw);
|
|
1057
|
+
this.data.add(record);
|
|
1058
|
+
this.allData.add(record);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// -----------------------------------------------------------------------
|
|
1062
|
+
// CRUD
|
|
1063
|
+
// -----------------------------------------------------------------------
|
|
1064
|
+
add(...records) {
|
|
1065
|
+
for (const record of records) {
|
|
1066
|
+
this.data.add(record);
|
|
1067
|
+
this.allData.add(record);
|
|
1068
|
+
}
|
|
1069
|
+
this.invalidateGroups();
|
|
1070
|
+
this.fire("add", this, records);
|
|
1071
|
+
this.fire("datachanged", this);
|
|
1072
|
+
return records;
|
|
1073
|
+
}
|
|
1074
|
+
insert(index, ...records) {
|
|
1075
|
+
for (let i = 0; i < records.length; i++) {
|
|
1076
|
+
this.data.insert(index + i, records[i]);
|
|
1077
|
+
this.allData.add(records[i]);
|
|
1078
|
+
}
|
|
1079
|
+
this.invalidateGroups();
|
|
1080
|
+
this.fire("add", this, records, index);
|
|
1081
|
+
this.fire("datachanged", this);
|
|
1082
|
+
return records;
|
|
1083
|
+
}
|
|
1084
|
+
remove(...records) {
|
|
1085
|
+
const removed = [];
|
|
1086
|
+
for (const record of records) {
|
|
1087
|
+
if (this.data.remove(record)) {
|
|
1088
|
+
this.allData.remove(record);
|
|
1089
|
+
removed.push(record);
|
|
1090
|
+
if (!record.phantom) {
|
|
1091
|
+
this.removedRecords.push(record);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (removed.length > 0) {
|
|
1096
|
+
this.invalidateGroups();
|
|
1097
|
+
this.fire("remove", this, removed);
|
|
1098
|
+
this.fire("datachanged", this);
|
|
1099
|
+
}
|
|
1100
|
+
return removed;
|
|
1101
|
+
}
|
|
1102
|
+
removeAt(index, count = 1) {
|
|
1103
|
+
const removed = this.data.removeAt(index, count);
|
|
1104
|
+
for (const record of removed) {
|
|
1105
|
+
this.allData.remove(record);
|
|
1106
|
+
if (!record.phantom) {
|
|
1107
|
+
this.removedRecords.push(record);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
if (removed.length > 0) {
|
|
1111
|
+
this.invalidateGroups();
|
|
1112
|
+
this.fire("remove", this, removed);
|
|
1113
|
+
this.fire("datachanged", this);
|
|
1114
|
+
}
|
|
1115
|
+
return removed;
|
|
1116
|
+
}
|
|
1117
|
+
removeAll(silent) {
|
|
1118
|
+
this.data.clear();
|
|
1119
|
+
this.allData.clear();
|
|
1120
|
+
this.invalidateGroups();
|
|
1121
|
+
if (!silent) {
|
|
1122
|
+
this.fire("clear", this);
|
|
1123
|
+
this.fire("datachanged", this);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// -----------------------------------------------------------------------
|
|
1127
|
+
// Lookup
|
|
1128
|
+
// -----------------------------------------------------------------------
|
|
1129
|
+
getAt(index) {
|
|
1130
|
+
return this.data.getAt(index);
|
|
1131
|
+
}
|
|
1132
|
+
getById(id) {
|
|
1133
|
+
return this.data.getByKey(id);
|
|
1134
|
+
}
|
|
1135
|
+
getRange(start, end) {
|
|
1136
|
+
return this.data.getRange(start, end);
|
|
1137
|
+
}
|
|
1138
|
+
getCount() {
|
|
1139
|
+
return this.data.getCount();
|
|
1140
|
+
}
|
|
1141
|
+
getTotalCount() {
|
|
1142
|
+
return this.allData.getCount();
|
|
1143
|
+
}
|
|
1144
|
+
indexOf(record) {
|
|
1145
|
+
return this.data.indexOf(record);
|
|
1146
|
+
}
|
|
1147
|
+
contains(record) {
|
|
1148
|
+
return this.data.contains(record);
|
|
1149
|
+
}
|
|
1150
|
+
first() {
|
|
1151
|
+
return this.data.first();
|
|
1152
|
+
}
|
|
1153
|
+
last() {
|
|
1154
|
+
return this.data.last();
|
|
1155
|
+
}
|
|
1156
|
+
each(fn) {
|
|
1157
|
+
this.data.each(fn);
|
|
1158
|
+
}
|
|
1159
|
+
collect(fieldName) {
|
|
1160
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1161
|
+
this.data.each((r) => {
|
|
1162
|
+
seen.add(r.get(fieldName));
|
|
1163
|
+
});
|
|
1164
|
+
return [...seen];
|
|
1165
|
+
}
|
|
1166
|
+
getData() {
|
|
1167
|
+
return this.data;
|
|
1168
|
+
}
|
|
1169
|
+
// -----------------------------------------------------------------------
|
|
1170
|
+
// Sorting
|
|
1171
|
+
// -----------------------------------------------------------------------
|
|
1172
|
+
sort(fieldOrSorters, direction = "ASC") {
|
|
1173
|
+
if (typeof fieldOrSorters === "string") {
|
|
1174
|
+
this.currentSorters = [{ property: fieldOrSorters, direction }];
|
|
1175
|
+
} else if (Array.isArray(fieldOrSorters)) {
|
|
1176
|
+
this.currentSorters = fieldOrSorters;
|
|
1177
|
+
}
|
|
1178
|
+
if (this.currentSorters.length > 0) {
|
|
1179
|
+
this.data.sort(buildComparator(this.currentSorters));
|
|
1180
|
+
}
|
|
1181
|
+
this.invalidateGroups();
|
|
1182
|
+
this.fire("sort", this, this.currentSorters);
|
|
1183
|
+
}
|
|
1184
|
+
getSorters() {
|
|
1185
|
+
return [...this.currentSorters];
|
|
1186
|
+
}
|
|
1187
|
+
// -----------------------------------------------------------------------
|
|
1188
|
+
// Filtering
|
|
1189
|
+
// -----------------------------------------------------------------------
|
|
1190
|
+
filter(fieldOrFilters, value) {
|
|
1191
|
+
if (typeof fieldOrFilters === "string") {
|
|
1192
|
+
this.currentFilters = [{ property: fieldOrFilters, value }];
|
|
1193
|
+
} else {
|
|
1194
|
+
this.currentFilters = fieldOrFilters;
|
|
1195
|
+
}
|
|
1196
|
+
this.applyFilters();
|
|
1197
|
+
this.fire("filter", this, this.currentFilters);
|
|
1198
|
+
}
|
|
1199
|
+
clearFilter(suppressEvent) {
|
|
1200
|
+
this.currentFilters = [];
|
|
1201
|
+
this.applyFilters();
|
|
1202
|
+
if (!suppressEvent) {
|
|
1203
|
+
this.fire("filter", this, []);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
getFilters() {
|
|
1207
|
+
return [...this.currentFilters];
|
|
1208
|
+
}
|
|
1209
|
+
applyFilters() {
|
|
1210
|
+
if (this.currentFilters.length === 0) {
|
|
1211
|
+
this.data = new Collection((r) => r.getId());
|
|
1212
|
+
this.allData.each((r) => this.data.add(r));
|
|
1213
|
+
} else {
|
|
1214
|
+
this.data = new Collection((r) => r.getId());
|
|
1215
|
+
this.allData.each((r) => {
|
|
1216
|
+
const passes = this.currentFilters.every((f) => matchFilter(r, f));
|
|
1217
|
+
if (passes) this.data.add(r);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (this.currentSorters.length > 0) {
|
|
1221
|
+
this.data.sort(buildComparator(this.currentSorters));
|
|
1222
|
+
}
|
|
1223
|
+
this.invalidateGroups();
|
|
1224
|
+
}
|
|
1225
|
+
// -----------------------------------------------------------------------
|
|
1226
|
+
// Grouping
|
|
1227
|
+
// -----------------------------------------------------------------------
|
|
1228
|
+
group(field, direction = "ASC") {
|
|
1229
|
+
this.groupField = field;
|
|
1230
|
+
this.groupDirection = direction;
|
|
1231
|
+
this.invalidateGroups();
|
|
1232
|
+
this.fire("groupchange", this, field, direction);
|
|
1233
|
+
}
|
|
1234
|
+
clearGrouping() {
|
|
1235
|
+
this.groupField = null;
|
|
1236
|
+
this.cachedGroups = null;
|
|
1237
|
+
this.fire("groupchange", this, null, null);
|
|
1238
|
+
}
|
|
1239
|
+
isGrouped() {
|
|
1240
|
+
return this.groupField !== null;
|
|
1241
|
+
}
|
|
1242
|
+
getGroups() {
|
|
1243
|
+
if (!this.groupField) return [];
|
|
1244
|
+
if (this.cachedGroups) return this.cachedGroups;
|
|
1245
|
+
const map = /* @__PURE__ */ new Map();
|
|
1246
|
+
this.data.each((r) => {
|
|
1247
|
+
const key = String(r.get(this.groupField) ?? "");
|
|
1248
|
+
if (!map.has(key)) map.set(key, []);
|
|
1249
|
+
map.get(key).push(r);
|
|
1250
|
+
});
|
|
1251
|
+
const groups = [...map.entries()].map(([name, children]) => ({ name, children }));
|
|
1252
|
+
const dir = this.groupDirection === "DESC" ? -1 : 1;
|
|
1253
|
+
groups.sort((a, b) => {
|
|
1254
|
+
if (a.name < b.name) return -1 * dir;
|
|
1255
|
+
if (a.name > b.name) return 1 * dir;
|
|
1256
|
+
return 0;
|
|
1257
|
+
});
|
|
1258
|
+
this.cachedGroups = groups;
|
|
1259
|
+
return groups;
|
|
1260
|
+
}
|
|
1261
|
+
invalidateGroups() {
|
|
1262
|
+
this.cachedGroups = null;
|
|
1263
|
+
}
|
|
1264
|
+
// -----------------------------------------------------------------------
|
|
1265
|
+
// Sync / change tracking
|
|
1266
|
+
// -----------------------------------------------------------------------
|
|
1267
|
+
getModifiedRecords() {
|
|
1268
|
+
const result = [];
|
|
1269
|
+
this.data.each((r) => {
|
|
1270
|
+
if (r.isModified()) result.push(r);
|
|
1271
|
+
});
|
|
1272
|
+
return result;
|
|
1273
|
+
}
|
|
1274
|
+
getNewRecords() {
|
|
1275
|
+
const result = [];
|
|
1276
|
+
this.data.each((r) => {
|
|
1277
|
+
if (r.phantom) result.push(r);
|
|
1278
|
+
});
|
|
1279
|
+
return result;
|
|
1280
|
+
}
|
|
1281
|
+
getRemovedRecords() {
|
|
1282
|
+
return [...this.removedRecords];
|
|
1283
|
+
}
|
|
1284
|
+
commitChanges() {
|
|
1285
|
+
this.data.each((r) => {
|
|
1286
|
+
if (r.isModified()) r.commit(true);
|
|
1287
|
+
});
|
|
1288
|
+
this.removedRecords.length = 0;
|
|
1289
|
+
}
|
|
1290
|
+
rejectChanges() {
|
|
1291
|
+
this.data.each((r) => {
|
|
1292
|
+
if (r.isModified()) r.reject(true);
|
|
1293
|
+
});
|
|
1294
|
+
for (const record of this.removedRecords) {
|
|
1295
|
+
this.data.add(record);
|
|
1296
|
+
this.allData.add(record);
|
|
1297
|
+
}
|
|
1298
|
+
this.removedRecords.length = 0;
|
|
1299
|
+
this.invalidateGroups();
|
|
1300
|
+
}
|
|
1301
|
+
// -----------------------------------------------------------------------
|
|
1302
|
+
// Event helper
|
|
1303
|
+
// -----------------------------------------------------------------------
|
|
1304
|
+
fire(eventName, ...args) {
|
|
1305
|
+
if (typeof this.fireEvent === "function") {
|
|
1306
|
+
this.fireEvent(eventName, ...args);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
// src/ResultSet.ts
|
|
1312
|
+
var ResultSet = class {
|
|
1313
|
+
records;
|
|
1314
|
+
total;
|
|
1315
|
+
success;
|
|
1316
|
+
message;
|
|
1317
|
+
constructor(config) {
|
|
1318
|
+
this.records = config.records;
|
|
1319
|
+
this.total = config.total ?? config.records.length;
|
|
1320
|
+
this.success = config.success;
|
|
1321
|
+
this.message = config.message;
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
// src/reader/Reader.ts
|
|
1326
|
+
function getNestedValue2(obj, path) {
|
|
1327
|
+
const segments = path.split(".");
|
|
1328
|
+
let current = obj;
|
|
1329
|
+
for (const seg of segments) {
|
|
1330
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
1331
|
+
current = current[seg];
|
|
1332
|
+
}
|
|
1333
|
+
return current;
|
|
1334
|
+
}
|
|
1335
|
+
var JsonReader = class {
|
|
1336
|
+
config;
|
|
1337
|
+
constructor(config) {
|
|
1338
|
+
this.config = config;
|
|
1339
|
+
}
|
|
1340
|
+
read(data) {
|
|
1341
|
+
const { model, rootProperty, totalProperty, successProperty, messageProperty } = this.config;
|
|
1342
|
+
let rawRecords;
|
|
1343
|
+
let total;
|
|
1344
|
+
let success = true;
|
|
1345
|
+
let message;
|
|
1346
|
+
if (rootProperty) {
|
|
1347
|
+
const root = getNestedValue2(data, rootProperty);
|
|
1348
|
+
rawRecords = Array.isArray(root) ? root : [];
|
|
1349
|
+
} else {
|
|
1350
|
+
rawRecords = Array.isArray(data) ? data : [];
|
|
1351
|
+
}
|
|
1352
|
+
if (totalProperty && typeof data === "object" && data !== null) {
|
|
1353
|
+
const t = getNestedValue2(data, totalProperty);
|
|
1354
|
+
if (typeof t === "number") total = t;
|
|
1355
|
+
}
|
|
1356
|
+
if (successProperty && typeof data === "object" && data !== null) {
|
|
1357
|
+
const s = getNestedValue2(data, successProperty);
|
|
1358
|
+
if (typeof s === "boolean") success = s;
|
|
1359
|
+
}
|
|
1360
|
+
if (messageProperty && typeof data === "object" && data !== null) {
|
|
1361
|
+
const m = getNestedValue2(data, messageProperty);
|
|
1362
|
+
if (typeof m === "string") message = m;
|
|
1363
|
+
}
|
|
1364
|
+
const records = rawRecords.map((raw) => model.create(raw));
|
|
1365
|
+
return new ResultSet({
|
|
1366
|
+
records,
|
|
1367
|
+
total: total ?? records.length,
|
|
1368
|
+
success,
|
|
1369
|
+
message
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
var ArrayReader = class {
|
|
1374
|
+
config;
|
|
1375
|
+
constructor(config) {
|
|
1376
|
+
this.config = config;
|
|
1377
|
+
}
|
|
1378
|
+
read(data) {
|
|
1379
|
+
const { model } = this.config;
|
|
1380
|
+
const fieldNames = model.fields.map(
|
|
1381
|
+
(f) => typeof f === "string" ? f : f.name
|
|
1382
|
+
);
|
|
1383
|
+
const records = data.map((row) => {
|
|
1384
|
+
const obj = {};
|
|
1385
|
+
for (let i = 0; i < fieldNames.length; i++) {
|
|
1386
|
+
if (i < row.length) {
|
|
1387
|
+
obj[fieldNames[i]] = row[i];
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return model.create(obj);
|
|
1391
|
+
});
|
|
1392
|
+
return new ResultSet({ records, success: true });
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
var XmlReader = class {
|
|
1396
|
+
config;
|
|
1397
|
+
constructor(config) {
|
|
1398
|
+
this.config = config;
|
|
1399
|
+
}
|
|
1400
|
+
read(xmlString) {
|
|
1401
|
+
const { model, record: tagName } = this.config;
|
|
1402
|
+
const parser = new DOMParser();
|
|
1403
|
+
const doc = parser.parseFromString(xmlString, "text/xml");
|
|
1404
|
+
const nodes = doc.getElementsByTagName(tagName);
|
|
1405
|
+
const fieldNames = model.fields.map(
|
|
1406
|
+
(f) => typeof f === "string" ? f : f.name
|
|
1407
|
+
);
|
|
1408
|
+
const records = [];
|
|
1409
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1410
|
+
const node = nodes[i];
|
|
1411
|
+
const obj = {};
|
|
1412
|
+
for (const fieldName of fieldNames) {
|
|
1413
|
+
let el = node.getElementsByTagName(fieldName)[0];
|
|
1414
|
+
if (!el) {
|
|
1415
|
+
el = node.getElementsByTagName(fieldName[0])[0];
|
|
1416
|
+
}
|
|
1417
|
+
if (el) {
|
|
1418
|
+
obj[fieldName] = el.textContent ?? "";
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
records.push(model.create(obj));
|
|
1422
|
+
}
|
|
1423
|
+
return new ResultSet({ records, success: true });
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/writer/Writer.ts
|
|
1428
|
+
var JsonWriter = class {
|
|
1429
|
+
config;
|
|
1430
|
+
constructor(config) {
|
|
1431
|
+
this.config = {
|
|
1432
|
+
writeAllFields: true,
|
|
1433
|
+
allowSingle: false,
|
|
1434
|
+
encode: false,
|
|
1435
|
+
...config
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
write(records) {
|
|
1439
|
+
const serialized = records.map((r) => this.serializeRecord(r));
|
|
1440
|
+
let result;
|
|
1441
|
+
if (this.config.allowSingle && serialized.length === 1) {
|
|
1442
|
+
result = serialized[0];
|
|
1443
|
+
} else {
|
|
1444
|
+
result = serialized;
|
|
1445
|
+
}
|
|
1446
|
+
if (this.config.rootProperty) {
|
|
1447
|
+
result = { [this.config.rootProperty]: result };
|
|
1448
|
+
}
|
|
1449
|
+
if (this.config.encode) {
|
|
1450
|
+
return JSON.stringify(result);
|
|
1451
|
+
}
|
|
1452
|
+
return result;
|
|
1453
|
+
}
|
|
1454
|
+
serializeRecord(record) {
|
|
1455
|
+
if (this.config.writeAllFields) {
|
|
1456
|
+
return record.getData({ serialize: true });
|
|
1457
|
+
}
|
|
1458
|
+
const Ctor = record.constructor;
|
|
1459
|
+
const changes = record.getChanges();
|
|
1460
|
+
const result = {};
|
|
1461
|
+
const idProp = Ctor.idProperty;
|
|
1462
|
+
result[idProp] = record.get(idProp);
|
|
1463
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
1464
|
+
result[key] = value;
|
|
1465
|
+
}
|
|
1466
|
+
return result;
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
var XmlWriter = class {
|
|
1470
|
+
config;
|
|
1471
|
+
constructor(config) {
|
|
1472
|
+
this.config = {
|
|
1473
|
+
documentRoot: "xmlData",
|
|
1474
|
+
record: "record",
|
|
1475
|
+
...config
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
write(records) {
|
|
1479
|
+
const root = this.config.documentRoot;
|
|
1480
|
+
const tag = this.config.record;
|
|
1481
|
+
const parts = [`<${root}>`];
|
|
1482
|
+
for (const record of records) {
|
|
1483
|
+
parts.push(`<${tag}>`);
|
|
1484
|
+
const data = record.getData({ serialize: true });
|
|
1485
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1486
|
+
parts.push(`<${key}>${escapeXml(String(value ?? ""))}</${key}>`);
|
|
1487
|
+
}
|
|
1488
|
+
parts.push(`</${tag}>`);
|
|
1489
|
+
}
|
|
1490
|
+
parts.push(`</${root}>`);
|
|
1491
|
+
return parts.join("");
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
function escapeXml(str) {
|
|
1495
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/proxy/Proxy.ts
|
|
1499
|
+
var Proxy2 = class {
|
|
1500
|
+
model;
|
|
1501
|
+
constructor(config) {
|
|
1502
|
+
this.model = config.model;
|
|
1503
|
+
}
|
|
1504
|
+
async batch(options) {
|
|
1505
|
+
const { Operation: OpClass } = await import("./Operation-4K76GXM7.js");
|
|
1506
|
+
const operations = [];
|
|
1507
|
+
let allSuccess = true;
|
|
1508
|
+
if (options.create && options.create.length > 0) {
|
|
1509
|
+
const op = new OpClass({ action: "create", records: options.create });
|
|
1510
|
+
const rs = await this.create(op);
|
|
1511
|
+
op.setResult(rs);
|
|
1512
|
+
operations.push(op);
|
|
1513
|
+
if (!rs.success) allSuccess = false;
|
|
1514
|
+
}
|
|
1515
|
+
if (options.update && options.update.length > 0) {
|
|
1516
|
+
const op = new OpClass({ action: "update", records: options.update });
|
|
1517
|
+
const rs = await this.update(op);
|
|
1518
|
+
op.setResult(rs);
|
|
1519
|
+
operations.push(op);
|
|
1520
|
+
if (!rs.success) allSuccess = false;
|
|
1521
|
+
}
|
|
1522
|
+
if (options.destroy && options.destroy.length > 0) {
|
|
1523
|
+
const op = new OpClass({ action: "destroy", records: options.destroy });
|
|
1524
|
+
const rs = await this.destroy(op);
|
|
1525
|
+
op.setResult(rs);
|
|
1526
|
+
operations.push(op);
|
|
1527
|
+
if (!rs.success) allSuccess = false;
|
|
1528
|
+
}
|
|
1529
|
+
return { success: allSuccess, operations };
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
// src/proxy/ClientProxy.ts
|
|
1534
|
+
var MemoryProxy = class extends Proxy2 {
|
|
1535
|
+
store;
|
|
1536
|
+
constructor(config) {
|
|
1537
|
+
super(config);
|
|
1538
|
+
this.store = config.data ? [...config.data] : [];
|
|
1539
|
+
}
|
|
1540
|
+
async read(operation) {
|
|
1541
|
+
let data = [...this.store];
|
|
1542
|
+
if (operation.filters && operation.filters.length > 0) {
|
|
1543
|
+
data = data.filter((raw) => {
|
|
1544
|
+
return operation.filters.every((f) => {
|
|
1545
|
+
const fieldVal = raw[f.property];
|
|
1546
|
+
const op = f.operator ?? "=";
|
|
1547
|
+
switch (op) {
|
|
1548
|
+
case "=":
|
|
1549
|
+
return fieldVal === f.value;
|
|
1550
|
+
case "!=":
|
|
1551
|
+
return fieldVal !== f.value;
|
|
1552
|
+
case "<":
|
|
1553
|
+
return fieldVal < f.value;
|
|
1554
|
+
case "<=":
|
|
1555
|
+
return fieldVal <= f.value;
|
|
1556
|
+
case ">":
|
|
1557
|
+
return fieldVal > f.value;
|
|
1558
|
+
case ">=":
|
|
1559
|
+
return fieldVal >= f.value;
|
|
1560
|
+
default:
|
|
1561
|
+
return true;
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
if (operation.sorters && operation.sorters.length > 0) {
|
|
1567
|
+
data.sort((a, b) => {
|
|
1568
|
+
for (const sorter of operation.sorters) {
|
|
1569
|
+
const va = a[sorter.property];
|
|
1570
|
+
const vb = b[sorter.property];
|
|
1571
|
+
const dir = sorter.direction === "DESC" ? -1 : 1;
|
|
1572
|
+
if (va < vb) return -1 * dir;
|
|
1573
|
+
if (va > vb) return 1 * dir;
|
|
1574
|
+
}
|
|
1575
|
+
return 0;
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
const total = data.length;
|
|
1579
|
+
if (operation.start !== void 0 || operation.limit !== void 0) {
|
|
1580
|
+
const start = operation.start ?? 0;
|
|
1581
|
+
const limit = operation.limit ?? data.length;
|
|
1582
|
+
data = data.slice(start, start + limit);
|
|
1583
|
+
}
|
|
1584
|
+
const records = data.map((raw) => this.model.create(raw));
|
|
1585
|
+
return new ResultSet({ records, total, success: true });
|
|
1586
|
+
}
|
|
1587
|
+
async create(operation) {
|
|
1588
|
+
for (const record of operation.records) {
|
|
1589
|
+
this.store.push(record.getData());
|
|
1590
|
+
}
|
|
1591
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1592
|
+
}
|
|
1593
|
+
async update(operation) {
|
|
1594
|
+
for (const record of operation.records) {
|
|
1595
|
+
const id = record.getId();
|
|
1596
|
+
const idProp = this.model.idProperty ?? "id";
|
|
1597
|
+
const idx = this.store.findIndex((r) => r[idProp] === id);
|
|
1598
|
+
if (idx !== -1) {
|
|
1599
|
+
this.store[idx] = record.getData();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1603
|
+
}
|
|
1604
|
+
async destroy(operation) {
|
|
1605
|
+
for (const record of operation.records) {
|
|
1606
|
+
const id = record.getId();
|
|
1607
|
+
const idProp = this.model.idProperty ?? "id";
|
|
1608
|
+
const idx = this.store.findIndex((r) => r[idProp] === id);
|
|
1609
|
+
if (idx !== -1) {
|
|
1610
|
+
this.store.splice(idx, 1);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
var WebStorageProxy = class extends Proxy2 {
|
|
1617
|
+
storageId;
|
|
1618
|
+
constructor(config) {
|
|
1619
|
+
super(config);
|
|
1620
|
+
this.storageId = config.id;
|
|
1621
|
+
}
|
|
1622
|
+
loadAll() {
|
|
1623
|
+
const raw = this.getStorage().getItem(this.storageId);
|
|
1624
|
+
if (!raw) return [];
|
|
1625
|
+
try {
|
|
1626
|
+
return JSON.parse(raw);
|
|
1627
|
+
} catch {
|
|
1628
|
+
return [];
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
saveAll(data) {
|
|
1632
|
+
this.getStorage().setItem(this.storageId, JSON.stringify(data));
|
|
1633
|
+
}
|
|
1634
|
+
async read(_operation) {
|
|
1635
|
+
const data = this.loadAll();
|
|
1636
|
+
const records = data.map((raw) => this.model.create(raw));
|
|
1637
|
+
return new ResultSet({ records, success: true });
|
|
1638
|
+
}
|
|
1639
|
+
async create(operation) {
|
|
1640
|
+
const data = this.loadAll();
|
|
1641
|
+
for (const record of operation.records) {
|
|
1642
|
+
data.push(record.getData({ serialize: true }));
|
|
1643
|
+
}
|
|
1644
|
+
this.saveAll(data);
|
|
1645
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1646
|
+
}
|
|
1647
|
+
async update(operation) {
|
|
1648
|
+
const data = this.loadAll();
|
|
1649
|
+
const idProp = this.model.idProperty ?? "id";
|
|
1650
|
+
for (const record of operation.records) {
|
|
1651
|
+
const id = record.getId();
|
|
1652
|
+
const idx = data.findIndex((r) => r[idProp] === id);
|
|
1653
|
+
if (idx !== -1) {
|
|
1654
|
+
data[idx] = record.getData({ serialize: true });
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
this.saveAll(data);
|
|
1658
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1659
|
+
}
|
|
1660
|
+
async destroy(operation) {
|
|
1661
|
+
let data = this.loadAll();
|
|
1662
|
+
const idProp = this.model.idProperty ?? "id";
|
|
1663
|
+
for (const record of operation.records) {
|
|
1664
|
+
const id = record.getId();
|
|
1665
|
+
data = data.filter((r) => r[idProp] !== id);
|
|
1666
|
+
}
|
|
1667
|
+
this.saveAll(data);
|
|
1668
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
var LocalStorageProxy = class extends WebStorageProxy {
|
|
1672
|
+
getStorage() {
|
|
1673
|
+
return localStorage;
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
var SessionStorageProxy = class extends WebStorageProxy {
|
|
1677
|
+
getStorage() {
|
|
1678
|
+
return sessionStorage;
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
// src/proxy/ServerProxy.ts
|
|
1683
|
+
var ACTION_METHODS = {
|
|
1684
|
+
read: "GET",
|
|
1685
|
+
create: "POST",
|
|
1686
|
+
update: "PUT",
|
|
1687
|
+
destroy: "DELETE"
|
|
1688
|
+
};
|
|
1689
|
+
var AjaxProxy = class extends Proxy2 {
|
|
1690
|
+
url;
|
|
1691
|
+
api;
|
|
1692
|
+
headers;
|
|
1693
|
+
timeout;
|
|
1694
|
+
withCredentials;
|
|
1695
|
+
reader;
|
|
1696
|
+
writer;
|
|
1697
|
+
constructor(config) {
|
|
1698
|
+
super(config);
|
|
1699
|
+
this.url = config.url;
|
|
1700
|
+
this.api = config.api ?? {};
|
|
1701
|
+
this.headers = config.headers ?? {};
|
|
1702
|
+
this.timeout = config.timeout ?? 3e4;
|
|
1703
|
+
this.withCredentials = config.withCredentials ?? false;
|
|
1704
|
+
this.reader = new JsonReader({ model: config.model });
|
|
1705
|
+
this.writer = new JsonWriter({});
|
|
1706
|
+
}
|
|
1707
|
+
async read(operation, signal) {
|
|
1708
|
+
return this.doRequest(operation, "read", signal);
|
|
1709
|
+
}
|
|
1710
|
+
async create(operation) {
|
|
1711
|
+
return this.doRequest(operation, "create");
|
|
1712
|
+
}
|
|
1713
|
+
async update(operation) {
|
|
1714
|
+
return this.doRequest(operation, "update");
|
|
1715
|
+
}
|
|
1716
|
+
async destroy(operation) {
|
|
1717
|
+
return this.doRequest(operation, "destroy");
|
|
1718
|
+
}
|
|
1719
|
+
buildUrl(operation, action) {
|
|
1720
|
+
const base = this.api[action] ?? this.url;
|
|
1721
|
+
const params = { ...operation.params };
|
|
1722
|
+
if (operation.start !== void 0) params.start = operation.start;
|
|
1723
|
+
if (operation.limit !== void 0) params.limit = operation.limit;
|
|
1724
|
+
if (operation.page !== void 0) params.page = operation.page;
|
|
1725
|
+
const qs = Object.entries(params).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
1726
|
+
return qs ? `${base}?${qs}` : base;
|
|
1727
|
+
}
|
|
1728
|
+
async doRequest(operation, action, signal) {
|
|
1729
|
+
const method = ACTION_METHODS[action] ?? "GET";
|
|
1730
|
+
const url = this.buildUrl(operation, action);
|
|
1731
|
+
const init = {
|
|
1732
|
+
method,
|
|
1733
|
+
headers: {
|
|
1734
|
+
"Content-Type": "application/json",
|
|
1735
|
+
...this.headers
|
|
1736
|
+
},
|
|
1737
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
1738
|
+
signal
|
|
1739
|
+
};
|
|
1740
|
+
if (method !== "GET" && operation.records.length > 0) {
|
|
1741
|
+
init.body = JSON.stringify(this.writer.write(operation.records));
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
const response = await fetch(url, init);
|
|
1745
|
+
if (!response.ok) {
|
|
1746
|
+
let message = `HTTP ${response.status}`;
|
|
1747
|
+
try {
|
|
1748
|
+
const body = await response.json();
|
|
1749
|
+
if (body?.message) message = body.message;
|
|
1750
|
+
} catch {
|
|
1751
|
+
}
|
|
1752
|
+
return new ResultSet({ records: [], success: false, message });
|
|
1753
|
+
}
|
|
1754
|
+
const data = await response.json();
|
|
1755
|
+
if (action === "read") {
|
|
1756
|
+
return this.reader.read(data);
|
|
1757
|
+
}
|
|
1758
|
+
const records = Array.isArray(data) ? data.map((d) => this.model.create(d)) : data && typeof data === "object" && "id" in data ? [this.model.create(data)] : operation.records;
|
|
1759
|
+
return new ResultSet({ records, success: true });
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
return new ResultSet({
|
|
1762
|
+
records: [],
|
|
1763
|
+
success: false,
|
|
1764
|
+
message: err?.message ?? "Unknown error"
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
var RestProxy = class extends AjaxProxy {
|
|
1770
|
+
appendId;
|
|
1771
|
+
constructor(config) {
|
|
1772
|
+
super(config);
|
|
1773
|
+
this.appendId = config.appendId ?? true;
|
|
1774
|
+
}
|
|
1775
|
+
buildUrl(operation, action) {
|
|
1776
|
+
let base = this.api[action] ?? this.url;
|
|
1777
|
+
if (this.appendId && action !== "create" && operation.records.length === 1) {
|
|
1778
|
+
const id = operation.records[0].getId();
|
|
1779
|
+
if (id !== void 0 && id !== null && id !== 0 && id !== "") {
|
|
1780
|
+
base = `${base}/${id}`;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
const params = { ...operation.params };
|
|
1784
|
+
if (operation.start !== void 0) params.start = operation.start;
|
|
1785
|
+
if (operation.limit !== void 0) params.limit = operation.limit;
|
|
1786
|
+
if (operation.page !== void 0) params.page = operation.page;
|
|
1787
|
+
const qs = Object.entries(params).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
1788
|
+
return qs ? `${base}?${qs}` : base;
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
// src/store/TreeStore.ts
|
|
1793
|
+
import { Base as Base3, Observable as Observable3 } from "@framesquared/core";
|
|
1794
|
+
|
|
1795
|
+
// src/mixin/NodeInterface.ts
|
|
1796
|
+
function applyNodeInterface(model, depth = 0) {
|
|
1797
|
+
const node = model;
|
|
1798
|
+
node.parentNode = node.parentNode ?? null;
|
|
1799
|
+
node.childNodes = node.childNodes ?? [];
|
|
1800
|
+
node.firstChild = node.firstChild ?? null;
|
|
1801
|
+
node.lastChild = node.lastChild ?? null;
|
|
1802
|
+
node.previousSibling = node.previousSibling ?? null;
|
|
1803
|
+
node.nextSibling = node.nextSibling ?? null;
|
|
1804
|
+
node.depth = depth;
|
|
1805
|
+
node.expanded = node.expanded ?? false;
|
|
1806
|
+
node.loaded = node.loaded ?? false;
|
|
1807
|
+
node.isLeaf = function() {
|
|
1808
|
+
if (this.get("leaf") === true) return true;
|
|
1809
|
+
return this.childNodes.length === 0;
|
|
1810
|
+
};
|
|
1811
|
+
node.isRoot = function() {
|
|
1812
|
+
return this.parentNode === null;
|
|
1813
|
+
};
|
|
1814
|
+
node.isExpanded = function() {
|
|
1815
|
+
return this.expanded;
|
|
1816
|
+
};
|
|
1817
|
+
node.isLoaded = function() {
|
|
1818
|
+
return this.loaded;
|
|
1819
|
+
};
|
|
1820
|
+
node.appendChild = function(child) {
|
|
1821
|
+
const c = child;
|
|
1822
|
+
if (c.parentNode) {
|
|
1823
|
+
c.parentNode.removeChild(c);
|
|
1824
|
+
}
|
|
1825
|
+
c.parentNode = this;
|
|
1826
|
+
c.depth = this.depth + 1;
|
|
1827
|
+
const children = this.childNodes;
|
|
1828
|
+
const prevLast = children.length > 0 ? children[children.length - 1] : null;
|
|
1829
|
+
children.push(c);
|
|
1830
|
+
if (prevLast) {
|
|
1831
|
+
prevLast.nextSibling = c;
|
|
1832
|
+
c.previousSibling = prevLast;
|
|
1833
|
+
} else {
|
|
1834
|
+
c.previousSibling = null;
|
|
1835
|
+
}
|
|
1836
|
+
c.nextSibling = null;
|
|
1837
|
+
this.firstChild = children[0];
|
|
1838
|
+
this.lastChild = children[children.length - 1];
|
|
1839
|
+
updateDepthRecursive(c, this.depth + 1);
|
|
1840
|
+
};
|
|
1841
|
+
node.removeChild = function(child) {
|
|
1842
|
+
const children = this.childNodes;
|
|
1843
|
+
const idx = children.indexOf(child);
|
|
1844
|
+
if (idx === -1) return;
|
|
1845
|
+
children.splice(idx, 1);
|
|
1846
|
+
if (child.previousSibling) {
|
|
1847
|
+
child.previousSibling.nextSibling = child.nextSibling;
|
|
1848
|
+
}
|
|
1849
|
+
if (child.nextSibling) {
|
|
1850
|
+
child.nextSibling.previousSibling = child.previousSibling;
|
|
1851
|
+
}
|
|
1852
|
+
child.parentNode = null;
|
|
1853
|
+
child.previousSibling = null;
|
|
1854
|
+
child.nextSibling = null;
|
|
1855
|
+
this.firstChild = children.length > 0 ? children[0] : null;
|
|
1856
|
+
this.lastChild = children.length > 0 ? children[children.length - 1] : null;
|
|
1857
|
+
};
|
|
1858
|
+
node.insertBefore = function(newChild, refChild) {
|
|
1859
|
+
const children = this.childNodes;
|
|
1860
|
+
const refIdx = children.indexOf(refChild);
|
|
1861
|
+
if (refIdx === -1) {
|
|
1862
|
+
this.appendChild(newChild);
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
if (newChild.parentNode) {
|
|
1866
|
+
newChild.parentNode.removeChild(newChild);
|
|
1867
|
+
}
|
|
1868
|
+
newChild.parentNode = this;
|
|
1869
|
+
newChild.depth = this.depth + 1;
|
|
1870
|
+
children.splice(refIdx, 0, newChild);
|
|
1871
|
+
rebuildSiblings(children);
|
|
1872
|
+
this.firstChild = children[0];
|
|
1873
|
+
this.lastChild = children[children.length - 1];
|
|
1874
|
+
updateDepthRecursive(newChild, this.depth + 1);
|
|
1875
|
+
};
|
|
1876
|
+
node.getPath = function(separator = "/") {
|
|
1877
|
+
const parts = [];
|
|
1878
|
+
let current = this;
|
|
1879
|
+
while (current) {
|
|
1880
|
+
parts.unshift(current.get("text") ?? String(current.getId()));
|
|
1881
|
+
current = current.parentNode;
|
|
1882
|
+
}
|
|
1883
|
+
return separator + parts.join(separator);
|
|
1884
|
+
};
|
|
1885
|
+
node.cascadeBy = function(fn) {
|
|
1886
|
+
cascadeByImpl(this, fn);
|
|
1887
|
+
};
|
|
1888
|
+
node.bubble = function(fn) {
|
|
1889
|
+
let current = this;
|
|
1890
|
+
while (current) {
|
|
1891
|
+
if (fn(current) === false) return;
|
|
1892
|
+
current = current.parentNode;
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
node.contains = function(descendant) {
|
|
1896
|
+
let current = descendant.parentNode;
|
|
1897
|
+
while (current) {
|
|
1898
|
+
if (current === this) return true;
|
|
1899
|
+
current = current.parentNode;
|
|
1900
|
+
}
|
|
1901
|
+
return false;
|
|
1902
|
+
};
|
|
1903
|
+
node.findChild = function(field, value, deep = false) {
|
|
1904
|
+
for (const child of this.childNodes) {
|
|
1905
|
+
if (child.get(field) === value) return child;
|
|
1906
|
+
if (deep) {
|
|
1907
|
+
const found = child.findChild(field, value, true);
|
|
1908
|
+
if (found) return found;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
return void 0;
|
|
1912
|
+
};
|
|
1913
|
+
node.sort = function(sorters) {
|
|
1914
|
+
this.childNodes.sort((a, b) => {
|
|
1915
|
+
for (const s of sorters) {
|
|
1916
|
+
const va = a.get(s.property);
|
|
1917
|
+
const vb = b.get(s.property);
|
|
1918
|
+
const dir = s.direction === "DESC" ? -1 : 1;
|
|
1919
|
+
if (va < vb) return -1 * dir;
|
|
1920
|
+
if (va > vb) return 1 * dir;
|
|
1921
|
+
}
|
|
1922
|
+
return 0;
|
|
1923
|
+
});
|
|
1924
|
+
rebuildSiblings(this.childNodes);
|
|
1925
|
+
this.firstChild = this.childNodes[0] ?? null;
|
|
1926
|
+
this.lastChild = this.childNodes[this.childNodes.length - 1] ?? null;
|
|
1927
|
+
};
|
|
1928
|
+
node.serialize = function() {
|
|
1929
|
+
const data = this.getData();
|
|
1930
|
+
if (this.childNodes.length > 0) {
|
|
1931
|
+
data.children = this.childNodes.map((c) => c.serialize());
|
|
1932
|
+
}
|
|
1933
|
+
return data;
|
|
1934
|
+
};
|
|
1935
|
+
node.eachChild = function(fn) {
|
|
1936
|
+
for (let i = 0; i < this.childNodes.length; i++) {
|
|
1937
|
+
if (fn(this.childNodes[i], i) === false) return;
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
node.indexOf = function(child) {
|
|
1941
|
+
return this.childNodes.indexOf(child);
|
|
1942
|
+
};
|
|
1943
|
+
node.getChildAt = function(index) {
|
|
1944
|
+
return this.childNodes[index];
|
|
1945
|
+
};
|
|
1946
|
+
node.childCount = function() {
|
|
1947
|
+
return this.childNodes.length;
|
|
1948
|
+
};
|
|
1949
|
+
node.hasChildNodes = function() {
|
|
1950
|
+
return this.childNodes.length > 0;
|
|
1951
|
+
};
|
|
1952
|
+
node.getChildren = function(deep = false) {
|
|
1953
|
+
if (!deep) {
|
|
1954
|
+
return [...this.childNodes];
|
|
1955
|
+
}
|
|
1956
|
+
const result = [];
|
|
1957
|
+
const collect = (n) => {
|
|
1958
|
+
for (const child of n.childNodes) {
|
|
1959
|
+
result.push(child);
|
|
1960
|
+
collect(child);
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1963
|
+
collect(this);
|
|
1964
|
+
return result;
|
|
1965
|
+
};
|
|
1966
|
+
node.findChildBy = function(predicate, deep = false) {
|
|
1967
|
+
for (const child of this.childNodes) {
|
|
1968
|
+
const matched = predicate(child);
|
|
1969
|
+
if (matched !== false && matched !== void 0 && matched !== null) return child;
|
|
1970
|
+
if (deep) {
|
|
1971
|
+
const found = child.findChildBy(predicate, true);
|
|
1972
|
+
if (found) return found;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return void 0;
|
|
1976
|
+
};
|
|
1977
|
+
node.removeAll = function() {
|
|
1978
|
+
const removed = [...this.childNodes];
|
|
1979
|
+
for (const child of removed) {
|
|
1980
|
+
child.parentNode = null;
|
|
1981
|
+
child.previousSibling = null;
|
|
1982
|
+
child.nextSibling = null;
|
|
1983
|
+
}
|
|
1984
|
+
this.childNodes = [];
|
|
1985
|
+
this.firstChild = null;
|
|
1986
|
+
this.lastChild = null;
|
|
1987
|
+
return removed;
|
|
1988
|
+
};
|
|
1989
|
+
node.copy = function(deep = true) {
|
|
1990
|
+
const copyCounter = { i: 0 };
|
|
1991
|
+
return copyNodeImpl(this, deep, copyCounter);
|
|
1992
|
+
};
|
|
1993
|
+
node.getDepth = function() {
|
|
1994
|
+
return this.depth;
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function rebuildSiblings(children) {
|
|
1998
|
+
for (let i = 0; i < children.length; i++) {
|
|
1999
|
+
children[i].previousSibling = i > 0 ? children[i - 1] : null;
|
|
2000
|
+
children[i].nextSibling = i < children.length - 1 ? children[i + 1] : null;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
function updateDepthRecursive(node, depth) {
|
|
2004
|
+
node.depth = depth;
|
|
2005
|
+
for (const child of node.childNodes) {
|
|
2006
|
+
updateDepthRecursive(child, depth + 1);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
function copyNodeImpl(node, deep, counter) {
|
|
2010
|
+
counter.i += 1;
|
|
2011
|
+
const newId = "copy-" + Date.now() + "-" + counter.i;
|
|
2012
|
+
const data = node.getData();
|
|
2013
|
+
const Ctor = node.constructor;
|
|
2014
|
+
const copyModel = Ctor.create({ ...data, id: newId });
|
|
2015
|
+
applyNodeInterface(copyModel, 0);
|
|
2016
|
+
const copyNode = copyModel;
|
|
2017
|
+
if (deep) {
|
|
2018
|
+
for (const child of node.childNodes) {
|
|
2019
|
+
const childCopy = copyNodeImpl(child, true, counter);
|
|
2020
|
+
copyNode.appendChild(childCopy);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return copyNode;
|
|
2024
|
+
}
|
|
2025
|
+
function cascadeByImpl(node, fn) {
|
|
2026
|
+
if (fn(node) === false) return false;
|
|
2027
|
+
for (const child of [...node.childNodes]) {
|
|
2028
|
+
if (!cascadeByImpl(child, fn)) return false;
|
|
2029
|
+
}
|
|
2030
|
+
return true;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
// src/model/TreeModel.ts
|
|
2034
|
+
var TreeModel = class extends Model {
|
|
2035
|
+
static $className = "Ext.data.TreeModel";
|
|
2036
|
+
static idProperty = "id";
|
|
2037
|
+
static defaultRootId = "root";
|
|
2038
|
+
static defaultRootText = "Root";
|
|
2039
|
+
static fields = [
|
|
2040
|
+
{ name: "id", type: "auto" /* AUTO */ },
|
|
2041
|
+
{ name: "text", type: "string" /* STRING */, defaultValue: "" },
|
|
2042
|
+
{ name: "leaf", type: "boolean" /* BOOLEAN */, defaultValue: false },
|
|
2043
|
+
{ name: "cls", type: "string" /* STRING */, defaultValue: "" },
|
|
2044
|
+
{ name: "iconCls", type: "string" /* STRING */, defaultValue: "" },
|
|
2045
|
+
{ name: "icon", type: "string" /* STRING */, defaultValue: "" },
|
|
2046
|
+
{ name: "href", type: "string" /* STRING */, defaultValue: "" },
|
|
2047
|
+
{ name: "hrefTarget", type: "string" /* STRING */, defaultValue: "" },
|
|
2048
|
+
{ name: "qtip", type: "string" /* STRING */, defaultValue: "" },
|
|
2049
|
+
{ name: "qtitle", type: "string" /* STRING */, defaultValue: "" },
|
|
2050
|
+
{ name: "allowDrop", type: "boolean" /* BOOLEAN */, defaultValue: true },
|
|
2051
|
+
{ name: "allowDrag", type: "boolean" /* BOOLEAN */, defaultValue: true }
|
|
2052
|
+
];
|
|
2053
|
+
/**
|
|
2054
|
+
* Factory method that creates a TreeModel instance with NodeInterface
|
|
2055
|
+
* already applied.
|
|
2056
|
+
*/
|
|
2057
|
+
static create(data) {
|
|
2058
|
+
const instance = super.create(data);
|
|
2059
|
+
applyNodeInterface(instance, 0);
|
|
2060
|
+
return instance;
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
|
|
2064
|
+
// src/store/TreeStore.ts
|
|
2065
|
+
var ObservableMixin3 = Observable3;
|
|
2066
|
+
function ensureObservable3(instance) {
|
|
2067
|
+
const proto = ObservableMixin3.prototype;
|
|
2068
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
2069
|
+
if (name === "constructor") continue;
|
|
2070
|
+
if (name in instance) continue;
|
|
2071
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
2072
|
+
if (desc && typeof desc.value === "function") {
|
|
2073
|
+
instance[name] = desc.value.bind(instance);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
var TreeStore = class extends Base3 {
|
|
2078
|
+
static $className = "Ext.data.TreeStore";
|
|
2079
|
+
ModelClass;
|
|
2080
|
+
rootNode;
|
|
2081
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
2082
|
+
$destroyHooks = [];
|
|
2083
|
+
constructor(config) {
|
|
2084
|
+
super();
|
|
2085
|
+
ensureObservable3(this);
|
|
2086
|
+
this.ModelClass = config.model ?? TreeModel;
|
|
2087
|
+
if (config.root) {
|
|
2088
|
+
this.replaceRoot(this.buildNode(config.root, 0));
|
|
2089
|
+
} else {
|
|
2090
|
+
const empty = this.ModelClass.create({ id: "__root__", text: "Root" });
|
|
2091
|
+
applyNodeInterface(empty, 0);
|
|
2092
|
+
this.replaceRoot(empty);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
// -----------------------------------------------------------------------
|
|
2096
|
+
// Root
|
|
2097
|
+
// -----------------------------------------------------------------------
|
|
2098
|
+
getRoot() {
|
|
2099
|
+
return this.rootNode;
|
|
2100
|
+
}
|
|
2101
|
+
getRootNode() {
|
|
2102
|
+
return this.rootNode;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Replaces the root with new data (or a pre-built node).
|
|
2106
|
+
* Fires 'rootchange'.
|
|
2107
|
+
*/
|
|
2108
|
+
setRoot(nodeOrData) {
|
|
2109
|
+
let node;
|
|
2110
|
+
if (this.isNodeInterface(nodeOrData)) {
|
|
2111
|
+
node = nodeOrData;
|
|
2112
|
+
} else {
|
|
2113
|
+
node = this.buildNode(nodeOrData, 0);
|
|
2114
|
+
}
|
|
2115
|
+
this.replaceRoot(node);
|
|
2116
|
+
return node;
|
|
2117
|
+
}
|
|
2118
|
+
isNodeInterface(value) {
|
|
2119
|
+
return typeof value === "object" && value !== null && "childNodes" in value && "parentNode" in value && typeof value.isRoot === "function";
|
|
2120
|
+
}
|
|
2121
|
+
replaceRoot(node) {
|
|
2122
|
+
this.rootNode = node;
|
|
2123
|
+
this.nodeMap.clear();
|
|
2124
|
+
this.registerRecursive(node);
|
|
2125
|
+
this.fire("rootchange", this, node);
|
|
2126
|
+
}
|
|
2127
|
+
// -----------------------------------------------------------------------
|
|
2128
|
+
// Lookup
|
|
2129
|
+
// -----------------------------------------------------------------------
|
|
2130
|
+
getNodeById(id) {
|
|
2131
|
+
return this.nodeMap.get(id);
|
|
2132
|
+
}
|
|
2133
|
+
// -----------------------------------------------------------------------
|
|
2134
|
+
// Tree operations
|
|
2135
|
+
// -----------------------------------------------------------------------
|
|
2136
|
+
appendChild(parent, child) {
|
|
2137
|
+
parent.appendChild(child);
|
|
2138
|
+
this.registerRecursive(child);
|
|
2139
|
+
this.fire("nodeappend", this, child, parent);
|
|
2140
|
+
this.fire("datachanged", this);
|
|
2141
|
+
}
|
|
2142
|
+
removeChild(parent, child) {
|
|
2143
|
+
parent.removeChild(child);
|
|
2144
|
+
this.unregisterRecursive(child);
|
|
2145
|
+
this.fire("noderemove", this, child, parent);
|
|
2146
|
+
this.fire("datachanged", this);
|
|
2147
|
+
}
|
|
2148
|
+
insertBefore(node, refNode) {
|
|
2149
|
+
const parent = refNode.parentNode;
|
|
2150
|
+
if (!parent) return;
|
|
2151
|
+
parent.insertBefore(node, refNode);
|
|
2152
|
+
this.registerRecursive(node);
|
|
2153
|
+
this.fire("nodeinsert", this, node, refNode, parent);
|
|
2154
|
+
this.fire("datachanged", this);
|
|
2155
|
+
}
|
|
2156
|
+
// -----------------------------------------------------------------------
|
|
2157
|
+
// Expand / Collapse
|
|
2158
|
+
// -----------------------------------------------------------------------
|
|
2159
|
+
expandNode(node) {
|
|
2160
|
+
node.expanded = true;
|
|
2161
|
+
node.loaded = true;
|
|
2162
|
+
this.fire("nodeexpand", this, node);
|
|
2163
|
+
}
|
|
2164
|
+
collapseNode(node) {
|
|
2165
|
+
node.expanded = false;
|
|
2166
|
+
this.fire("nodecollapse", this, node);
|
|
2167
|
+
}
|
|
2168
|
+
// -----------------------------------------------------------------------
|
|
2169
|
+
// Deep search
|
|
2170
|
+
// -----------------------------------------------------------------------
|
|
2171
|
+
findNode(field, value) {
|
|
2172
|
+
return this.findNodeImpl(this.rootNode, field, value);
|
|
2173
|
+
}
|
|
2174
|
+
findNodeImpl(node, field, value) {
|
|
2175
|
+
const model = node;
|
|
2176
|
+
const nodeValue = field === "id" ? model.getId() : model.get(field);
|
|
2177
|
+
if (nodeValue === value) return node;
|
|
2178
|
+
for (const child of node.childNodes) {
|
|
2179
|
+
const found = this.findNodeImpl(child, field, value);
|
|
2180
|
+
if (found) return found;
|
|
2181
|
+
}
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
// -----------------------------------------------------------------------
|
|
2185
|
+
// Collapse / Expand all
|
|
2186
|
+
// -----------------------------------------------------------------------
|
|
2187
|
+
collapseAll() {
|
|
2188
|
+
this.rootNode.cascadeBy((node) => {
|
|
2189
|
+
node.expanded = false;
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
expandAll() {
|
|
2193
|
+
this.rootNode.cascadeBy((node) => {
|
|
2194
|
+
node.expanded = true;
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
// -----------------------------------------------------------------------
|
|
2198
|
+
// Count helpers
|
|
2199
|
+
// -----------------------------------------------------------------------
|
|
2200
|
+
getCount() {
|
|
2201
|
+
return this.flattenNodes().length;
|
|
2202
|
+
}
|
|
2203
|
+
getTotalCount() {
|
|
2204
|
+
return this.nodeMap.size;
|
|
2205
|
+
}
|
|
2206
|
+
// -----------------------------------------------------------------------
|
|
2207
|
+
// Flatten (for rendering in a flat list / grid)
|
|
2208
|
+
// -----------------------------------------------------------------------
|
|
2209
|
+
flattenNodes() {
|
|
2210
|
+
const result = [];
|
|
2211
|
+
this.flattenWalk(this.rootNode, result);
|
|
2212
|
+
return result;
|
|
2213
|
+
}
|
|
2214
|
+
flattenWalk(node, out) {
|
|
2215
|
+
out.push(node);
|
|
2216
|
+
if (node.expanded || node.isRoot()) {
|
|
2217
|
+
for (const child of node.childNodes) {
|
|
2218
|
+
this.flattenWalk(child, out);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
// -----------------------------------------------------------------------
|
|
2223
|
+
// Build tree from nested data
|
|
2224
|
+
// -----------------------------------------------------------------------
|
|
2225
|
+
buildNode(data, depth) {
|
|
2226
|
+
const children = data.children;
|
|
2227
|
+
const expanded = data.expanded;
|
|
2228
|
+
const nodeData = { ...data };
|
|
2229
|
+
delete nodeData.children;
|
|
2230
|
+
delete nodeData.expanded;
|
|
2231
|
+
const model = this.ModelClass.create(nodeData);
|
|
2232
|
+
applyNodeInterface(model, depth);
|
|
2233
|
+
const node = model;
|
|
2234
|
+
if (expanded) {
|
|
2235
|
+
node.expanded = true;
|
|
2236
|
+
node.loaded = true;
|
|
2237
|
+
}
|
|
2238
|
+
if (children && Array.isArray(children)) {
|
|
2239
|
+
for (const childData of children) {
|
|
2240
|
+
const childNode = this.buildNode(childData, depth + 1);
|
|
2241
|
+
node.appendChild(childNode);
|
|
2242
|
+
}
|
|
2243
|
+
node.loaded = true;
|
|
2244
|
+
}
|
|
2245
|
+
return node;
|
|
2246
|
+
}
|
|
2247
|
+
// -----------------------------------------------------------------------
|
|
2248
|
+
// Node registration (for id-based lookup)
|
|
2249
|
+
// -----------------------------------------------------------------------
|
|
2250
|
+
registerRecursive(node) {
|
|
2251
|
+
const id = node.getId();
|
|
2252
|
+
if (id !== void 0 && id !== null) {
|
|
2253
|
+
this.nodeMap.set(id, node);
|
|
2254
|
+
}
|
|
2255
|
+
for (const child of node.childNodes) {
|
|
2256
|
+
this.registerRecursive(child);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
unregisterRecursive(node) {
|
|
2260
|
+
const id = node.getId();
|
|
2261
|
+
if (id !== void 0 && id !== null) {
|
|
2262
|
+
this.nodeMap.delete(id);
|
|
2263
|
+
}
|
|
2264
|
+
for (const child of node.childNodes) {
|
|
2265
|
+
this.unregisterRecursive(child);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
// -----------------------------------------------------------------------
|
|
2269
|
+
// Event helper
|
|
2270
|
+
// -----------------------------------------------------------------------
|
|
2271
|
+
fire(eventName, ...args) {
|
|
2272
|
+
if (typeof this.fireEvent === "function") {
|
|
2273
|
+
this.fireEvent(eventName, ...args);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
|
|
2278
|
+
// src/reader/TreeReader.ts
|
|
2279
|
+
var TreeReader = class extends JsonReader {
|
|
2280
|
+
childrenProperty;
|
|
2281
|
+
constructor(config) {
|
|
2282
|
+
super(config);
|
|
2283
|
+
this.childrenProperty = config.childrenProperty ?? "children";
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Reads data, recursively collecting ALL nodes (at all levels) into a flat
|
|
2287
|
+
* ResultSet. The original children references are preserved on the raw
|
|
2288
|
+
* objects, so the tree structure is available to callers (e.g. TreeStore).
|
|
2289
|
+
*/
|
|
2290
|
+
read(data) {
|
|
2291
|
+
const flat = [];
|
|
2292
|
+
const rootRecords = this.extractRootArray(data);
|
|
2293
|
+
this.collectFlat(rootRecords, flat);
|
|
2294
|
+
const model = this.config.model;
|
|
2295
|
+
const records = flat.map((raw) => model.create(raw));
|
|
2296
|
+
return new ResultSet({ records, total: records.length, success: true });
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Returns `{ root, records }` where `root` is the first raw record and
|
|
2300
|
+
* `records` is the full flat list of all nodes.
|
|
2301
|
+
*/
|
|
2302
|
+
readTree(data) {
|
|
2303
|
+
const flat = [];
|
|
2304
|
+
this.collectFlat([data], flat);
|
|
2305
|
+
return { root: data, records: flat };
|
|
2306
|
+
}
|
|
2307
|
+
// ---------------------------------------------------------------------------
|
|
2308
|
+
// Internal helpers
|
|
2309
|
+
// ---------------------------------------------------------------------------
|
|
2310
|
+
extractRootArray(data) {
|
|
2311
|
+
if (this.config.rootProperty) {
|
|
2312
|
+
const root = getNestedValue3(data, this.config.rootProperty);
|
|
2313
|
+
return Array.isArray(root) ? root : [];
|
|
2314
|
+
}
|
|
2315
|
+
return Array.isArray(data) ? data : [];
|
|
2316
|
+
}
|
|
2317
|
+
collectFlat(items, out) {
|
|
2318
|
+
for (const item of items) {
|
|
2319
|
+
out.push(item);
|
|
2320
|
+
const children = item[this.childrenProperty];
|
|
2321
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
2322
|
+
this.collectFlat(children, out);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
function getNestedValue3(obj, path) {
|
|
2328
|
+
const segments = path.split(".");
|
|
2329
|
+
let current = obj;
|
|
2330
|
+
for (const seg of segments) {
|
|
2331
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
2332
|
+
current = current[seg];
|
|
2333
|
+
}
|
|
2334
|
+
return current;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/writer/TreeWriter.ts
|
|
2338
|
+
var TreeWriter = class {
|
|
2339
|
+
childrenProperty;
|
|
2340
|
+
constructor(config) {
|
|
2341
|
+
this.childrenProperty = config?.childrenProperty ?? "children";
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Serializes an array of root-level NodeInterface records to plain objects.
|
|
2345
|
+
* Children are nested under the configured `childrenProperty`.
|
|
2346
|
+
*/
|
|
2347
|
+
writeRecords(records) {
|
|
2348
|
+
return records.map((r) => this.writeRecord(r));
|
|
2349
|
+
}
|
|
2350
|
+
writeRecord(record) {
|
|
2351
|
+
const data = record.getData();
|
|
2352
|
+
if (record.childNodes && record.childNodes.length > 0) {
|
|
2353
|
+
data[this.childrenProperty] = record.childNodes.map(
|
|
2354
|
+
(c) => this.writeRecord(c)
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
return data;
|
|
2358
|
+
}
|
|
2359
|
+
};
|
|
2360
|
+
|
|
2361
|
+
// src/store/BufferedStore.ts
|
|
2362
|
+
import { Base as Base4, Observable as Observable4 } from "@framesquared/core";
|
|
2363
|
+
var ObservableMixin4 = Observable4;
|
|
2364
|
+
function ensureObservable4(instance) {
|
|
2365
|
+
const proto = ObservableMixin4.prototype;
|
|
2366
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
2367
|
+
if (name === "constructor") continue;
|
|
2368
|
+
if (name in instance) continue;
|
|
2369
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
2370
|
+
if (desc && typeof desc.value === "function") {
|
|
2371
|
+
instance[name] = desc.value.bind(instance);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
var BufferedStore = class extends Base4 {
|
|
2376
|
+
static $className = "Ext.data.BufferedStore";
|
|
2377
|
+
pageSize;
|
|
2378
|
+
leadingBufferZone;
|
|
2379
|
+
trailingBufferZone;
|
|
2380
|
+
proxyRef;
|
|
2381
|
+
/** Page number → array of Model records for that page. */
|
|
2382
|
+
pageMap = /* @__PURE__ */ new Map();
|
|
2383
|
+
/** Total count from the server. */
|
|
2384
|
+
totalCount = 0;
|
|
2385
|
+
/** All loaded records in a flat sparse index. */
|
|
2386
|
+
recordMap = /* @__PURE__ */ new Map();
|
|
2387
|
+
/** Pages currently being fetched (to avoid duplicate requests). */
|
|
2388
|
+
pendingPages = /* @__PURE__ */ new Set();
|
|
2389
|
+
$destroyHooks = [];
|
|
2390
|
+
constructor(config) {
|
|
2391
|
+
super();
|
|
2392
|
+
ensureObservable4(this);
|
|
2393
|
+
this.pageSize = config.pageSize ?? 25;
|
|
2394
|
+
this.leadingBufferZone = config.leadingBufferZone ?? 0;
|
|
2395
|
+
this.trailingBufferZone = config.trailingBufferZone ?? 0;
|
|
2396
|
+
this.proxyRef = config.proxy ?? null;
|
|
2397
|
+
}
|
|
2398
|
+
// -----------------------------------------------------------------------
|
|
2399
|
+
// Public API
|
|
2400
|
+
// -----------------------------------------------------------------------
|
|
2401
|
+
getPageSize() {
|
|
2402
|
+
return this.pageSize;
|
|
2403
|
+
}
|
|
2404
|
+
getCount() {
|
|
2405
|
+
return this.recordMap.size;
|
|
2406
|
+
}
|
|
2407
|
+
getTotalCount() {
|
|
2408
|
+
return this.totalCount;
|
|
2409
|
+
}
|
|
2410
|
+
getAt(index) {
|
|
2411
|
+
return this.recordMap.get(index);
|
|
2412
|
+
}
|
|
2413
|
+
isPageLoaded(pageNumber) {
|
|
2414
|
+
return this.pageMap.has(pageNumber);
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Ensures the given range [start, end] of row indices is loaded.
|
|
2418
|
+
* Loads missing pages from the proxy and fires 'guaranteedrange'
|
|
2419
|
+
* when the requested range is fully available.
|
|
2420
|
+
*/
|
|
2421
|
+
async guaranteeRange(start, end) {
|
|
2422
|
+
const startPage = Math.floor(start / this.pageSize);
|
|
2423
|
+
const endPage = Math.floor(end / this.pageSize);
|
|
2424
|
+
const pagesToLoad = [];
|
|
2425
|
+
for (let p = startPage; p <= endPage; p++) {
|
|
2426
|
+
if (!this.pageMap.has(p) && !this.pendingPages.has(p)) {
|
|
2427
|
+
pagesToLoad.push(p);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
if (pagesToLoad.length > 0) {
|
|
2431
|
+
await Promise.all(pagesToLoad.map((p) => this.loadPage(p)));
|
|
2432
|
+
}
|
|
2433
|
+
const records = [];
|
|
2434
|
+
for (let i = start; i <= end; i++) {
|
|
2435
|
+
const rec = this.recordMap.get(i);
|
|
2436
|
+
if (rec) records.push(rec);
|
|
2437
|
+
}
|
|
2438
|
+
this.fire("guaranteedrange", this, records, start, end);
|
|
2439
|
+
this.prefetchBufferZones(startPage, endPage);
|
|
2440
|
+
}
|
|
2441
|
+
// -----------------------------------------------------------------------
|
|
2442
|
+
// Page loading
|
|
2443
|
+
// -----------------------------------------------------------------------
|
|
2444
|
+
async loadPage(pageNumber) {
|
|
2445
|
+
if (!this.proxyRef) return;
|
|
2446
|
+
if (this.pageMap.has(pageNumber)) return;
|
|
2447
|
+
this.pendingPages.add(pageNumber);
|
|
2448
|
+
try {
|
|
2449
|
+
const start = pageNumber * this.pageSize;
|
|
2450
|
+
const op = new Operation({
|
|
2451
|
+
action: "read",
|
|
2452
|
+
start,
|
|
2453
|
+
limit: this.pageSize,
|
|
2454
|
+
page: pageNumber + 1
|
|
2455
|
+
// 1-based page for server
|
|
2456
|
+
});
|
|
2457
|
+
const rs = await this.proxyRef.read(op);
|
|
2458
|
+
if (rs.success) {
|
|
2459
|
+
this.pageMap.set(pageNumber, rs.records);
|
|
2460
|
+
for (let i = 0; i < rs.records.length; i++) {
|
|
2461
|
+
this.recordMap.set(start + i, rs.records[i]);
|
|
2462
|
+
}
|
|
2463
|
+
if (rs.total > 0) {
|
|
2464
|
+
this.totalCount = rs.total;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
} finally {
|
|
2468
|
+
this.pendingPages.delete(pageNumber);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
// -----------------------------------------------------------------------
|
|
2472
|
+
// Prefetching
|
|
2473
|
+
// -----------------------------------------------------------------------
|
|
2474
|
+
prefetchBufferZones(startPage, endPage) {
|
|
2475
|
+
if (this.leadingBufferZone <= 0 && this.trailingBufferZone <= 0) return;
|
|
2476
|
+
const leadingPages = Math.ceil(this.leadingBufferZone / this.pageSize);
|
|
2477
|
+
const trailingPages = Math.ceil(this.trailingBufferZone / this.pageSize);
|
|
2478
|
+
for (let p = startPage - leadingPages; p < startPage; p++) {
|
|
2479
|
+
if (p >= 0 && !this.pageMap.has(p) && !this.pendingPages.has(p)) {
|
|
2480
|
+
this.loadPage(p);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
const maxPage = this.totalCount > 0 ? Math.floor((this.totalCount - 1) / this.pageSize) : endPage + trailingPages;
|
|
2484
|
+
for (let p = endPage + 1; p <= endPage + trailingPages && p <= maxPage; p++) {
|
|
2485
|
+
if (!this.pageMap.has(p) && !this.pendingPages.has(p)) {
|
|
2486
|
+
this.loadPage(p);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
// -----------------------------------------------------------------------
|
|
2491
|
+
// Event helper
|
|
2492
|
+
// -----------------------------------------------------------------------
|
|
2493
|
+
fire(eventName, ...args) {
|
|
2494
|
+
if (typeof this.fireEvent === "function") {
|
|
2495
|
+
this.fireEvent(eventName, ...args);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2500
|
+
// src/proxy/GraphQLProxy.ts
|
|
2501
|
+
var GraphQLProxy = class extends Proxy2 {
|
|
2502
|
+
url;
|
|
2503
|
+
query;
|
|
2504
|
+
mutation;
|
|
2505
|
+
rootProperty;
|
|
2506
|
+
headers;
|
|
2507
|
+
constructor(config) {
|
|
2508
|
+
super(config);
|
|
2509
|
+
this.url = config.url;
|
|
2510
|
+
this.query = config.query ?? "";
|
|
2511
|
+
this.mutation = config.mutation ?? "";
|
|
2512
|
+
this.rootProperty = config.rootProperty ?? "data";
|
|
2513
|
+
this.headers = config.headers ?? {};
|
|
2514
|
+
}
|
|
2515
|
+
async read(operation) {
|
|
2516
|
+
return this.doRequest(this.query, operation.params);
|
|
2517
|
+
}
|
|
2518
|
+
async create(operation) {
|
|
2519
|
+
const variables = { ...operation.params };
|
|
2520
|
+
if (operation.records.length > 0) {
|
|
2521
|
+
variables.input = operation.records[0].getData();
|
|
2522
|
+
}
|
|
2523
|
+
return this.doRequest(this.mutation, variables);
|
|
2524
|
+
}
|
|
2525
|
+
async update(operation) {
|
|
2526
|
+
const variables = { ...operation.params };
|
|
2527
|
+
if (operation.records.length > 0) {
|
|
2528
|
+
variables.input = operation.records[0].getData();
|
|
2529
|
+
}
|
|
2530
|
+
return this.doRequest(this.mutation, variables);
|
|
2531
|
+
}
|
|
2532
|
+
async destroy(operation) {
|
|
2533
|
+
const variables = { ...operation.params };
|
|
2534
|
+
if (operation.records.length > 0) {
|
|
2535
|
+
variables.id = operation.records[0].getId();
|
|
2536
|
+
}
|
|
2537
|
+
return this.doRequest(this.mutation, variables);
|
|
2538
|
+
}
|
|
2539
|
+
async doRequest(queryStr, variables) {
|
|
2540
|
+
try {
|
|
2541
|
+
const response = await fetch(this.url, {
|
|
2542
|
+
method: "POST",
|
|
2543
|
+
headers: { "Content-Type": "application/json", ...this.headers },
|
|
2544
|
+
body: JSON.stringify({ query: queryStr, variables })
|
|
2545
|
+
});
|
|
2546
|
+
const data = await response.json();
|
|
2547
|
+
if (data.errors && data.errors.length > 0) {
|
|
2548
|
+
return new ResultSet({
|
|
2549
|
+
records: [],
|
|
2550
|
+
success: false,
|
|
2551
|
+
message: data.errors[0].message
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
const raw = this.extractByPath(data, this.rootProperty);
|
|
2555
|
+
const items = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
2556
|
+
const records = items.map((item) => this.model.create(item));
|
|
2557
|
+
return new ResultSet({ records, success: true });
|
|
2558
|
+
} catch (err) {
|
|
2559
|
+
return new ResultSet({
|
|
2560
|
+
records: [],
|
|
2561
|
+
success: false,
|
|
2562
|
+
message: err?.message ?? "GraphQL request failed"
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
extractByPath(obj, path) {
|
|
2567
|
+
const parts = path.split(".");
|
|
2568
|
+
let current = obj;
|
|
2569
|
+
for (const part of parts) {
|
|
2570
|
+
if (current == null) return void 0;
|
|
2571
|
+
current = current[part];
|
|
2572
|
+
}
|
|
2573
|
+
return current;
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
// src/proxy/WebSocketProxy.ts
|
|
2578
|
+
var WebSocketProxy = class extends Proxy2 {
|
|
2579
|
+
url;
|
|
2580
|
+
reconnect;
|
|
2581
|
+
reconnectInterval;
|
|
2582
|
+
ws = null;
|
|
2583
|
+
listeners = {};
|
|
2584
|
+
intentionalClose = false;
|
|
2585
|
+
reconnectTimer = null;
|
|
2586
|
+
constructor(config) {
|
|
2587
|
+
super(config);
|
|
2588
|
+
this.url = config.url;
|
|
2589
|
+
this.reconnect = config.reconnect ?? false;
|
|
2590
|
+
this.reconnectInterval = config.reconnectInterval ?? 3e3;
|
|
2591
|
+
}
|
|
2592
|
+
// -----------------------------------------------------------------------
|
|
2593
|
+
// Connection management
|
|
2594
|
+
// -----------------------------------------------------------------------
|
|
2595
|
+
connect() {
|
|
2596
|
+
this.intentionalClose = false;
|
|
2597
|
+
this.ws = new WebSocket(this.url);
|
|
2598
|
+
this.ws.onopen = () => this.fire("open");
|
|
2599
|
+
this.ws.onerror = (e) => this.fire("error", e);
|
|
2600
|
+
this.ws.onmessage = (e) => {
|
|
2601
|
+
try {
|
|
2602
|
+
const data = JSON.parse(e.data);
|
|
2603
|
+
this.fire("message", data);
|
|
2604
|
+
} catch {
|
|
2605
|
+
this.fire("message", e.data);
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2608
|
+
this.ws.onclose = () => {
|
|
2609
|
+
this.fire("close");
|
|
2610
|
+
if (this.reconnect && !this.intentionalClose) {
|
|
2611
|
+
this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectInterval);
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
disconnect() {
|
|
2616
|
+
this.intentionalClose = true;
|
|
2617
|
+
if (this.reconnectTimer) {
|
|
2618
|
+
clearTimeout(this.reconnectTimer);
|
|
2619
|
+
this.reconnectTimer = null;
|
|
2620
|
+
}
|
|
2621
|
+
this.ws?.close();
|
|
2622
|
+
this.ws = null;
|
|
2623
|
+
}
|
|
2624
|
+
// -----------------------------------------------------------------------
|
|
2625
|
+
// Send operations
|
|
2626
|
+
// -----------------------------------------------------------------------
|
|
2627
|
+
send(operation) {
|
|
2628
|
+
if (!this.ws || this.ws.readyState !== 1) return;
|
|
2629
|
+
const message = {
|
|
2630
|
+
action: operation.action,
|
|
2631
|
+
records: operation.records.map((r) => r.getData()),
|
|
2632
|
+
params: operation.params
|
|
2633
|
+
};
|
|
2634
|
+
this.ws.send(JSON.stringify(message));
|
|
2635
|
+
}
|
|
2636
|
+
// -----------------------------------------------------------------------
|
|
2637
|
+
// Proxy interface (returns empty ResultSets — real data comes via events)
|
|
2638
|
+
// -----------------------------------------------------------------------
|
|
2639
|
+
async read(operation) {
|
|
2640
|
+
this.send(operation);
|
|
2641
|
+
return new ResultSet({ records: [], success: true });
|
|
2642
|
+
}
|
|
2643
|
+
async create(operation) {
|
|
2644
|
+
this.send(operation);
|
|
2645
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
2646
|
+
}
|
|
2647
|
+
async update(operation) {
|
|
2648
|
+
this.send(operation);
|
|
2649
|
+
return new ResultSet({ records: operation.records, success: true });
|
|
2650
|
+
}
|
|
2651
|
+
async destroy(operation) {
|
|
2652
|
+
this.send(operation);
|
|
2653
|
+
return new ResultSet({ records: [], success: true });
|
|
2654
|
+
}
|
|
2655
|
+
// -----------------------------------------------------------------------
|
|
2656
|
+
// Events
|
|
2657
|
+
// -----------------------------------------------------------------------
|
|
2658
|
+
on(event, fn) {
|
|
2659
|
+
(this.listeners[event] ??= []).push(fn);
|
|
2660
|
+
}
|
|
2661
|
+
off(event, fn) {
|
|
2662
|
+
const list = this.listeners[event];
|
|
2663
|
+
if (!list) return;
|
|
2664
|
+
const idx = list.indexOf(fn);
|
|
2665
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
2666
|
+
}
|
|
2667
|
+
fire(event, ...args) {
|
|
2668
|
+
(this.listeners[event] ?? []).forEach((fn) => fn(...args));
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
// src/proxy/BatchProxy.ts
|
|
2673
|
+
var BatchProxy = class extends Proxy2 {
|
|
2674
|
+
url;
|
|
2675
|
+
headers;
|
|
2676
|
+
constructor(config) {
|
|
2677
|
+
super(config);
|
|
2678
|
+
this.url = config.url;
|
|
2679
|
+
this.headers = config.headers ?? {};
|
|
2680
|
+
}
|
|
2681
|
+
/**
|
|
2682
|
+
* Send multiple operations as a single batched request.
|
|
2683
|
+
* Returns an array of ResultSets, one per operation.
|
|
2684
|
+
*/
|
|
2685
|
+
async sendBatch(operations) {
|
|
2686
|
+
const body = {
|
|
2687
|
+
operations: operations.map((op) => ({
|
|
2688
|
+
action: op.action,
|
|
2689
|
+
records: op.records.map((r) => r.getData()),
|
|
2690
|
+
params: op.params
|
|
2691
|
+
}))
|
|
2692
|
+
};
|
|
2693
|
+
try {
|
|
2694
|
+
const response = await fetch(this.url, {
|
|
2695
|
+
method: "POST",
|
|
2696
|
+
headers: { "Content-Type": "application/json", ...this.headers },
|
|
2697
|
+
body: JSON.stringify(body)
|
|
2698
|
+
});
|
|
2699
|
+
const data = await response.json();
|
|
2700
|
+
const results = [];
|
|
2701
|
+
if (data.results && Array.isArray(data.results)) {
|
|
2702
|
+
for (const r of data.results) {
|
|
2703
|
+
const records = (r.records ?? []).map((item) => this.model.create(item));
|
|
2704
|
+
results.push(new ResultSet({
|
|
2705
|
+
records,
|
|
2706
|
+
success: r.success ?? false,
|
|
2707
|
+
message: r.message
|
|
2708
|
+
}));
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
return results;
|
|
2712
|
+
} catch (err) {
|
|
2713
|
+
return operations.map(() => new ResultSet({
|
|
2714
|
+
records: [],
|
|
2715
|
+
success: false,
|
|
2716
|
+
message: err?.message ?? "Batch request failed"
|
|
2717
|
+
}));
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
// Proxy interface — delegate to sendBatch with single operation
|
|
2721
|
+
async read(operation) {
|
|
2722
|
+
const results = await this.sendBatch([operation]);
|
|
2723
|
+
return results[0] ?? new ResultSet({ records: [], success: false });
|
|
2724
|
+
}
|
|
2725
|
+
async create(operation) {
|
|
2726
|
+
const results = await this.sendBatch([operation]);
|
|
2727
|
+
return results[0] ?? new ResultSet({ records: [], success: false });
|
|
2728
|
+
}
|
|
2729
|
+
async update(operation) {
|
|
2730
|
+
const results = await this.sendBatch([operation]);
|
|
2731
|
+
return results[0] ?? new ResultSet({ records: [], success: false });
|
|
2732
|
+
}
|
|
2733
|
+
async destroy(operation) {
|
|
2734
|
+
const results = await this.sendBatch([operation]);
|
|
2735
|
+
return results[0] ?? new ResultSet({ records: [], success: false });
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
// src/data/Connection.ts
|
|
2740
|
+
var requestInterceptors = [];
|
|
2741
|
+
var responseInterceptors = [];
|
|
2742
|
+
var defaultHeaders = {};
|
|
2743
|
+
var errorHandler = null;
|
|
2744
|
+
var Connection = {
|
|
2745
|
+
/**
|
|
2746
|
+
* Fetch with interceptors and default headers applied.
|
|
2747
|
+
*/
|
|
2748
|
+
async fetch(url, init = {}) {
|
|
2749
|
+
init.headers = { ...defaultHeaders, ...init.headers ?? {} };
|
|
2750
|
+
let currentUrl = url;
|
|
2751
|
+
let currentInit = init;
|
|
2752
|
+
for (const interceptor of requestInterceptors) {
|
|
2753
|
+
[currentUrl, currentInit] = interceptor(currentUrl, currentInit);
|
|
2754
|
+
}
|
|
2755
|
+
try {
|
|
2756
|
+
let response = await fetch(currentUrl, currentInit);
|
|
2757
|
+
for (const interceptor of responseInterceptors) {
|
|
2758
|
+
response = interceptor(response);
|
|
2759
|
+
}
|
|
2760
|
+
return response;
|
|
2761
|
+
} catch (err) {
|
|
2762
|
+
if (errorHandler) errorHandler(err);
|
|
2763
|
+
throw err;
|
|
2764
|
+
}
|
|
2765
|
+
},
|
|
2766
|
+
addRequestInterceptor(fn) {
|
|
2767
|
+
requestInterceptors.push(fn);
|
|
2768
|
+
},
|
|
2769
|
+
addResponseInterceptor(fn) {
|
|
2770
|
+
responseInterceptors.push(fn);
|
|
2771
|
+
},
|
|
2772
|
+
setDefaultHeaders(headers) {
|
|
2773
|
+
defaultHeaders = { ...headers };
|
|
2774
|
+
},
|
|
2775
|
+
setErrorHandler(handler) {
|
|
2776
|
+
errorHandler = handler;
|
|
2777
|
+
},
|
|
2778
|
+
reset() {
|
|
2779
|
+
requestInterceptors.length = 0;
|
|
2780
|
+
responseInterceptors.length = 0;
|
|
2781
|
+
defaultHeaders = {};
|
|
2782
|
+
errorHandler = null;
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
|
|
2786
|
+
// src/data/Session.ts
|
|
2787
|
+
var Session = class {
|
|
2788
|
+
created = /* @__PURE__ */ new Map();
|
|
2789
|
+
updated = /* @__PURE__ */ new Map();
|
|
2790
|
+
destroyed = /* @__PURE__ */ new Map();
|
|
2791
|
+
// -----------------------------------------------------------------------
|
|
2792
|
+
// Track operations
|
|
2793
|
+
// -----------------------------------------------------------------------
|
|
2794
|
+
trackCreate(record) {
|
|
2795
|
+
const key = this.recordKey(record);
|
|
2796
|
+
this.created.set(key, record);
|
|
2797
|
+
}
|
|
2798
|
+
trackUpdate(record) {
|
|
2799
|
+
const key = this.recordKey(record);
|
|
2800
|
+
if (this.created.has(key)) return;
|
|
2801
|
+
this.updated.set(key, record);
|
|
2802
|
+
}
|
|
2803
|
+
trackDestroy(record) {
|
|
2804
|
+
const key = this.recordKey(record);
|
|
2805
|
+
if (this.created.has(key)) {
|
|
2806
|
+
this.created.delete(key);
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
this.updated.delete(key);
|
|
2810
|
+
this.destroyed.set(key, record);
|
|
2811
|
+
}
|
|
2812
|
+
// -----------------------------------------------------------------------
|
|
2813
|
+
// Accessors
|
|
2814
|
+
// -----------------------------------------------------------------------
|
|
2815
|
+
getCreated() {
|
|
2816
|
+
return [...this.created.values()];
|
|
2817
|
+
}
|
|
2818
|
+
getUpdated() {
|
|
2819
|
+
return [...this.updated.values()];
|
|
2820
|
+
}
|
|
2821
|
+
getDestroyed() {
|
|
2822
|
+
return [...this.destroyed.values()];
|
|
2823
|
+
}
|
|
2824
|
+
getChanges() {
|
|
2825
|
+
return {
|
|
2826
|
+
create: this.getCreated(),
|
|
2827
|
+
update: this.getUpdated(),
|
|
2828
|
+
destroy: this.getDestroyed()
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
isDirty() {
|
|
2832
|
+
return this.created.size > 0 || this.updated.size > 0 || this.destroyed.size > 0;
|
|
2833
|
+
}
|
|
2834
|
+
// -----------------------------------------------------------------------
|
|
2835
|
+
// Save via BatchProxy
|
|
2836
|
+
// -----------------------------------------------------------------------
|
|
2837
|
+
async save(proxy) {
|
|
2838
|
+
const ops = [];
|
|
2839
|
+
const creates = this.getCreated();
|
|
2840
|
+
const updates = this.getUpdated();
|
|
2841
|
+
const destroys = this.getDestroyed();
|
|
2842
|
+
if (creates.length > 0) {
|
|
2843
|
+
ops.push(new Operation({ action: "create", records: creates }));
|
|
2844
|
+
}
|
|
2845
|
+
if (updates.length > 0) {
|
|
2846
|
+
ops.push(new Operation({ action: "update", records: updates }));
|
|
2847
|
+
}
|
|
2848
|
+
if (destroys.length > 0) {
|
|
2849
|
+
ops.push(new Operation({ action: "destroy", records: destroys }));
|
|
2850
|
+
}
|
|
2851
|
+
if (ops.length === 0) {
|
|
2852
|
+
return { success: true, results: [] };
|
|
2853
|
+
}
|
|
2854
|
+
const results = await proxy.sendBatch(ops);
|
|
2855
|
+
const success = results.every((r) => r.success);
|
|
2856
|
+
return { success, results };
|
|
2857
|
+
}
|
|
2858
|
+
// -----------------------------------------------------------------------
|
|
2859
|
+
// Commit / Reject
|
|
2860
|
+
// -----------------------------------------------------------------------
|
|
2861
|
+
commit() {
|
|
2862
|
+
this.created.clear();
|
|
2863
|
+
this.updated.clear();
|
|
2864
|
+
this.destroyed.clear();
|
|
2865
|
+
}
|
|
2866
|
+
reject() {
|
|
2867
|
+
this.created.clear();
|
|
2868
|
+
this.updated.clear();
|
|
2869
|
+
this.destroyed.clear();
|
|
2870
|
+
}
|
|
2871
|
+
// -----------------------------------------------------------------------
|
|
2872
|
+
// Helpers
|
|
2873
|
+
// -----------------------------------------------------------------------
|
|
2874
|
+
recordKey(record) {
|
|
2875
|
+
return record;
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
export {
|
|
2879
|
+
AjaxProxy,
|
|
2880
|
+
ArrayReader,
|
|
2881
|
+
Association,
|
|
2882
|
+
AutoField,
|
|
2883
|
+
BatchProxy,
|
|
2884
|
+
BelongsTo,
|
|
2885
|
+
BooleanField,
|
|
2886
|
+
BufferedStore,
|
|
2887
|
+
Collection,
|
|
2888
|
+
Connection,
|
|
2889
|
+
DateField,
|
|
2890
|
+
Field,
|
|
2891
|
+
FieldType,
|
|
2892
|
+
FloatField,
|
|
2893
|
+
GraphQLProxy,
|
|
2894
|
+
HasMany,
|
|
2895
|
+
HasOne,
|
|
2896
|
+
IntField,
|
|
2897
|
+
JsonReader,
|
|
2898
|
+
JsonWriter,
|
|
2899
|
+
LocalStorageProxy,
|
|
2900
|
+
ManyToMany,
|
|
2901
|
+
ManyToManyCollection,
|
|
2902
|
+
MemoryProxy,
|
|
2903
|
+
Model,
|
|
2904
|
+
ModelCollection,
|
|
2905
|
+
Operation,
|
|
2906
|
+
Proxy2 as Proxy,
|
|
2907
|
+
RestProxy,
|
|
2908
|
+
ResultSet,
|
|
2909
|
+
Schema,
|
|
2910
|
+
Session,
|
|
2911
|
+
SessionStorageProxy,
|
|
2912
|
+
Store,
|
|
2913
|
+
StringField,
|
|
2914
|
+
TreeModel,
|
|
2915
|
+
TreeReader,
|
|
2916
|
+
TreeStore,
|
|
2917
|
+
TreeWriter,
|
|
2918
|
+
WebSocketProxy,
|
|
2919
|
+
XmlReader,
|
|
2920
|
+
XmlWriter,
|
|
2921
|
+
applyNodeInterface,
|
|
2922
|
+
createField,
|
|
2923
|
+
email,
|
|
2924
|
+
exclusion,
|
|
2925
|
+
formatValidator,
|
|
2926
|
+
inclusion,
|
|
2927
|
+
length,
|
|
2928
|
+
presence,
|
|
2929
|
+
rangeValidator
|
|
2930
|
+
};
|
|
2931
|
+
//# sourceMappingURL=index.js.map
|