@mrtdown/core 2.0.0-alpha.1

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 (182) hide show
  1. package/README.md +107 -0
  2. package/dist/constants.d.ts +10 -0
  3. package/dist/constants.js +11 -0
  4. package/dist/constants.js.map +1 -0
  5. package/dist/helpers/calculateDurationWithinServiceHours.d.ts +2 -0
  6. package/dist/helpers/calculateDurationWithinServiceHours.js +13 -0
  7. package/dist/helpers/calculateDurationWithinServiceHours.js.map +1 -0
  8. package/dist/helpers/calculateDurationWithinServiceHours.test.d.ts +1 -0
  9. package/dist/helpers/calculateDurationWithinServiceHours.test.js +83 -0
  10. package/dist/helpers/calculateDurationWithinServiceHours.test.js.map +1 -0
  11. package/dist/helpers/computeImpactFromEvidenceClaims.d.ts +21 -0
  12. package/dist/helpers/computeImpactFromEvidenceClaims.js +293 -0
  13. package/dist/helpers/computeImpactFromEvidenceClaims.js.map +1 -0
  14. package/dist/helpers/computeImpactFromEvidenceClaims.test.d.ts +1 -0
  15. package/dist/helpers/computeImpactFromEvidenceClaims.test.js +544 -0
  16. package/dist/helpers/computeImpactFromEvidenceClaims.test.js.map +1 -0
  17. package/dist/helpers/computeStartOfDaysWithinInterval.d.ts +2 -0
  18. package/dist/helpers/computeStartOfDaysWithinInterval.js +15 -0
  19. package/dist/helpers/computeStartOfDaysWithinInterval.js.map +1 -0
  20. package/dist/helpers/computeStartOfDaysWithinInterval.test.d.ts +1 -0
  21. package/dist/helpers/computeStartOfDaysWithinInterval.test.js +126 -0
  22. package/dist/helpers/computeStartOfDaysWithinInterval.test.js.map +1 -0
  23. package/dist/helpers/estimateOpenAICost.d.ts +40 -0
  24. package/dist/helpers/estimateOpenAICost.js +55 -0
  25. package/dist/helpers/estimateOpenAICost.js.map +1 -0
  26. package/dist/helpers/keyForAffectedEntity.d.ts +7 -0
  27. package/dist/helpers/keyForAffectedEntity.js +14 -0
  28. package/dist/helpers/keyForAffectedEntity.js.map +1 -0
  29. package/dist/helpers/normalizeRecurringPeriod.d.ts +7 -0
  30. package/dist/helpers/normalizeRecurringPeriod.js +118 -0
  31. package/dist/helpers/normalizeRecurringPeriod.js.map +1 -0
  32. package/dist/helpers/normalizeRecurringPeriod.test.d.ts +1 -0
  33. package/dist/helpers/normalizeRecurringPeriod.test.js +93 -0
  34. package/dist/helpers/normalizeRecurringPeriod.test.js.map +1 -0
  35. package/dist/helpers/resolvePeriods.d.ts +224 -0
  36. package/dist/helpers/resolvePeriods.js +207 -0
  37. package/dist/helpers/resolvePeriods.js.map +1 -0
  38. package/dist/helpers/resolvePeriods.test.d.ts +1 -0
  39. package/dist/helpers/resolvePeriods.test.js +239 -0
  40. package/dist/helpers/resolvePeriods.test.js.map +1 -0
  41. package/dist/helpers/splitIntervalByServiceHours.d.ts +2 -0
  42. package/dist/helpers/splitIntervalByServiceHours.js +30 -0
  43. package/dist/helpers/splitIntervalByServiceHours.js.map +1 -0
  44. package/dist/helpers/splitIntervalByServiceHours.test.d.ts +1 -0
  45. package/dist/helpers/splitIntervalByServiceHours.test.js +152 -0
  46. package/dist/helpers/splitIntervalByServiceHours.test.js.map +1 -0
  47. package/dist/helpers/sumIntervalDuration.d.ts +2 -0
  48. package/dist/helpers/sumIntervalDuration.js +9 -0
  49. package/dist/helpers/sumIntervalDuration.js.map +1 -0
  50. package/dist/index.d.ts +18 -0
  51. package/dist/index.js +19 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/repo/MRTDownRepository.d.ts +23 -0
  54. package/dist/repo/MRTDownRepository.js +28 -0
  55. package/dist/repo/MRTDownRepository.js.map +1 -0
  56. package/dist/repo/common/FileStore.d.ts +12 -0
  57. package/dist/repo/common/FileStore.js +27 -0
  58. package/dist/repo/common/FileStore.js.map +1 -0
  59. package/dist/repo/common/StandardRepository.d.ts +32 -0
  60. package/dist/repo/common/StandardRepository.js +58 -0
  61. package/dist/repo/common/StandardRepository.js.map +1 -0
  62. package/dist/repo/common/store.d.ts +29 -0
  63. package/dist/repo/common/store.js +2 -0
  64. package/dist/repo/common/store.js.map +1 -0
  65. package/dist/repo/issue/IssueRepository.d.ts +36 -0
  66. package/dist/repo/issue/IssueRepository.js +177 -0
  67. package/dist/repo/issue/IssueRepository.js.map +1 -0
  68. package/dist/repo/issue/helpers/deriveCurrentState.d.ts +51 -0
  69. package/dist/repo/issue/helpers/deriveCurrentState.js +113 -0
  70. package/dist/repo/issue/helpers/deriveCurrentState.js.map +1 -0
  71. package/dist/repo/issue/helpers/deriveCurrentState.test.d.ts +1 -0
  72. package/dist/repo/issue/helpers/deriveCurrentState.test.js +477 -0
  73. package/dist/repo/issue/helpers/deriveCurrentState.test.js.map +1 -0
  74. package/dist/repo/landmark/LandmarkRepository.d.ts +7 -0
  75. package/dist/repo/landmark/LandmarkRepository.js +12 -0
  76. package/dist/repo/landmark/LandmarkRepository.js.map +1 -0
  77. package/dist/repo/line/LineRepository.d.ts +13 -0
  78. package/dist/repo/line/LineRepository.js +32 -0
  79. package/dist/repo/line/LineRepository.js.map +1 -0
  80. package/dist/repo/operator/OperatorRepository.d.ts +7 -0
  81. package/dist/repo/operator/OperatorRepository.js +12 -0
  82. package/dist/repo/operator/OperatorRepository.js.map +1 -0
  83. package/dist/repo/service/ServiceRepository.d.ts +19 -0
  84. package/dist/repo/service/ServiceRepository.js +39 -0
  85. package/dist/repo/service/ServiceRepository.js.map +1 -0
  86. package/dist/repo/station/StationRepository.d.ts +13 -0
  87. package/dist/repo/station/StationRepository.js +30 -0
  88. package/dist/repo/station/StationRepository.js.map +1 -0
  89. package/dist/repo/town/TownRepository.d.ts +7 -0
  90. package/dist/repo/town/TownRepository.js +12 -0
  91. package/dist/repo/town/TownRepository.js.map +1 -0
  92. package/dist/schema/Landmark.d.ts +11 -0
  93. package/dist/schema/Landmark.js +7 -0
  94. package/dist/schema/Landmark.js.map +1 -0
  95. package/dist/schema/Line.d.ts +58 -0
  96. package/dist/schema/Line.js +35 -0
  97. package/dist/schema/Line.js.map +1 -0
  98. package/dist/schema/Operator.d.ts +16 -0
  99. package/dist/schema/Operator.js +12 -0
  100. package/dist/schema/Operator.js.map +1 -0
  101. package/dist/schema/Service.d.ts +55 -0
  102. package/dist/schema/Service.js +24 -0
  103. package/dist/schema/Service.js.map +1 -0
  104. package/dist/schema/Station.d.ts +29 -0
  105. package/dist/schema/Station.js +25 -0
  106. package/dist/schema/Station.js.map +1 -0
  107. package/dist/schema/Town.d.ts +11 -0
  108. package/dist/schema/Town.js +7 -0
  109. package/dist/schema/Town.js.map +1 -0
  110. package/dist/schema/common.d.ts +24 -0
  111. package/dist/schema/common.js +23 -0
  112. package/dist/schema/common.js.map +1 -0
  113. package/dist/schema/issue/bundle.d.ts +239 -0
  114. package/dist/schema/issue/bundle.js +11 -0
  115. package/dist/schema/issue/bundle.js.map +1 -0
  116. package/dist/schema/issue/cause.d.ts +51 -0
  117. package/dist/schema/issue/cause.js +30 -0
  118. package/dist/schema/issue/cause.js.map +1 -0
  119. package/dist/schema/issue/claim.d.ts +149 -0
  120. package/dist/schema/issue/claim.js +36 -0
  121. package/dist/schema/issue/claim.js.map +1 -0
  122. package/dist/schema/issue/entity.d.ts +176 -0
  123. package/dist/schema/issue/entity.js +35 -0
  124. package/dist/schema/issue/entity.js.map +1 -0
  125. package/dist/schema/issue/evidence.d.ts +124 -0
  126. package/dist/schema/issue/evidence.js +30 -0
  127. package/dist/schema/issue/evidence.js.map +1 -0
  128. package/dist/schema/issue/facilityEffect.d.ts +15 -0
  129. package/dist/schema/issue/facilityEffect.js +12 -0
  130. package/dist/schema/issue/facilityEffect.js.map +1 -0
  131. package/dist/schema/issue/id.d.ts +3 -0
  132. package/dist/schema/issue/id.js +6 -0
  133. package/dist/schema/issue/id.js.map +1 -0
  134. package/dist/schema/issue/impactEvent.d.ts +373 -0
  135. package/dist/schema/issue/impactEvent.js +43 -0
  136. package/dist/schema/issue/impactEvent.js.map +1 -0
  137. package/dist/schema/issue/issue.d.ts +19 -0
  138. package/dist/schema/issue/issue.js +13 -0
  139. package/dist/schema/issue/issue.js.map +1 -0
  140. package/dist/schema/issue/issueType.d.ts +7 -0
  141. package/dist/schema/issue/issueType.js +3 -0
  142. package/dist/schema/issue/issueType.js.map +1 -0
  143. package/dist/schema/issue/period.d.ts +72 -0
  144. package/dist/schema/issue/period.js +32 -0
  145. package/dist/schema/issue/period.js.map +1 -0
  146. package/dist/schema/issue/serviceEffect.d.ts +29 -0
  147. package/dist/schema/issue/serviceEffect.js +21 -0
  148. package/dist/schema/issue/serviceEffect.js.map +1 -0
  149. package/dist/schema/issue/serviceScope.d.ts +38 -0
  150. package/dist/schema/issue/serviceScope.js +30 -0
  151. package/dist/schema/issue/serviceScope.js.map +1 -0
  152. package/dist/util/assert.d.ts +1 -0
  153. package/dist/util/assert.js +6 -0
  154. package/dist/util/assert.js.map +1 -0
  155. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.d.ts +7 -0
  156. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js +24 -0
  157. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js.map +1 -0
  158. package/dist/util/ingestContent/index.d.ts +12 -0
  159. package/dist/util/ingestContent/index.js +171 -0
  160. package/dist/util/ingestContent/index.js.map +1 -0
  161. package/dist/util/ingestContent/types.d.ts +32 -0
  162. package/dist/util/ingestContent/types.js +2 -0
  163. package/dist/util/ingestContent/types.js.map +1 -0
  164. package/dist/write/MRTDownWriter.d.ts +27 -0
  165. package/dist/write/MRTDownWriter.js +27 -0
  166. package/dist/write/MRTDownWriter.js.map +1 -0
  167. package/dist/write/common/FileWriteStore.d.ts +13 -0
  168. package/dist/write/common/FileWriteStore.js +31 -0
  169. package/dist/write/common/FileWriteStore.js.map +1 -0
  170. package/dist/write/common/StandardWriter.d.ts +14 -0
  171. package/dist/write/common/StandardWriter.js +17 -0
  172. package/dist/write/common/StandardWriter.js.map +1 -0
  173. package/dist/write/common/store.d.ts +32 -0
  174. package/dist/write/common/store.js +2 -0
  175. package/dist/write/common/store.js.map +1 -0
  176. package/dist/write/id/IdGenerator.d.ts +18 -0
  177. package/dist/write/id/IdGenerator.js +23 -0
  178. package/dist/write/id/IdGenerator.js.map +1 -0
  179. package/dist/write/issue/IssueWriter.d.ts +12 -0
  180. package/dist/write/issue/IssueWriter.js +33 -0
  181. package/dist/write/issue/IssueWriter.js.map +1 -0
  182. package/package.json +80 -0
@@ -0,0 +1,477 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { keyForAffectedEntity } from '../../../helpers/keyForAffectedEntity.js';
3
+ import { deriveCurrentState } from './deriveCurrentState.js';
4
+ const defaultTarget = { type: 'service', serviceId: 'NSL' };
5
+ const nslKey = keyForAffectedEntity(defaultTarget);
6
+ describe('deriveCurrentState', () => {
7
+ const createMockIssue = () => ({
8
+ id: '2025-01-01-test-issue',
9
+ type: 'disruption',
10
+ title: {
11
+ 'en-SG': 'Test Issue',
12
+ 'zh-Hans': null,
13
+ ms: null,
14
+ ta: null,
15
+ },
16
+ titleMeta: {
17
+ source: 'test',
18
+ },
19
+ });
20
+ const createMockBundle = (impact) => ({
21
+ issue: createMockIssue(),
22
+ evidence: [],
23
+ impactEvents: impact,
24
+ path: 'test/path',
25
+ });
26
+ test('returns empty state for bundle with no impacts', () => {
27
+ const bundle = createMockBundle([]);
28
+ const result = deriveCurrentState(bundle);
29
+ expect(result).toEqual({
30
+ services: {},
31
+ servicesProvenance: {},
32
+ facilities: {},
33
+ facilitiesProvenance: {},
34
+ impactEventIds: [],
35
+ });
36
+ });
37
+ test('handles service_effects.set impact', () => {
38
+ const effect = { kind: 'delay', duration: null };
39
+ const bundle = createMockBundle([
40
+ {
41
+ id: 'ie_test_001',
42
+ type: 'service_effects.set',
43
+ entity: defaultTarget,
44
+ ts: '2025-01-01T10:00:00Z',
45
+ effect,
46
+ basis: {
47
+ evidenceId: 'evidence-1',
48
+ },
49
+ },
50
+ ]);
51
+ const result = deriveCurrentState(bundle);
52
+ expect(result.services[nslKey]).toMatchObject({
53
+ id: 'NSL',
54
+ effect,
55
+ scopes: [],
56
+ periods: [],
57
+ });
58
+ expect(result.servicesProvenance[nslKey]).toEqual({
59
+ effect: { evidenceId: 'evidence-1' },
60
+ });
61
+ expect(result.impactEventIds).toEqual(['ie_test_001']);
62
+ });
63
+ test('handles periods_set impact', () => {
64
+ const periods = [
65
+ {
66
+ kind: 'fixed',
67
+ startAt: '2025-01-01T10:00:00+08:00',
68
+ endAt: '2025-01-01T12:00:00+08:00',
69
+ },
70
+ {
71
+ kind: 'fixed',
72
+ startAt: '2025-01-01T14:00:00+08:00',
73
+ endAt: null,
74
+ },
75
+ ];
76
+ const bundle = createMockBundle([
77
+ {
78
+ id: 'ie_test_001',
79
+ type: 'periods.set',
80
+ entity: defaultTarget,
81
+ ts: '2025-01-01T10:00:00Z',
82
+ periods,
83
+ basis: {
84
+ evidenceId: 'evidence-3',
85
+ },
86
+ },
87
+ ]);
88
+ const result = deriveCurrentState(bundle);
89
+ expect(result.services[nslKey]).toMatchObject({
90
+ id: 'NSL',
91
+ effect: null,
92
+ scopes: [],
93
+ periods,
94
+ });
95
+ expect(result.servicesProvenance[nslKey]).toEqual({
96
+ periods: { evidenceId: 'evidence-3' },
97
+ });
98
+ expect(result.impactEventIds).toEqual(['ie_test_001']);
99
+ });
100
+ test('handles causes.set impact', () => {
101
+ const causes = ['signal.fault', 'track.fault'];
102
+ const bundle = createMockBundle([
103
+ {
104
+ id: 'ie_test_cause_001',
105
+ type: 'causes.set',
106
+ entity: defaultTarget,
107
+ ts: '2025-01-01T10:00:00Z',
108
+ causes: [...causes],
109
+ basis: { evidenceId: 'evidence-cause' },
110
+ },
111
+ ]);
112
+ const result = deriveCurrentState(bundle);
113
+ expect(result.services[nslKey]).toMatchObject({
114
+ id: 'NSL',
115
+ effect: null,
116
+ scopes: [],
117
+ periods: [],
118
+ causes,
119
+ });
120
+ expect(result.servicesProvenance[nslKey]).toEqual({
121
+ causes: { evidenceId: 'evidence-cause' },
122
+ });
123
+ expect(result.impactEventIds).toEqual(['ie_test_cause_001']);
124
+ });
125
+ test('handles service_scopes_set impact', () => {
126
+ const serviceScopes = [
127
+ {
128
+ type: 'service.whole',
129
+ },
130
+ {
131
+ type: 'service.segment',
132
+ fromStationId: 'station-1',
133
+ toStationId: 'station-2',
134
+ },
135
+ ];
136
+ const bundle = createMockBundle([
137
+ {
138
+ id: 'ie_test_001',
139
+ type: 'service_scopes.set',
140
+ entity: defaultTarget,
141
+ ts: '2025-01-01T10:00:00Z',
142
+ serviceScopes,
143
+ basis: {
144
+ evidenceId: 'evidence-5',
145
+ },
146
+ },
147
+ ]);
148
+ const result = deriveCurrentState(bundle);
149
+ expect(result.services[nslKey]).toMatchObject({
150
+ id: 'NSL',
151
+ effect: null,
152
+ scopes: serviceScopes,
153
+ periods: [],
154
+ });
155
+ expect(result.servicesProvenance[nslKey]).toEqual({
156
+ scopes: { evidenceId: 'evidence-5' },
157
+ });
158
+ expect(result.impactEventIds).toEqual(['ie_test_001']);
159
+ });
160
+ test('handles multiple impacts - later impacts overwrite earlier ones', () => {
161
+ const firstEffect = { kind: 'delay', duration: null };
162
+ const secondEffect = { kind: 'no-service' };
163
+ const bundle = createMockBundle([
164
+ {
165
+ id: 'ie_test_001',
166
+ type: 'service_effects.set',
167
+ entity: defaultTarget,
168
+ ts: '2025-01-01T10:00:00Z',
169
+ effect: firstEffect,
170
+ basis: {
171
+ evidenceId: 'evidence-1',
172
+ },
173
+ },
174
+ {
175
+ id: 'ie_test_002',
176
+ type: 'service_effects.set',
177
+ entity: defaultTarget,
178
+ ts: '2025-01-01T11:00:00Z',
179
+ effect: secondEffect,
180
+ basis: {
181
+ evidenceId: 'evidence-2',
182
+ },
183
+ },
184
+ ]);
185
+ const result = deriveCurrentState(bundle);
186
+ expect(result.services[nslKey].effect).toEqual(secondEffect);
187
+ expect(result.servicesProvenance[nslKey].effect?.evidenceId).toBe('evidence-2');
188
+ expect(result.impactEventIds).toEqual(['ie_test_002']);
189
+ });
190
+ test('handles mixed impact types', () => {
191
+ const effect = { kind: 'delay', duration: null };
192
+ const periods = [
193
+ {
194
+ kind: 'fixed',
195
+ startAt: '2025-01-01T10:00:00+08:00',
196
+ endAt: '2025-01-01T12:00:00+08:00',
197
+ },
198
+ ];
199
+ const serviceScopes = [
200
+ {
201
+ type: 'service.point',
202
+ stationId: 'station-1',
203
+ },
204
+ ];
205
+ const bundle = createMockBundle([
206
+ {
207
+ id: 'ie_test_001',
208
+ type: 'service_effects.set',
209
+ entity: defaultTarget,
210
+ ts: '2025-01-01T10:00:00Z',
211
+ effect,
212
+ basis: {
213
+ evidenceId: 'evidence-1',
214
+ },
215
+ },
216
+ {
217
+ id: 'ie_test_002',
218
+ type: 'periods.set',
219
+ entity: defaultTarget,
220
+ ts: '2025-01-01T11:00:00Z',
221
+ periods,
222
+ basis: {
223
+ evidenceId: 'evidence-2',
224
+ },
225
+ },
226
+ {
227
+ id: 'ie_test_003',
228
+ type: 'service_scopes.set',
229
+ entity: defaultTarget,
230
+ ts: '2025-01-01T12:00:00Z',
231
+ serviceScopes,
232
+ basis: {
233
+ evidenceId: 'evidence-3',
234
+ },
235
+ },
236
+ ]);
237
+ const result = deriveCurrentState(bundle);
238
+ expect(result.services[nslKey].effect).toEqual(effect);
239
+ expect(result.services[nslKey].periods).toEqual(periods);
240
+ expect(result.services[nslKey].scopes).toEqual(serviceScopes);
241
+ expect(result.servicesProvenance[nslKey].effect?.evidenceId).toBe('evidence-1');
242
+ expect(result.servicesProvenance[nslKey].periods?.evidenceId).toBe('evidence-2');
243
+ expect(result.servicesProvenance[nslKey].scopes?.evidenceId).toBe('evidence-3');
244
+ expect(result.impactEventIds).toEqual([
245
+ 'ie_test_001',
246
+ 'ie_test_003',
247
+ 'ie_test_002',
248
+ ]);
249
+ });
250
+ test('handles all service effect kinds', () => {
251
+ const effectKinds = [
252
+ 'delay',
253
+ 'no-service',
254
+ 'reduced-service',
255
+ 'service-hours-adjustment',
256
+ ];
257
+ for (const kind of effectKinds) {
258
+ const effect = kind === 'delay' ? { kind: 'delay', duration: null } : { kind };
259
+ const bundle = createMockBundle([
260
+ {
261
+ id: 'ie_test_001',
262
+ type: 'service_effects.set',
263
+ entity: defaultTarget,
264
+ ts: '2025-01-01T10:00:00Z',
265
+ effect,
266
+ basis: {
267
+ evidenceId: 'evidence-1',
268
+ },
269
+ },
270
+ ]);
271
+ const result = deriveCurrentState(bundle);
272
+ expect(result.services[nslKey].effect).toEqual(effect);
273
+ }
274
+ });
275
+ test('handles all scope types', () => {
276
+ const allScopes = [
277
+ {
278
+ type: 'service.whole',
279
+ },
280
+ {
281
+ type: 'service.segment',
282
+ fromStationId: 'station-1',
283
+ toStationId: 'station-2',
284
+ },
285
+ {
286
+ type: 'service.point',
287
+ stationId: 'station-3',
288
+ },
289
+ ];
290
+ const bundle = createMockBundle([
291
+ {
292
+ id: 'ie_test_001',
293
+ type: 'service_scopes.set',
294
+ entity: defaultTarget,
295
+ ts: '2025-01-01T10:00:00Z',
296
+ serviceScopes: allScopes,
297
+ basis: {
298
+ evidenceId: 'evidence-1',
299
+ },
300
+ },
301
+ ]);
302
+ const result = deriveCurrentState(bundle);
303
+ expect(result.services[nslKey].scopes).toEqual(allScopes);
304
+ expect(result.services[nslKey].scopes).toHaveLength(3);
305
+ expect(result.impactEventIds).toEqual(['ie_test_001']);
306
+ });
307
+ test('handles empty arrays in impacts', () => {
308
+ const bundle = createMockBundle([
309
+ {
310
+ id: 'ie_test_001',
311
+ type: 'service_effects.set',
312
+ entity: defaultTarget,
313
+ ts: '2025-01-01T10:00:00Z',
314
+ effect: { kind: 'no-service' },
315
+ basis: {
316
+ evidenceId: 'evidence-1',
317
+ },
318
+ },
319
+ {
320
+ id: 'ie_test_002',
321
+ type: 'periods.set',
322
+ entity: defaultTarget,
323
+ ts: '2025-01-01T11:00:00Z',
324
+ periods: [],
325
+ basis: {
326
+ evidenceId: 'evidence-2',
327
+ },
328
+ },
329
+ {
330
+ id: 'ie_test_003',
331
+ type: 'service_scopes.set',
332
+ entity: defaultTarget,
333
+ ts: '2025-01-01T12:00:00Z',
334
+ serviceScopes: [],
335
+ basis: {
336
+ evidenceId: 'evidence-3',
337
+ },
338
+ },
339
+ ]);
340
+ const result = deriveCurrentState(bundle);
341
+ expect(result.services[nslKey].effect).toEqual({ kind: 'no-service' });
342
+ expect(result.services[nslKey].periods).toEqual([]);
343
+ expect(result.services[nslKey].scopes).toEqual([]);
344
+ expect(result.impactEventIds).toEqual([
345
+ 'ie_test_001',
346
+ 'ie_test_003',
347
+ 'ie_test_002',
348
+ ]);
349
+ });
350
+ test('preserves order of impacts when processing', () => {
351
+ const firstPeriods = [
352
+ {
353
+ kind: 'fixed',
354
+ startAt: '2025-01-01T10:00:00+08:00',
355
+ endAt: '2025-01-01T12:00:00+08:00',
356
+ },
357
+ ];
358
+ const secondPeriods = [
359
+ {
360
+ kind: 'fixed',
361
+ startAt: '2025-01-01T14:00:00+08:00',
362
+ endAt: null,
363
+ },
364
+ ];
365
+ const bundle = createMockBundle([
366
+ {
367
+ id: 'ie_test_001',
368
+ type: 'periods.set',
369
+ entity: defaultTarget,
370
+ ts: '2025-01-01T10:00:00Z',
371
+ periods: firstPeriods,
372
+ basis: {
373
+ evidenceId: 'evidence-1',
374
+ },
375
+ },
376
+ {
377
+ id: 'ie_test_002',
378
+ type: 'periods.set',
379
+ entity: defaultTarget,
380
+ ts: '2025-01-01T11:00:00Z',
381
+ periods: secondPeriods,
382
+ basis: {
383
+ evidenceId: 'evidence-2',
384
+ },
385
+ },
386
+ ]);
387
+ const result = deriveCurrentState(bundle);
388
+ expect(result.services[nslKey].periods).toEqual(secondPeriods);
389
+ expect(result.servicesProvenance[nslKey].periods?.evidenceId).toBe('evidence-2');
390
+ expect(result.impactEventIds).toEqual(['ie_test_002']);
391
+ });
392
+ test('groups impacts by target - different targets produce separate entries', () => {
393
+ const nslTarget = { type: 'service', serviceId: 'NSL' };
394
+ const ewlTarget = { type: 'service', serviceId: 'EWL' };
395
+ const nslKeyLocal = keyForAffectedEntity(nslTarget);
396
+ const ewlKey = keyForAffectedEntity(ewlTarget);
397
+ const bundle = createMockBundle([
398
+ {
399
+ id: 'ie_test_001',
400
+ type: 'service_effects.set',
401
+ entity: nslTarget,
402
+ ts: '2025-01-01T10:00:00Z',
403
+ effect: { kind: 'delay', duration: null },
404
+ basis: { evidenceId: 'evidence-1' },
405
+ },
406
+ {
407
+ id: 'ie_test_002',
408
+ type: 'service_effects.set',
409
+ entity: ewlTarget,
410
+ ts: '2025-01-01T10:00:00Z',
411
+ effect: { kind: 'no-service' },
412
+ basis: { evidenceId: 'evidence-2' },
413
+ },
414
+ ]);
415
+ const result = deriveCurrentState(bundle);
416
+ expect(Object.keys(result.services)).toHaveLength(2);
417
+ expect(result.services[nslKeyLocal].effect).toEqual({
418
+ kind: 'delay',
419
+ duration: null,
420
+ });
421
+ expect(result.services[nslKeyLocal]).toMatchObject({ id: 'NSL' });
422
+ expect(result.services[ewlKey].effect).toEqual({ kind: 'no-service' });
423
+ expect(result.services[ewlKey]).toMatchObject({ id: 'EWL' });
424
+ expect(result.impactEventIds).toEqual(['ie_test_002']);
425
+ });
426
+ test('handles facility impacts', () => {
427
+ const facilityEntity = {
428
+ type: 'facility',
429
+ stationId: 'JUR',
430
+ kind: 'lift',
431
+ };
432
+ const facilityKey = keyForAffectedEntity(facilityEntity);
433
+ const bundle = createMockBundle([
434
+ {
435
+ id: 'ie_test_001',
436
+ type: 'facility_effects.set',
437
+ entity: facilityEntity,
438
+ ts: '2025-01-01T10:00:00Z',
439
+ effect: { kind: 'facility-out-of-service' },
440
+ basis: { evidenceId: 'evidence-1' },
441
+ },
442
+ {
443
+ id: 'ie_test_002',
444
+ type: 'periods.set',
445
+ entity: facilityEntity,
446
+ ts: '2025-01-01T10:00:00Z',
447
+ periods: [
448
+ {
449
+ kind: 'fixed',
450
+ startAt: '2025-01-01T10:00:00+08:00',
451
+ endAt: '2025-01-01T12:00:00+08:00',
452
+ },
453
+ ],
454
+ basis: { evidenceId: 'evidence-2' },
455
+ },
456
+ ]);
457
+ const result = deriveCurrentState(bundle);
458
+ expect(result.facilities[facilityKey]).toMatchObject({
459
+ stationId: 'JUR',
460
+ kind: 'lift',
461
+ effect: { kind: 'facility-out-of-service' },
462
+ periods: [
463
+ {
464
+ kind: 'fixed',
465
+ startAt: '2025-01-01T10:00:00+08:00',
466
+ endAt: '2025-01-01T12:00:00+08:00',
467
+ },
468
+ ],
469
+ });
470
+ expect(result.facilitiesProvenance[facilityKey]).toEqual({
471
+ effect: { evidenceId: 'evidence-1' },
472
+ periods: { evidenceId: 'evidence-2' },
473
+ });
474
+ expect(result.impactEventIds).toEqual(['ie_test_002', 'ie_test_001']);
475
+ });
476
+ });
477
+ //# sourceMappingURL=deriveCurrentState.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveCurrentState.test.js","sourceRoot":"/","sources":["repo/issue/helpers/deriveCurrentState.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, test } from 'vitest';\nimport type { IssueBundle } from '#schema/issue/bundle.js';\nimport type { Period } from '#schema/issue/period.js';\nimport type { ServiceEffect } from '#schema/issue/serviceEffect.js';\nimport type { ServiceScope } from '#schema/issue/serviceScope.js';\nimport { keyForAffectedEntity } from '../../../helpers/keyForAffectedEntity.js';\nimport { deriveCurrentState } from './deriveCurrentState.js';\n\nconst defaultTarget = { type: 'service' as const, serviceId: 'NSL' };\nconst nslKey = keyForAffectedEntity(defaultTarget);\n\ndescribe('deriveCurrentState', () => {\n const createMockIssue = () => ({\n id: '2025-01-01-test-issue',\n type: 'disruption' as const,\n title: {\n 'en-SG': 'Test Issue',\n 'zh-Hans': null,\n ms: null,\n ta: null,\n },\n titleMeta: {\n source: 'test',\n },\n });\n\n const createMockBundle = (\n impact: IssueBundle['impactEvents'],\n ): IssueBundle => ({\n issue: createMockIssue(),\n evidence: [],\n impactEvents: impact,\n path: 'test/path',\n });\n\n test('returns empty state for bundle with no impacts', () => {\n const bundle = createMockBundle([]);\n const result = deriveCurrentState(bundle);\n\n expect(result).toEqual({\n services: {},\n servicesProvenance: {},\n facilities: {},\n facilitiesProvenance: {},\n impactEventIds: [],\n });\n });\n\n test('handles service_effects.set impact', () => {\n const effect: ServiceEffect = { kind: 'delay', duration: null };\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n effect,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey]).toMatchObject({\n id: 'NSL',\n effect,\n scopes: [],\n periods: [],\n });\n expect(result.servicesProvenance[nslKey]).toEqual({\n effect: { evidenceId: 'evidence-1' },\n });\n expect(result.impactEventIds).toEqual(['ie_test_001']);\n });\n\n test('handles periods_set impact', () => {\n const periods: Period[] = [\n {\n kind: 'fixed',\n startAt: '2025-01-01T10:00:00+08:00',\n endAt: '2025-01-01T12:00:00+08:00',\n },\n {\n kind: 'fixed',\n startAt: '2025-01-01T14:00:00+08:00',\n endAt: null,\n },\n ];\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'periods.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n periods,\n basis: {\n evidenceId: 'evidence-3',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey]).toMatchObject({\n id: 'NSL',\n effect: null,\n scopes: [],\n periods,\n });\n expect(result.servicesProvenance[nslKey]).toEqual({\n periods: { evidenceId: 'evidence-3' },\n });\n expect(result.impactEventIds).toEqual(['ie_test_001']);\n });\n\n test('handles causes.set impact', () => {\n const causes = ['signal.fault', 'track.fault'] as const;\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_cause_001',\n type: 'causes.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n causes: [...causes],\n basis: { evidenceId: 'evidence-cause' },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey]).toMatchObject({\n id: 'NSL',\n effect: null,\n scopes: [],\n periods: [],\n causes,\n });\n expect(result.servicesProvenance[nslKey]).toEqual({\n causes: { evidenceId: 'evidence-cause' },\n });\n expect(result.impactEventIds).toEqual(['ie_test_cause_001']);\n });\n\n test('handles service_scopes_set impact', () => {\n const serviceScopes: ServiceScope[] = [\n {\n type: 'service.whole',\n },\n {\n type: 'service.segment',\n fromStationId: 'station-1',\n toStationId: 'station-2',\n },\n ];\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_scopes.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n serviceScopes,\n basis: {\n evidenceId: 'evidence-5',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey]).toMatchObject({\n id: 'NSL',\n effect: null,\n scopes: serviceScopes,\n periods: [],\n });\n expect(result.servicesProvenance[nslKey]).toEqual({\n scopes: { evidenceId: 'evidence-5' },\n });\n expect(result.impactEventIds).toEqual(['ie_test_001']);\n });\n\n test('handles multiple impacts - later impacts overwrite earlier ones', () => {\n const firstEffect: ServiceEffect = { kind: 'delay', duration: null };\n const secondEffect: ServiceEffect = { kind: 'no-service' };\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n effect: firstEffect,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n {\n id: 'ie_test_002',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T11:00:00Z',\n effect: secondEffect,\n basis: {\n evidenceId: 'evidence-2',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].effect).toEqual(secondEffect);\n expect(result.servicesProvenance[nslKey].effect?.evidenceId).toBe(\n 'evidence-2',\n );\n expect(result.impactEventIds).toEqual(['ie_test_002']);\n });\n\n test('handles mixed impact types', () => {\n const effect: ServiceEffect = { kind: 'delay', duration: null };\n const periods: Period[] = [\n {\n kind: 'fixed',\n startAt: '2025-01-01T10:00:00+08:00',\n endAt: '2025-01-01T12:00:00+08:00',\n },\n ];\n const serviceScopes: ServiceScope[] = [\n {\n type: 'service.point',\n stationId: 'station-1',\n },\n ];\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n effect,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n {\n id: 'ie_test_002',\n type: 'periods.set',\n entity: defaultTarget,\n ts: '2025-01-01T11:00:00Z',\n periods,\n basis: {\n evidenceId: 'evidence-2',\n },\n },\n {\n id: 'ie_test_003',\n type: 'service_scopes.set',\n entity: defaultTarget,\n ts: '2025-01-01T12:00:00Z',\n serviceScopes,\n basis: {\n evidenceId: 'evidence-3',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].effect).toEqual(effect);\n expect(result.services[nslKey].periods).toEqual(periods);\n expect(result.services[nslKey].scopes).toEqual(serviceScopes);\n expect(result.servicesProvenance[nslKey].effect?.evidenceId).toBe(\n 'evidence-1',\n );\n expect(result.servicesProvenance[nslKey].periods?.evidenceId).toBe(\n 'evidence-2',\n );\n expect(result.servicesProvenance[nslKey].scopes?.evidenceId).toBe(\n 'evidence-3',\n );\n expect(result.impactEventIds).toEqual([\n 'ie_test_001',\n 'ie_test_003',\n 'ie_test_002',\n ]);\n });\n\n test('handles all service effect kinds', () => {\n const effectKinds: ServiceEffect['kind'][] = [\n 'delay',\n 'no-service',\n 'reduced-service',\n 'service-hours-adjustment',\n ];\n\n for (const kind of effectKinds) {\n const effect: ServiceEffect =\n kind === 'delay' ? { kind: 'delay', duration: null } : { kind };\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n effect,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].effect).toEqual(effect);\n }\n });\n\n test('handles all scope types', () => {\n const allScopes: ServiceScope[] = [\n {\n type: 'service.whole',\n },\n {\n type: 'service.segment',\n fromStationId: 'station-1',\n toStationId: 'station-2',\n },\n {\n type: 'service.point',\n stationId: 'station-3',\n },\n ];\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_scopes.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n serviceScopes: allScopes,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].scopes).toEqual(allScopes);\n expect(result.services[nslKey].scopes).toHaveLength(3);\n expect(result.impactEventIds).toEqual(['ie_test_001']);\n });\n\n test('handles empty arrays in impacts', () => {\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n effect: { kind: 'no-service' },\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n {\n id: 'ie_test_002',\n type: 'periods.set',\n entity: defaultTarget,\n ts: '2025-01-01T11:00:00Z',\n periods: [],\n basis: {\n evidenceId: 'evidence-2',\n },\n },\n {\n id: 'ie_test_003',\n type: 'service_scopes.set',\n entity: defaultTarget,\n ts: '2025-01-01T12:00:00Z',\n serviceScopes: [],\n basis: {\n evidenceId: 'evidence-3',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].effect).toEqual({ kind: 'no-service' });\n expect(result.services[nslKey].periods).toEqual([]);\n expect(result.services[nslKey].scopes).toEqual([]);\n expect(result.impactEventIds).toEqual([\n 'ie_test_001',\n 'ie_test_003',\n 'ie_test_002',\n ]);\n });\n\n test('preserves order of impacts when processing', () => {\n const firstPeriods: Period[] = [\n {\n kind: 'fixed',\n startAt: '2025-01-01T10:00:00+08:00',\n endAt: '2025-01-01T12:00:00+08:00',\n },\n ];\n const secondPeriods: Period[] = [\n {\n kind: 'fixed',\n startAt: '2025-01-01T14:00:00+08:00',\n endAt: null,\n },\n ];\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'periods.set',\n entity: defaultTarget,\n ts: '2025-01-01T10:00:00Z',\n periods: firstPeriods,\n basis: {\n evidenceId: 'evidence-1',\n },\n },\n {\n id: 'ie_test_002',\n type: 'periods.set',\n entity: defaultTarget,\n ts: '2025-01-01T11:00:00Z',\n periods: secondPeriods,\n basis: {\n evidenceId: 'evidence-2',\n },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.services[nslKey].periods).toEqual(secondPeriods);\n expect(result.servicesProvenance[nslKey].periods?.evidenceId).toBe(\n 'evidence-2',\n );\n expect(result.impactEventIds).toEqual(['ie_test_002']);\n });\n\n test('groups impacts by target - different targets produce separate entries', () => {\n const nslTarget = { type: 'service' as const, serviceId: 'NSL' };\n const ewlTarget = { type: 'service' as const, serviceId: 'EWL' };\n const nslKeyLocal = keyForAffectedEntity(nslTarget);\n const ewlKey = keyForAffectedEntity(ewlTarget);\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'service_effects.set',\n entity: nslTarget,\n ts: '2025-01-01T10:00:00Z',\n effect: { kind: 'delay', duration: null },\n basis: { evidenceId: 'evidence-1' },\n },\n {\n id: 'ie_test_002',\n type: 'service_effects.set',\n entity: ewlTarget,\n ts: '2025-01-01T10:00:00Z',\n effect: { kind: 'no-service' },\n basis: { evidenceId: 'evidence-2' },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(Object.keys(result.services)).toHaveLength(2);\n expect(result.services[nslKeyLocal].effect).toEqual({\n kind: 'delay',\n duration: null,\n });\n expect(result.services[nslKeyLocal]).toMatchObject({ id: 'NSL' });\n expect(result.services[ewlKey].effect).toEqual({ kind: 'no-service' });\n expect(result.services[ewlKey]).toMatchObject({ id: 'EWL' });\n expect(result.impactEventIds).toEqual(['ie_test_002']);\n });\n\n test('handles facility impacts', () => {\n const facilityEntity = {\n type: 'facility' as const,\n stationId: 'JUR',\n kind: 'lift' as const,\n };\n const facilityKey = keyForAffectedEntity(facilityEntity);\n\n const bundle = createMockBundle([\n {\n id: 'ie_test_001',\n type: 'facility_effects.set',\n entity: facilityEntity,\n ts: '2025-01-01T10:00:00Z',\n effect: { kind: 'facility-out-of-service' },\n basis: { evidenceId: 'evidence-1' },\n },\n {\n id: 'ie_test_002',\n type: 'periods.set',\n entity: facilityEntity,\n ts: '2025-01-01T10:00:00Z',\n periods: [\n {\n kind: 'fixed',\n startAt: '2025-01-01T10:00:00+08:00',\n endAt: '2025-01-01T12:00:00+08:00',\n },\n ],\n basis: { evidenceId: 'evidence-2' },\n },\n ]);\n\n const result = deriveCurrentState(bundle);\n\n expect(result.facilities[facilityKey]).toMatchObject({\n stationId: 'JUR',\n kind: 'lift',\n effect: { kind: 'facility-out-of-service' },\n periods: [\n {\n kind: 'fixed',\n startAt: '2025-01-01T10:00:00+08:00',\n endAt: '2025-01-01T12:00:00+08:00',\n },\n ],\n });\n expect(result.facilitiesProvenance[facilityKey]).toEqual({\n effect: { evidenceId: 'evidence-1' },\n periods: { evidenceId: 'evidence-2' },\n });\n expect(result.impactEventIds).toEqual(['ie_test_002', 'ie_test_001']);\n });\n});\n"]}
@@ -0,0 +1,7 @@
1
+ import { type Landmark } from '../../schema/Landmark.js';
2
+ import { StandardRepository } from '../common/StandardRepository.js';
3
+ import type { IStore } from '../common/store.js';
4
+ export declare class LandmarkRepository extends StandardRepository<Landmark> {
5
+ constructor(store: IStore);
6
+ protected parseItem(json: unknown): Landmark;
7
+ }
@@ -0,0 +1,12 @@
1
+ import { DIR_LANDMARK } from '../../constants.js';
2
+ import { LandmarkSchema } from '../../schema/Landmark.js';
3
+ import { StandardRepository } from '../common/StandardRepository.js';
4
+ export class LandmarkRepository extends StandardRepository {
5
+ constructor(store) {
6
+ super(store, DIR_LANDMARK);
7
+ }
8
+ parseItem(json) {
9
+ return LandmarkSchema.parse(json);
10
+ }
11
+ }
12
+ //# sourceMappingURL=LandmarkRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LandmarkRepository.js","sourceRoot":"/","sources":["repo/landmark/LandmarkRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAiB,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,MAAM,OAAO,kBAAmB,SAAQ,kBAA4B;IAClE,YAAY,KAAa;QACvB,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAC7B,CAAC;IAES,SAAS,CAAC,IAAa;QAC/B,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CACF","sourcesContent":["import { DIR_LANDMARK } from '../../constants.js';\nimport { type Landmark, LandmarkSchema } from '../../schema/Landmark.js';\nimport { StandardRepository } from '../common/StandardRepository.js';\nimport type { IStore } from '../common/store.js';\n\nexport class LandmarkRepository extends StandardRepository<Landmark> {\n constructor(store: IStore) {\n super(store, DIR_LANDMARK);\n }\n\n protected parseItem(json: unknown): Landmark {\n return LandmarkSchema.parse(json);\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { type Line } from '../../schema/Line.js';
2
+ import { StandardRepository } from '../common/StandardRepository.js';
3
+ import type { IStore } from '../common/store.js';
4
+ export declare class LineRepository extends StandardRepository<Line> {
5
+ constructor(store: IStore);
6
+ protected parseItem(json: unknown): Line;
7
+ /**
8
+ * Search lines by name.
9
+ * @param names
10
+ * @returns
11
+ */
12
+ searchByName(names: string[]): Line[];
13
+ }
@@ -0,0 +1,32 @@
1
+ import Fuse from 'fuse.js';
2
+ import { DIR_LINE } from '../../constants.js';
3
+ import { LineSchema } from '../../schema/Line.js';
4
+ import { StandardRepository } from '../common/StandardRepository.js';
5
+ export class LineRepository extends StandardRepository {
6
+ constructor(store) {
7
+ super(store, DIR_LINE);
8
+ }
9
+ parseItem(json) {
10
+ return LineSchema.parse(json);
11
+ }
12
+ /**
13
+ * Search lines by name.
14
+ * @param names
15
+ * @returns
16
+ */
17
+ searchByName(names) {
18
+ this.loadAll();
19
+ const fuse = new Fuse(Array.from(this.byId.values()), {
20
+ keys: ['id', 'name.en-SG'],
21
+ includeScore: true,
22
+ threshold: 0.3,
23
+ });
24
+ const results = fuse.search({
25
+ $or: names.flatMap((name) => {
26
+ return [{ id: name }, { 'name.en-SG': name }];
27
+ }),
28
+ });
29
+ return results.map((r) => r.item);
30
+ }
31
+ }
32
+ //# sourceMappingURL=LineRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LineRepository.js","sourceRoot":"/","sources":["repo/line/LineRepository.ts"],"names":[],"mappings":"AAAA,OAAO,IAAyB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAa,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,MAAM,OAAO,cAAe,SAAQ,kBAAwB;IAC1D,YAAY,KAAa;QACvB,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACzB,CAAC;IAES,SAAS,CAAC,IAAa;QAC/B,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,KAAe;QAC1B,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;YACpD,IAAI,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC;YAC1B,YAAY,EAAE,IAAI;YAClB,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;YAC1B,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC1B,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAiB,CAAC;YAChE,CAAC,CAAC;SACH,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CACF","sourcesContent":["import Fuse, { type Expression } from 'fuse.js';\nimport { DIR_LINE } from '../../constants.js';\nimport { type Line, LineSchema } from '../../schema/Line.js';\nimport { StandardRepository } from '../common/StandardRepository.js';\nimport type { IStore } from '../common/store.js';\n\nexport class LineRepository extends StandardRepository<Line> {\n constructor(store: IStore) {\n super(store, DIR_LINE);\n }\n\n protected parseItem(json: unknown): Line {\n return LineSchema.parse(json);\n }\n\n /**\n * Search lines by name.\n * @param names\n * @returns\n */\n searchByName(names: string[]): Line[] {\n this.loadAll();\n const fuse = new Fuse(Array.from(this.byId.values()), {\n keys: ['id', 'name.en-SG'],\n includeScore: true,\n threshold: 0.3,\n });\n const results = fuse.search({\n $or: names.flatMap((name) => {\n return [{ id: name }, { 'name.en-SG': name }] as Expression[];\n }),\n });\n return results.map((r) => r.item);\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { type Operator } from '../../schema/Operator.js';
2
+ import { StandardRepository } from '../common/StandardRepository.js';
3
+ import type { IStore } from '../common/store.js';
4
+ export declare class OperatorRepository extends StandardRepository<Operator> {
5
+ constructor(store: IStore);
6
+ protected parseItem(json: unknown): Operator;
7
+ }
@@ -0,0 +1,12 @@
1
+ import { DIR_OPERATOR } from '../../constants.js';
2
+ import { OperatorSchema } from '../../schema/Operator.js';
3
+ import { StandardRepository } from '../common/StandardRepository.js';
4
+ export class OperatorRepository extends StandardRepository {
5
+ constructor(store) {
6
+ super(store, DIR_OPERATOR);
7
+ }
8
+ parseItem(json) {
9
+ return OperatorSchema.parse(json);
10
+ }
11
+ }
12
+ //# sourceMappingURL=OperatorRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OperatorRepository.js","sourceRoot":"/","sources":["repo/operator/OperatorRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAiB,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,MAAM,OAAO,kBAAmB,SAAQ,kBAA4B;IAClE,YAAY,KAAa;QACvB,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAC7B,CAAC;IAES,SAAS,CAAC,IAAa;QAC/B,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CACF","sourcesContent":["import { DIR_OPERATOR } from '../../constants.js';\nimport { type Operator, OperatorSchema } from '../../schema/Operator.js';\nimport { StandardRepository } from '../common/StandardRepository.js';\nimport type { IStore } from '../common/store.js';\n\nexport class OperatorRepository extends StandardRepository<Operator> {\n constructor(store: IStore) {\n super(store, DIR_OPERATOR);\n }\n\n protected parseItem(json: unknown): Operator {\n return OperatorSchema.parse(json);\n }\n}\n"]}