@statezero/core 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.
Files changed (89) hide show
  1. package/dist/adaptors/react/composables.d.ts +1 -0
  2. package/dist/adaptors/react/composables.js +4 -0
  3. package/dist/adaptors/react/index.d.ts +1 -0
  4. package/dist/adaptors/react/index.js +1 -0
  5. package/dist/adaptors/vue/composables.d.ts +2 -0
  6. package/dist/adaptors/vue/composables.js +36 -0
  7. package/dist/adaptors/vue/index.d.ts +2 -0
  8. package/dist/adaptors/vue/index.js +2 -0
  9. package/dist/adaptors/vue/reactivity.d.ts +18 -0
  10. package/dist/adaptors/vue/reactivity.js +125 -0
  11. package/dist/cli/commands/syncModels.d.ts +132 -0
  12. package/dist/cli/commands/syncModels.js +1040 -0
  13. package/dist/cli/configFileLoader.d.ts +10 -0
  14. package/dist/cli/configFileLoader.js +85 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +14 -0
  17. package/dist/config.d.ts +52 -0
  18. package/dist/config.js +242 -0
  19. package/dist/core/eventReceivers.d.ts +179 -0
  20. package/dist/core/eventReceivers.js +210 -0
  21. package/dist/core/utils.d.ts +8 -0
  22. package/dist/core/utils.js +62 -0
  23. package/dist/filtering/localFiltering.d.ts +116 -0
  24. package/dist/filtering/localFiltering.js +834 -0
  25. package/dist/flavours/django/dates.d.ts +33 -0
  26. package/dist/flavours/django/dates.js +99 -0
  27. package/dist/flavours/django/errors.d.ts +138 -0
  28. package/dist/flavours/django/errors.js +187 -0
  29. package/dist/flavours/django/f.d.ts +6 -0
  30. package/dist/flavours/django/f.js +91 -0
  31. package/dist/flavours/django/files.d.ts +76 -0
  32. package/dist/flavours/django/files.js +338 -0
  33. package/dist/flavours/django/makeApiCall.d.ts +20 -0
  34. package/dist/flavours/django/makeApiCall.js +169 -0
  35. package/dist/flavours/django/manager.d.ts +197 -0
  36. package/dist/flavours/django/manager.js +222 -0
  37. package/dist/flavours/django/model.d.ts +112 -0
  38. package/dist/flavours/django/model.js +253 -0
  39. package/dist/flavours/django/operationFactory.d.ts +65 -0
  40. package/dist/flavours/django/operationFactory.js +216 -0
  41. package/dist/flavours/django/q.d.ts +70 -0
  42. package/dist/flavours/django/q.js +43 -0
  43. package/dist/flavours/django/queryExecutor.d.ts +131 -0
  44. package/dist/flavours/django/queryExecutor.js +468 -0
  45. package/dist/flavours/django/querySet.d.ts +412 -0
  46. package/dist/flavours/django/querySet.js +601 -0
  47. package/dist/flavours/django/tempPk.d.ts +19 -0
  48. package/dist/flavours/django/tempPk.js +48 -0
  49. package/dist/flavours/django/utils.d.ts +19 -0
  50. package/dist/flavours/django/utils.js +29 -0
  51. package/dist/index.d.ts +38 -0
  52. package/dist/index.js +38 -0
  53. package/dist/react-entry.d.ts +2 -0
  54. package/dist/react-entry.js +2 -0
  55. package/dist/reactiveAdaptor.d.ts +24 -0
  56. package/dist/reactiveAdaptor.js +38 -0
  57. package/dist/setup.d.ts +15 -0
  58. package/dist/setup.js +22 -0
  59. package/dist/syncEngine/cache/cache.d.ts +75 -0
  60. package/dist/syncEngine/cache/cache.js +341 -0
  61. package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
  62. package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
  63. package/dist/syncEngine/registries/metricRegistry.d.ts +53 -0
  64. package/dist/syncEngine/registries/metricRegistry.js +162 -0
  65. package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
  66. package/dist/syncEngine/registries/modelStoreRegistry.js +56 -0
  67. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +55 -0
  68. package/dist/syncEngine/registries/querysetStoreRegistry.js +244 -0
  69. package/dist/syncEngine/stores/metricStore.d.ts +55 -0
  70. package/dist/syncEngine/stores/metricStore.js +222 -0
  71. package/dist/syncEngine/stores/modelStore.d.ts +40 -0
  72. package/dist/syncEngine/stores/modelStore.js +405 -0
  73. package/dist/syncEngine/stores/operation.d.ts +99 -0
  74. package/dist/syncEngine/stores/operation.js +224 -0
  75. package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
  76. package/dist/syncEngine/stores/operationEventHandlers.js +239 -0
  77. package/dist/syncEngine/stores/querysetStore.d.ts +32 -0
  78. package/dist/syncEngine/stores/querysetStore.js +200 -0
  79. package/dist/syncEngine/stores/reactivity.d.ts +3 -0
  80. package/dist/syncEngine/stores/reactivity.js +4 -0
  81. package/dist/syncEngine/stores/utils.d.ts +14 -0
  82. package/dist/syncEngine/stores/utils.js +32 -0
  83. package/dist/syncEngine/sync.d.ts +32 -0
  84. package/dist/syncEngine/sync.js +169 -0
  85. package/dist/vue-entry.d.ts +6 -0
  86. package/dist/vue-entry.js +2 -0
  87. package/license.md +116 -0
  88. package/package.json +123 -0
  89. package/readme.md +222 -0
@@ -0,0 +1,601 @@
1
+ /**
2
+ * @typedef {'count'|'sum'|'avg'|'min'|'max'} AggregateFunction
3
+ */
4
+ /**
5
+ * @typedef {Object} Aggregation
6
+ * @property {AggregateFunction} function - The aggregation function.
7
+ * @property {string} field - The field to aggregate.
8
+ * @property {string} [alias] - Optional alias for the aggregated field.
9
+ */
10
+ /**
11
+ * @typedef {'filter'|'or'|'and'|'not'|'exclude'|'get'|'create'|'update'|'delete'|'get_or_create'|'update_or_create'|'first'|'last'|'exists'|'search'} QueryOperationType
12
+ */
13
+ /**
14
+ * @typedef {Object} QueryNode
15
+ * @property {QueryOperationType} type - The operation type.
16
+ * @property {Object.<string, any>} [conditions] - Filter conditions.
17
+ * @property {QueryNode[]} [children] - Child query nodes.
18
+ * @property {any} [lookup] - Extra parameter for operations that need it.
19
+ * @property {Partial<any>} [defaults] - Default values for create operations.
20
+ * @property {number} [pk] - Primary key value.
21
+ * @property {any} [data] - Data payload.
22
+ * @property {string} [searchQuery] - Search term for search operations.
23
+ * @property {string[]} [searchFields] - Optional array of field names for search.
24
+ */
25
+ /**
26
+ * @typedef {Object} SerializerOptions
27
+ * @property {number} [depth] - How deep to serialize nested objects.
28
+ * @property {string[]} [fields] - Fields to include.
29
+ * @property {number} [limit] - Limit for pagination.
30
+ * @property {number} [offset] - Offset for pagination.
31
+ * @property {number} [overfetch] - Overfetch additional items, for smooth optimistic delete replacement.
32
+ * @property {string} [namespace] - Custom namespace for real-time updates.
33
+ */
34
+ /**
35
+ * @template T
36
+ * @typedef {Object.<string, any>} FieldLookup
37
+ */
38
+ /**
39
+ * @template T
40
+ * @typedef {Object.<string, any>} ObjectLookup
41
+ */
42
+ /**
43
+ * Django-specific Q helper type.
44
+ *
45
+ * A QCondition is either a partial object of type T or a combination
46
+ * of partial field and object lookups.
47
+ *
48
+ * @template T
49
+ * @typedef {Partial<T> | (Partial<FieldLookup<T>> & Partial<ObjectLookup<T>>)} QCondition
50
+ */
51
+ /**
52
+ * Django-specific Q helper type representing a logical grouping of conditions.
53
+ *
54
+ * @template T
55
+ * @typedef {Object} QObject
56
+ * @property {'AND'|'OR'} operator - The logical operator.
57
+ * @property {Array<QCondition<T>|QObject<T>>} conditions - An array of conditions or nested Q objects.
58
+ */
59
+ /**
60
+ * Creates a Q object for combining conditions.
61
+ *
62
+ * @template T
63
+ * @param {'AND'|'OR'} operator - The operator to combine conditions.
64
+ * @param {...(QCondition<T>|QObject<T>)} conditions - The conditions to combine.
65
+ * @returns {QObject<T>} The combined Q object.
66
+ */
67
+ export function Q(operator, ...conditions) {
68
+ return {
69
+ operator,
70
+ conditions,
71
+ };
72
+ }
73
+ import { MultipleObjectsReturned, DoesNotExist, parseStateZeroError } from './errors.js';
74
+ import { Model } from './model.js';
75
+ import axios from 'axios';
76
+ import { QueryExecutor } from './queryExecutor.js';
77
+ import { json } from 'stream/consumers';
78
+ import { v7 } from 'uuid';
79
+ import hash from 'object-hash';
80
+ import rfdc from 'rfdc';
81
+ const clone = rfdc();
82
+ /**
83
+ * A QuerySet provides a fluent API for constructing and executing queries.
84
+ *
85
+ * @template T
86
+ */
87
+ export class QuerySet {
88
+ /**
89
+ * Creates a new QuerySet.
90
+ *
91
+ * @param {ModelConstructor} ModelClass - The model constructor.
92
+ * @param {Object} [config={}] - The configuration for the QuerySet.
93
+ * @param {QueryNode[]} [config.nodes] - Array of query nodes.
94
+ * @param {Array<{ field: string, direction: 'asc'|'desc' }>} [config.orderBy] - Ordering configuration.
95
+ * @param {Set<string>} [config.fields] - Set of fields to retrieve.
96
+ * @param {Aggregation[]} [config.aggregations] - Aggregation operations.
97
+ * @param {string} [config.initialQueryset] - The initial queryset identifier.
98
+ * @param {SerializerOptions} [config.serializerOptions] - Serializer options.
99
+ * @param {boolean} [config.materialized] - Whether the queryset is materialized.
100
+ */
101
+ constructor(ModelClass, config = {}, parent = null) {
102
+ this.ModelClass = ModelClass;
103
+ this.nodes = config.nodes || [];
104
+ this._orderBy = config.orderBy;
105
+ this._fields = config.fields || new Set();
106
+ this._aggregations = config.aggregations || [];
107
+ this._initialQueryset = config.initialQueryset;
108
+ this._serializerOptions = config.serializerOptions || {};
109
+ this._materialized = config.materialized || false;
110
+ this.__uuid = v7();
111
+ this.__parent = parent;
112
+ this.__reactivityId = parent?.__reactivityId;
113
+ }
114
+ /**
115
+ * Clones this QuerySet, creating a new instance with the same configuration.
116
+ *
117
+ * @returns {QuerySet} A new QuerySet instance.
118
+ */
119
+ clone() {
120
+ return new QuerySet(this.ModelClass, {
121
+ nodes: [...this.nodes],
122
+ orderBy: this._orderBy ? [...this._orderBy] : undefined,
123
+ fields: new Set(this._fields),
124
+ aggregations: [...this._aggregations],
125
+ initialQueryset: this._initialQueryset,
126
+ serializerOptions: { ...this._serializerOptions },
127
+ materialized: this._materialized,
128
+ }, this);
129
+ }
130
+ get semanticKey() {
131
+ return JSON.stringify({
132
+ ModelClass: {
133
+ configKey: this.ModelClass.configKey,
134
+ modelName: this.ModelClass.modelName,
135
+ },
136
+ ast: this.build(),
137
+ });
138
+ }
139
+ get key() {
140
+ return this.__uuid;
141
+ }
142
+ /**
143
+ * Ensures the QuerySet is still lazy (not materialized).
144
+ *
145
+ * @private
146
+ * @throws {Error} If the QuerySet is already materialized.
147
+ */
148
+ ensureNotMaterialized() {
149
+ if (this._materialized) {
150
+ throw new Error("Cannot chain further operations on a materialized QuerySet.");
151
+ }
152
+ }
153
+ /**
154
+ * Returns the model constructor for this QuerySet.
155
+ *
156
+ * @returns {ModelConstructor} The model constructor.
157
+ */
158
+ get modelClass() {
159
+ return this.ModelClass;
160
+ }
161
+ /**
162
+ * Sets serializer options for the QuerySet.
163
+ *
164
+ * @param {SerializerOptions} options - The serializer options to set.
165
+ * @returns {QuerySet} This QuerySet instance for chaining.
166
+ */
167
+ setSerializerOptions(options) {
168
+ this._serializerOptions = { ...this._serializerOptions, ...options };
169
+ return this;
170
+ }
171
+ /**
172
+ * Filters the QuerySet with the provided conditions.
173
+ *
174
+ * @param {Object} conditions - The filter conditions.
175
+ * @returns {QuerySet} A new QuerySet with the filter applied.
176
+ */
177
+ filter(conditions) {
178
+ this.ensureNotMaterialized();
179
+ const { Q: qConditions, ...filters } = conditions;
180
+ const newNodes = [...this.nodes];
181
+ if (Object.keys(filters).length > 0) {
182
+ newNodes.push({
183
+ type: 'filter',
184
+ conditions: filters
185
+ });
186
+ }
187
+ if (qConditions && qConditions.length) {
188
+ newNodes.push({
189
+ type: 'and',
190
+ children: qConditions.map(q => this.processQObject(q))
191
+ });
192
+ }
193
+ return new QuerySet(this.ModelClass, {
194
+ ...this._getConfig(),
195
+ nodes: newNodes
196
+ }, this);
197
+ }
198
+ /**
199
+ * Excludes the specified conditions from the QuerySet.
200
+ *
201
+ * @param {Object} conditions - The conditions to exclude.
202
+ * @returns {QuerySet} A new QuerySet with the exclusion applied.
203
+ */
204
+ exclude(conditions) {
205
+ this.ensureNotMaterialized();
206
+ const { Q: qConditions, ...filters } = conditions;
207
+ const newNodes = [...this.nodes];
208
+ let childNode = null;
209
+ if (Object.keys(filters).length > 0 && qConditions && qConditions.length) {
210
+ childNode = {
211
+ type: 'and',
212
+ children: [
213
+ { type: 'filter', conditions: filters },
214
+ { type: 'and', children: qConditions.map(q => this.processQObject(q)) }
215
+ ]
216
+ };
217
+ }
218
+ else if (Object.keys(filters).length > 0) {
219
+ childNode = {
220
+ type: 'filter',
221
+ conditions: filters
222
+ };
223
+ }
224
+ else if (qConditions && qConditions.length) {
225
+ childNode = {
226
+ type: 'and',
227
+ children: qConditions.map(q => this.processQObject(q))
228
+ };
229
+ }
230
+ const excludeNode = {
231
+ type: 'exclude',
232
+ child: childNode
233
+ };
234
+ newNodes.push(excludeNode);
235
+ return new QuerySet(this.ModelClass, {
236
+ ...this._getConfig(),
237
+ nodes: newNodes
238
+ }, this);
239
+ }
240
+ /**
241
+ * Orders the QuerySet by the specified fields.
242
+ *
243
+ * @param {...string} fields - Fields to order by.
244
+ * @returns {QuerySet} A new QuerySet with ordering applied.
245
+ */
246
+ orderBy(...fields) {
247
+ this.ensureNotMaterialized();
248
+ return new QuerySet(this.ModelClass, {
249
+ ...this._getConfig(),
250
+ orderBy: fields
251
+ }, this);
252
+ }
253
+ /**
254
+ * Applies a search to the QuerySet using the specified search query and fields.
255
+ *
256
+ * @param {string} searchQuery - The search query.
257
+ * @param {string[]} [searchFields] - The fields to search.
258
+ * @returns {QuerySet} A new QuerySet with the search applied.
259
+ */
260
+ search(searchQuery, searchFields) {
261
+ this.ensureNotMaterialized();
262
+ const newNodes = [...this.nodes];
263
+ newNodes.push({
264
+ type: 'search',
265
+ searchQuery,
266
+ searchFields: searchFields
267
+ });
268
+ return new QuerySet(this.ModelClass, {
269
+ ...this._getConfig(),
270
+ nodes: newNodes
271
+ }, this);
272
+ }
273
+ /**
274
+ * Processes a Q object or condition into a QueryNode.
275
+ *
276
+ * @private
277
+ * @param {QObject|QCondition} q - The query object or condition.
278
+ * @returns {QueryNode} The processed QueryNode.
279
+ */
280
+ processQObject(q) {
281
+ if ('operator' in q && 'conditions' in q) {
282
+ return {
283
+ type: q.operator === 'AND' ? 'and' : 'or',
284
+ children: Array.isArray(q.conditions)
285
+ ? q.conditions.map(c => this.processQObject(c))
286
+ : []
287
+ };
288
+ }
289
+ else {
290
+ return {
291
+ type: 'filter',
292
+ conditions: q
293
+ };
294
+ }
295
+ }
296
+ /**
297
+ * Aggregates the QuerySet using the specified function.
298
+ *
299
+ * @param {AggregateFunction} fn - The aggregation function.
300
+ * @param {string} field - The field to aggregate.
301
+ * @param {string} [alias] - An optional alias for the aggregated field.
302
+ * @returns {QuerySet} A new QuerySet with the aggregation applied.
303
+ */
304
+ aggregate(fn, field, alias) {
305
+ this.ensureNotMaterialized();
306
+ return new QuerySet(this.ModelClass, {
307
+ ...this._getConfig(),
308
+ aggregations: [...this._aggregations, {
309
+ function: fn,
310
+ field: field,
311
+ alias
312
+ }]
313
+ }, this);
314
+ }
315
+ /**
316
+ * Executes a count query on the QuerySet.
317
+ *
318
+ * @param {string} [field] - The field to count.
319
+ * @returns {Promise<number>} A promise that resolves to the count.
320
+ */
321
+ count(field) {
322
+ this.ensureNotMaterialized();
323
+ const newQs = new QuerySet(this.ModelClass, {
324
+ ...this._getConfig(),
325
+ materialized: true
326
+ }, this);
327
+ return QueryExecutor.execute(newQs, 'count', { field: field || this.ModelClass.primaryKeyField });
328
+ }
329
+ /**
330
+ * Executes a sum aggregation on the QuerySet.
331
+ *
332
+ * @param {string} field - The field to sum.
333
+ * @returns {Promise<number>} A promise that resolves to the sum.
334
+ */
335
+ sum(field) {
336
+ this.ensureNotMaterialized();
337
+ const newQs = new QuerySet(this.ModelClass, {
338
+ ...this._getConfig(),
339
+ materialized: true
340
+ }, this);
341
+ return QueryExecutor.execute(newQs, 'sum', { field });
342
+ }
343
+ /**
344
+ * Executes an average aggregation on the QuerySet.
345
+ *
346
+ * @param {string} field - The field to average.
347
+ * @returns {Promise<number>} A promise that resolves to the average.
348
+ */
349
+ avg(field) {
350
+ this.ensureNotMaterialized();
351
+ const newQs = new QuerySet(this.ModelClass, {
352
+ ...this._getConfig(),
353
+ materialized: true
354
+ }, this);
355
+ return QueryExecutor.execute(newQs, 'avg', { field });
356
+ }
357
+ /**
358
+ * Executes a min aggregation on the QuerySet.
359
+ *
360
+ * @param {string} field - The field to find the minimum value for.
361
+ * @returns {Promise<any>} A promise that resolves to the minimum value.
362
+ */
363
+ min(field) {
364
+ this.ensureNotMaterialized();
365
+ const newQs = new QuerySet(this.ModelClass, {
366
+ ...this._getConfig(),
367
+ materialized: true
368
+ }, this);
369
+ return QueryExecutor.execute(newQs, 'min', { field });
370
+ }
371
+ /**
372
+ * Executes a max aggregation on the QuerySet.
373
+ *
374
+ * @param {string} field - The field to find the maximum value for.
375
+ * @returns {Promise<any>} A promise that resolves to the maximum value.
376
+ */
377
+ max(field) {
378
+ this.ensureNotMaterialized();
379
+ const newQs = new QuerySet(this.ModelClass, {
380
+ ...this._getConfig(),
381
+ materialized: true
382
+ }, this);
383
+ return QueryExecutor.execute(newQs, 'max', { field });
384
+ }
385
+ /**
386
+ * Retrieves the first record of the QuerySet.
387
+ *
388
+ * @param {SerializerOptions} [serializerOptions] - Optional serializer options.
389
+ * @returns {Promise<T|null>} A promise that resolves to the first record or null.
390
+ */
391
+ first(serializerOptions) {
392
+ this.ensureNotMaterialized();
393
+ const newQs = new QuerySet(this.ModelClass, {
394
+ ...this._getConfig(),
395
+ serializerOptions: serializerOptions ? { ...this._serializerOptions, ...serializerOptions } : this._serializerOptions,
396
+ materialized: true
397
+ }, this);
398
+ return QueryExecutor.execute(newQs, 'first');
399
+ }
400
+ /**
401
+ * Retrieves the last record of the QuerySet.
402
+ *
403
+ * @param {SerializerOptions} [serializerOptions] - Optional serializer options.
404
+ * @returns {Promise<T|null>} A promise that resolves to the last record or null.
405
+ */
406
+ last(serializerOptions) {
407
+ this.ensureNotMaterialized();
408
+ const newQs = new QuerySet(this.ModelClass, {
409
+ ...this._getConfig(),
410
+ serializerOptions: serializerOptions ? { ...this._serializerOptions, ...serializerOptions } : this._serializerOptions,
411
+ materialized: true
412
+ }, this);
413
+ return QueryExecutor.execute(newQs, 'last');
414
+ }
415
+ /**
416
+ * Checks if any records exist in the QuerySet.
417
+ *
418
+ * @returns {Promise<boolean>} A promise that resolves to true if records exist, otherwise false.
419
+ */
420
+ exists() {
421
+ this.ensureNotMaterialized();
422
+ const newQs = new QuerySet(this.ModelClass, {
423
+ ...this._getConfig(),
424
+ materialized: true
425
+ }, this);
426
+ return QueryExecutor.execute(newQs, 'exists');
427
+ }
428
+ /**
429
+ * Applies serializer options to the QuerySet.
430
+ *
431
+ * @param {SerializerOptions} [serializerOptions] - Optional serializer options.
432
+ * @returns {QuerySet} A new QuerySet with the serializer options applied.
433
+ */
434
+ all(serializerOptions) {
435
+ this.ensureNotMaterialized();
436
+ if (serializerOptions) {
437
+ return new QuerySet(this.ModelClass, {
438
+ ...this._getConfig(),
439
+ serializerOptions: { ...this._serializerOptions, ...serializerOptions }
440
+ }, this);
441
+ }
442
+ return this;
443
+ }
444
+ /**
445
+ * Creates a new record in the QuerySet.
446
+ * @param {Object} data - The fields and values for the new record.
447
+ * @returns {Promise<any>} The created model instance.
448
+ */
449
+ async create(data) {
450
+ this.ensureNotMaterialized();
451
+ // Materialize for create
452
+ const newQs = new QuerySet(this.ModelClass, {
453
+ ...this._getConfig(),
454
+ materialized: true
455
+ }, this);
456
+ return QueryExecutor.execute(newQs, 'create', { data });
457
+ }
458
+ /**
459
+ * Updates records in the QuerySet.
460
+ *
461
+ * @param {Object} updates - The fields to update.
462
+ * @returns {Promise<[number, Object]>} A promise that resolves to a tuple with the number of updated records and a mapping of model names to counts.
463
+ */
464
+ update(updates) {
465
+ if (arguments.length > 1) {
466
+ throw new Error('Update accepts only accepts an object of the updates to apply. Use filter() before calling update() to select elements.');
467
+ }
468
+ this.ensureNotMaterialized();
469
+ const newQs = new QuerySet(this.ModelClass, {
470
+ ...this._getConfig(),
471
+ materialized: true
472
+ });
473
+ return QueryExecutor.execute(newQs, 'update', { data: updates });
474
+ }
475
+ /**
476
+ * Deletes records in the QuerySet.
477
+ *
478
+ * @returns {Promise<[number, Object]>} A promise that resolves to a tuple with the number of deleted records and a mapping of model names to counts.
479
+ */
480
+ delete() {
481
+ if (arguments.length > 0) {
482
+ throw new Error('delete() does not accept arguments and will delete the entire queryset. Use filter() before calling delete() to select elements.');
483
+ }
484
+ this.ensureNotMaterialized();
485
+ const newQs = new QuerySet(this.ModelClass, {
486
+ ...this._getConfig(),
487
+ materialized: true
488
+ }, this);
489
+ return QueryExecutor.execute(newQs, 'delete');
490
+ }
491
+ /**
492
+ * Retrieves a single record from the QuerySet.
493
+ *
494
+ * @param {Object} [filters] - Optional filters to apply.
495
+ * @param {SerializerOptions} [serializerOptions] - Optional serializer options.
496
+ * @returns {Promise<T>} A promise that resolves to the retrieved record.
497
+ * @throws {MultipleObjectsReturned} If more than one record is found.
498
+ * @throws {DoesNotExist} If no records are found.
499
+ */
500
+ get(filters, serializerOptions) {
501
+ this.ensureNotMaterialized();
502
+ let newQs = this;
503
+ if (filters) {
504
+ newQs = this.filter(filters);
505
+ }
506
+ if (serializerOptions) {
507
+ newQs = new QuerySet(this.ModelClass, {
508
+ ...newQs._getConfig(),
509
+ serializerOptions: { ...newQs._serializerOptions, ...serializerOptions }
510
+ }, this);
511
+ }
512
+ const materializedQs = new QuerySet(this.ModelClass, {
513
+ ...newQs._getConfig(),
514
+ materialized: true
515
+ }, this);
516
+ const result = QueryExecutor.execute(materializedQs, 'get');
517
+ if (result === null) {
518
+ throw new DoesNotExist();
519
+ }
520
+ return result;
521
+ }
522
+ /**
523
+ * Builds the final query object to be sent to the backend (simple jsonable object format).
524
+ *
525
+ * @returns {Object} The final query object.
526
+ */
527
+ build() {
528
+ let searchData = null;
529
+ const nonSearchNodes = [];
530
+ for (const node of this.nodes) {
531
+ if (node.type === 'search') {
532
+ searchData = {
533
+ searchQuery: node.searchQuery || '',
534
+ searchFields: node.searchFields
535
+ };
536
+ }
537
+ else {
538
+ nonSearchNodes.push(node);
539
+ }
540
+ }
541
+ const filterNode = nonSearchNodes.length === 0 ? null :
542
+ nonSearchNodes.length === 1 ? nonSearchNodes[0] : {
543
+ type: 'and',
544
+ children: nonSearchNodes
545
+ };
546
+ return clone({
547
+ filter: filterNode,
548
+ search: searchData,
549
+ aggregations: this._aggregations,
550
+ orderBy: this._orderBy,
551
+ serializerOptions: this._serializerOptions
552
+ });
553
+ }
554
+ /**
555
+ * Returns the current configuration of the QuerySet.
556
+ *
557
+ * @private
558
+ * @returns {Object} The current QuerySet configuration.
559
+ */
560
+ _getConfig() {
561
+ return {
562
+ nodes: this.nodes,
563
+ orderBy: this._orderBy,
564
+ fields: this._fields,
565
+ aggregations: this._aggregations,
566
+ initialQueryset: this._initialQueryset,
567
+ serializerOptions: this._serializerOptions
568
+ };
569
+ }
570
+ /**
571
+ * Materializes the QuerySet into an array of model instances.
572
+ *
573
+ * @param {SerializerOptions} [serializerOptions] - Optional serializer options.
574
+ * @returns {Promise<T[]>} A promise that resolves to an array of model instances.
575
+ */
576
+ fetch(serializerOptions) {
577
+ let querySet = this;
578
+ if (serializerOptions) {
579
+ querySet = new QuerySet(this.ModelClass, {
580
+ ...this._getConfig(),
581
+ serializerOptions: { ...this._serializerOptions, ...serializerOptions }
582
+ }, this);
583
+ }
584
+ const materializedQs = new QuerySet(this.ModelClass, {
585
+ ...querySet._getConfig(),
586
+ materialized: true
587
+ }, this);
588
+ return QueryExecutor.execute(materializedQs, 'list');
589
+ }
590
+ /**
591
+ * Implements the async iterator protocol so that you can iterate over the QuerySet.
592
+ *
593
+ * @returns {AsyncIterator<T>} An async iterator over the model instances.
594
+ */
595
+ async *[Symbol.asyncIterator]() {
596
+ const items = await this.fetch();
597
+ for (const item of items) {
598
+ yield item;
599
+ }
600
+ }
601
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Check if a string contains a temporary PK template
3
+ * @param {string} str - The string to check
4
+ * @returns {boolean} - True if the string contains a TempPK tag
5
+ */
6
+ export function containsTempPk(str: string): boolean;
7
+ /**
8
+ * Create a temporary PK with handlebars syntax
9
+ */
10
+ export function createTempPk(uuid: any): string;
11
+ /**
12
+ * Register a real PK for a temporary PK
13
+ */
14
+ export function setRealPk(uuid: any, realPk: any): void;
15
+ /**
16
+ * Replace all temporary PKs in an object (or string) with their real values
17
+ */
18
+ export function replaceTempPks(payload: any): any;
19
+ export const tempPkMap: Map<any, any>;
@@ -0,0 +1,48 @@
1
+ import Handlebars from 'handlebars';
2
+ import superjson from 'superjson';
3
+ // Simple map to store temporary PK to real PK mappings
4
+ export const tempPkMap = new Map();
5
+ /**
6
+ * Check if a string contains a temporary PK template
7
+ * @param {string} str - The string to check
8
+ * @returns {boolean} - True if the string contains a TempPK tag
9
+ */
10
+ export function containsTempPk(str) {
11
+ return (typeof str === 'string' &&
12
+ /\{\{\s*TempPK_[^}\s]+\s*\}\}/.test(str));
13
+ }
14
+ /**
15
+ * Create a temporary PK with handlebars syntax
16
+ */
17
+ export function createTempPk(uuid) {
18
+ const tempPk = `{{TempPK_${uuid}}}`;
19
+ return tempPk;
20
+ }
21
+ /**
22
+ * Register a real PK for a temporary PK
23
+ */
24
+ export function setRealPk(uuid, realPk) {
25
+ const key = `TempPK_${uuid}`;
26
+ tempPkMap.set(key, realPk);
27
+ }
28
+ /**
29
+ * Replace all temporary PKs in an object (or string) with their real values
30
+ */
31
+ export function replaceTempPks(payload) {
32
+ if (tempPkMap.size === 0)
33
+ return payload;
34
+ const context = Object.fromEntries(tempPkMap);
35
+ try {
36
+ if (typeof payload === 'string') {
37
+ const template = Handlebars.compile(payload, { noEscape: true });
38
+ return template(context);
39
+ }
40
+ const str = superjson.stringify(payload);
41
+ const template = Handlebars.compile(str, { noEscape: true });
42
+ const replaced = template(context);
43
+ return superjson.parse(replaced);
44
+ }
45
+ catch (error) {
46
+ return payload;
47
+ }
48
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Given a live reactive handle and a Promise, wire them together so
3
+ * the handle is also thenable.
4
+ *
5
+ * @template T
6
+ * @param {LiveQueryset} live
7
+ * @param {Promise<T>} promise
8
+ * @returns {LiveQueryset & Promise<T>}
9
+ */
10
+ export function makeLiveThenable<T>(live: LiveQueryset, promise: Promise<T>): LiveQueryset & Promise<T>;
11
+ /**
12
+ * Remove the thenable hooks and clear the optimistic flag in-place, so the object
13
+ * can be returned without causing a recursive await loop.
14
+ *
15
+ * @template T
16
+ * @param {T} live
17
+ * @returns {T}
18
+ */
19
+ export function breakThenable<T>(live: T): T;