sgerp-frontend-lib 0.1.1 → 0.1.2

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 (255) hide show
  1. package/README.md +1 -1
  2. package/dist/connection-manager.d.ts +48 -0
  3. package/dist/connection-manager.d.ts.map +1 -0
  4. package/dist/connection-manager.js +156 -0
  5. package/dist/index.js +20 -2
  6. package/dist/locales/locale-server.d.ts +10 -0
  7. package/dist/locales/locale-server.d.ts.map +1 -0
  8. package/dist/locales/locale-server.js +49 -0
  9. package/dist/locales/locale.d.ts +23 -0
  10. package/dist/locales/locale.d.ts.map +1 -0
  11. package/dist/locales/locale.js +145 -0
  12. package/dist/locales/locale_en.d.ts.map +1 -1
  13. package/dist/locales/locale_en.js +84 -9
  14. package/dist/locales/locale_ja.d.ts.map +1 -1
  15. package/dist/locales/locale_ja.js +84 -9
  16. package/dist/locales/locale_ms.d.ts.map +1 -1
  17. package/dist/locales/locale_ms.js +84 -9
  18. package/dist/locales/useLocalization.d.ts +11 -0
  19. package/dist/locales/useLocalization.d.ts.map +1 -0
  20. package/dist/locales/useLocalization.js +64 -0
  21. package/dist/locales.d.ts +228 -0
  22. package/dist/locales.d.ts.map +1 -0
  23. package/dist/locales.js +229 -0
  24. package/dist/sgerp/api/client.d.ts +45 -0
  25. package/dist/sgerp/api/client.d.ts.map +1 -0
  26. package/dist/sgerp/api/client.js +141 -0
  27. package/dist/sgerp/client.js +341 -0
  28. package/dist/sgerp/collection.d.ts +391 -0
  29. package/dist/sgerp/collection.d.ts.map +1 -0
  30. package/dist/sgerp/collection.js +756 -0
  31. package/dist/sgerp/collections/account/user.d.ts +8 -0
  32. package/dist/sgerp/collections/account/user.d.ts.map +1 -0
  33. package/dist/sgerp/collections/account/user.js +10 -0
  34. package/dist/sgerp/collections/masstransit/building.d.ts +26 -0
  35. package/dist/sgerp/collections/masstransit/building.d.ts.map +1 -0
  36. package/dist/sgerp/collections/masstransit/building.js +53 -0
  37. package/dist/sgerp/collections/masstransit/transitstop.d.ts +15 -0
  38. package/dist/sgerp/collections/masstransit/transitstop.d.ts.map +1 -0
  39. package/dist/sgerp/collections/masstransit/transitstop.js +54 -0
  40. package/dist/sgerp/collections/masstransit/transitstopset.d.ts +10 -0
  41. package/dist/sgerp/collections/masstransit/transitstopset.d.ts.map +1 -0
  42. package/dist/sgerp/collections/masstransit/transitstopset.js +10 -0
  43. package/dist/sgerp/collections/sharing/organization-project.d.ts +11 -0
  44. package/dist/sgerp/collections/sharing/organization-project.d.ts.map +1 -0
  45. package/dist/sgerp/collections/sharing/organization-project.js +13 -0
  46. package/dist/sgerp/collections/sharing/organization.d.ts +11 -0
  47. package/dist/sgerp/collections/sharing/organization.d.ts.map +1 -0
  48. package/dist/sgerp/collections/sharing/organization.js +13 -0
  49. package/dist/sgerp/collections/sharing/pricing.d.ts +18 -0
  50. package/dist/sgerp/collections/sharing/pricing.d.ts.map +1 -0
  51. package/dist/sgerp/collections/sharing/pricing.js +23 -0
  52. package/dist/sgerp/collections/sharing/project-member.d.ts +8 -0
  53. package/dist/sgerp/collections/sharing/project-member.d.ts.map +1 -0
  54. package/dist/sgerp/collections/sharing/project-member.js +10 -0
  55. package/dist/sgerp/collections/sharing/project.d.ts +8 -0
  56. package/dist/sgerp/collections/sharing/project.d.ts.map +1 -0
  57. package/dist/sgerp/collections/sharing/project.js +10 -0
  58. package/dist/sgerp/collections/simulation/booking.d.ts +11 -0
  59. package/dist/sgerp/collections/simulation/booking.d.ts.map +1 -0
  60. package/dist/sgerp/collections/simulation/booking.js +13 -0
  61. package/dist/sgerp/collections/simulation/dataset.d.ts +14 -0
  62. package/dist/sgerp/collections/simulation/dataset.d.ts.map +1 -0
  63. package/dist/sgerp/collections/simulation/dataset.js +17 -0
  64. package/dist/sgerp/collections/simulation/driver.d.ts +12 -0
  65. package/dist/sgerp/collections/simulation/driver.d.ts.map +1 -0
  66. package/dist/sgerp/collections/simulation/driver.js +40 -0
  67. package/dist/sgerp/collections/simulation/geofence.d.ts +24 -0
  68. package/dist/sgerp/collections/simulation/geofence.d.ts.map +1 -0
  69. package/dist/sgerp/collections/simulation/geofence.js +66 -0
  70. package/dist/sgerp/collections/simulation/mapboxtoken.d.ts +18 -0
  71. package/dist/sgerp/collections/simulation/mapboxtoken.d.ts.map +1 -0
  72. package/dist/sgerp/collections/simulation/mapboxtoken.js +24 -0
  73. package/dist/sgerp/collections/simulation/node.js +13 -0
  74. package/dist/sgerp/collections/simulation/operationslocation.d.ts +28 -0
  75. package/dist/sgerp/collections/simulation/operationslocation.d.ts.map +1 -0
  76. package/dist/sgerp/collections/simulation/operationslocation.js +64 -0
  77. package/dist/sgerp/collections/simulation/operationslocationgroup.d.ts +12 -0
  78. package/dist/sgerp/collections/simulation/operationslocationgroup.d.ts.map +1 -0
  79. package/dist/sgerp/collections/simulation/operationslocationgroup.js +24 -0
  80. package/dist/sgerp/collections/simulation/routingprofile.d.ts +10 -0
  81. package/dist/sgerp/collections/simulation/routingprofile.d.ts.map +1 -0
  82. package/dist/sgerp/collections/simulation/routingprofile.js +10 -0
  83. package/dist/sgerp/collections/simulation/simulation.d.ts +12 -0
  84. package/dist/sgerp/collections/simulation/simulation.d.ts.map +1 -0
  85. package/dist/sgerp/collections/simulation/simulation.js +35 -0
  86. package/dist/sgerp/collections/simulation/simulationprocessor.d.ts +19 -0
  87. package/dist/sgerp/collections/simulation/simulationprocessor.d.ts.map +1 -0
  88. package/dist/sgerp/collections/simulation/simulationprocessor.js +27 -0
  89. package/dist/sgerp/collections/simulation/vehicle.js +13 -0
  90. package/dist/sgerp/collections/simulation/vehicletype.d.ts +11 -0
  91. package/dist/sgerp/collections/simulation/vehicletype.d.ts.map +1 -0
  92. package/dist/sgerp/collections/simulation/vehicletype.js +13 -0
  93. package/dist/sgerp/collections/sms_notify/smsscheduled.d.ts +11 -0
  94. package/dist/sgerp/collections/sms_notify/smsscheduled.d.ts.map +1 -0
  95. package/dist/sgerp/collections/sms_notify/smsscheduled.js +13 -0
  96. package/dist/sgerp/collections/transportation/passenger.d.ts +8 -0
  97. package/dist/sgerp/collections/transportation/passenger.d.ts.map +1 -0
  98. package/dist/sgerp/collections/transportation/passenger.js +10 -0
  99. package/dist/sgerp/collections/transportation/ticket.d.ts +11 -0
  100. package/dist/sgerp/collections/transportation/ticket.d.ts.map +1 -0
  101. package/dist/sgerp/collections/transportation/ticket.js +13 -0
  102. package/dist/sgerp/collections/transportation/transaction.d.ts +11 -0
  103. package/dist/sgerp/collections/transportation/transaction.d.ts.map +1 -0
  104. package/dist/sgerp/collections/transportation/transaction.js +13 -0
  105. package/dist/sgerp/constants/bulkoperations-enums.d.ts +71 -0
  106. package/dist/sgerp/constants/bulkoperations-enums.d.ts.map +1 -0
  107. package/dist/sgerp/constants/bulkoperations-enums.js +99 -0
  108. package/dist/sgerp/constants/messages-enums.d.ts +47 -0
  109. package/dist/sgerp/constants/messages-enums.d.ts.map +1 -0
  110. package/dist/sgerp/constants/messages-enums.js +67 -0
  111. package/dist/sgerp/constants/scheduling-enums.d.ts +42 -0
  112. package/dist/sgerp/constants/scheduling-enums.d.ts.map +1 -0
  113. package/dist/sgerp/constants/scheduling-enums.js +61 -0
  114. package/dist/sgerp/constants/simulation-enums.d.ts +93 -0
  115. package/dist/sgerp/constants/simulation-enums.d.ts.map +1 -0
  116. package/dist/sgerp/constants/simulation-enums.js +147 -0
  117. package/dist/sgerp/constants/solver-strategies.d.ts +75 -0
  118. package/dist/sgerp/constants/solver-strategies.d.ts.map +1 -0
  119. package/dist/sgerp/constants/solver-strategies.js +26 -0
  120. package/dist/sgerp/context/sgerp-context.js +134 -0
  121. package/dist/sgerp/domains.d.ts +47 -0
  122. package/dist/sgerp/domains.d.ts.map +1 -0
  123. package/dist/sgerp/domains.js +45 -0
  124. package/dist/sgerp/hooks/use-collection.js +122 -0
  125. package/dist/sgerp/hooks/use-live-updates.js +16 -13
  126. package/dist/sgerp/index.js +153 -0
  127. package/dist/sgerp/map-states/operations-location-state.d.ts +12 -0
  128. package/dist/sgerp/map-states/operations-location-state.d.ts.map +1 -0
  129. package/dist/sgerp/map-states/operations-location-state.js +86 -0
  130. package/dist/sgerp/routing/index.d.ts +51 -0
  131. package/dist/sgerp/routing/index.d.ts.map +1 -0
  132. package/dist/sgerp/routing/index.js +373 -0
  133. package/dist/sgerp/simulation-logic/fetchUtils.js +226 -0
  134. package/dist/sgerp/simulation-logic/index.js +27 -7
  135. package/dist/sgerp/simulation-logic/manualEditUtils.js +69 -0
  136. package/dist/sgerp/simulation-logic/mapUtils.js +58 -0
  137. package/dist/sgerp/simulation-logic/optimisticUpdateUtils.js +8 -5
  138. package/dist/sgerp/simulation-logic/referenceUtils.js +110 -0
  139. package/dist/sgerp/simulation-logic/routeCalculationUtils.js +43 -0
  140. package/dist/sgerp/simulation-logic/timeShiftUtils.js +63 -0
  141. package/dist/sgerp/types/account/user.d.ts +38 -0
  142. package/dist/sgerp/types/account/user.d.ts.map +1 -0
  143. package/dist/sgerp/types/account/user.js +2 -0
  144. package/dist/sgerp/types/bulkoperations/index.d.ts +37 -0
  145. package/dist/sgerp/types/bulkoperations/index.d.ts.map +1 -0
  146. package/dist/sgerp/types/bulkoperations/index.js +35 -0
  147. package/dist/sgerp/types/bulkoperations/vehicle-upload.d.ts +335 -0
  148. package/dist/sgerp/types/bulkoperations/vehicle-upload.d.ts.map +1 -0
  149. package/dist/sgerp/types/bulkoperations/vehicle-upload.js +49 -0
  150. package/dist/sgerp/types/config.d.ts +25 -0
  151. package/dist/sgerp/types/config.d.ts.map +1 -0
  152. package/dist/sgerp/types/config.js +2 -0
  153. package/dist/sgerp/types/geojson.d.ts +45 -0
  154. package/dist/sgerp/types/geojson.d.ts.map +1 -0
  155. package/dist/sgerp/types/geojson.js +6 -0
  156. package/dist/sgerp/types/map.d.ts +24 -0
  157. package/dist/sgerp/types/map.d.ts.map +1 -0
  158. package/dist/sgerp/types/map.js +2 -0
  159. package/dist/sgerp/types/masstransit/building.d.ts +24 -0
  160. package/dist/sgerp/types/masstransit/building.d.ts.map +1 -0
  161. package/dist/sgerp/types/masstransit/building.js +2 -0
  162. package/dist/sgerp/types/masstransit/transitstop.d.ts +35 -0
  163. package/dist/sgerp/types/masstransit/transitstop.d.ts.map +1 -0
  164. package/dist/sgerp/types/masstransit/transitstop.js +2 -0
  165. package/dist/sgerp/types/masstransit/transitstopset.d.ts +14 -0
  166. package/dist/sgerp/types/masstransit/transitstopset.d.ts.map +1 -0
  167. package/dist/sgerp/types/masstransit/transitstopset.js +2 -0
  168. package/dist/sgerp/types/sharing/organization-project.d.ts +14 -0
  169. package/dist/sgerp/types/sharing/organization-project.d.ts.map +1 -0
  170. package/dist/sgerp/types/sharing/organization-project.js +2 -0
  171. package/dist/sgerp/types/sharing/organization.d.ts +14 -0
  172. package/dist/sgerp/types/sharing/organization.d.ts.map +1 -0
  173. package/dist/sgerp/types/sharing/organization.js +2 -0
  174. package/dist/sgerp/types/sharing/pricing.d.ts +98 -0
  175. package/dist/sgerp/types/sharing/pricing.d.ts.map +1 -0
  176. package/dist/sgerp/types/sharing/pricing.js +2 -0
  177. package/dist/sgerp/types/sharing/project-member.d.ts +15 -0
  178. package/dist/sgerp/types/sharing/project-member.d.ts.map +1 -0
  179. package/dist/sgerp/types/sharing/project-member.js +2 -0
  180. package/dist/sgerp/types/sharing/project.d.ts +16 -0
  181. package/dist/sgerp/types/sharing/project.d.ts.map +1 -0
  182. package/dist/sgerp/types/sharing/project.js +2 -0
  183. package/dist/sgerp/types/simulation/booking.d.ts +128 -0
  184. package/dist/sgerp/types/simulation/booking.d.ts.map +1 -0
  185. package/dist/sgerp/types/simulation/booking.js +24 -0
  186. package/dist/sgerp/types/simulation/calculation-params.d.ts +20 -0
  187. package/dist/sgerp/types/simulation/calculation-params.d.ts.map +1 -0
  188. package/dist/sgerp/types/simulation/calculation-params.js +2 -0
  189. package/dist/sgerp/types/simulation/dataset.d.ts +16 -0
  190. package/dist/sgerp/types/simulation/dataset.d.ts.map +1 -0
  191. package/dist/sgerp/types/simulation/dataset.js +2 -0
  192. package/dist/sgerp/types/simulation/driver.d.ts +19 -0
  193. package/dist/sgerp/types/simulation/driver.d.ts.map +1 -0
  194. package/dist/sgerp/types/simulation/driver.js +2 -0
  195. package/dist/sgerp/types/simulation/geofence.d.ts +19 -0
  196. package/dist/sgerp/types/simulation/geofence.d.ts.map +1 -0
  197. package/dist/sgerp/types/simulation/geofence.js +2 -0
  198. package/dist/sgerp/types/simulation/logistics-api-settings.d.ts +98 -0
  199. package/dist/sgerp/types/simulation/logistics-api-settings.d.ts.map +1 -0
  200. package/dist/sgerp/types/simulation/logistics-api-settings.js +104 -0
  201. package/dist/sgerp/types/simulation/mapboxtoken.d.ts +13 -0
  202. package/dist/sgerp/types/simulation/mapboxtoken.d.ts.map +1 -0
  203. package/dist/sgerp/types/simulation/mapboxtoken.js +2 -0
  204. package/dist/sgerp/types/simulation/node.js +5 -2
  205. package/dist/sgerp/types/simulation/operationslocation.d.ts +40 -0
  206. package/dist/sgerp/types/simulation/operationslocation.d.ts.map +1 -0
  207. package/dist/sgerp/types/simulation/operationslocation.js +2 -0
  208. package/dist/sgerp/types/simulation/operationslocationgroup.d.ts +11 -0
  209. package/dist/sgerp/types/simulation/operationslocationgroup.d.ts.map +1 -0
  210. package/dist/sgerp/types/simulation/operationslocationgroup.js +2 -0
  211. package/dist/sgerp/types/simulation/pricing.d.ts +171 -0
  212. package/dist/sgerp/types/simulation/pricing.d.ts.map +1 -0
  213. package/dist/sgerp/types/simulation/pricing.js +2 -0
  214. package/dist/sgerp/types/simulation/route-cost-modification.d.ts +35 -0
  215. package/dist/sgerp/types/simulation/route-cost-modification.d.ts.map +1 -0
  216. package/dist/sgerp/types/simulation/route-cost-modification.js +5 -0
  217. package/dist/sgerp/types/simulation/routing-engine-settings.d.ts +42 -0
  218. package/dist/sgerp/types/simulation/routing-engine-settings.d.ts.map +1 -0
  219. package/dist/sgerp/types/simulation/routing-engine-settings.js +35 -0
  220. package/dist/sgerp/types/simulation/routingprofile.d.ts +32 -0
  221. package/dist/sgerp/types/simulation/routingprofile.d.ts.map +1 -0
  222. package/dist/sgerp/types/simulation/routingprofile.js +37 -0
  223. package/dist/sgerp/types/simulation/simulation.d.ts +120 -0
  224. package/dist/sgerp/types/simulation/simulation.d.ts.map +1 -0
  225. package/dist/sgerp/types/simulation/simulation.js +2 -0
  226. package/dist/sgerp/types/simulation/simulationprocessor.d.ts +31 -0
  227. package/dist/sgerp/types/simulation/simulationprocessor.d.ts.map +1 -0
  228. package/dist/sgerp/types/simulation/simulationprocessor.js +2 -0
  229. package/dist/sgerp/types/simulation/vehicle.js +2 -1
  230. package/dist/sgerp/types/simulation/vehicletype.d.ts +33 -0
  231. package/dist/sgerp/types/simulation/vehicletype.d.ts.map +1 -0
  232. package/dist/sgerp/types/simulation/vehicletype.js +2 -0
  233. package/dist/sgerp/types/sms_notify/smsscheduled.d.ts +36 -0
  234. package/dist/sgerp/types/sms_notify/smsscheduled.d.ts.map +1 -0
  235. package/dist/sgerp/types/sms_notify/smsscheduled.js +6 -0
  236. package/dist/sgerp/types/transportation/passenger.d.ts +49 -0
  237. package/dist/sgerp/types/transportation/passenger.d.ts.map +1 -0
  238. package/dist/sgerp/types/transportation/passenger.js +13 -0
  239. package/dist/sgerp/types/transportation/ticket.d.ts +31 -0
  240. package/dist/sgerp/types/transportation/ticket.d.ts.map +1 -0
  241. package/dist/sgerp/types/transportation/ticket.js +2 -0
  242. package/dist/sgerp/types/transportation/transaction.d.ts +71 -0
  243. package/dist/sgerp/types/transportation/transaction.d.ts.map +1 -0
  244. package/dist/sgerp/types/transportation/transaction.js +54 -0
  245. package/dist/sgerp/utils/bookingPayload.d.ts +80 -0
  246. package/dist/sgerp/utils/bookingPayload.d.ts.map +1 -0
  247. package/dist/sgerp/utils/bookingPayload.js +44 -0
  248. package/dist/sgerp/utils/color.d.ts +30 -0
  249. package/dist/sgerp/utils/color.d.ts.map +1 -0
  250. package/dist/sgerp/utils/color.js +117 -0
  251. package/dist/sgerp/utils/enum-helpers.d.ts +41 -0
  252. package/dist/sgerp/utils/enum-helpers.d.ts.map +1 -0
  253. package/dist/sgerp/utils/enum-helpers.js +54 -0
  254. package/dist/sgerp/utils/routeUtils.js +171 -0
  255. 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;