cry-synced-db-client 0.1.69 → 0.1.72
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/dist/index.js +347 -67
- package/dist/src/db/SyncedDb.d.ts +23 -5
- package/dist/src/db/sync/SyncEngine.d.ts +10 -0
- package/dist/src/db/types/managers.d.ts +4 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/types/DbEntity.d.ts +1 -0
- package/dist/src/types/I_RestInterface.d.ts +5 -1
- package/dist/src/types/I_SyncedDb.d.ts +25 -6
- package/dist/src/utils/localQuery.d.ts +20 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -121,6 +121,103 @@ function filterByQuery(items, query) {
|
|
|
121
121
|
}
|
|
122
122
|
return items.filter((item) => matchesQuery(item, query));
|
|
123
123
|
}
|
|
124
|
+
function sortItems(items, sort) {
|
|
125
|
+
const sortEntries = Object.entries(sort);
|
|
126
|
+
if (sortEntries.length === 0)
|
|
127
|
+
return items;
|
|
128
|
+
return [...items].sort((a, b) => {
|
|
129
|
+
for (const [field, direction] of sortEntries) {
|
|
130
|
+
const aVal = getNestedValue(a, field);
|
|
131
|
+
const bVal = getNestedValue(b, field);
|
|
132
|
+
const cmp = compareValues(aVal, bVal);
|
|
133
|
+
if (cmp !== 0)
|
|
134
|
+
return cmp * direction;
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function compareValues(a, b) {
|
|
140
|
+
if (a === b)
|
|
141
|
+
return 0;
|
|
142
|
+
if (a == null && b == null)
|
|
143
|
+
return 0;
|
|
144
|
+
if (a == null)
|
|
145
|
+
return -1;
|
|
146
|
+
if (b == null)
|
|
147
|
+
return 1;
|
|
148
|
+
if (a instanceof Date && b instanceof Date) {
|
|
149
|
+
return a.getTime() - b.getTime();
|
|
150
|
+
}
|
|
151
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
152
|
+
return a - b;
|
|
153
|
+
}
|
|
154
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
155
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
156
|
+
}
|
|
157
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
158
|
+
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
159
|
+
}
|
|
160
|
+
const sa = String(a);
|
|
161
|
+
const sb = String(b);
|
|
162
|
+
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
163
|
+
}
|
|
164
|
+
function applySkipLimit(items, skip, limit) {
|
|
165
|
+
let result = items;
|
|
166
|
+
if (skip && skip > 0) {
|
|
167
|
+
result = result.slice(skip);
|
|
168
|
+
}
|
|
169
|
+
if (limit != null && limit >= 0) {
|
|
170
|
+
result = result.slice(0, limit);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
function projectItem(item, project) {
|
|
175
|
+
const entries = Object.entries(project);
|
|
176
|
+
if (entries.length === 0)
|
|
177
|
+
return item;
|
|
178
|
+
const hasIncludes = entries.some(([, v]) => v === true || v === 1);
|
|
179
|
+
if (hasIncludes) {
|
|
180
|
+
const result = {};
|
|
181
|
+
const excludeId = project._id === false || project._id === 0;
|
|
182
|
+
if (!excludeId && item._id !== undefined) {
|
|
183
|
+
result._id = item._id;
|
|
184
|
+
}
|
|
185
|
+
for (const [field, value] of entries) {
|
|
186
|
+
if (field === "_id")
|
|
187
|
+
continue;
|
|
188
|
+
if (value === true || value === 1) {
|
|
189
|
+
const val = getNestedValue(item, field);
|
|
190
|
+
if (val !== undefined) {
|
|
191
|
+
result[field] = val;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
} else {
|
|
197
|
+
const result = { ...item };
|
|
198
|
+
for (const [field, value] of entries) {
|
|
199
|
+
if (value === false || value === 0) {
|
|
200
|
+
delete result[field];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function applyQueryOpts(items, opts) {
|
|
207
|
+
if (!opts)
|
|
208
|
+
return items;
|
|
209
|
+
let result = items;
|
|
210
|
+
if (opts.sort && Object.keys(opts.sort).length > 0) {
|
|
211
|
+
result = sortItems(result, opts.sort);
|
|
212
|
+
}
|
|
213
|
+
if (opts.skip || opts.limit != null) {
|
|
214
|
+
result = applySkipLimit(result, opts.skip, opts.limit);
|
|
215
|
+
}
|
|
216
|
+
if (opts.project) {
|
|
217
|
+
result = result.map((item) => projectItem(item, opts.project));
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
124
221
|
|
|
125
222
|
// src/db/managers/InMemManager.ts
|
|
126
223
|
class InMemManager {
|
|
@@ -2069,11 +2166,12 @@ class SyncEngine {
|
|
|
2069
2166
|
const allUpdatedIds = {};
|
|
2070
2167
|
for (const [collectionName, config] of configMap) {
|
|
2071
2168
|
const serverData = allServerData[collectionName] || [];
|
|
2169
|
+
delete allServerData[collectionName];
|
|
2072
2170
|
receivedCount += serverData.length;
|
|
2073
2171
|
collectionStats[collectionName] = {
|
|
2074
2172
|
receivedCount: serverData.length,
|
|
2075
2173
|
sentCount: 0,
|
|
2076
|
-
receivedItems:
|
|
2174
|
+
receivedItems: []
|
|
2077
2175
|
};
|
|
2078
2176
|
const stats = await this.processIncomingServerData(collectionName, config, serverData);
|
|
2079
2177
|
conflictsResolved += stats.conflictsResolved;
|
|
@@ -2313,60 +2411,79 @@ class SyncEngine {
|
|
|
2313
2411
|
}
|
|
2314
2412
|
return { sentCount };
|
|
2315
2413
|
}
|
|
2414
|
+
async processCollectionServerData(collectionName, serverData) {
|
|
2415
|
+
const config = this.collections.get(collectionName);
|
|
2416
|
+
if (!config)
|
|
2417
|
+
return { updatedIds: [] };
|
|
2418
|
+
const result = await this.processIncomingServerData(collectionName, config, serverData);
|
|
2419
|
+
if (result.updatedIds.length > 0) {
|
|
2420
|
+
this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
|
|
2421
|
+
}
|
|
2422
|
+
return { updatedIds: result.updatedIds };
|
|
2423
|
+
}
|
|
2424
|
+
static SYNC_BATCH_SIZE = 200;
|
|
2316
2425
|
async processIncomingServerData(collectionName, config, serverData) {
|
|
2317
2426
|
if (serverData.length === 0) {
|
|
2318
2427
|
return { conflictsResolved: 0, maxTs: undefined, updatedIds: [] };
|
|
2319
2428
|
}
|
|
2320
2429
|
let maxTs;
|
|
2321
2430
|
let conflictsResolved = 0;
|
|
2322
|
-
const
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
const
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2431
|
+
const allUpdatedIds = [];
|
|
2432
|
+
const BATCH = SyncEngine.SYNC_BATCH_SIZE;
|
|
2433
|
+
for (let offset = 0;offset < serverData.length; offset += BATCH) {
|
|
2434
|
+
const chunk = serverData.slice(offset, offset + BATCH);
|
|
2435
|
+
const chunkIds = chunk.map((item) => item._id);
|
|
2436
|
+
const localItems = await this.dexieDb.getByIds(collectionName, chunkIds);
|
|
2437
|
+
const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(collectionName, chunkIds);
|
|
2438
|
+
const dexieBatch = [];
|
|
2439
|
+
const inMemSaveBatch = [];
|
|
2440
|
+
const inMemDeleteIds = [];
|
|
2441
|
+
for (let i = 0;i < chunk.length; i++) {
|
|
2442
|
+
const serverItem = chunk[i];
|
|
2443
|
+
const localItem = localItems[i];
|
|
2444
|
+
const dirtyChange = dirtyChangesMap.get(String(serverItem._id));
|
|
2445
|
+
if (serverItem._ts) {
|
|
2446
|
+
if (!maxTs || this.compareTimestamps(serverItem._ts, maxTs) > 0) {
|
|
2447
|
+
maxTs = serverItem._ts;
|
|
2448
|
+
}
|
|
2335
2449
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2450
|
+
if (localItem) {
|
|
2451
|
+
if (dirtyChange) {
|
|
2452
|
+
conflictsResolved++;
|
|
2453
|
+
const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem, "sync");
|
|
2454
|
+
dexieBatch.push(resolved);
|
|
2455
|
+
if (!resolved._deleted && !resolved._archived) {
|
|
2456
|
+
inMemSaveBatch.push(resolved);
|
|
2457
|
+
} else {
|
|
2458
|
+
inMemDeleteIds.push(serverItem._id);
|
|
2459
|
+
}
|
|
2344
2460
|
} else {
|
|
2345
|
-
|
|
2461
|
+
dexieBatch.push(serverItem);
|
|
2462
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
2463
|
+
inMemSaveBatch.push(serverItem);
|
|
2464
|
+
} else {
|
|
2465
|
+
inMemDeleteIds.push(serverItem._id);
|
|
2466
|
+
}
|
|
2346
2467
|
}
|
|
2347
2468
|
} else {
|
|
2348
2469
|
dexieBatch.push(serverItem);
|
|
2349
|
-
if (!serverItem._deleted) {
|
|
2470
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
2350
2471
|
inMemSaveBatch.push(serverItem);
|
|
2351
|
-
} else {
|
|
2352
|
-
inMemDeleteIds.push(serverItem._id);
|
|
2353
2472
|
}
|
|
2354
2473
|
}
|
|
2355
|
-
} else {
|
|
2356
|
-
dexieBatch.push(serverItem);
|
|
2357
|
-
if (!serverItem._deleted) {
|
|
2358
|
-
inMemSaveBatch.push(serverItem);
|
|
2359
|
-
}
|
|
2360
2474
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2475
|
+
if (dexieBatch.length > 0) {
|
|
2476
|
+
await this.dexieDb.saveMany(collectionName, dexieBatch);
|
|
2477
|
+
}
|
|
2478
|
+
if (inMemSaveBatch.length > 0) {
|
|
2479
|
+
this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
|
|
2480
|
+
}
|
|
2481
|
+
if (inMemDeleteIds.length > 0) {
|
|
2482
|
+
this.deps.writeToInMemBatch(collectionName, inMemDeleteIds.map((id) => ({ _id: id })), "delete");
|
|
2483
|
+
}
|
|
2484
|
+
for (const id of chunkIds) {
|
|
2485
|
+
allUpdatedIds.push(String(id));
|
|
2486
|
+
}
|
|
2370
2487
|
}
|
|
2371
2488
|
if (maxTs) {
|
|
2372
2489
|
await this.dexieDb.setSyncMeta(collectionName, maxTs);
|
|
@@ -2376,8 +2493,7 @@ class SyncEngine {
|
|
|
2376
2493
|
lastSyncTs: maxTs
|
|
2377
2494
|
});
|
|
2378
2495
|
}
|
|
2379
|
-
|
|
2380
|
-
return { conflictsResolved, maxTs, updatedIds };
|
|
2496
|
+
return { conflictsResolved, maxTs, updatedIds: allUpdatedIds };
|
|
2381
2497
|
}
|
|
2382
2498
|
compareTimestamps(a, b) {
|
|
2383
2499
|
const aT = typeof a === "object" && "t" in a ? a.t : 0;
|
|
@@ -2597,12 +2713,12 @@ class ServerUpdateHandler {
|
|
|
2597
2713
|
if (dirtyChange && !metaChanged) {
|
|
2598
2714
|
await this.dexieDb.clearDirtyChange(collection, serverItem._id);
|
|
2599
2715
|
}
|
|
2600
|
-
if (!serverItem._deleted) {
|
|
2716
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
2601
2717
|
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
2602
2718
|
}
|
|
2603
2719
|
} else {
|
|
2604
2720
|
await this.dexieDb.insert(collection, serverItem);
|
|
2605
|
-
if (!serverItem._deleted) {
|
|
2721
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
2606
2722
|
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
|
|
2607
2723
|
}
|
|
2608
2724
|
}
|
|
@@ -2628,7 +2744,7 @@ class ServerUpdateHandler {
|
|
|
2628
2744
|
}
|
|
2629
2745
|
const currentInMemState = { ...localItem, ...pendingChange.data };
|
|
2630
2746
|
const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
|
|
2631
|
-
if (!merged._deleted) {
|
|
2747
|
+
if (!merged._deleted && !merged._archived) {
|
|
2632
2748
|
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
2633
2749
|
}
|
|
2634
2750
|
return;
|
|
@@ -2640,7 +2756,7 @@ class ServerUpdateHandler {
|
|
|
2640
2756
|
if (metaChanged) {
|
|
2641
2757
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
2642
2758
|
}
|
|
2643
|
-
if (!merged._deleted) {
|
|
2759
|
+
if (!merged._deleted && !merged._archived) {
|
|
2644
2760
|
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
2645
2761
|
}
|
|
2646
2762
|
} else {
|
|
@@ -2648,7 +2764,7 @@ class ServerUpdateHandler {
|
|
|
2648
2764
|
return;
|
|
2649
2765
|
const merged = this.mergeLocalWithDelta(localItem, serverDelta);
|
|
2650
2766
|
await this.dexieDb.save(collection, serverDelta._id, merged);
|
|
2651
|
-
if (!merged._deleted) {
|
|
2767
|
+
if (!merged._deleted && !merged._archived) {
|
|
2652
2768
|
this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
|
|
2653
2769
|
} else {
|
|
2654
2770
|
this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete");
|
|
@@ -2918,6 +3034,8 @@ class SyncedDb {
|
|
|
2918
3034
|
unsubscribeServerUpdates;
|
|
2919
3035
|
cleanupNotifierCallbacks;
|
|
2920
3036
|
beforeUnloadHandler;
|
|
3037
|
+
defaultReturnDeleted;
|
|
3038
|
+
defaultReturnArchived;
|
|
2921
3039
|
onSync;
|
|
2922
3040
|
onConflictResolved;
|
|
2923
3041
|
onWsNotification;
|
|
@@ -2932,6 +3050,8 @@ class SyncedDb {
|
|
|
2932
3050
|
this.updaterId = Math.random().toString(36).substring(2, 15);
|
|
2933
3051
|
this.syncedDbInstanceId = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2934
3052
|
const windowId = config._testWindowId ?? this.getOrCreateWindowId();
|
|
3053
|
+
this.defaultReturnDeleted = config.returnDeleted ?? false;
|
|
3054
|
+
this.defaultReturnArchived = config.returnArchived ?? false;
|
|
2935
3055
|
this.onSync = config.onSync;
|
|
2936
3056
|
this.onConflictResolved = config.onConflictResolved;
|
|
2937
3057
|
this.onWsNotification = config.onWsNotification;
|
|
@@ -3140,8 +3260,15 @@ class SyncedDb {
|
|
|
3140
3260
|
await this.pendingChanges.recoverPendingWrites();
|
|
3141
3261
|
for (const [name] of this.collections) {
|
|
3142
3262
|
const data = await this.dexieDb.getAll(name);
|
|
3143
|
-
|
|
3144
|
-
|
|
3263
|
+
let writeIdx = 0;
|
|
3264
|
+
for (let i = 0;i < data.length; i++) {
|
|
3265
|
+
const item = data[i];
|
|
3266
|
+
if (!item._deleted && !item._archived) {
|
|
3267
|
+
data[writeIdx++] = item;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
data.length = writeIdx;
|
|
3271
|
+
this.inMemManager.initCollection(name, data);
|
|
3145
3272
|
const meta = await this.dexieDb.getSyncMeta(name);
|
|
3146
3273
|
if (meta) {
|
|
3147
3274
|
this.syncMetaCache.set(name, meta);
|
|
@@ -3231,41 +3358,119 @@ class SyncedDb {
|
|
|
3231
3358
|
async setOnline(online) {
|
|
3232
3359
|
await this.connectionManager.setOnline(online);
|
|
3233
3360
|
}
|
|
3234
|
-
async findById(collection, id) {
|
|
3361
|
+
async findById(collection, id, opts) {
|
|
3235
3362
|
this.assertCollection(collection);
|
|
3363
|
+
opts = this.resolveOpts(opts);
|
|
3364
|
+
if ((opts?.returnDeleted || opts?.returnArchived) && this.isOnline()) {
|
|
3365
|
+
try {
|
|
3366
|
+
const serverItem = await this.connectionManager.withRestTimeout(this.restInterface.findById(collection, id), "findById");
|
|
3367
|
+
if (serverItem) {
|
|
3368
|
+
await this.dexieDb.saveMany(collection, [serverItem]);
|
|
3369
|
+
if (!serverItem._deleted && !serverItem._archived) {
|
|
3370
|
+
this.inMemManager.writeBatch(collection, [serverItem], "upsert");
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
} catch {}
|
|
3374
|
+
}
|
|
3236
3375
|
const item = await this.dexieDb.getById(collection, id);
|
|
3237
|
-
if (!item
|
|
3376
|
+
if (!item)
|
|
3238
3377
|
return null;
|
|
3239
|
-
|
|
3378
|
+
if (!opts?.returnDeleted && item._deleted)
|
|
3379
|
+
return null;
|
|
3380
|
+
if (!opts?.returnArchived && item._archived)
|
|
3381
|
+
return null;
|
|
3382
|
+
let result = item;
|
|
3383
|
+
if (opts?.project) {
|
|
3384
|
+
const [projected] = applyQueryOpts([result], { project: opts.project });
|
|
3385
|
+
result = projected;
|
|
3386
|
+
}
|
|
3387
|
+
if (opts?.referToServer && this.isOnline()) {
|
|
3388
|
+
this.referToServerSync(collection);
|
|
3389
|
+
}
|
|
3390
|
+
return result;
|
|
3240
3391
|
}
|
|
3241
|
-
async findByIds(collection, ids) {
|
|
3392
|
+
async findByIds(collection, ids, opts) {
|
|
3242
3393
|
this.assertCollection(collection);
|
|
3394
|
+
opts = this.resolveOpts(opts);
|
|
3243
3395
|
if (ids.length === 0)
|
|
3244
3396
|
return [];
|
|
3397
|
+
if ((opts?.returnDeleted || opts?.returnArchived) && this.isOnline()) {
|
|
3398
|
+
try {
|
|
3399
|
+
const serverItems = await this.connectionManager.withRestTimeout(this.restInterface.findByIds(collection, ids), "findByIds");
|
|
3400
|
+
if (serverItems && serverItems.length > 0) {
|
|
3401
|
+
await this.dexieDb.saveMany(collection, serverItems);
|
|
3402
|
+
const toInMem = serverItems.filter((s) => !s._deleted && !s._archived);
|
|
3403
|
+
if (toInMem.length > 0) {
|
|
3404
|
+
this.inMemManager.writeBatch(collection, toInMem, "upsert");
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
} catch {}
|
|
3408
|
+
}
|
|
3245
3409
|
const items = await this.dexieDb.getByIds(collection, ids);
|
|
3246
3410
|
const results = [];
|
|
3247
3411
|
for (const item of items) {
|
|
3248
|
-
if (
|
|
3249
|
-
|
|
3250
|
-
|
|
3412
|
+
if (!item)
|
|
3413
|
+
continue;
|
|
3414
|
+
if (!opts?.returnDeleted && item._deleted)
|
|
3415
|
+
continue;
|
|
3416
|
+
if (!opts?.returnArchived && item._archived)
|
|
3417
|
+
continue;
|
|
3418
|
+
results.push(item);
|
|
3251
3419
|
}
|
|
3252
|
-
|
|
3420
|
+
const final = applyQueryOpts(results, opts);
|
|
3421
|
+
if (opts?.referToServer && this.isOnline()) {
|
|
3422
|
+
this.referToServerSync(collection);
|
|
3423
|
+
}
|
|
3424
|
+
return final;
|
|
3253
3425
|
}
|
|
3254
|
-
async findOne(collection, query) {
|
|
3426
|
+
async findOne(collection, query, opts) {
|
|
3255
3427
|
this.assertCollection(collection);
|
|
3428
|
+
opts = this.resolveOpts(opts);
|
|
3429
|
+
if ((opts?.returnDeleted || opts?.returnArchived) && this.isOnline()) {
|
|
3430
|
+
await this.syncCollectionForFind(collection, query, opts);
|
|
3431
|
+
}
|
|
3256
3432
|
const all = await this.dexieDb.getAll(collection);
|
|
3257
|
-
const active = all.filter((item) =>
|
|
3433
|
+
const active = all.filter((item) => {
|
|
3434
|
+
if (!opts?.returnDeleted && item._deleted)
|
|
3435
|
+
return false;
|
|
3436
|
+
if (!opts?.returnArchived && item._archived)
|
|
3437
|
+
return false;
|
|
3438
|
+
return true;
|
|
3439
|
+
});
|
|
3258
3440
|
const filtered = filterByQuery(active, query);
|
|
3259
|
-
if (filtered.length === 0)
|
|
3441
|
+
if (filtered.length === 0) {
|
|
3442
|
+
if (opts?.referToServer && this.isOnline()) {
|
|
3443
|
+
this.referToServerSync(collection, query);
|
|
3444
|
+
}
|
|
3260
3445
|
return null;
|
|
3261
|
-
|
|
3446
|
+
}
|
|
3447
|
+
const sorted = applyQueryOpts(filtered, { sort: opts?.sort, project: opts?.project });
|
|
3448
|
+
const result = sorted[0];
|
|
3449
|
+
if (opts?.referToServer && this.isOnline()) {
|
|
3450
|
+
this.referToServerSync(collection, query);
|
|
3451
|
+
}
|
|
3452
|
+
return result;
|
|
3262
3453
|
}
|
|
3263
|
-
async find(collection, query) {
|
|
3454
|
+
async find(collection, query, opts) {
|
|
3264
3455
|
this.assertCollection(collection);
|
|
3456
|
+
opts = this.resolveOpts(opts);
|
|
3457
|
+
if ((opts?.returnDeleted || opts?.returnArchived) && this.isOnline()) {
|
|
3458
|
+
await this.syncCollectionForFind(collection, query, opts);
|
|
3459
|
+
}
|
|
3265
3460
|
const all = await this.dexieDb.getAll(collection);
|
|
3266
|
-
const active = all.filter((item) =>
|
|
3461
|
+
const active = all.filter((item) => {
|
|
3462
|
+
if (!opts?.returnDeleted && item._deleted)
|
|
3463
|
+
return false;
|
|
3464
|
+
if (!opts?.returnArchived && item._archived)
|
|
3465
|
+
return false;
|
|
3466
|
+
return true;
|
|
3467
|
+
});
|
|
3267
3468
|
const filtered = query ? filterByQuery(active, query) : active;
|
|
3268
|
-
|
|
3469
|
+
const result = applyQueryOpts(filtered, opts);
|
|
3470
|
+
if (opts?.referToServer && this.isOnline()) {
|
|
3471
|
+
this.referToServerSync(collection, query);
|
|
3472
|
+
}
|
|
3473
|
+
return result;
|
|
3269
3474
|
}
|
|
3270
3475
|
async aggregate(collection, pipeline, opts) {
|
|
3271
3476
|
this.assertCollection(collection);
|
|
@@ -3274,6 +3479,67 @@ class SyncedDb {
|
|
|
3274
3479
|
}
|
|
3275
3480
|
return this.connectionManager.withRestTimeout(this.restInterface.aggregate(collection, pipeline, opts), "aggregate");
|
|
3276
3481
|
}
|
|
3482
|
+
async syncCollectionForFind(collection, query, opts) {
|
|
3483
|
+
const meta = this.syncMetaCache.get(collection);
|
|
3484
|
+
const timestamp = meta?.lastSyncTs || 0;
|
|
3485
|
+
try {
|
|
3486
|
+
const serverData = await this.connectionManager.withRestTimeout(this.restInterface.findNewer(collection, timestamp, query, {
|
|
3487
|
+
returnDeleted: opts?.returnDeleted || false,
|
|
3488
|
+
returnArchived: opts?.returnArchived || false
|
|
3489
|
+
}), "syncCollectionForFind");
|
|
3490
|
+
if (serverData.length > 0) {
|
|
3491
|
+
await this.syncEngine.processCollectionServerData(collection, serverData);
|
|
3492
|
+
}
|
|
3493
|
+
} catch {}
|
|
3494
|
+
}
|
|
3495
|
+
referToServerSync(collection, query) {
|
|
3496
|
+
const meta = this.syncMetaCache.get(collection);
|
|
3497
|
+
const timestamp = meta?.lastSyncTs || 0;
|
|
3498
|
+
this.connectionManager.withRestTimeout(this.restInterface.findNewer(collection, timestamp, query, { returnDeleted: true }), "referToServer").then(async (serverData) => {
|
|
3499
|
+
if (serverData.length > 0) {
|
|
3500
|
+
await this.syncEngine.processCollectionServerData(collection, serverData);
|
|
3501
|
+
}
|
|
3502
|
+
}).catch((err) => {
|
|
3503
|
+
console.error(`referToServer sync failed for ${collection}:`, err);
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
async ensureItemsAreLoaded(collection, ids, withDeleted) {
|
|
3507
|
+
this.assertCollection(collection);
|
|
3508
|
+
if (ids.length === 0)
|
|
3509
|
+
return;
|
|
3510
|
+
const localItems = await this.dexieDb.getByIds(collection, ids);
|
|
3511
|
+
const missingIds = [];
|
|
3512
|
+
for (let i = 0;i < ids.length; i++) {
|
|
3513
|
+
if (!localItems[i]) {
|
|
3514
|
+
missingIds.push(ids[i]);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
if (missingIds.length === 0)
|
|
3518
|
+
return;
|
|
3519
|
+
if (!this.isOnline())
|
|
3520
|
+
return;
|
|
3521
|
+
const serverItems = await this.connectionManager.withRestTimeout(this.restInterface.findByIds(collection, missingIds), "ensureItemsAreLoaded");
|
|
3522
|
+
if (!serverItems || serverItems.length === 0)
|
|
3523
|
+
return;
|
|
3524
|
+
const toSaveDexie = [];
|
|
3525
|
+
const toSaveInMem = [];
|
|
3526
|
+
for (const item of serverItems) {
|
|
3527
|
+
if (!item)
|
|
3528
|
+
continue;
|
|
3529
|
+
if (!withDeleted && item._deleted)
|
|
3530
|
+
continue;
|
|
3531
|
+
toSaveDexie.push(item);
|
|
3532
|
+
if (!item._deleted && !item._archived) {
|
|
3533
|
+
toSaveInMem.push(item);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
if (toSaveDexie.length > 0) {
|
|
3537
|
+
await this.dexieDb.saveMany(collection, toSaveDexie);
|
|
3538
|
+
}
|
|
3539
|
+
if (toSaveInMem.length > 0) {
|
|
3540
|
+
this.inMemManager.writeBatch(collection, toSaveInMem, "upsert");
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3277
3543
|
async save(collection, id, update) {
|
|
3278
3544
|
this.assertCollection(collection);
|
|
3279
3545
|
const existing = await this.dexieDb.getById(collection, id);
|
|
@@ -3288,7 +3554,7 @@ class SyncedDb {
|
|
|
3288
3554
|
this.pendingChanges.schedule(collection, id, newData, 0, "save");
|
|
3289
3555
|
const currentMem = this.inMemDb.getById(collection, id);
|
|
3290
3556
|
const merged = { ...currentMem || existing || { _id: id }, ...update };
|
|
3291
|
-
if (!existing?._deleted) {
|
|
3557
|
+
if (!existing?._deleted && !existing?._archived) {
|
|
3292
3558
|
this.inMemManager.writeBatch(collection, [merged], "upsert");
|
|
3293
3559
|
}
|
|
3294
3560
|
return merged;
|
|
@@ -3308,7 +3574,7 @@ class SyncedDb {
|
|
|
3308
3574
|
this.assertCollection(collection);
|
|
3309
3575
|
const id = data._id || new ObjectId2;
|
|
3310
3576
|
const existing = await this.dexieDb.getById(collection, id);
|
|
3311
|
-
if (existing && !existing._deleted) {
|
|
3577
|
+
if (existing && !existing._deleted && !existing._archived) {
|
|
3312
3578
|
console.warn(`SyncedDb.insert: Object ${String(id)} already exists in ${collection}, overwriting`);
|
|
3313
3579
|
}
|
|
3314
3580
|
const insertChanges = { ...data, _lastUpdaterId: this.updaterId };
|
|
@@ -3588,6 +3854,16 @@ class SyncedDb {
|
|
|
3588
3854
|
getOrCreateWindowId() {
|
|
3589
3855
|
return `shared-${this.tenant}`;
|
|
3590
3856
|
}
|
|
3857
|
+
resolveOpts(opts) {
|
|
3858
|
+
if (!this.defaultReturnDeleted && !this.defaultReturnArchived) {
|
|
3859
|
+
return opts;
|
|
3860
|
+
}
|
|
3861
|
+
return {
|
|
3862
|
+
...opts,
|
|
3863
|
+
returnDeleted: opts?.returnDeleted ?? this.defaultReturnDeleted,
|
|
3864
|
+
returnArchived: opts?.returnArchived ?? this.defaultReturnArchived
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3591
3867
|
assertCollection(name) {
|
|
3592
3868
|
if (!this.collections.has(name)) {
|
|
3593
3869
|
throw new Error(`Collection "${name}" not configured`);
|
|
@@ -5949,7 +6225,7 @@ var unpackr = new Unpackr({ structuredClone: true });
|
|
|
5949
6225
|
var pack2 = (x) => packr.pack(preprocessForPack(x));
|
|
5950
6226
|
var unpack2 = (x) => unpackr.unpack(x);
|
|
5951
6227
|
var DEFAULT_TIMEOUT = 5000;
|
|
5952
|
-
var DEFAULT_PROGRESS_CHUNK_SIZE =
|
|
6228
|
+
var DEFAULT_PROGRESS_CHUNK_SIZE = 16 * 1024;
|
|
5953
6229
|
|
|
5954
6230
|
class RestProxy {
|
|
5955
6231
|
endpoint;
|
|
@@ -6493,9 +6769,13 @@ class Ebus2ProxyServerUpdateNotifier {
|
|
|
6493
6769
|
}
|
|
6494
6770
|
}
|
|
6495
6771
|
export {
|
|
6772
|
+
sortItems,
|
|
6496
6773
|
resolveConflict,
|
|
6774
|
+
projectItem,
|
|
6497
6775
|
matchesQuery,
|
|
6498
6776
|
filterByQuery,
|
|
6777
|
+
applySkipLimit,
|
|
6778
|
+
applyQueryOpts,
|
|
6499
6779
|
SyncedDb,
|
|
6500
6780
|
RestProxy,
|
|
6501
6781
|
Ebus2ProxyServerUpdateNotifier,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AggregateOptions } from "mongodb";
|
|
2
2
|
import type { I_SyncedDb, SyncedDbConfig, WsNotificationInfo } from "../types/I_SyncedDb";
|
|
3
3
|
import type { MetaUpdateBroadcast } from "../types/I_DexieDb";
|
|
4
|
-
import type { QuerySpec, UpdateSpec, InsertSpec, BatchSpec } from "../types/I_RestInterface";
|
|
4
|
+
import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec } from "../types/I_RestInterface";
|
|
5
5
|
import type { Id, DbEntity } from "../types/DbEntity";
|
|
6
6
|
/**
|
|
7
7
|
* Main synchronized database implementation.
|
|
@@ -33,6 +33,8 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
33
33
|
private unsubscribeServerUpdates?;
|
|
34
34
|
private cleanupNotifierCallbacks?;
|
|
35
35
|
private beforeUnloadHandler?;
|
|
36
|
+
private readonly defaultReturnDeleted;
|
|
37
|
+
private readonly defaultReturnArchived;
|
|
36
38
|
private readonly onSync?;
|
|
37
39
|
private readonly onConflictResolved?;
|
|
38
40
|
private readonly onWsNotification?;
|
|
@@ -52,11 +54,22 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
52
54
|
forceOffline(forced: boolean): void;
|
|
53
55
|
isForcedOffline(): boolean;
|
|
54
56
|
setOnline(online: boolean): Promise<void>;
|
|
55
|
-
findById<T extends DbEntity>(collection: string, id: Id): Promise<T | null>;
|
|
56
|
-
findByIds<T extends DbEntity>(collection: string, ids: Id[]): Promise<T[]>;
|
|
57
|
-
findOne<T extends DbEntity>(collection: string, query: QuerySpec<T
|
|
58
|
-
find<T extends DbEntity>(collection: string, query?: QuerySpec<T
|
|
57
|
+
findById<T extends DbEntity>(collection: string, id: Id, opts?: QueryOpts): Promise<T | null>;
|
|
58
|
+
findByIds<T extends DbEntity>(collection: string, ids: Id[], opts?: QueryOpts): Promise<T[]>;
|
|
59
|
+
findOne<T extends DbEntity>(collection: string, query: QuerySpec<T>, opts?: QueryOpts): Promise<T | null>;
|
|
60
|
+
find<T extends DbEntity>(collection: string, query?: QuerySpec<T>, opts?: QueryOpts): Promise<T[]>;
|
|
59
61
|
aggregate<T>(collection: string, pipeline: object[], opts?: AggregateOptions): Promise<T[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Sync a collection from server before querying locally.
|
|
64
|
+
* Used when returnDeleted/returnArchived is specified on find/findOne.
|
|
65
|
+
*/
|
|
66
|
+
private syncCollectionForFind;
|
|
67
|
+
/**
|
|
68
|
+
* Fire-and-forget background sync for a single collection.
|
|
69
|
+
* Calls findNewer with the last sync timestamp and processes results.
|
|
70
|
+
*/
|
|
71
|
+
private referToServerSync;
|
|
72
|
+
ensureItemsAreLoaded(collection: string, ids: string[], withDeleted?: boolean): Promise<void>;
|
|
60
73
|
save<T extends DbEntity>(collection: string, id: Id, update: Partial<T>): Promise<T>;
|
|
61
74
|
upsert<T extends DbEntity>(collection: string, query: QuerySpec<T>, update: UpdateSpec<T>): Promise<T>;
|
|
62
75
|
insert<T extends DbEntity>(collection: string, data: InsertSpec<T>): Promise<T>;
|
|
@@ -96,5 +109,10 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
96
109
|
* so we use a constant value to ensure all tabs compete for the same lock.
|
|
97
110
|
*/
|
|
98
111
|
private getOrCreateWindowId;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve effective QueryOpts by merging global defaults with per-call opts.
|
|
114
|
+
* Per-call values take precedence over global defaults.
|
|
115
|
+
*/
|
|
116
|
+
private resolveOpts;
|
|
99
117
|
private assertCollection;
|
|
100
118
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - Processing incoming data with conflict resolution
|
|
7
7
|
* - Uploading dirty items to server
|
|
8
8
|
*/
|
|
9
|
+
import type { LocalDbEntity } from "../../types/DbEntity";
|
|
9
10
|
import type { I_SyncEngine, SyncEngineConfig } from "../types/managers";
|
|
10
11
|
import type { UploadResult } from "../types/internal";
|
|
11
12
|
export declare class SyncEngine implements I_SyncEngine {
|
|
@@ -30,6 +31,15 @@ export declare class SyncEngine implements I_SyncEngine {
|
|
|
30
31
|
* Upload dirty items for a specific collection.
|
|
31
32
|
*/
|
|
32
33
|
uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Process incoming server data for a single collection.
|
|
36
|
+
* Used by referToServer to process findNewer results.
|
|
37
|
+
*/
|
|
38
|
+
processCollectionServerData(collectionName: string, serverData: LocalDbEntity[]): Promise<{
|
|
39
|
+
updatedIds: string[];
|
|
40
|
+
}>;
|
|
41
|
+
/** Max items to process per batch in processIncomingServerData */
|
|
42
|
+
private static readonly SYNC_BATCH_SIZE;
|
|
33
43
|
private processIncomingServerData;
|
|
34
44
|
private compareTimestamps;
|
|
35
45
|
private resolveCollectionConflict;
|
|
@@ -242,6 +242,10 @@ export interface I_SyncEngine {
|
|
|
242
242
|
uploadDirtyItems(calledFrom?: string): Promise<UploadResult>;
|
|
243
243
|
/** Upload dirty items for a specific collection. */
|
|
244
244
|
uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
|
|
245
|
+
/** Process incoming server data for a single collection (used by referToServer). */
|
|
246
|
+
processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[]): Promise<{
|
|
247
|
+
updatedIds: string[];
|
|
248
|
+
}>;
|
|
245
249
|
}
|
|
246
250
|
export interface ServerUpdateHandlerCallbacks {
|
|
247
251
|
onWsNotification?: (info: WsNotificationInfo) => void;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export type { RestProxyConfig } from "./db/RestProxy";
|
|
|
8
8
|
export { Ebus2ProxyServerUpdateNotifier } from "./db/Ebus2ProxyServerUpdateNotifier";
|
|
9
9
|
export type { Ebus2ProxyServerUpdateNotifierConfig } from "./db/Ebus2ProxyServerUpdateNotifier";
|
|
10
10
|
export { resolveConflict } from "./utils/conflictResolution";
|
|
11
|
-
export { filterByQuery, matchesQuery } from "./utils/localQuery";
|
|
11
|
+
export { filterByQuery, matchesQuery, sortItems, applySkipLimit, projectItem, applyQueryOpts } from "./utils/localQuery";
|
|
@@ -5,7 +5,7 @@ export type CollectionUpdateResult = Types.CollectionUpdateResult;
|
|
|
5
5
|
export type Obj = {
|
|
6
6
|
[key: string]: any;
|
|
7
7
|
};
|
|
8
|
-
export type QuerySpec<T> = Partial<Record<keyof T | "_deleted" | "_blocked" | "_ts" | "_id", any>>;
|
|
8
|
+
export type QuerySpec<T> = Partial<Record<keyof T | "_deleted" | "_archived" | "_blocked" | "_ts" | "_id", any>>;
|
|
9
9
|
export type Projection = SchemaMember<any, Document | number | boolean | any>;
|
|
10
10
|
export type QueryOpts = Partial<{
|
|
11
11
|
project: Projection;
|
|
@@ -16,6 +16,10 @@ export type QueryOpts = Partial<{
|
|
|
16
16
|
readPreference: ReadPreference;
|
|
17
17
|
/** Če je true, vrne tudi soft-deleted objekte (z _deleted poljem) */
|
|
18
18
|
returnDeleted: boolean;
|
|
19
|
+
/** Če je true, vrne tudi archived objekte (z _archived poljem) */
|
|
20
|
+
returnArchived: boolean;
|
|
21
|
+
/** Če je true, vrne lokalne rezultate in nato v ozadju sinhronizira s serverjem */
|
|
22
|
+
referToServer: boolean;
|
|
19
23
|
}>;
|
|
20
24
|
export type KeyOf<T> = keyof T | "$bit" | "$set" | "$inc" | "$currentDate" | "$min" | "$max" | "$mul" | "$rename" | "$setOnInsert" | "$unset" | "$pull" | "$push" | "$pop" | "$addToSet" | "$pushAll" | "_rev" | "_ts" | "_csq" | "_deleted";
|
|
21
25
|
export type InsertKeyOf<T> = keyof T | "_rev" | "_ts" | "_csq" | "_deleted";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AggregateOptions } from "mongodb";
|
|
2
2
|
import type { Id, DbEntity, LocalDbEntity } from "./DbEntity";
|
|
3
|
-
import type { QuerySpec, UpdateSpec, InsertSpec, BatchSpec, I_RestInterface, CollectionUpdateRequest, CollectionUpdateResult, GetNewerSpec } from "./I_RestInterface";
|
|
3
|
+
import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec, I_RestInterface, CollectionUpdateRequest, CollectionUpdateResult, GetNewerSpec } from "./I_RestInterface";
|
|
4
4
|
import type { I_DexieDb } from "./I_DexieDb";
|
|
5
5
|
import type { I_InMemDb } from "./I_InMemDb";
|
|
6
6
|
import type { I_ServerUpdateNotifier } from "./I_ServerUpdateNotifier";
|
|
@@ -206,7 +206,7 @@ export interface CollectionSyncStats {
|
|
|
206
206
|
receivedCount: number;
|
|
207
207
|
/** Number of dirty items sent to server for this collection */
|
|
208
208
|
sentCount: number;
|
|
209
|
-
/**
|
|
209
|
+
/** @deprecated Use receivedCount instead. Will be empty array in future streaming mode. */
|
|
210
210
|
receivedItems: LocalDbEntity[];
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
@@ -351,6 +351,20 @@ export interface SyncedDbConfig {
|
|
|
351
351
|
* whenever objects are written to in-mem. Default: false.
|
|
352
352
|
*/
|
|
353
353
|
useObjectMetadata?: boolean;
|
|
354
|
+
/**
|
|
355
|
+
* Global default for returning soft-deleted items in find operations.
|
|
356
|
+
* When true, all find* methods include deleted items unless overridden per-call.
|
|
357
|
+
* Per-call opts.returnDeleted takes precedence over this setting.
|
|
358
|
+
* Default: false
|
|
359
|
+
*/
|
|
360
|
+
returnDeleted?: boolean;
|
|
361
|
+
/**
|
|
362
|
+
* Global default for returning archived items in find operations.
|
|
363
|
+
* When true, all find* methods include archived items unless overridden per-call.
|
|
364
|
+
* Per-call opts.returnArchived takes precedence over this setting.
|
|
365
|
+
* Default: false
|
|
366
|
+
*/
|
|
367
|
+
returnArchived?: boolean;
|
|
354
368
|
/**
|
|
355
369
|
* Enable sync on wake from sleep for leader tab.
|
|
356
370
|
* When enabled, detects wake via pageshow/focus/visibilitychange events
|
|
@@ -436,13 +450,18 @@ export interface I_SyncedDb {
|
|
|
436
450
|
*/
|
|
437
451
|
isForcedOffline(): boolean;
|
|
438
452
|
/** Poišče objekt po ID-ju */
|
|
439
|
-
findById<T extends DbEntity>(collection: string, id: Id): Promise<T | null>;
|
|
453
|
+
findById<T extends DbEntity>(collection: string, id: Id, opts?: QueryOpts): Promise<T | null>;
|
|
440
454
|
/** Poišče objekte po ID-jih */
|
|
441
|
-
findByIds<T extends DbEntity>(collection: string, ids: Id[]): Promise<T[]>;
|
|
455
|
+
findByIds<T extends DbEntity>(collection: string, ids: Id[], opts?: QueryOpts): Promise<T[]>;
|
|
442
456
|
/** Poišče prvi objekt, ki ustreza poizvedbi */
|
|
443
|
-
findOne<T extends DbEntity>(collection: string, query: QuerySpec<T
|
|
457
|
+
findOne<T extends DbEntity>(collection: string, query: QuerySpec<T>, opts?: QueryOpts): Promise<T | null>;
|
|
444
458
|
/** Poišče vse objekte, ki ustrezajo poizvedbi */
|
|
445
|
-
find<T extends DbEntity>(collection: string, query?: QuerySpec<T
|
|
459
|
+
find<T extends DbEntity>(collection: string, query?: QuerySpec<T>, opts?: QueryOpts): Promise<T[]>;
|
|
460
|
+
/**
|
|
461
|
+
* Preveri prisotnost podanih ID-jev v lokalni bazi.
|
|
462
|
+
* Manjkajoče naloži s serverja (če je online).
|
|
463
|
+
*/
|
|
464
|
+
ensureItemsAreLoaded(collection: string, ids: string[], withDeleted?: boolean): Promise<void>;
|
|
446
465
|
/** Izvede agregacijo na serverju (offline vrne []) */
|
|
447
466
|
aggregate<T>(collection: string, pipeline: object[], opts?: AggregateOptions): Promise<T[]>;
|
|
448
467
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QuerySpec } from "../types/I_RestInterface";
|
|
1
|
+
import type { QuerySpec, QueryOpts, Projection } from "../types/I_RestInterface";
|
|
2
2
|
import type { DbEntity } from "../types/DbEntity";
|
|
3
3
|
/**
|
|
4
4
|
* Preveri, ali objekt ustreza MongoDB-style query specifikaciji
|
|
@@ -9,3 +9,22 @@ export declare function matchesQuery<T extends DbEntity>(item: T, query: QuerySp
|
|
|
9
9
|
* Filtriraj array objektov po query
|
|
10
10
|
*/
|
|
11
11
|
export declare function filterByQuery<T extends DbEntity>(items: T[], query?: QuerySpec<T>): T[];
|
|
12
|
+
/**
|
|
13
|
+
* Sortiraj array objektov po MongoDB-style sort specifikaciji.
|
|
14
|
+
* Podpira multi-field sort: { name: 1, age: -1 }
|
|
15
|
+
*/
|
|
16
|
+
export declare function sortItems<T>(items: T[], sort: Record<string, -1 | 1>): T[];
|
|
17
|
+
/**
|
|
18
|
+
* Uporabi skip in limit na array.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applySkipLimit<T>(items: T[], skip?: number, limit?: number): T[];
|
|
21
|
+
/**
|
|
22
|
+
* Uporabi projekcijo na en objekt.
|
|
23
|
+
* Podpira include mode ({ field: true }) in exclude mode ({ field: false }).
|
|
24
|
+
* _id je vedno vključen, razen če je eksplicitno izključen.
|
|
25
|
+
*/
|
|
26
|
+
export declare function projectItem<T>(item: T, project: Projection): Partial<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Uporabi QueryOpts na array: sort → skip → limit → project
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyQueryOpts<T>(items: T[], opts?: QueryOpts): T[];
|