sgerp-frontend-lib 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/connection-manager.d.ts +48 -0
- package/dist/connection-manager.d.ts.map +1 -0
- package/dist/connection-manager.js +156 -0
- package/dist/index.js +20 -2
- package/dist/locales/locale-server.d.ts +10 -0
- package/dist/locales/locale-server.d.ts.map +1 -0
- package/dist/locales/locale-server.js +49 -0
- package/dist/locales/locale.d.ts +23 -0
- package/dist/locales/locale.d.ts.map +1 -0
- package/dist/locales/locale.js +145 -0
- package/dist/locales/locale_en.d.ts.map +1 -1
- package/dist/locales/locale_en.js +84 -9
- package/dist/locales/locale_ja.d.ts.map +1 -1
- package/dist/locales/locale_ja.js +84 -9
- package/dist/locales/locale_ms.d.ts.map +1 -1
- package/dist/locales/locale_ms.js +84 -9
- package/dist/locales/useLocalization.d.ts +11 -0
- package/dist/locales/useLocalization.d.ts.map +1 -0
- package/dist/locales/useLocalization.js +64 -0
- package/dist/locales.d.ts +228 -0
- package/dist/locales.d.ts.map +1 -0
- package/dist/locales.js +229 -0
- package/dist/sgerp/api/client.d.ts +45 -0
- package/dist/sgerp/api/client.d.ts.map +1 -0
- package/dist/sgerp/api/client.js +141 -0
- package/dist/sgerp/client.js +341 -0
- package/dist/sgerp/collection.d.ts +391 -0
- package/dist/sgerp/collection.d.ts.map +1 -0
- package/dist/sgerp/collection.js +756 -0
- package/dist/sgerp/collections/account/user.d.ts +8 -0
- package/dist/sgerp/collections/account/user.d.ts.map +1 -0
- package/dist/sgerp/collections/account/user.js +10 -0
- package/dist/sgerp/collections/masstransit/building.d.ts +26 -0
- package/dist/sgerp/collections/masstransit/building.d.ts.map +1 -0
- package/dist/sgerp/collections/masstransit/building.js +53 -0
- package/dist/sgerp/collections/masstransit/transitstop.d.ts +15 -0
- package/dist/sgerp/collections/masstransit/transitstop.d.ts.map +1 -0
- package/dist/sgerp/collections/masstransit/transitstop.js +54 -0
- package/dist/sgerp/collections/masstransit/transitstopset.d.ts +10 -0
- package/dist/sgerp/collections/masstransit/transitstopset.d.ts.map +1 -0
- package/dist/sgerp/collections/masstransit/transitstopset.js +10 -0
- package/dist/sgerp/collections/sharing/organization-project.d.ts +11 -0
- package/dist/sgerp/collections/sharing/organization-project.d.ts.map +1 -0
- package/dist/sgerp/collections/sharing/organization-project.js +13 -0
- package/dist/sgerp/collections/sharing/organization.d.ts +11 -0
- package/dist/sgerp/collections/sharing/organization.d.ts.map +1 -0
- package/dist/sgerp/collections/sharing/organization.js +13 -0
- package/dist/sgerp/collections/sharing/pricing.d.ts +18 -0
- package/dist/sgerp/collections/sharing/pricing.d.ts.map +1 -0
- package/dist/sgerp/collections/sharing/pricing.js +23 -0
- package/dist/sgerp/collections/sharing/project-member.d.ts +8 -0
- package/dist/sgerp/collections/sharing/project-member.d.ts.map +1 -0
- package/dist/sgerp/collections/sharing/project-member.js +10 -0
- package/dist/sgerp/collections/sharing/project.d.ts +8 -0
- package/dist/sgerp/collections/sharing/project.d.ts.map +1 -0
- package/dist/sgerp/collections/sharing/project.js +10 -0
- package/dist/sgerp/collections/simulation/booking.d.ts +11 -0
- package/dist/sgerp/collections/simulation/booking.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/booking.js +13 -0
- package/dist/sgerp/collections/simulation/dataset.d.ts +14 -0
- package/dist/sgerp/collections/simulation/dataset.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/dataset.js +17 -0
- package/dist/sgerp/collections/simulation/driver.d.ts +12 -0
- package/dist/sgerp/collections/simulation/driver.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/driver.js +40 -0
- package/dist/sgerp/collections/simulation/geofence.d.ts +24 -0
- package/dist/sgerp/collections/simulation/geofence.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/geofence.js +66 -0
- package/dist/sgerp/collections/simulation/mapboxtoken.d.ts +18 -0
- package/dist/sgerp/collections/simulation/mapboxtoken.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/mapboxtoken.js +24 -0
- package/dist/sgerp/collections/simulation/node.js +13 -0
- package/dist/sgerp/collections/simulation/operationslocation.d.ts +28 -0
- package/dist/sgerp/collections/simulation/operationslocation.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/operationslocation.js +64 -0
- package/dist/sgerp/collections/simulation/operationslocationgroup.d.ts +12 -0
- package/dist/sgerp/collections/simulation/operationslocationgroup.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/operationslocationgroup.js +24 -0
- package/dist/sgerp/collections/simulation/routingprofile.d.ts +10 -0
- package/dist/sgerp/collections/simulation/routingprofile.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/routingprofile.js +10 -0
- package/dist/sgerp/collections/simulation/simulation.d.ts +12 -0
- package/dist/sgerp/collections/simulation/simulation.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/simulation.js +35 -0
- package/dist/sgerp/collections/simulation/simulationprocessor.d.ts +19 -0
- package/dist/sgerp/collections/simulation/simulationprocessor.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/simulationprocessor.js +27 -0
- package/dist/sgerp/collections/simulation/vehicle.js +13 -0
- package/dist/sgerp/collections/simulation/vehicletype.d.ts +11 -0
- package/dist/sgerp/collections/simulation/vehicletype.d.ts.map +1 -0
- package/dist/sgerp/collections/simulation/vehicletype.js +13 -0
- package/dist/sgerp/collections/sms_notify/smsscheduled.d.ts +11 -0
- package/dist/sgerp/collections/sms_notify/smsscheduled.d.ts.map +1 -0
- package/dist/sgerp/collections/sms_notify/smsscheduled.js +13 -0
- package/dist/sgerp/collections/transportation/passenger.d.ts +8 -0
- package/dist/sgerp/collections/transportation/passenger.d.ts.map +1 -0
- package/dist/sgerp/collections/transportation/passenger.js +10 -0
- package/dist/sgerp/collections/transportation/ticket.d.ts +11 -0
- package/dist/sgerp/collections/transportation/ticket.d.ts.map +1 -0
- package/dist/sgerp/collections/transportation/ticket.js +13 -0
- package/dist/sgerp/collections/transportation/transaction.d.ts +11 -0
- package/dist/sgerp/collections/transportation/transaction.d.ts.map +1 -0
- package/dist/sgerp/collections/transportation/transaction.js +13 -0
- package/dist/sgerp/constants/bulkoperations-enums.d.ts +71 -0
- package/dist/sgerp/constants/bulkoperations-enums.d.ts.map +1 -0
- package/dist/sgerp/constants/bulkoperations-enums.js +99 -0
- package/dist/sgerp/constants/messages-enums.d.ts +47 -0
- package/dist/sgerp/constants/messages-enums.d.ts.map +1 -0
- package/dist/sgerp/constants/messages-enums.js +67 -0
- package/dist/sgerp/constants/scheduling-enums.d.ts +42 -0
- package/dist/sgerp/constants/scheduling-enums.d.ts.map +1 -0
- package/dist/sgerp/constants/scheduling-enums.js +61 -0
- package/dist/sgerp/constants/simulation-enums.d.ts +93 -0
- package/dist/sgerp/constants/simulation-enums.d.ts.map +1 -0
- package/dist/sgerp/constants/simulation-enums.js +147 -0
- package/dist/sgerp/constants/solver-strategies.d.ts +75 -0
- package/dist/sgerp/constants/solver-strategies.d.ts.map +1 -0
- package/dist/sgerp/constants/solver-strategies.js +26 -0
- package/dist/sgerp/context/sgerp-context.js +134 -0
- package/dist/sgerp/domains.d.ts +47 -0
- package/dist/sgerp/domains.d.ts.map +1 -0
- package/dist/sgerp/domains.js +45 -0
- package/dist/sgerp/hooks/use-collection.js +122 -0
- package/dist/sgerp/hooks/use-live-updates.js +16 -13
- package/dist/sgerp/index.js +153 -0
- package/dist/sgerp/map-states/operations-location-state.d.ts +12 -0
- package/dist/sgerp/map-states/operations-location-state.d.ts.map +1 -0
- package/dist/sgerp/map-states/operations-location-state.js +86 -0
- package/dist/sgerp/routing/index.d.ts +51 -0
- package/dist/sgerp/routing/index.d.ts.map +1 -0
- package/dist/sgerp/routing/index.js +373 -0
- package/dist/sgerp/simulation-logic/fetchUtils.d.ts +8 -8
- package/dist/sgerp/simulation-logic/fetchUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/fetchUtils.js +226 -0
- package/dist/sgerp/simulation-logic/index.js +27 -7
- package/dist/sgerp/simulation-logic/manualEditUtils.d.ts +1 -1
- package/dist/sgerp/simulation-logic/manualEditUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/manualEditUtils.js +69 -0
- package/dist/sgerp/simulation-logic/mapUtils.js +58 -0
- package/dist/sgerp/simulation-logic/optimisticUpdateUtils.js +8 -5
- package/dist/sgerp/simulation-logic/referenceUtils.d.ts +3 -3
- package/dist/sgerp/simulation-logic/referenceUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/referenceUtils.js +110 -0
- package/dist/sgerp/simulation-logic/routeCalculationUtils.d.ts +3 -3
- package/dist/sgerp/simulation-logic/routeCalculationUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/routeCalculationUtils.js +43 -0
- package/dist/sgerp/simulation-logic/timeShiftUtils.d.ts +1 -1
- package/dist/sgerp/simulation-logic/timeShiftUtils.d.ts.map +1 -1
- package/dist/sgerp/simulation-logic/timeShiftUtils.js +63 -0
- package/dist/sgerp/types/account/user.d.ts +38 -0
- package/dist/sgerp/types/account/user.d.ts.map +1 -0
- package/dist/sgerp/types/account/user.js +2 -0
- package/dist/sgerp/types/bulkoperations/index.d.ts +37 -0
- package/dist/sgerp/types/bulkoperations/index.d.ts.map +1 -0
- package/dist/sgerp/types/bulkoperations/index.js +35 -0
- package/dist/sgerp/types/bulkoperations/vehicle-upload.d.ts +335 -0
- package/dist/sgerp/types/bulkoperations/vehicle-upload.d.ts.map +1 -0
- package/dist/sgerp/types/bulkoperations/vehicle-upload.js +49 -0
- package/dist/sgerp/types/config.d.ts +25 -0
- package/dist/sgerp/types/config.d.ts.map +1 -0
- package/dist/sgerp/types/config.js +2 -0
- package/dist/sgerp/types/geojson.d.ts +45 -0
- package/dist/sgerp/types/geojson.d.ts.map +1 -0
- package/dist/sgerp/types/geojson.js +6 -0
- package/dist/sgerp/types/map.d.ts +24 -0
- package/dist/sgerp/types/map.d.ts.map +1 -0
- package/dist/sgerp/types/map.js +2 -0
- package/dist/sgerp/types/masstransit/building.d.ts +24 -0
- package/dist/sgerp/types/masstransit/building.d.ts.map +1 -0
- package/dist/sgerp/types/masstransit/building.js +2 -0
- package/dist/sgerp/types/masstransit/transitstop.d.ts +35 -0
- package/dist/sgerp/types/masstransit/transitstop.d.ts.map +1 -0
- package/dist/sgerp/types/masstransit/transitstop.js +2 -0
- package/dist/sgerp/types/masstransit/transitstopset.d.ts +14 -0
- package/dist/sgerp/types/masstransit/transitstopset.d.ts.map +1 -0
- package/dist/sgerp/types/masstransit/transitstopset.js +2 -0
- package/dist/sgerp/types/sharing/organization-project.d.ts +14 -0
- package/dist/sgerp/types/sharing/organization-project.d.ts.map +1 -0
- package/dist/sgerp/types/sharing/organization-project.js +2 -0
- package/dist/sgerp/types/sharing/organization.d.ts +14 -0
- package/dist/sgerp/types/sharing/organization.d.ts.map +1 -0
- package/dist/sgerp/types/sharing/organization.js +2 -0
- package/dist/sgerp/types/sharing/pricing.d.ts +98 -0
- package/dist/sgerp/types/sharing/pricing.d.ts.map +1 -0
- package/dist/sgerp/types/sharing/pricing.js +2 -0
- package/dist/sgerp/types/sharing/project-member.d.ts +15 -0
- package/dist/sgerp/types/sharing/project-member.d.ts.map +1 -0
- package/dist/sgerp/types/sharing/project-member.js +2 -0
- package/dist/sgerp/types/sharing/project.d.ts +16 -0
- package/dist/sgerp/types/sharing/project.d.ts.map +1 -0
- package/dist/sgerp/types/sharing/project.js +2 -0
- package/dist/sgerp/types/simulation/booking.d.ts +128 -0
- package/dist/sgerp/types/simulation/booking.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/booking.js +24 -0
- package/dist/sgerp/types/simulation/calculation-params.d.ts +20 -0
- package/dist/sgerp/types/simulation/calculation-params.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/calculation-params.js +2 -0
- package/dist/sgerp/types/simulation/dataset.d.ts +16 -0
- package/dist/sgerp/types/simulation/dataset.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/dataset.js +2 -0
- package/dist/sgerp/types/simulation/driver.d.ts +19 -0
- package/dist/sgerp/types/simulation/driver.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/driver.js +2 -0
- package/dist/sgerp/types/simulation/geofence.d.ts +19 -0
- package/dist/sgerp/types/simulation/geofence.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/geofence.js +2 -0
- package/dist/sgerp/types/simulation/logistics-api-settings.d.ts +98 -0
- package/dist/sgerp/types/simulation/logistics-api-settings.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/logistics-api-settings.js +104 -0
- package/dist/sgerp/types/simulation/mapboxtoken.d.ts +13 -0
- package/dist/sgerp/types/simulation/mapboxtoken.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/mapboxtoken.js +2 -0
- package/dist/sgerp/types/simulation/node.js +5 -2
- package/dist/sgerp/types/simulation/operationslocation.d.ts +40 -0
- package/dist/sgerp/types/simulation/operationslocation.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/operationslocation.js +2 -0
- package/dist/sgerp/types/simulation/operationslocationgroup.d.ts +11 -0
- package/dist/sgerp/types/simulation/operationslocationgroup.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/operationslocationgroup.js +2 -0
- package/dist/sgerp/types/simulation/pricing.d.ts +171 -0
- package/dist/sgerp/types/simulation/pricing.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/pricing.js +2 -0
- package/dist/sgerp/types/simulation/route-cost-modification.d.ts +35 -0
- package/dist/sgerp/types/simulation/route-cost-modification.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/route-cost-modification.js +5 -0
- package/dist/sgerp/types/simulation/routing-engine-settings.d.ts +42 -0
- package/dist/sgerp/types/simulation/routing-engine-settings.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/routing-engine-settings.js +35 -0
- package/dist/sgerp/types/simulation/routingprofile.d.ts +32 -0
- package/dist/sgerp/types/simulation/routingprofile.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/routingprofile.js +37 -0
- package/dist/sgerp/types/simulation/simulation.d.ts +120 -0
- package/dist/sgerp/types/simulation/simulation.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/simulation.js +2 -0
- package/dist/sgerp/types/simulation/simulationprocessor.d.ts +31 -0
- package/dist/sgerp/types/simulation/simulationprocessor.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/simulationprocessor.js +2 -0
- package/dist/sgerp/types/simulation/vehicle.js +2 -1
- package/dist/sgerp/types/simulation/vehicletype.d.ts +33 -0
- package/dist/sgerp/types/simulation/vehicletype.d.ts.map +1 -0
- package/dist/sgerp/types/simulation/vehicletype.js +2 -0
- package/dist/sgerp/types/sms_notify/smsscheduled.d.ts +36 -0
- package/dist/sgerp/types/sms_notify/smsscheduled.d.ts.map +1 -0
- package/dist/sgerp/types/sms_notify/smsscheduled.js +6 -0
- package/dist/sgerp/types/transportation/passenger.d.ts +49 -0
- package/dist/sgerp/types/transportation/passenger.d.ts.map +1 -0
- package/dist/sgerp/types/transportation/passenger.js +13 -0
- package/dist/sgerp/types/transportation/ticket.d.ts +31 -0
- package/dist/sgerp/types/transportation/ticket.d.ts.map +1 -0
- package/dist/sgerp/types/transportation/ticket.js +2 -0
- package/dist/sgerp/types/transportation/transaction.d.ts +71 -0
- package/dist/sgerp/types/transportation/transaction.d.ts.map +1 -0
- package/dist/sgerp/types/transportation/transaction.js +54 -0
- package/dist/sgerp/utils/bookingPayload.d.ts +80 -0
- package/dist/sgerp/utils/bookingPayload.d.ts.map +1 -0
- package/dist/sgerp/utils/bookingPayload.js +44 -0
- package/dist/sgerp/utils/color.d.ts +30 -0
- package/dist/sgerp/utils/color.d.ts.map +1 -0
- package/dist/sgerp/utils/color.js +117 -0
- package/dist/sgerp/utils/enum-helpers.d.ts +41 -0
- package/dist/sgerp/utils/enum-helpers.d.ts.map +1 -0
- package/dist/sgerp/utils/enum-helpers.js +54 -0
- package/dist/sgerp/utils/routeUtils.d.ts +1 -1
- package/dist/sgerp/utils/routeUtils.d.ts.map +1 -1
- package/dist/sgerp/utils/routeUtils.js +171 -0
- package/package.json +1 -1
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Collection = void 0;
|
|
7
|
+
const underscore_1 = __importDefault(require("underscore"));
|
|
8
|
+
/**
|
|
9
|
+
* Base Collection class inspired by Backbone.js
|
|
10
|
+
* Provides indexing, pagination, and utility methods for model arrays
|
|
11
|
+
* Uses underscore.js for collection operations
|
|
12
|
+
* Supports React integration via change listeners
|
|
13
|
+
*/
|
|
14
|
+
class Collection {
|
|
15
|
+
constructor(fetchFn, models, meta) {
|
|
16
|
+
this._models = [];
|
|
17
|
+
this._index = new Map();
|
|
18
|
+
this._meta = {
|
|
19
|
+
has_more: false,
|
|
20
|
+
next: null,
|
|
21
|
+
next_offset: null,
|
|
22
|
+
};
|
|
23
|
+
this._offset = 0;
|
|
24
|
+
this._limit = 20;
|
|
25
|
+
this.lastParams = {};
|
|
26
|
+
this._listeners = new Set();
|
|
27
|
+
this._dependentCalls = [];
|
|
28
|
+
// Batched getAsync state
|
|
29
|
+
this._batchQueue = new Map();
|
|
30
|
+
this._batchTimeout = null;
|
|
31
|
+
this._isBatchFetching = false;
|
|
32
|
+
this._cacheHits = new Set();
|
|
33
|
+
this.fetchFn = fetchFn;
|
|
34
|
+
if (models) {
|
|
35
|
+
this.reset(models, meta);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Register a dependent call configuration
|
|
40
|
+
* This will automatically fetch related data after the main collection is loaded
|
|
41
|
+
*/
|
|
42
|
+
registerDependentCall(config) {
|
|
43
|
+
this._dependentCalls.push(config);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to collection changes (for React integration)
|
|
47
|
+
*/
|
|
48
|
+
onChange(listener) {
|
|
49
|
+
this._listeners.add(listener);
|
|
50
|
+
// Return unsubscribe function
|
|
51
|
+
return () => {
|
|
52
|
+
this._listeners.delete(listener);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Notify all listeners of changes
|
|
57
|
+
*/
|
|
58
|
+
_notifyChange() {
|
|
59
|
+
this._listeners.forEach(listener => listener());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all models in the collection
|
|
63
|
+
*/
|
|
64
|
+
get models() {
|
|
65
|
+
return this._models;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the number of models in the collection
|
|
69
|
+
*/
|
|
70
|
+
get length() {
|
|
71
|
+
return this._models.length;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get pagination metadata
|
|
75
|
+
*/
|
|
76
|
+
get meta() {
|
|
77
|
+
return this._meta;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if there are more pages to fetch
|
|
81
|
+
*/
|
|
82
|
+
get hasMore() {
|
|
83
|
+
return this._meta.has_more;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a model by ID from the index
|
|
87
|
+
*/
|
|
88
|
+
get(id) {
|
|
89
|
+
return this._index.get(id);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Asynchronously get a model by ID with automatic batched fetching
|
|
93
|
+
* If the model is not in the collection, it will be fetched along with other requested IDs
|
|
94
|
+
* All getAsync calls within 50ms are batched into a single API request
|
|
95
|
+
*
|
|
96
|
+
* @param id - The ID of the model to fetch
|
|
97
|
+
* @returns Promise that resolves to the model or null if not found
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const project = await api.collections.project.getAsync(123);
|
|
101
|
+
* if (project) {
|
|
102
|
+
* console.log(project.name);
|
|
103
|
+
* }
|
|
104
|
+
*/
|
|
105
|
+
async getAsync(id, forceFresh = false) {
|
|
106
|
+
// Check if already in collection (skip if forceFresh is true)
|
|
107
|
+
const existing = this._index.get(id);
|
|
108
|
+
if (existing && !forceFresh) {
|
|
109
|
+
this._cacheHits.add(id);
|
|
110
|
+
// Ensure batch will be processed to log cache hits
|
|
111
|
+
if (!this._batchTimeout) {
|
|
112
|
+
this._batchTimeout = setTimeout(() => {
|
|
113
|
+
this._processBatch();
|
|
114
|
+
}, 50);
|
|
115
|
+
}
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
// Return a promise that will be resolved when batch fetch completes
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
// Add to batch queue
|
|
121
|
+
if (!this._batchQueue.has(id)) {
|
|
122
|
+
this._batchQueue.set(id, []);
|
|
123
|
+
}
|
|
124
|
+
this._batchQueue.get(id).push(resolve);
|
|
125
|
+
// Clear existing timeout and set new one (debounce)
|
|
126
|
+
if (this._batchTimeout) {
|
|
127
|
+
clearTimeout(this._batchTimeout);
|
|
128
|
+
}
|
|
129
|
+
this._batchTimeout = setTimeout(() => {
|
|
130
|
+
this._processBatch();
|
|
131
|
+
}, 50); // 50ms debounce
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Process the batched getAsync requests
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
async _processBatch() {
|
|
139
|
+
if (this._isBatchFetching) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const idsToFetch = Array.from(this._batchQueue.keys());
|
|
143
|
+
const cacheHits = Array.from(this._cacheHits);
|
|
144
|
+
const resolvers = new Map(this._batchQueue);
|
|
145
|
+
this._batchQueue.clear();
|
|
146
|
+
this._cacheHits.clear();
|
|
147
|
+
this._isBatchFetching = true;
|
|
148
|
+
// Log summary
|
|
149
|
+
if (cacheHits.length > 0 || idsToFetch.length > 0) {
|
|
150
|
+
const parts = [];
|
|
151
|
+
if (cacheHits.length > 0) {
|
|
152
|
+
parts.push(`${cacheHits.length} from cache: [${cacheHits.join(', ')}]`);
|
|
153
|
+
}
|
|
154
|
+
if (idsToFetch.length > 0) {
|
|
155
|
+
parts.push(`${idsToFetch.length} to fetch: [${idsToFetch.join(', ')}]`);
|
|
156
|
+
}
|
|
157
|
+
console.log(`[Collection._processBatch] ${parts.join(' | ')}`);
|
|
158
|
+
}
|
|
159
|
+
if (idsToFetch.length === 0) {
|
|
160
|
+
this._isBatchFetching = false;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
// Fetch all IDs in one request (using fetchFn directly to avoid reset)
|
|
165
|
+
const response = await this.fetchFn({
|
|
166
|
+
id__in: idsToFetch.join(','),
|
|
167
|
+
limit: idsToFetch.length,
|
|
168
|
+
});
|
|
169
|
+
// Add fetched models to collection without clearing existing ones
|
|
170
|
+
if (response.objects && response.objects.length > 0) {
|
|
171
|
+
this.add(response.objects);
|
|
172
|
+
}
|
|
173
|
+
// Resolve all promises with the fetched models
|
|
174
|
+
idsToFetch.forEach(id => {
|
|
175
|
+
const model = this._index.get(id) || null;
|
|
176
|
+
const callbacks = resolvers.get(id);
|
|
177
|
+
if (callbacks) {
|
|
178
|
+
callbacks.forEach(callback => callback(model));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error('[Collection._processBatch] Failed to fetch batched models:', error);
|
|
184
|
+
// Resolve all promises with null on error
|
|
185
|
+
idsToFetch.forEach(id => {
|
|
186
|
+
const callbacks = resolvers.get(id);
|
|
187
|
+
if (callbacks) {
|
|
188
|
+
callbacks.forEach(callback => callback(null));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
this._isBatchFetching = false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get a model at a specific position
|
|
198
|
+
*/
|
|
199
|
+
at(index) {
|
|
200
|
+
return this._models[index];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Add models to the collection and update the index
|
|
204
|
+
* Creates a new array reference for React compatibility
|
|
205
|
+
* Only triggers change notification if data actually changed
|
|
206
|
+
*/
|
|
207
|
+
add(models) {
|
|
208
|
+
const modelsArray = Array.isArray(models) ? models : [models];
|
|
209
|
+
const newModels = [...this._models];
|
|
210
|
+
let hasChanges = false;
|
|
211
|
+
modelsArray.forEach(model => {
|
|
212
|
+
// Update or add to models array
|
|
213
|
+
const existingIndex = newModels.findIndex(m => m.id === model.id);
|
|
214
|
+
if (existingIndex >= 0) {
|
|
215
|
+
// Always update existing items (backend may not update modified_at)
|
|
216
|
+
newModels[existingIndex] = model;
|
|
217
|
+
this._index.set(model.id, model);
|
|
218
|
+
hasChanges = true;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
newModels.push(model);
|
|
222
|
+
this._index.set(model.id, model);
|
|
223
|
+
hasChanges = true;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
// Only update and notify if something actually changed
|
|
227
|
+
if (hasChanges) {
|
|
228
|
+
// Create new array reference for React to detect changes
|
|
229
|
+
this._models = newModels;
|
|
230
|
+
this._notifyChange();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Prepend models to the beginning of the collection
|
|
235
|
+
* Useful for adding newly created items to the top of a list
|
|
236
|
+
*/
|
|
237
|
+
prepend(models) {
|
|
238
|
+
const modelsArray = Array.isArray(models) ? models : [models];
|
|
239
|
+
const newModels = [...this._models];
|
|
240
|
+
modelsArray.forEach(model => {
|
|
241
|
+
// Remove if already exists
|
|
242
|
+
const existingIndex = newModels.findIndex(m => m.id === model.id);
|
|
243
|
+
if (existingIndex >= 0) {
|
|
244
|
+
newModels.splice(existingIndex, 1);
|
|
245
|
+
}
|
|
246
|
+
// Add to the beginning
|
|
247
|
+
newModels.unshift(model);
|
|
248
|
+
// Update index
|
|
249
|
+
this._index.set(model.id, model);
|
|
250
|
+
});
|
|
251
|
+
// Create new array reference for React to detect changes
|
|
252
|
+
this._models = newModels;
|
|
253
|
+
this._notifyChange();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Prepend models to the beginning of the collection and fetch their related data
|
|
257
|
+
* This is useful when adding newly created items without refetching the entire collection
|
|
258
|
+
* @param models - Model(s) to prepend
|
|
259
|
+
* @param config - Optional request configuration
|
|
260
|
+
*/
|
|
261
|
+
async prependWithRelations(models, config) {
|
|
262
|
+
// First prepend the model(s) to the collection
|
|
263
|
+
this.prepend(models);
|
|
264
|
+
// Then execute dependent calls to fetch and attach related data
|
|
265
|
+
// executeDependentCalls processes all models in _models, so it will enrich the prepended models
|
|
266
|
+
await this.executeDependentCalls(config);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Remove a model by ID
|
|
270
|
+
*/
|
|
271
|
+
remove(id) {
|
|
272
|
+
this._models = this._models.filter(m => m.id !== id);
|
|
273
|
+
this._index.delete(id);
|
|
274
|
+
this._notifyChange();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Reset the collection with new models
|
|
278
|
+
*/
|
|
279
|
+
reset(models, meta) {
|
|
280
|
+
this._models = [];
|
|
281
|
+
this._index.clear();
|
|
282
|
+
if (meta) {
|
|
283
|
+
this._meta = meta;
|
|
284
|
+
}
|
|
285
|
+
this.add(models);
|
|
286
|
+
// No need to call _notifyChange here since add() calls it
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get the maximum modified_at value from the collection
|
|
290
|
+
* Useful for incremental updates
|
|
291
|
+
*/
|
|
292
|
+
getMaxModifiedAt() {
|
|
293
|
+
if (this._models.length === 0)
|
|
294
|
+
return null;
|
|
295
|
+
const modifiedAtValues = this._models
|
|
296
|
+
.map(m => m.modified_at)
|
|
297
|
+
.filter(Boolean);
|
|
298
|
+
if (modifiedAtValues.length === 0)
|
|
299
|
+
return null;
|
|
300
|
+
// String comparison works for ISO timestamps
|
|
301
|
+
return modifiedAtValues.reduce((max, current) => (current > max ? current : max), modifiedAtValues[0]);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Fetch and merge updates since the last modified_at timestamp
|
|
305
|
+
* Returns true if new data was fetched, false otherwise
|
|
306
|
+
*/
|
|
307
|
+
async fetchUpdates(additionalParams = {}, useGte = false) {
|
|
308
|
+
let maxModifiedAt = this.getMaxModifiedAt();
|
|
309
|
+
// If collection is empty, use Unix epoch (1970-01-01) to fetch all data
|
|
310
|
+
if (!maxModifiedAt) {
|
|
311
|
+
maxModifiedAt = '1970-01-01T00:00:00Z';
|
|
312
|
+
}
|
|
313
|
+
const filterKey = useGte ? 'modified_at__gte' : 'modified_at__gt';
|
|
314
|
+
const params = Object.assign(Object.assign({}, additionalParams), { [filterKey]: maxModifiedAt });
|
|
315
|
+
// Use fetchFn which already knows the collection name
|
|
316
|
+
const response = await this.fetchFn(params);
|
|
317
|
+
const updates = response.objects || [];
|
|
318
|
+
if (updates.length > 0) {
|
|
319
|
+
// Merge updates into existing collection
|
|
320
|
+
this.add(updates);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Clear all models from the collection
|
|
327
|
+
*/
|
|
328
|
+
clear() {
|
|
329
|
+
this._models = [];
|
|
330
|
+
this._index.clear();
|
|
331
|
+
this._meta = {
|
|
332
|
+
has_more: false,
|
|
333
|
+
next: null,
|
|
334
|
+
next_offset: null,
|
|
335
|
+
};
|
|
336
|
+
this._notifyChange();
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Iterate over each model
|
|
340
|
+
*/
|
|
341
|
+
forEach(callback) {
|
|
342
|
+
this._models.forEach(callback);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Map over models
|
|
346
|
+
*/
|
|
347
|
+
map(callback) {
|
|
348
|
+
return this._models.map(callback);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Filter models
|
|
352
|
+
*/
|
|
353
|
+
filter(callback) {
|
|
354
|
+
return this._models.filter(callback);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Find a model
|
|
358
|
+
*/
|
|
359
|
+
find(callback) {
|
|
360
|
+
return this._models.find(callback);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Check if collection contains a model with the given ID
|
|
364
|
+
*/
|
|
365
|
+
has(id) {
|
|
366
|
+
return this._index.has(id);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Convert to plain array
|
|
370
|
+
*/
|
|
371
|
+
toArray() {
|
|
372
|
+
return [...this._models];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Convert to JSON
|
|
376
|
+
*/
|
|
377
|
+
toJSON() {
|
|
378
|
+
return this._models;
|
|
379
|
+
}
|
|
380
|
+
// ============================================
|
|
381
|
+
// Underscore.js Collection Methods
|
|
382
|
+
// ============================================
|
|
383
|
+
/**
|
|
384
|
+
* Extract a single property from each model
|
|
385
|
+
* @example projects.pluck('name') // ['Project 1', 'Project 2']
|
|
386
|
+
*/
|
|
387
|
+
pluck(key) {
|
|
388
|
+
return underscore_1.default.pluck(this._models, key);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Filter models that match all the given properties
|
|
392
|
+
* @example projects.where({ timezone: 'UTC' })
|
|
393
|
+
*/
|
|
394
|
+
where(properties) {
|
|
395
|
+
return underscore_1.default.where(this._models, properties);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Find the first model that matches all the given properties
|
|
399
|
+
* @example projects.findWhere({ timezone: 'UTC' })
|
|
400
|
+
*/
|
|
401
|
+
findWhere(properties) {
|
|
402
|
+
return underscore_1.default.findWhere(this._models, properties);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Sort models by a property or function
|
|
406
|
+
* @example projects.sortBy('name')
|
|
407
|
+
* @example projects.sortBy(p => p.name.toLowerCase())
|
|
408
|
+
*/
|
|
409
|
+
sortBy(iteratee) {
|
|
410
|
+
return underscore_1.default.sortBy(this._models, iteratee);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Group models by a property or function
|
|
414
|
+
* @example projects.groupBy('timezone')
|
|
415
|
+
* @example projects.groupBy(p => p.name[0]) // Group by first letter
|
|
416
|
+
*/
|
|
417
|
+
groupBy(iteratee) {
|
|
418
|
+
return underscore_1.default.groupBy(this._models, iteratee);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Count models by a property or function
|
|
422
|
+
* @example projects.countBy('timezone')
|
|
423
|
+
*/
|
|
424
|
+
countBy(iteratee) {
|
|
425
|
+
return underscore_1.default.countBy(this._models, iteratee);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Index models by a property or function (returns a map)
|
|
429
|
+
* @example projects.indexBy('name')
|
|
430
|
+
*/
|
|
431
|
+
indexBy(iteratee) {
|
|
432
|
+
return underscore_1.default.indexBy(this._models, iteratee);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Split collection into two arrays: [matches, non-matches]
|
|
436
|
+
* @example const [active, inactive] = projects.partition(p => !p.is_invalidated)
|
|
437
|
+
*/
|
|
438
|
+
partition(predicate) {
|
|
439
|
+
return underscore_1.default.partition(this._models, predicate);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get n random models from the collection
|
|
443
|
+
* @example projects.sample(3) // Get 3 random projects
|
|
444
|
+
*/
|
|
445
|
+
sample(n) {
|
|
446
|
+
if (n === undefined) {
|
|
447
|
+
return underscore_1.default.sample(this._models);
|
|
448
|
+
}
|
|
449
|
+
return underscore_1.default.sample(this._models, n);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Shuffle the models (returns new array)
|
|
453
|
+
* @example projects.shuffle()
|
|
454
|
+
*/
|
|
455
|
+
shuffle() {
|
|
456
|
+
return underscore_1.default.shuffle(this._models);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get the first n models (or first model if n not specified)
|
|
460
|
+
* @example projects.first() // First project
|
|
461
|
+
* @example projects.first(5) // First 5 projects
|
|
462
|
+
*/
|
|
463
|
+
first(n) {
|
|
464
|
+
if (n === undefined) {
|
|
465
|
+
return underscore_1.default.first(this._models);
|
|
466
|
+
}
|
|
467
|
+
return underscore_1.default.first(this._models, n);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get the last n models (or last model if n not specified)
|
|
471
|
+
* @example projects.last() // Last project
|
|
472
|
+
* @example projects.last(5) // Last 5 projects
|
|
473
|
+
*/
|
|
474
|
+
last(n) {
|
|
475
|
+
if (n === undefined) {
|
|
476
|
+
return underscore_1.default.last(this._models);
|
|
477
|
+
}
|
|
478
|
+
return underscore_1.default.last(this._models, n);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get all models except the last n
|
|
482
|
+
* @example projects.initial(2) // All except last 2
|
|
483
|
+
*/
|
|
484
|
+
initial(n = 1) {
|
|
485
|
+
return underscore_1.default.initial(this._models, n);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get all models except the first n
|
|
489
|
+
* @example projects.rest(2) // All except first 2
|
|
490
|
+
*/
|
|
491
|
+
rest(n = 1) {
|
|
492
|
+
return underscore_1.default.rest(this._models, n);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Exclude models with the given IDs
|
|
496
|
+
* @example projects.without(1, 2, 3)
|
|
497
|
+
*/
|
|
498
|
+
without(...ids) {
|
|
499
|
+
return underscore_1.default.reject(this._models, model => ids.includes(model.id));
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if collection is empty
|
|
503
|
+
* @example projects.isEmpty()
|
|
504
|
+
*/
|
|
505
|
+
isEmpty() {
|
|
506
|
+
return underscore_1.default.isEmpty(this._models);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Get the size of the collection (alias for length)
|
|
510
|
+
* @example projects.size()
|
|
511
|
+
*/
|
|
512
|
+
size() {
|
|
513
|
+
return underscore_1.default.size(this._models);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Test if all models pass the predicate
|
|
517
|
+
* @example projects.every(p => !p.is_invalidated)
|
|
518
|
+
*/
|
|
519
|
+
every(predicate) {
|
|
520
|
+
return underscore_1.default.every(this._models, predicate);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Test if any model passes the predicate
|
|
524
|
+
* @example projects.some(p => p.timezone === 'UTC')
|
|
525
|
+
*/
|
|
526
|
+
some(predicate) {
|
|
527
|
+
return underscore_1.default.some(this._models, predicate);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Opposite of filter - returns models that don't match
|
|
531
|
+
* @example projects.reject(p => p.is_invalidated)
|
|
532
|
+
*/
|
|
533
|
+
reject(predicate) {
|
|
534
|
+
return underscore_1.default.reject(this._models, predicate);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Find the maximum value by a property or function
|
|
538
|
+
* @example projects.max('created_at')
|
|
539
|
+
* @example projects.max(p => p.name.length)
|
|
540
|
+
*/
|
|
541
|
+
max(iteratee) {
|
|
542
|
+
return underscore_1.default.max(this._models, iteratee);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Find the minimum value by a property or function
|
|
546
|
+
* @example projects.min('created_at')
|
|
547
|
+
* @example projects.min(p => p.name.length)
|
|
548
|
+
*/
|
|
549
|
+
min(iteratee) {
|
|
550
|
+
return underscore_1.default.min(this._models, iteratee);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Reduce the collection to a single value
|
|
554
|
+
* @example projects.reduce((sum, p) => sum + 1, 0)
|
|
555
|
+
*/
|
|
556
|
+
reduce(callback, initialValue) {
|
|
557
|
+
return underscore_1.default.reduce(this._models, callback, initialValue);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Get unique models (removes duplicates by ID)
|
|
561
|
+
* @example projects.uniq()
|
|
562
|
+
*/
|
|
563
|
+
uniq() {
|
|
564
|
+
return underscore_1.default.uniq(this._models, model => model.id);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Check if the collection contains a model matching the predicate
|
|
568
|
+
* @example projects.contains(p => p.name === 'Test')
|
|
569
|
+
*/
|
|
570
|
+
contains(predicate) {
|
|
571
|
+
return underscore_1.default.some(this._models, predicate);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Invoke a method on each model (if models have methods)
|
|
575
|
+
* @example projects.invoke('toString')
|
|
576
|
+
*/
|
|
577
|
+
invoke(methodName, ...args) {
|
|
578
|
+
return underscore_1.default.invoke(this._models, methodName, ...args);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Execute all dependent calls after the main collection is loaded
|
|
582
|
+
*/
|
|
583
|
+
async executeDependentCalls(config) {
|
|
584
|
+
if (this._dependentCalls.length === 0 || this._models.length === 0) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
for (const dependentCall of this._dependentCalls) {
|
|
588
|
+
// Extract all IDs from current models
|
|
589
|
+
const ids = this._models
|
|
590
|
+
.map(model => dependentCall.extractIds(model))
|
|
591
|
+
.filter((id) => id !== null && id !== undefined);
|
|
592
|
+
// Remove duplicates
|
|
593
|
+
const uniqueIds = Array.from(new Set(ids));
|
|
594
|
+
if (uniqueIds.length === 0) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
// Build params for dependent API call
|
|
598
|
+
const idParam = dependentCall.idParam || 'id__in';
|
|
599
|
+
const dependentParams = {
|
|
600
|
+
[idParam]: uniqueIds.join(','),
|
|
601
|
+
};
|
|
602
|
+
if (dependentCall.onlyFields && dependentCall.onlyFields.length > 0) {
|
|
603
|
+
dependentParams.only_fields = dependentCall.onlyFields.join(',');
|
|
604
|
+
}
|
|
605
|
+
// Add extra filter parameters if provided
|
|
606
|
+
if (dependentCall.extraParams) {
|
|
607
|
+
Object.assign(dependentParams, dependentCall.extraParams);
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
// Fetch related data
|
|
611
|
+
const dependentResponse = await dependentCall.fetchFn(dependentParams, config);
|
|
612
|
+
if (dependentCall.isArray) {
|
|
613
|
+
// One-to-many relationship: group related objects by match field
|
|
614
|
+
const matchField = dependentCall.matchField || 'id';
|
|
615
|
+
const relatedArrayMap = new Map();
|
|
616
|
+
dependentResponse.objects.forEach(obj => {
|
|
617
|
+
const matchValue = obj[matchField];
|
|
618
|
+
if (matchValue !== null && matchValue !== undefined) {
|
|
619
|
+
if (!relatedArrayMap.has(matchValue)) {
|
|
620
|
+
relatedArrayMap.set(matchValue, new Map());
|
|
621
|
+
}
|
|
622
|
+
// Use Map to deduplicate by ID
|
|
623
|
+
relatedArrayMap.get(matchValue).set(obj.id, obj);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
// Merge related arrays back into models
|
|
627
|
+
this._models = this._models.map(model => {
|
|
628
|
+
const relatedId = dependentCall.extractIds(model);
|
|
629
|
+
if (relatedId !== null && relatedId !== undefined) {
|
|
630
|
+
const relatedMap = relatedArrayMap.get(relatedId);
|
|
631
|
+
const relatedArray = relatedMap ? Array.from(relatedMap.values()) : [];
|
|
632
|
+
return Object.assign(Object.assign({}, model), { [dependentCall.targetField]: relatedArray });
|
|
633
|
+
}
|
|
634
|
+
return Object.assign(Object.assign({}, model), { [dependentCall.targetField]: [] });
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
// One-to-one relationship: map by ID
|
|
639
|
+
const relatedMap = new Map(dependentResponse.objects.map(obj => [obj.id, obj]));
|
|
640
|
+
// Merge related data back into models
|
|
641
|
+
this._models = this._models.map(model => {
|
|
642
|
+
const relatedId = dependentCall.extractIds(model);
|
|
643
|
+
if (relatedId !== null && relatedId !== undefined) {
|
|
644
|
+
const relatedObj = relatedMap.get(relatedId);
|
|
645
|
+
if (relatedObj) {
|
|
646
|
+
return Object.assign(Object.assign({}, model), { [dependentCall.targetField]: relatedObj });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return model;
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
// Update the index with merged models
|
|
653
|
+
this._models.forEach(model => {
|
|
654
|
+
this._index.set(model.id, model);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
console.error(`Failed to fetch dependent data for ${dependentCall.collectionName}:`, error);
|
|
659
|
+
// Continue with other dependent calls even if one fails
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Notify listeners of the changes
|
|
663
|
+
this._notifyChange();
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Fetch the first page of models
|
|
667
|
+
*/
|
|
668
|
+
async fetch(params, config) {
|
|
669
|
+
const paramsObj = (params && typeof params === 'object' ? params : {});
|
|
670
|
+
this.lastParams = paramsObj;
|
|
671
|
+
this._limit = paramsObj.limit || 20;
|
|
672
|
+
this._offset = paramsObj.offset || 0;
|
|
673
|
+
const response = await this.fetchFn(Object.assign(Object.assign({}, paramsObj), { limit: this._limit, offset: this._offset }), config);
|
|
674
|
+
this.reset(response.objects, response.meta);
|
|
675
|
+
// Execute dependent calls to fetch related data
|
|
676
|
+
await this.executeDependentCalls(config);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Fetch the next page and append to the collection
|
|
680
|
+
*/
|
|
681
|
+
async fetchNext(config) {
|
|
682
|
+
if (!this._meta.has_more) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
// Calculate next offset
|
|
686
|
+
this._offset += this._limit;
|
|
687
|
+
// Use lastParams to preserve filters, just update offset
|
|
688
|
+
const response = await this.fetchFn(Object.assign(Object.assign({}, this.lastParams), { limit: this._limit, offset: this._offset }), config);
|
|
689
|
+
this.add(response.objects);
|
|
690
|
+
this._meta = response.meta;
|
|
691
|
+
// Execute dependent calls for the newly loaded page
|
|
692
|
+
await this.executeDependentCalls(config);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Fetch all pages and populate the collection
|
|
696
|
+
*/
|
|
697
|
+
async fetchAll(params, config) {
|
|
698
|
+
// Fetch first page with provided params
|
|
699
|
+
const paramsObj = (params && typeof params === 'object' ? params : {});
|
|
700
|
+
await this.fetch(Object.assign(Object.assign({}, paramsObj), { limit: 1000 }), config);
|
|
701
|
+
// Fetch remaining pages
|
|
702
|
+
while (this._meta.has_more) {
|
|
703
|
+
await this.fetchNext(config);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Convert collection models to GeoJSON FeatureCollection
|
|
708
|
+
*
|
|
709
|
+
* @param toFeature - Optional function to convert a model to a GeoJSON Feature.
|
|
710
|
+
* If not provided, subclasses should override this method.
|
|
711
|
+
* @returns GeoJSON FeatureCollection or null if conversion is not supported
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* // With callback
|
|
715
|
+
* const geojson = collection.toGeoJSON((location) => ({
|
|
716
|
+
* type: 'Feature',
|
|
717
|
+
* geometry: location.point,
|
|
718
|
+
* properties: {
|
|
719
|
+
* name: location.name,
|
|
720
|
+
* code: location.code
|
|
721
|
+
* },
|
|
722
|
+
* id: location.id
|
|
723
|
+
* }));
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* // Override in subclass
|
|
727
|
+
* class MyCollection extends Collection<MyModel> {
|
|
728
|
+
* toGeoJSON(): GeoJSONFeatureCollection {
|
|
729
|
+
* return {
|
|
730
|
+
* type: 'FeatureCollection',
|
|
731
|
+
* features: this.models.map(model => ({
|
|
732
|
+
* type: 'Feature',
|
|
733
|
+
* geometry: model.geometry,
|
|
734
|
+
* properties: { name: model.name },
|
|
735
|
+
* id: model.id
|
|
736
|
+
* }))
|
|
737
|
+
* };
|
|
738
|
+
* }
|
|
739
|
+
* }
|
|
740
|
+
*/
|
|
741
|
+
toGeoJSON(toFeature) {
|
|
742
|
+
if (!toFeature) {
|
|
743
|
+
// No conversion function provided and not overridden in subclass
|
|
744
|
+
console.warn('toGeoJSON: No conversion function provided. Pass a toFeature callback or override this method in a subclass.');
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
const features = this._models
|
|
748
|
+
.map(model => toFeature(model))
|
|
749
|
+
.filter((feature) => feature !== null);
|
|
750
|
+
return {
|
|
751
|
+
type: 'FeatureCollection',
|
|
752
|
+
features
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
exports.Collection = Collection;
|