@sincpro/mobile 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +74 -0
  2. package/dist/adapters/Bluetooth.adapter.d.ts +14 -0
  3. package/dist/adapters/Bluetooth.adapter.js +100 -0
  4. package/dist/adapters/Geo.adapter.d.ts +8 -0
  5. package/dist/adapters/Geo.adapter.js +30 -0
  6. package/dist/adapters/JsonSerializer.adapter.d.ts +28 -0
  7. package/dist/adapters/JsonSerializer.adapter.js +47 -0
  8. package/dist/adapters/Network.adapter.d.ts +4 -0
  9. package/dist/adapters/Network.adapter.js +13 -0
  10. package/dist/adapters/ReceiptExporter.adapter.d.ts +19 -0
  11. package/dist/adapters/ReceiptExporter.adapter.js +142 -0
  12. package/dist/adapters/repositories/database_table.repository.d.ts +13 -0
  13. package/dist/adapters/repositories/database_table.repository.js +29 -0
  14. package/dist/adapters/repositories/domain_event.repository.d.ts +32 -0
  15. package/dist/adapters/repositories/domain_event.repository.js +147 -0
  16. package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +23 -0
  17. package/dist/adapters/repositories/domain_event_dead_letter.repository.js +81 -0
  18. package/dist/adapters/repositories/setting.repository.d.ts +10 -0
  19. package/dist/adapters/repositories/setting.repository.js +21 -0
  20. package/dist/adapters/webview.adapter.d.ts +10 -0
  21. package/dist/adapters/webview.adapter.js +166 -0
  22. package/dist/domain/connectivity/bluetooth.d.ts +19 -0
  23. package/dist/domain/connectivity/bluetooth.js +1 -0
  24. package/dist/domain/connectivity/events.d.ts +17 -0
  25. package/dist/domain/connectivity/events.js +17 -0
  26. package/dist/domain/connectivity/geo.d.ts +4 -0
  27. package/dist/domain/connectivity/geo.js +1 -0
  28. package/dist/domain/connectivity/index.d.ts +3 -0
  29. package/dist/domain/connectivity/index.js +3 -0
  30. package/dist/domain/connectivity/network.d.ts +7 -0
  31. package/dist/domain/connectivity/network.js +1 -0
  32. package/dist/domain/database/database.d.ts +16 -0
  33. package/dist/domain/database/database.js +1 -0
  34. package/dist/domain/database/index.d.ts +2 -0
  35. package/dist/domain/database/index.js +2 -0
  36. package/dist/domain/database/repository.d.ts +19 -0
  37. package/dist/domain/database/repository.js +1 -0
  38. package/dist/domain/entity/entity.d.ts +109 -0
  39. package/dist/domain/entity/entity.js +246 -0
  40. package/dist/domain/entity/entity_collection.d.ts +396 -0
  41. package/dist/domain/entity/entity_collection.js +824 -0
  42. package/dist/domain/entity/index.d.ts +3 -0
  43. package/dist/domain/entity/index.js +3 -0
  44. package/dist/domain/entity/value_object.d.ts +12 -0
  45. package/dist/domain/entity/value_object.js +39 -0
  46. package/dist/domain/event_sourcing/domain_event.d.ts +58 -0
  47. package/dist/domain/event_sourcing/domain_event.js +164 -0
  48. package/dist/domain/event_sourcing/event.d.ts +25 -0
  49. package/dist/domain/event_sourcing/event.js +25 -0
  50. package/dist/domain/event_sourcing/event_handler.d.ts +7 -0
  51. package/dist/domain/event_sourcing/event_handler.js +13 -0
  52. package/dist/domain/event_sourcing/index.d.ts +2 -0
  53. package/dist/domain/event_sourcing/index.js +2 -0
  54. package/dist/domain/events.d.ts +33 -0
  55. package/dist/domain/events.js +32 -0
  56. package/dist/domain/icon.d.ts +1 -0
  57. package/dist/domain/icon.js +1 -0
  58. package/dist/domain/index.d.ts +9 -0
  59. package/dist/domain/index.js +9 -0
  60. package/dist/domain/print/driver_registry.d.ts +4 -0
  61. package/dist/domain/print/driver_registry.js +29 -0
  62. package/dist/domain/print/events.d.ts +13 -0
  63. package/dist/domain/print/events.js +13 -0
  64. package/dist/domain/print/index.d.ts +2 -0
  65. package/dist/domain/print/index.js +1 -0
  66. package/dist/domain/print/printer.d.ts +35 -0
  67. package/dist/domain/print/printer.js +1 -0
  68. package/dist/domain/receipt.d.ts +31 -0
  69. package/dist/domain/receipt.js +1 -0
  70. package/dist/domain/repositories.d.ts +6 -0
  71. package/dist/domain/repositories.js +7 -0
  72. package/dist/domain/settings.d.ts +7 -0
  73. package/dist/domain/settings.js +1 -0
  74. package/dist/domain/webview/events.d.ts +11 -0
  75. package/dist/domain/webview/events.js +11 -0
  76. package/dist/domain/webview/index.d.ts +1 -0
  77. package/dist/domain/webview/index.js +1 -0
  78. package/dist/domain/webview/webview.d.ts +57 -0
  79. package/dist/domain/webview/webview.js +9 -0
  80. package/dist/entrypoints/cron/Cron.d.ts +13 -0
  81. package/dist/entrypoints/cron/Cron.js +57 -0
  82. package/dist/entrypoints/cron/checkNetworkStatus.cron.d.ts +3 -0
  83. package/dist/entrypoints/cron/checkNetworkStatus.cron.js +10 -0
  84. package/dist/entrypoints/db/index.d.ts +2 -0
  85. package/dist/entrypoints/db/index.js +2 -0
  86. package/dist/entrypoints/db/migrations.d.ts +10 -0
  87. package/dist/entrypoints/db/migrations.js +115 -0
  88. package/dist/entrypoints/db/repositories.d.ts +8 -0
  89. package/dist/entrypoints/db/repositories.js +34 -0
  90. package/dist/entrypoints/queue/QueueProcessor.d.ts +12 -0
  91. package/dist/entrypoints/queue/QueueProcessor.js +75 -0
  92. package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +7 -0
  93. package/dist/entrypoints/queue/activateDomain.subscriber.js +15 -0
  94. package/dist/entrypoints/queue/newAppSettings.handler.d.ts +7 -0
  95. package/dist/entrypoints/queue/newAppSettings.handler.js +17 -0
  96. package/dist/entrypoints/queue/printImage.subscriber.d.ts +7 -0
  97. package/dist/entrypoints/queue/printImage.subscriber.js +14 -0
  98. package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +7 -0
  99. package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +23 -0
  100. package/dist/entrypoints/ui/AppShell.d.ts +15 -0
  101. package/dist/entrypoints/ui/AppShell.js +58 -0
  102. package/dist/entrypoints/ui/common_provider.d.ts +35 -0
  103. package/dist/entrypoints/ui/common_provider.js +244 -0
  104. package/dist/entrypoints/ui/domain_switcher.d.ts +19 -0
  105. package/dist/entrypoints/ui/domain_switcher.js +22 -0
  106. package/dist/entrypoints/ui/theme.d.ts +15 -0
  107. package/dist/entrypoints/ui/theme.js +16 -0
  108. package/dist/exceptions.d.ts +22 -0
  109. package/dist/exceptions.js +60 -0
  110. package/dist/framework/base_module.d.ts +14 -0
  111. package/dist/framework/base_module.js +40 -0
  112. package/dist/framework/createApp.d.ts +6 -0
  113. package/dist/framework/createApp.js +9 -0
  114. package/dist/framework/domain_module.d.ts +13 -0
  115. package/dist/framework/domain_module.js +18 -0
  116. package/dist/framework/kernel.d.ts +18 -0
  117. package/dist/framework/kernel.js +60 -0
  118. package/dist/framework/orchestrator.d.ts +29 -0
  119. package/dist/framework/orchestrator.js +118 -0
  120. package/dist/index.d.ts +16 -0
  121. package/dist/index.js +11 -0
  122. package/dist/infrastructure/database/connector.d.ts +13 -0
  123. package/dist/infrastructure/database/connector.js +165 -0
  124. package/dist/infrastructure/database/index.d.ts +2 -0
  125. package/dist/infrastructure/database/index.js +2 -0
  126. package/dist/infrastructure/database/mapped.d.ts +128 -0
  127. package/dist/infrastructure/database/mapped.js +174 -0
  128. package/dist/infrastructure/database/utils.d.ts +10 -0
  129. package/dist/infrastructure/database/utils.js +35 -0
  130. package/dist/infrastructure/logger.d.ts +45 -0
  131. package/dist/infrastructure/logger.js +125 -0
  132. package/dist/infrastructure/ui/ToastHost.d.ts +1 -0
  133. package/dist/infrastructure/ui/ToastHost.js +19 -0
  134. package/dist/infrastructure/ui/UIEventBus.d.ts +12 -0
  135. package/dist/infrastructure/ui/UIEventBus.js +65 -0
  136. package/dist/infrastructure/ui/errorHandler.d.ts +1 -0
  137. package/dist/infrastructure/ui/errorHandler.js +20 -0
  138. package/dist/infrastructure/ui/events.d.ts +1 -0
  139. package/dist/infrastructure/ui/events.js +1 -0
  140. package/dist/infrastructure/workers/CronWorker.d.ts +42 -0
  141. package/dist/infrastructure/workers/CronWorker.js +143 -0
  142. package/dist/infrastructure/workers/EventBus.d.ts +67 -0
  143. package/dist/infrastructure/workers/EventBus.js +279 -0
  144. package/dist/infrastructure/workers/index.d.ts +2 -0
  145. package/dist/infrastructure/workers/index.js +2 -0
  146. package/dist/services/bluetooth.service.d.ts +14 -0
  147. package/dist/services/bluetooth.service.js +72 -0
  148. package/dist/services/database_table.service.d.ts +8 -0
  149. package/dist/services/database_table.service.js +19 -0
  150. package/dist/services/dead_letter_queue.service.d.ts +38 -0
  151. package/dist/services/dead_letter_queue.service.js +99 -0
  152. package/dist/services/event.service.d.ts +7 -0
  153. package/dist/services/event.service.js +24 -0
  154. package/dist/services/network.service.d.ts +7 -0
  155. package/dist/services/network.service.js +43 -0
  156. package/dist/services/printer.service.d.ts +25 -0
  157. package/dist/services/printer.service.js +220 -0
  158. package/dist/services/webview.service.d.ts +17 -0
  159. package/dist/services/webview.service.js +59 -0
  160. package/dist/tools/utils/Initials.d.ts +1 -0
  161. package/dist/tools/utils/Initials.js +12 -0
  162. package/dist/tools/utils/collections.d.ts +120 -0
  163. package/dist/tools/utils/collections.js +158 -0
  164. package/dist/tools/utils/date.d.ts +70 -0
  165. package/dist/tools/utils/date.js +126 -0
  166. package/dist/tools/utils/maps.d.ts +4 -0
  167. package/dist/tools/utils/maps.js +20 -0
  168. package/dist/tools/utils/monetary.d.ts +3 -0
  169. package/dist/tools/utils/monetary.js +31 -0
  170. package/dist/tools/utils/quantity.d.ts +11 -0
  171. package/dist/tools/utils/quantity.js +44 -0
  172. package/dist/tools/utils/searchTools.d.ts +39 -0
  173. package/dist/tools/utils/searchTools.js +56 -0
  174. package/dist/tools/utils/serializer.d.ts +2 -0
  175. package/dist/tools/utils/serializer.js +44 -0
  176. package/dist/ui/components/atoms/DebugBanner.d.ts +2 -0
  177. package/dist/ui/components/atoms/DebugBanner.js +15 -0
  178. package/dist/ui/components/atoms/index.d.ts +1 -0
  179. package/dist/ui/components/atoms/index.js +1 -0
  180. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.d.ts +8 -0
  181. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +61 -0
  182. package/dist/ui/components/molecules/DeadLetterErrorBlock.d.ts +8 -0
  183. package/dist/ui/components/molecules/DeadLetterErrorBlock.js +14 -0
  184. package/dist/ui/components/molecules/EventTimelineItem.d.ts +7 -0
  185. package/dist/ui/components/molecules/EventTimelineItem.js +65 -0
  186. package/dist/ui/components/molecules/ProcessToast.d.ts +5 -0
  187. package/dist/ui/components/molecules/ProcessToast.js +51 -0
  188. package/dist/ui/components/molecules/ProcessToastProvider.d.ts +4 -0
  189. package/dist/ui/components/molecules/ProcessToastProvider.js +5 -0
  190. package/dist/ui/components/molecules/index.d.ts +5 -0
  191. package/dist/ui/components/molecules/index.js +5 -0
  192. package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +7 -0
  193. package/dist/ui/components/organisms/BluetoothPrinterSelector.js +92 -0
  194. package/dist/ui/components/organisms/DatabaseInfoRow.d.ts +11 -0
  195. package/dist/ui/components/organisms/DatabaseInfoRow.js +8 -0
  196. package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +7 -0
  197. package/dist/ui/components/organisms/DeadLetterQueueRow.js +72 -0
  198. package/dist/ui/components/organisms/EventRow.d.ts +7 -0
  199. package/dist/ui/components/organisms/EventRow.js +90 -0
  200. package/dist/ui/components/organisms/InjectableWebView.d.ts +35 -0
  201. package/dist/ui/components/organisms/InjectableWebView.js +169 -0
  202. package/dist/ui/components/organisms/Receipt.d.ts +11 -0
  203. package/dist/ui/components/organisms/Receipt.js +207 -0
  204. package/dist/ui/components/organisms/TableInfoInfoRow.d.ts +9 -0
  205. package/dist/ui/components/organisms/TableInfoInfoRow.js +19 -0
  206. package/dist/ui/components/organisms/index.d.ts +7 -0
  207. package/dist/ui/components/organisms/index.js +7 -0
  208. package/dist/ui/index.d.ts +4 -0
  209. package/dist/ui/index.js +4 -0
  210. package/dist/ui/layouts/router_layouts.d.ts +17 -0
  211. package/dist/ui/layouts/router_layouts.js +20 -0
  212. package/dist/ui/screens/database/database.context.d.ts +32 -0
  213. package/dist/ui/screens/database/database.context.js +158 -0
  214. package/dist/ui/screens/database/database.json_detail.d.ts +2 -0
  215. package/dist/ui/screens/database/database.json_detail.js +19 -0
  216. package/dist/ui/screens/database/database.list.d.ts +1 -0
  217. package/dist/ui/screens/database/database.list.js +41 -0
  218. package/dist/ui/screens/database/database.table_rows.d.ts +2 -0
  219. package/dist/ui/screens/database/database.table_rows.js +10 -0
  220. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +34 -0
  221. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.js +166 -0
  222. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.d.ts +2 -0
  223. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +24 -0
  224. package/dist/ui/screens/events/events.context.d.ts +25 -0
  225. package/dist/ui/screens/events/events.context.js +113 -0
  226. package/dist/ui/screens/events/events.list.d.ts +1 -0
  227. package/dist/ui/screens/events/events.list.js +26 -0
  228. package/dist/ui/screens/events/index.d.ts +1 -0
  229. package/dist/ui/screens/events/index.js +1 -0
  230. package/dist/ui/screens/index.d.ts +3 -0
  231. package/dist/ui/screens/index.js +3 -0
  232. package/package.json +125 -0
@@ -0,0 +1,824 @@
1
+ import { generateUUID } from "../../infrastructure/database/utils";
2
+ import { loggerQueueProcessor } from "../../infrastructure/logger";
3
+ import { EventBus } from "../../infrastructure/workers";
4
+ import { convertToArray } from "../../tools/utils/collections";
5
+ import { safeJsonStringify } from "../../tools/utils/serializer";
6
+ import { ValueObject } from "./value_object";
7
+ /**
8
+ * Type-safe, immutable collection for Entity aggregates with event sourcing support.
9
+ * Implements IEventSourced for domain event publishing and provides functional operations.
10
+ *
11
+ * @example
12
+ * const collection = new EntityCollection([priceList1, priceList2]);
13
+ *
14
+ * // Functional operations (immutable)
15
+ * const active = collection.filter(pl => pl.isActive).sortBy(pl => pl.name);
16
+ *
17
+ * // Aggregations
18
+ * const total = collection.sumBy(pl => pl.price);
19
+ * const cheapest = collection.minBy(pl => pl.price);
20
+ *
21
+ * // Set operations
22
+ * const union = collection1.union(collection2);
23
+ * const common = collection1.intersect(collection2);
24
+ *
25
+ * // Event sourcing
26
+ * await collection.publishEventWith(PRICE_LISTS_SYNCED, pl => ({ id: pl.remoteId, name: pl.name }));
27
+ */
28
+ export class EntityCollection extends ValueObject {
29
+ uuid;
30
+ eventIds = [];
31
+ entities = [];
32
+ _domainEventsV2 = [];
33
+ _correlationIdV2 = null;
34
+ constructor(entities = []) {
35
+ super();
36
+ this.uuid = generateUUID();
37
+ this.entities = entities;
38
+ }
39
+ /** Factory method for creating instances - override in subclasses */
40
+ createInstance(entities) {
41
+ return new EntityCollection(entities);
42
+ }
43
+ /** Creates collection from array of entities - polymorphic, works with subclasses */
44
+ static from(entities) {
45
+ return new this(entities);
46
+ }
47
+ /**
48
+ * Converts entity, array, or collection to plain array.
49
+ * Useful for repository save methods that accept multiple input types.
50
+ * Supports: single entity, arrays, Set, EntityCollection
51
+ *
52
+ * @example
53
+ * const entities = EntityCollection.toArrayFrom(entity);
54
+ * // Works with: single entity, array, Set, or EntityCollection
55
+ */
56
+ static toArrayFrom(entity) {
57
+ if (entity instanceof EntityCollection) {
58
+ return entity.toArray();
59
+ }
60
+ return convertToArray(entity);
61
+ }
62
+ /** Returns total number of entities in collection */
63
+ get length() {
64
+ return this.entities.length;
65
+ }
66
+ /** Checks if collection has no entities */
67
+ get isEmpty() {
68
+ return this.entities.length === 0;
69
+ }
70
+ /** Checks if collection has at least one entity */
71
+ get isNotEmpty() {
72
+ return this.entities.length > 0;
73
+ }
74
+ /**
75
+ * Enables native JavaScript/TypeScript boolean evaluation.
76
+ * Collection behaves like primitives in conditional contexts.
77
+ *
78
+ * Compatible with language keywords:
79
+ * - if (collection) { } // true if has entities
80
+ * - !collection // true if empty
81
+ * - collection && doSomething() // executes if not empty
82
+ * - collection || defaultValue // uses collection if not empty
83
+ * - collection ? a : b // ternary operator
84
+ * - while (collection) { } // loops while not empty
85
+ * - Boolean(collection) // explicit boolean conversion
86
+ * - +collection // converts to number (length)
87
+ * - String(collection) // converts to "EntityCollection(n)"
88
+ *
89
+ * @example
90
+ * if (priceLists) { console.log("Has items"); }
91
+ * const items = priceLists || new EntityCollection();
92
+ * !emptyCollection // true
93
+ */
94
+ [Symbol.toPrimitive](hint) {
95
+ if (hint === "number") {
96
+ return this.length;
97
+ }
98
+ if (hint === "string") {
99
+ return `EntityCollection(${this.length})`;
100
+ }
101
+ return this.isNotEmpty;
102
+ }
103
+ /**
104
+ * Returns numeric value for collection (length).
105
+ * Enables: +collection, Number(collection)
106
+ */
107
+ valueOf() {
108
+ return this.length;
109
+ }
110
+ /** Converts collection to plain array (creates new copy) */
111
+ toArray() {
112
+ return [...this.entities];
113
+ }
114
+ /** Gets entity at specific index (0-based) */
115
+ at(index) {
116
+ return this.entities[index];
117
+ }
118
+ /** Gets entity at specific index (0-based) */
119
+ get(index) {
120
+ return this.entities[index];
121
+ }
122
+ /** Checks if collection contains entity (uses Entity.equals) */
123
+ includes(entity) {
124
+ return this.entities.some((e) => e.equals(entity));
125
+ }
126
+ /** Finds index of entity in collection (uses Entity.equals) */
127
+ indexOf(entity) {
128
+ return this.entities.findIndex((e) => e.equals(entity));
129
+ }
130
+ /** Concatenates multiple collections into new collection */
131
+ concat(...collections) {
132
+ const allEntities = [this.entities];
133
+ for (const collection of collections) {
134
+ allEntities.push(collection.toArray());
135
+ }
136
+ return this.createInstance(allEntities.flat());
137
+ }
138
+ /** Returns subset of collection from start to end index */
139
+ slice(start, end) {
140
+ return this.createInstance(this.entities.slice(start, end));
141
+ }
142
+ /** Returns first entity in collection */
143
+ first() {
144
+ return this.entities[0];
145
+ }
146
+ /** Returns last entity in collection */
147
+ last() {
148
+ return this.entities[this.entities.length - 1];
149
+ }
150
+ /** Adds entity to collection (mutates) */
151
+ add(entity) {
152
+ this.entities.push(entity);
153
+ return this;
154
+ }
155
+ /** Adds multiple entities to collection (mutates) */
156
+ addAll(entities) {
157
+ this.entities.push(...entities);
158
+ return this;
159
+ }
160
+ /** Removes entity from collection (mutates, uses Entity.equals) */
161
+ remove(entity) {
162
+ const index = this.entities.findIndex((e) => e.equals(entity));
163
+ if (index !== -1) {
164
+ this.entities.splice(index, 1);
165
+ }
166
+ return this;
167
+ }
168
+ /** Removes all entities from collection (mutates) */
169
+ clear() {
170
+ this.entities = [];
171
+ return this;
172
+ }
173
+ /** Makes collection iterable (use with for...of loops) */
174
+ [Symbol.iterator]() {
175
+ return this.entities[Symbol.iterator]();
176
+ }
177
+ /**
178
+ * Splits collection into chunks of specified size
179
+ * @example
180
+ * const pages = collection.chunk(10); // [[...10], [...10], ...]
181
+ * pages.forEach((page, i) => console.log(`Page ${i}: ${page.length} items`));
182
+ */
183
+ chunk(size) {
184
+ const chunks = [];
185
+ for (let i = 0; i < this.entities.length; i += size) {
186
+ chunks.push(this.createInstance(this.entities.slice(i, i + size)));
187
+ }
188
+ return chunks;
189
+ }
190
+ /** Counts entities matching predicate (returns length if no predicate) */
191
+ count(predicate) {
192
+ if (!predicate)
193
+ return this.length;
194
+ return this.entities.filter(predicate).length;
195
+ }
196
+ /** Finds entity with minimum value from selector */
197
+ minBy(selector) {
198
+ if (this.isEmpty)
199
+ return undefined;
200
+ return this.entities.reduce((min, entity) => selector(entity) < selector(min) ? entity : min);
201
+ }
202
+ /** Finds entity with maximum value from selector */
203
+ maxBy(selector) {
204
+ if (this.isEmpty)
205
+ return undefined;
206
+ return this.entities.reduce((max, entity) => selector(entity) > selector(max) ? entity : max);
207
+ }
208
+ /** Sums numeric values from selector across all entities */
209
+ sumBy(selector) {
210
+ return this.entities.reduce((sum, entity) => sum + selector(entity), 0);
211
+ }
212
+ /** Calculates average of numeric values from selector */
213
+ averageBy(selector) {
214
+ if (this.isEmpty)
215
+ return 0;
216
+ return this.sumBy(selector) / this.length;
217
+ }
218
+ /** Finds last entity matching predicate (searches from end) */
219
+ findLast(predicate) {
220
+ for (let i = this.entities.length - 1; i >= 0; i--) {
221
+ if (predicate(this.entities[i])) {
222
+ return this.entities[i];
223
+ }
224
+ }
225
+ return undefined;
226
+ }
227
+ /**
228
+ * Converts collection to Map using key selector (for fast lookups)
229
+ * @example
230
+ * const priceListMap = collection.toMap(pl => pl.remoteId!);
231
+ * const priceList = priceListMap.get(123); // O(1) lookup
232
+ */
233
+ toMap(keySelector) {
234
+ const map = new Map();
235
+ for (const entity of this.entities) {
236
+ map.set(keySelector(entity), entity);
237
+ }
238
+ return map;
239
+ }
240
+ /**
241
+ * Converts collection to plain object using key and value selectors
242
+ * @example
243
+ * const names = collection.toRecord(pl => pl.remoteId!, pl => pl.name);
244
+ * // { 1: "Premium", 2: "Basic" }
245
+ */
246
+ toRecord(keySelector, valueSelector) {
247
+ const record = {};
248
+ for (const entity of this.entities) {
249
+ record[keySelector(entity)] = valueSelector(entity);
250
+ }
251
+ return record;
252
+ }
253
+ /** Checks if collection contains any of provided entities */
254
+ containsAny(entities) {
255
+ return entities.some((entity) => this.includes(entity));
256
+ }
257
+ /** Checks if collection contains all provided entities */
258
+ containsAll(entities) {
259
+ return entities.every((entity) => this.includes(entity));
260
+ }
261
+ /** Returns new collection with entities matching predicate */
262
+ filter(predicate) {
263
+ return this.createInstance(this.entities.filter(predicate));
264
+ }
265
+ /** Transforms entities to new type, returns new collection */
266
+ map(mapper) {
267
+ return new EntityCollection(this.entities.map(mapper));
268
+ }
269
+ /** Transforms entities to any type, returns plain array */
270
+ mapToArray(mapper) {
271
+ return this.entities.map(mapper);
272
+ }
273
+ /** Maps and flattens results into new collection */
274
+ flatMap(mapper) {
275
+ return new EntityCollection(this.entities.flatMap(mapper));
276
+ }
277
+ /** Maps and flattens results into plain array */
278
+ flatMapToArray(mapper) {
279
+ return this.entities.flatMap(mapper);
280
+ }
281
+ /** Finds first entity matching predicate */
282
+ find(predicate) {
283
+ return this.entities.find(predicate);
284
+ }
285
+ /** Finds index of first entity matching predicate */
286
+ findIndex(predicate) {
287
+ return this.entities.findIndex(predicate);
288
+ }
289
+ /** Checks if at least one entity matches predicate */
290
+ some(predicate) {
291
+ return this.entities.some(predicate);
292
+ }
293
+ /** Checks if all entities match predicate */
294
+ every(predicate) {
295
+ return this.entities.every(predicate);
296
+ }
297
+ /** Executes callback for each entity */
298
+ forEach(callback) {
299
+ this.entities.forEach(callback);
300
+ }
301
+ /** Reduces collection to single value using reducer function */
302
+ reduce(reducer, initialValue) {
303
+ return this.entities.reduce(reducer, initialValue);
304
+ }
305
+ /** Sorts collection using compare function, returns new collection */
306
+ sort(compareFn) {
307
+ return this.createInstance([...this.entities].sort(compareFn));
308
+ }
309
+ /** Sorts collection by key selector, returns new collection */
310
+ sortBy(keySelector) {
311
+ return this.sort((a, b) => {
312
+ const aVal = keySelector(a);
313
+ const bVal = keySelector(b);
314
+ if (aVal < bVal)
315
+ return -1;
316
+ if (aVal > bVal)
317
+ return 1;
318
+ return 0;
319
+ });
320
+ }
321
+ /**
322
+ * Groups entities by key into Map of collections
323
+ * @example
324
+ * const byTyp = collection.groupBy(pl => pl.currencyId);
325
+ * // Map<number, EntityCollection<PriceList>>
326
+ * byType.get(1)?.forEach(pl => console.log(pl.name));
327
+ */
328
+ groupBy(keySelector) {
329
+ const groups = new Map();
330
+ for (const entity of this.entities) {
331
+ const key = keySelector(entity);
332
+ if (!groups.has(key)) {
333
+ groups.set(key, []);
334
+ }
335
+ groups.get(key).push(entity);
336
+ }
337
+ const result = new Map();
338
+ for (const [key, entities] of groups.entries()) {
339
+ result.set(key, this.createInstance(entities));
340
+ }
341
+ return result;
342
+ }
343
+ /**
344
+ * Splits collection into [matching, non-matching] tuple
345
+ * @example
346
+ * const [active, inactive] = collection.partition(pl => pl.isActive);
347
+ */
348
+ partition(predicate) {
349
+ const truthy = [];
350
+ const falsy = [];
351
+ for (const entity of this.entities) {
352
+ if (predicate(entity)) {
353
+ truthy.push(entity);
354
+ }
355
+ else {
356
+ falsy.push(entity);
357
+ }
358
+ }
359
+ return [this.createInstance(truthy), this.createInstance(falsy)];
360
+ }
361
+ /** Returns collection with unique entities (uses UUID by default or custom key selector) */
362
+ distinct(keySelector) {
363
+ if (!keySelector) {
364
+ const seen = new Set();
365
+ const unique = [];
366
+ for (const entity of this.entities) {
367
+ if (!seen.has(entity.uuid)) {
368
+ seen.add(entity.uuid);
369
+ unique.push(entity);
370
+ }
371
+ }
372
+ return this.createInstance(unique);
373
+ }
374
+ const seen = new Set();
375
+ const unique = [];
376
+ for (const entity of this.entities) {
377
+ const key = keySelector(entity);
378
+ if (!seen.has(key)) {
379
+ seen.add(key);
380
+ unique.push(entity);
381
+ }
382
+ }
383
+ return this.createInstance(unique);
384
+ }
385
+ /** Returns first N entities from collection */
386
+ take(count) {
387
+ return this.createInstance(this.entities.slice(0, count));
388
+ }
389
+ /** Skips first N entities, returns rest */
390
+ skip(count) {
391
+ return this.createInstance(this.entities.slice(count));
392
+ }
393
+ /** Returns union of collections (no duplicates by uuid) */
394
+ union(other) {
395
+ const combined = [...this.entities, ...other.entities];
396
+ return this.createInstance(combined).distinct((e) => e.uuid);
397
+ }
398
+ /** Returns entities present in both collections */
399
+ intersect(other) {
400
+ const otherUuids = new Set(other.mapToArray((e) => e.uuid));
401
+ return this.filter((e) => otherUuids.has(e.uuid));
402
+ }
403
+ /** Returns entities in this collection but not in other */
404
+ except(other) {
405
+ const otherUuids = new Set(other.mapToArray((e) => e.uuid));
406
+ return this.filter((e) => !otherUuids.has(e.uuid));
407
+ }
408
+ /**
409
+ * Returns entities in this collection but not in other using custom selector
410
+ * @example
411
+ * const different = collection1.exceptBy(collection2, e => e.name);
412
+ */
413
+ exceptBy(other, keySelector) {
414
+ const otherKeys = new Set(other.mapToArray(keySelector));
415
+ return this.filter((e) => !otherKeys.has(keySelector(e)));
416
+ }
417
+ /**
418
+ * Finds entity by UUID
419
+ * @example
420
+ * const entity = collection.findByUuid("abc-123-def");
421
+ */
422
+ findByUuid(uuid) {
423
+ return this.find((e) => e.uuid === uuid);
424
+ }
425
+ /**
426
+ * Finds entities by multiple UUIDs
427
+ * @example
428
+ * const entities = collection.findByUuids(["uuid1", "uuid2", "uuid3"]);
429
+ */
430
+ findByUuids(uuids) {
431
+ const uuidSet = new Set(uuids);
432
+ return this.filter((e) => uuidSet.has(e.uuid));
433
+ }
434
+ /**
435
+ * Checks if collection contains entity with given UUID
436
+ * @example
437
+ * if (collection.hasUuid("abc-123")) { ... }
438
+ */
439
+ hasUuid(uuid) {
440
+ return this.some((e) => e.uuid === uuid);
441
+ }
442
+ /**
443
+ * Extracts all UUIDs as plain array
444
+ * @example
445
+ * const uuids = collection.getUuids();
446
+ * await deleteByUuids(uuids);
447
+ */
448
+ getUuids() {
449
+ return this.mapToArray((e) => e.uuid);
450
+ }
451
+ /**
452
+ * Creates a Map indexed by UUID for O(1) lookups
453
+ * @example
454
+ * const byUuid = collection.toMapByUuid();
455
+ * const entity = byUuid.get("abc-123"); // Fast lookup
456
+ */
457
+ toMapByUuid() {
458
+ return this.toMap((e) => e.uuid);
459
+ }
460
+ // ============================================
461
+ // V2 METHODS - DomainEvent class-based system
462
+ // ============================================
463
+ addDomainEvent(EventClass, payload) {
464
+ const event = EventClass.create(payload);
465
+ const existingEvent = this._domainEventsV2.find((e) => e.hasSameBusinessPayload(event));
466
+ if (existingEvent) {
467
+ loggerQueueProcessor.warn(`EntityCollection - V2 Event: '${event.name}' with identical payload already exists. Skipping duplicate.`);
468
+ return existingEvent.uuid;
469
+ }
470
+ if (!this._correlationIdV2) {
471
+ this._correlationIdV2 = generateUUID();
472
+ }
473
+ event
474
+ .withAggregateId(this.uuid)
475
+ .withCorrelationId(this._correlationIdV2)
476
+ .withSequence(this._domainEventsV2.length + 1);
477
+ this._domainEventsV2.push(event);
478
+ this.eventIds.push(event.uuid);
479
+ for (const entity of this.entities) {
480
+ entity.eventIds.push(event.uuid);
481
+ }
482
+ return event.uuid;
483
+ }
484
+ addDomainEventWithEntities(EventClass) {
485
+ const event = EventClass.create({
486
+ records: this.entities.map((e) => e.asJSON()),
487
+ });
488
+ const existingEvent = this._domainEventsV2.find((e) => e.hasSameBusinessPayload(event));
489
+ if (existingEvent) {
490
+ loggerQueueProcessor.warn(`EntityCollection - V2 Event: '${event.name}' with identical payload already exists. Skipping duplicate.`);
491
+ return existingEvent.uuid;
492
+ }
493
+ if (!this._correlationIdV2) {
494
+ this._correlationIdV2 = generateUUID();
495
+ }
496
+ event
497
+ .withAggregateId(this.uuid)
498
+ .withCorrelationId(this._correlationIdV2)
499
+ .withSequence(this._domainEventsV2.length + 1);
500
+ this._domainEventsV2.push(event);
501
+ this.eventIds.push(event.uuid);
502
+ for (const entity of this.entities) {
503
+ entity.eventIds.push(event.uuid);
504
+ }
505
+ return event.uuid;
506
+ }
507
+ getDomainEvents() {
508
+ return this._domainEventsV2.map((e) => e.clone());
509
+ }
510
+ clearDomainEvents() {
511
+ this._domainEventsV2 = [];
512
+ this._correlationIdV2 = null;
513
+ }
514
+ async publishAllDomainEvents() {
515
+ const events = this._domainEventsV2;
516
+ for (const event of events) {
517
+ await EventBus.publish(event);
518
+ }
519
+ this.clearDomainEvents();
520
+ }
521
+ async publishAllDomainEventsSync() {
522
+ const events = this._domainEventsV2;
523
+ const errors = [];
524
+ for (const event of events) {
525
+ try {
526
+ await EventBus.publishSync(event);
527
+ }
528
+ catch (error) {
529
+ loggerQueueProcessor.warn(`EntityCollection - Error publishing V2 domain event '${event.name}': ${error.message}`);
530
+ errors.push(error);
531
+ }
532
+ }
533
+ this.clearDomainEvents();
534
+ if (errors.length > 0) {
535
+ const errorList = errors.map((e) => ` - ${e.message}`).join("\n");
536
+ throw new Error(`Errors occurred while publishing V2 domain events synchronously:\n${errorList}`);
537
+ }
538
+ }
539
+ async publishDomainEvent(EventClass, payload) {
540
+ this.addDomainEvent(EventClass, payload);
541
+ await this.publishAllDomainEvents();
542
+ }
543
+ async publishDomainEventSync(EventClass, payload) {
544
+ this.addDomainEvent(EventClass, payload);
545
+ await this.publishAllDomainEventsSync();
546
+ }
547
+ async publishDomainEventWithEntities(EventClass) {
548
+ this.addDomainEventWithEntities(EventClass);
549
+ await this.publishAllDomainEvents();
550
+ }
551
+ async publishDomainEventWithEntitiesSync(EventClass) {
552
+ this.addDomainEventWithEntities(EventClass);
553
+ await this.publishAllDomainEventsSync();
554
+ }
555
+ /**
556
+ * Serializes collection as array of entity JSONs.
557
+ * Collection is a manager, not an entity, so it serializes as pure entity list.
558
+ * @example
559
+ * const json = collection.asJSON();
560
+ * await saveToDatabase(json);
561
+ */
562
+ asJSON(pretty = false) {
563
+ if (!pretty) {
564
+ return safeJsonStringify(this.entities.map((e) => e.asJSON(pretty)), pretty);
565
+ }
566
+ return this.entities.map((e) => e.asJSON(pretty));
567
+ }
568
+ /**
569
+ * Filters entities using criteria array. All criteria must match (AND).
570
+ * @example
571
+ * const filtered = collection.findByCriteria([
572
+ * { field: 'customerId', operator: '=', value: 123 },
573
+ * { field: 'state', operator: 'in', value: ['draft', 'posted'] }
574
+ * ]);
575
+ */
576
+ findByCriteria(criteria) {
577
+ const filtered = this.filter((entity) => {
578
+ return criteria.every((c) => {
579
+ const fieldValue = entity[c.field];
580
+ switch (c.operator) {
581
+ case "=":
582
+ return fieldValue === c.value;
583
+ case "!=":
584
+ return fieldValue !== c.value;
585
+ case "in":
586
+ return Array.isArray(c.value) && c.value.includes(fieldValue);
587
+ case "not_in":
588
+ return Array.isArray(c.value) && !c.value.includes(fieldValue);
589
+ case "like":
590
+ return (typeof fieldValue === "string" &&
591
+ fieldValue.toLowerCase().includes(String(c.value).toLowerCase()));
592
+ case ">":
593
+ return fieldValue > c.value;
594
+ case "<":
595
+ return fieldValue < c.value;
596
+ case ">=":
597
+ return fieldValue >= c.value;
598
+ case "<=":
599
+ return fieldValue <= c.value;
600
+ default:
601
+ return false;
602
+ }
603
+ });
604
+ });
605
+ return this.createInstance(filtered.toArray());
606
+ }
607
+ /**
608
+ * Deserializes collection from array of entity JSONs.
609
+ * Collection is a manager, not an entity, so it deserializes from pure entity list.
610
+ * @example
611
+ * const collection = EntityCollection.fromJSON<PriceList>(json, PriceList);
612
+ */
613
+ static fromJSON(json, EntityClass) {
614
+ if (!EntityClass) {
615
+ throw new Error("EntityClass is required for EntityCollection.fromJSON");
616
+ }
617
+ const data = typeof json === "string" ? JSON.parse(json) : json;
618
+ if (!Array.isArray(data)) {
619
+ throw new Error("EntityCollection.fromJSON expects array of entity JSONs");
620
+ }
621
+ const entities = data.map((entityData) => EntityClass.prototype.fromJSON.call(EntityClass, entityData));
622
+ return new EntityCollection(entities);
623
+ }
624
+ }
625
+ /**
626
+ * Collection for RemoteEntity aggregates with DTO mapping support.
627
+ * Extends EntityCollection with fromRemoteDTO for batch entity creation.
628
+ *
629
+ * @example
630
+ * const collection = RemoteEntityCollection.fromRemoteDTO(
631
+ * remoteDTOs,
632
+ * PriceList
633
+ * );
634
+ * await collection.publishIds(PRICE_LISTS_SYNCED);
635
+ */
636
+ export class RemoteEntityCollection extends EntityCollection {
637
+ /**
638
+ * Creates collection from remote DTOs using entity's fromRemoteDTO method
639
+ * @example
640
+ * const priceLists = RemoteEntityCollection.fromRemoteDTO(
641
+ * apiResponse.records,
642
+ * PriceList
643
+ * );
644
+ */
645
+ static fromRemoteDTO(dtos, EntityClass) {
646
+ const entities = dtos.map((dto) => EntityClass.fromRemoteDTO(dto));
647
+ return new RemoteEntityCollection(entities);
648
+ }
649
+ /** Factory method override to create RemoteEntityCollection instances */
650
+ createInstance(entities) {
651
+ return new RemoteEntityCollection(entities);
652
+ }
653
+ // ============================================================================
654
+ // INHERITED METHODS - All these return RemoteEntityCollection thanks to createInstance()
655
+ //
656
+ // The following methods are inherited from EntityCollection and automatically
657
+ // return RemoteEntityCollection<T> instead of EntityCollection<T>:
658
+ //
659
+ // - filter(predicate) → RemoteEntityCollection<T>
660
+ // - partition(predicate) → [RemoteEntityCollection<T>, RemoteEntityCollection<T>]
661
+ // - findByCriteria(criteria) → RemoteEntityCollection<T>
662
+ // - distinct(keySelector?) → RemoteEntityCollection<T>
663
+ // - slice(start?, end?) → RemoteEntityCollection<T>
664
+ // - take(count) → RemoteEntityCollection<T>
665
+ // - skip(count) → RemoteEntityCollection<T>
666
+ // - sort(compareFn) → RemoteEntityCollection<T>
667
+ // - sortBy(keySelector) → RemoteEntityCollection<T>
668
+ // - concat(...collections) → RemoteEntityCollection<T>
669
+ // - chunk(size) → RemoteEntityCollection<T>[]
670
+ // - groupBy(keySelector) → Map<K, RemoteEntityCollection<T>>
671
+ // - union(other) → RemoteEntityCollection<T>
672
+ // - intersect(other) → RemoteEntityCollection<T>
673
+ // - except(other) → RemoteEntityCollection<T>
674
+ // - exceptBy(other, key) → RemoteEntityCollection<T>
675
+ // ============================================================================
676
+ /**
677
+ * Finds entity by remoteId
678
+ * Returns undefined if entity doesn't have remoteId or not found
679
+ * @example
680
+ * const customer = customers.findByRemoteId(123);
681
+ */
682
+ findByRemoteId(remoteId) {
683
+ return this.find((e) => e.remoteId === remoteId);
684
+ }
685
+ /**
686
+ * Finds entities by multiple remoteIds
687
+ * @example
688
+ * const products = collection.findByRemoteIds([1, 2, 3, 4, 5]);
689
+ */
690
+ findByRemoteIds(remoteIds) {
691
+ const remoteIdSet = new Set(remoteIds);
692
+ return new RemoteEntityCollection(this.filter((e) => e.remoteId != null && remoteIdSet.has(e.remoteId)).toArray());
693
+ }
694
+ /**
695
+ * Checks if collection contains entity with given remoteId
696
+ * @example
697
+ * if (customers.hasRemoteId(123)) { ... }
698
+ */
699
+ hasRemoteId(remoteId) {
700
+ return this.some((e) => e.remoteId === remoteId);
701
+ }
702
+ /**
703
+ * Returns entities that have remoteId set (synced with backend)
704
+ * @example
705
+ * const syncedEntities = collection.withRemoteId();
706
+ */
707
+ withRemoteId() {
708
+ return new RemoteEntityCollection(this.filter((e) => e.remoteId != null).toArray());
709
+ }
710
+ /**
711
+ * Returns entities without remoteId (local-only, not synced)
712
+ * @example
713
+ * const localOnlyEntities = collection.withoutRemoteId();
714
+ */
715
+ withoutRemoteId() {
716
+ return new RemoteEntityCollection(this.filter((e) => e.remoteId == null).toArray());
717
+ }
718
+ /**
719
+ * Extracts all remoteIds as plain array (filters out null/undefined)
720
+ * @example
721
+ * const remoteIds = collection.getRemoteIds();
722
+ * await syncWithBackend(remoteIds);
723
+ */
724
+ getRemoteIds() {
725
+ return this.mapToArray((e) => e.remoteId).filter((id) => id != null);
726
+ }
727
+ /**
728
+ * Creates a Map indexed by remoteId for O(1) lookups
729
+ * Filters out entities without remoteId
730
+ * @example
731
+ * const byRemoteId = collection.toMapByRemoteId();
732
+ * const customer = byRemoteId.get(123); // Fast lookup
733
+ */
734
+ toMapByRemoteId() {
735
+ const map = new Map();
736
+ for (const entity of this) {
737
+ if (entity.remoteId != null) {
738
+ map.set(entity.remoteId, entity);
739
+ }
740
+ }
741
+ return map;
742
+ }
743
+ /**
744
+ * Returns entities in this collection but not in other, comparing by remoteId
745
+ * Useful for finding entities that exist locally but not in backend response
746
+ * @example
747
+ * const localOnly = localCollection.exceptByRemoteId(backendCollection);
748
+ */
749
+ exceptByRemoteId(other) {
750
+ const otherRemoteIds = new Set(other.getRemoteIds());
751
+ return new RemoteEntityCollection(this.filter((e) => e.remoteId == null || !otherRemoteIds.has(e.remoteId)).toArray());
752
+ }
753
+ /**
754
+ * Returns entities present in both collections, comparing by remoteId
755
+ * Useful for finding entities that exist in both local and backend
756
+ * @example
757
+ * const inBoth = localCollection.intersectByRemoteId(backendCollection);
758
+ */
759
+ intersectByRemoteId(other) {
760
+ const otherRemoteIds = new Set(other.getRemoteIds());
761
+ return new RemoteEntityCollection(this.filter((e) => e.remoteId != null && otherRemoteIds.has(e.remoteId)).toArray());
762
+ }
763
+ /**
764
+ * Removes duplicates by remoteId (keeps first occurrence)
765
+ * Entities without remoteId are kept and deduplicated by uuid
766
+ * @example
767
+ * const unique = collection.distinctByRemoteId();
768
+ */
769
+ distinctByRemoteId() {
770
+ const seen = new Set();
771
+ const unique = [];
772
+ for (const entity of this) {
773
+ const key = entity.remoteId ?? entity.uuid;
774
+ if (!seen.has(key)) {
775
+ seen.add(key);
776
+ unique.push(entity);
777
+ }
778
+ }
779
+ return new RemoteEntityCollection(unique);
780
+ }
781
+ /**
782
+ * Filters entities pending synchronization with backend
783
+ * @example
784
+ * const pending = collection.findPendingSync();
785
+ * for (const entity of pending) {
786
+ * await pushToBackend(entity);
787
+ * }
788
+ */
789
+ findPendingSync() {
790
+ return new RemoteEntityCollection(this.filter((e) => e.remoteState === "PENDING").toArray());
791
+ }
792
+ /**
793
+ * Filters entities successfully synced with backend
794
+ * @example
795
+ * const synced = collection.findSynced();
796
+ * console.log(`${synced.length} entities synced`);
797
+ */
798
+ findSynced() {
799
+ return new RemoteEntityCollection(this.filter((e) => e.remoteState === "SYNCED").toArray());
800
+ }
801
+ /**
802
+ * Filters entities that failed synchronization
803
+ * @example
804
+ * const failed = collection.findFailed();
805
+ * failed.forEach(e => logger.error(`Failed to sync: ${e.uuid}`));
806
+ */
807
+ findFailed() {
808
+ return new RemoteEntityCollection(this.filter((e) => e.remoteState === "FAILED").toArray());
809
+ }
810
+ /**
811
+ * Groups entities by remote state
812
+ * @example
813
+ * const byState = collection.groupByRemoteState();
814
+ * console.log(`Pending: ${byState.get('PENDING')?.length}`);
815
+ */
816
+ groupByRemoteState() {
817
+ const groups = this.groupBy((e) => e.remoteState);
818
+ const result = new Map();
819
+ for (const [state, entities] of groups.entries()) {
820
+ result.set(state, new RemoteEntityCollection(entities.toArray()));
821
+ }
822
+ return result;
823
+ }
824
+ }