ctrl-fx 0.1.0 → 0.1.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 (152) hide show
  1. package/README.md +13 -0
  2. package/dist/chunk-DZAR6PTR.js +3966 -0
  3. package/dist/chunk-KNHJPAIU.js +1800 -0
  4. package/dist/chunk-NSWOTCDU.js +916 -0
  5. package/dist/chunk-PKBMQBKP.js +7 -0
  6. package/dist/chunk-XICUXW4T.js +252 -0
  7. package/dist/db/index.d.ts +218 -16
  8. package/dist/db/index.js +53 -19
  9. package/dist/dom/index.d.ts +2 -248
  10. package/dist/dom/index.js +30 -266
  11. package/dist/effects.d.ts +2 -580
  12. package/dist/effects.js +111 -1556
  13. package/dist/index-fpCmXVEu.d.ts +1391 -0
  14. package/dist/index.d.ts +2 -41
  15. package/dist/index.js +18 -78
  16. package/dist/router.d.ts +11 -12
  17. package/dist/router.js +57 -68
  18. package/dist/testing.d.ts +66 -60
  19. package/dist/testing.js +39 -158
  20. package/dist/webcomponent.d.ts +7 -4
  21. package/dist/webcomponent.js +48 -40
  22. package/package.json +4 -3
  23. package/dist/clipboard.d.ts +0 -7
  24. package/dist/clipboard.d.ts.map +0 -1
  25. package/dist/clipboard.js +0 -3
  26. package/dist/db/effects.d.ts +0 -78
  27. package/dist/db/effects.d.ts.map +0 -1
  28. package/dist/db/effects.js +0 -98
  29. package/dist/db/errors.d.ts +0 -21
  30. package/dist/db/errors.d.ts.map +0 -1
  31. package/dist/db/errors.js +0 -13
  32. package/dist/db/index.d.ts.map +0 -1
  33. package/dist/db/keypath.d.ts +0 -40
  34. package/dist/db/keypath.d.ts.map +0 -1
  35. package/dist/db/keypath.js +0 -20
  36. package/dist/db/setup.d.ts +0 -83
  37. package/dist/db/setup.d.ts.map +0 -1
  38. package/dist/db/setup.js +0 -100
  39. package/dist/db/testdb.d.ts +0 -19
  40. package/dist/db/testdb.d.ts.map +0 -1
  41. package/dist/db/testdb.js +0 -34
  42. package/dist/dom/attrs.d.ts +0 -67
  43. package/dist/dom/attrs.d.ts.map +0 -1
  44. package/dist/dom/attrs.js +0 -154
  45. package/dist/dom/components.d.ts +0 -43
  46. package/dist/dom/components.d.ts.map +0 -1
  47. package/dist/dom/components.js +0 -86
  48. package/dist/dom/elems.d.ts +0 -121
  49. package/dist/dom/elems.d.ts.map +0 -1
  50. package/dist/dom/elems.js +0 -52
  51. package/dist/dom/events.d.ts +0 -232
  52. package/dist/dom/events.d.ts.map +0 -1
  53. package/dist/dom/events.js +0 -289
  54. package/dist/dom/index.d.ts.map +0 -1
  55. package/dist/dom/nodeid.d.ts +0 -9
  56. package/dist/dom/nodeid.d.ts.map +0 -1
  57. package/dist/dom/nodeid.js +0 -27
  58. package/dist/dom/svg.d.ts +0 -23
  59. package/dist/dom/svg.d.ts.map +0 -1
  60. package/dist/dom/svg.js +0 -1
  61. package/dist/dom/views.d.ts +0 -60
  62. package/dist/dom/views.d.ts.map +0 -1
  63. package/dist/dom/views.js +0 -154
  64. package/dist/effects.d.ts.map +0 -1
  65. package/dist/index.d.ts.map +0 -1
  66. package/dist/internal/clipboard.d.ts +0 -5
  67. package/dist/internal/clipboard.d.ts.map +0 -1
  68. package/dist/internal/clipboard.js +0 -33
  69. package/dist/internal/cmpmgr.d.ts +0 -46
  70. package/dist/internal/cmpmgr.d.ts.map +0 -1
  71. package/dist/internal/cmpmgr.js +0 -561
  72. package/dist/internal/dbmgr.d.ts +0 -11
  73. package/dist/internal/dbmgr.d.ts.map +0 -1
  74. package/dist/internal/dbmgr.js +0 -200
  75. package/dist/internal/dominterpreter.d.ts +0 -5
  76. package/dist/internal/dominterpreter.d.ts.map +0 -1
  77. package/dist/internal/dominterpreter.js +0 -337
  78. package/dist/internal/eventmgr.d.ts +0 -41
  79. package/dist/internal/eventmgr.d.ts.map +0 -1
  80. package/dist/internal/eventmgr.js +0 -696
  81. package/dist/internal/index.d.ts +0 -19
  82. package/dist/internal/index.d.ts.map +0 -1
  83. package/dist/internal/index.js +0 -38
  84. package/dist/internal/interpreter.d.ts +0 -10
  85. package/dist/internal/interpreter.d.ts.map +0 -1
  86. package/dist/internal/interpreter.js +0 -1
  87. package/dist/internal/locutils.d.ts +0 -4
  88. package/dist/internal/locutils.d.ts.map +0 -1
  89. package/dist/internal/locutils.js +0 -22
  90. package/dist/internal/net.d.ts +0 -4
  91. package/dist/internal/net.d.ts.map +0 -1
  92. package/dist/internal/net.js +0 -64
  93. package/dist/internal/realdom.d.ts +0 -249
  94. package/dist/internal/realdom.d.ts.map +0 -1
  95. package/dist/internal/realdom.js +0 -1
  96. package/dist/internal/styleregistry.d.ts +0 -6
  97. package/dist/internal/styleregistry.d.ts.map +0 -1
  98. package/dist/internal/styleregistry.js +0 -18
  99. package/dist/internal/taskreg.d.ts +0 -9
  100. package/dist/internal/taskreg.d.ts.map +0 -1
  101. package/dist/internal/taskreg.js +0 -28
  102. package/dist/internal/testing.d.ts +0 -12
  103. package/dist/internal/testing.d.ts.map +0 -1
  104. package/dist/internal/testing.js +0 -974
  105. package/dist/internal/testinginterpreter.d.ts +0 -10
  106. package/dist/internal/testinginterpreter.d.ts.map +0 -1
  107. package/dist/internal/testinginterpreter.js +0 -646
  108. package/dist/internal/vdom.d.ts +0 -93
  109. package/dist/internal/vdom.d.ts.map +0 -1
  110. package/dist/internal/vdom.js +0 -470
  111. package/dist/json/index.d.ts +0 -5
  112. package/dist/json/index.d.ts.map +0 -1
  113. package/dist/json/index.js +0 -2
  114. package/dist/json/validation.d.ts +0 -28
  115. package/dist/json/validation.d.ts.map +0 -1
  116. package/dist/json/validation.js +0 -8
  117. package/dist/net/fragment.d.ts +0 -10
  118. package/dist/net/fragment.d.ts.map +0 -1
  119. package/dist/net/fragment.js +0 -27
  120. package/dist/net/index.d.ts +0 -42
  121. package/dist/net/index.d.ts.map +0 -1
  122. package/dist/net/index.js +0 -60
  123. package/dist/net/location.d.ts +0 -21
  124. package/dist/net/location.d.ts.map +0 -1
  125. package/dist/net/location.js +0 -41
  126. package/dist/net/path.d.ts +0 -12
  127. package/dist/net/path.d.ts.map +0 -1
  128. package/dist/net/path.js +0 -29
  129. package/dist/net/queryparam.d.ts +0 -10
  130. package/dist/net/queryparam.d.ts.map +0 -1
  131. package/dist/net/queryparam.js +0 -34
  132. package/dist/router.d.ts.map +0 -1
  133. package/dist/scroll.d.ts +0 -7
  134. package/dist/scroll.d.ts.map +0 -1
  135. package/dist/scroll.js +0 -3
  136. package/dist/testing.d.ts.map +0 -1
  137. package/dist/utils/apply.d.ts +0 -9
  138. package/dist/utils/apply.d.ts.map +0 -1
  139. package/dist/utils/apply.js +0 -10
  140. package/dist/utils/index.d.ts +0 -22
  141. package/dist/utils/index.d.ts.map +0 -1
  142. package/dist/utils/index.js +0 -138
  143. package/dist/utils/lens.d.ts +0 -18
  144. package/dist/utils/lens.d.ts.map +0 -1
  145. package/dist/utils/lens.js +0 -27
  146. package/dist/utils/match.d.ts +0 -17
  147. package/dist/utils/match.d.ts.map +0 -1
  148. package/dist/utils/match.js +0 -28
  149. package/dist/utils/result.d.ts +0 -47
  150. package/dist/utils/result.d.ts.map +0 -1
  151. package/dist/utils/result.js +0 -97
  152. package/dist/webcomponent.d.ts.map +0 -1
@@ -1,10 +0,0 @@
1
- import { type TestConfig, type TestData } from '../testing';
2
- import type { Interpreter } from './interpreter';
3
- import type { RealWindow } from './realdom';
4
- export declare function makeTestingInterpreter<Custom = {}>(initialData: TestData<Custom>, window: RealWindow, config?: TestConfig): {
5
- interpreter: Interpreter;
6
- getData: () => TestData<Custom>;
7
- advanceTime: (ms: number) => void;
8
- fireBroadcast: (channel: string, message: unknown) => void;
9
- };
10
- //# sourceMappingURL=testinginterpreter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testinginterpreter.d.ts","sourceRoot":"","sources":["../../src/internal/testinginterpreter.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AAK3D,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAwO3C,wBAAgB,sBAAsB,CAAC,MAAM,GAAG,EAAE,EAChD,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC7B,MAAM,EAAE,UAAU,EAClB,MAAM,CAAC,EAAE,UAAU,GAClB;IACD,WAAW,EAAE,WAAW,CAAA;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC/B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC3D,CAyEA"}
@@ -1,646 +0,0 @@
1
- // Deterministic hash-based PRNG for test fallback — never uses Math.random().
2
- // Uses the 32-bit finalizer from MurmurHash3; distributes evenly over [0, 1).
3
- function seededRandom(callIndex) {
4
- let h = (callIndex + 1) | 0;
5
- h = Math.imul(h ^ (h >>> 16), 0x45d9f3b);
6
- h = Math.imul(h ^ (h >>> 16), 0x45d9f3b);
7
- h ^= h >>> 16;
8
- return (h >>> 0) / 0x100000000;
9
- }
10
- import { generateUuid } from '.';
11
- import { permission, uuidFromString } from '..';
12
- import { dbVersion } from '../db';
13
- import { serializeIdbKey, TestDatabaseData, } from '../db/testdb';
14
- import { taskId, } from '../effects';
15
- import { scrollElementError } from '../scroll';
16
- import { emptyLocation } from '../net/location';
17
- import {} from '../testing';
18
- import { exhaustivenessCheck } from '../utils';
19
- import { run as runDbEffect } from '../db/effects';
20
- import { run as runDbSetupEffect } from '../db/setup';
21
- import { failure, success } from '../utils/result';
22
- import { TaskRegistry } from './taskreg';
23
- /* eslint-enable @typescript-eslint/no-explicit-any */
24
- function snapshotTestData(acc) {
25
- return {
26
- window: {
27
- alerts: [...acc.window.alerts],
28
- confirms: [...acc.window.confirms],
29
- confirmResponses: [...acc.window.confirmResponses],
30
- prompts: [...acc.window.prompts],
31
- promptResponses: [...acc.window.promptResponses],
32
- },
33
- navigator: { appBadgeOperations: [...acc.navigator.appBadgeOperations] },
34
- clipboard: {
35
- content: acc.clipboard.content,
36
- writes: [...acc.clipboard.writes],
37
- },
38
- document: { title: acc.document.title },
39
- http: { interactions: [...acc.http.interactions] },
40
- clock: { calls: [...acc.clock.calls] },
41
- random: { calls: [...acc.random.calls] },
42
- console: { logs: [...acc.console.logs] },
43
- db: new TestDatabaseData(new Map([...acc.db.databases.entries()].map(([dbName, db]) => [
44
- dbName,
45
- new Map([...db.stores.entries()].map(([storeName, store]) => [
46
- storeName,
47
- {
48
- keyPath: store.keyPath,
49
- records: new Map(store.records),
50
- autoIncrementCounter: store.autoIncrementCounter,
51
- },
52
- ])),
53
- ]))),
54
- storage: {
55
- local: new Map(acc.storage.local),
56
- session: new Map(acc.storage.session),
57
- },
58
- navigation: {
59
- locationHistory: [...acc.navigation.locationHistory],
60
- },
61
- scroll: {
62
- windowScrolls: [...acc.scroll.windowScrolls],
63
- elementScrolls: [...acc.scroll.elementScrolls],
64
- },
65
- downloads: [...acc.downloads],
66
- broadcasts: {
67
- sent: [...acc.broadcasts.sent],
68
- },
69
- custom: acc.custom,
70
- };
71
- }
72
- class TestScheduler {
73
- constructor() {
74
- Object.defineProperty(this, "virtualNow", {
75
- enumerable: true,
76
- configurable: true,
77
- writable: true,
78
- value: 0
79
- });
80
- Object.defineProperty(this, "pending", {
81
- enumerable: true,
82
- configurable: true,
83
- writable: true,
84
- value: []
85
- });
86
- }
87
- schedule(
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- task,
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
- taskCallbacks, tskId, initialDelayInMillis, repeatIntervalInMillis) {
92
- this.pending.push({
93
- task,
94
- taskCallbacks,
95
- taskId: tskId,
96
- nextFireAtMillis: this.virtualNow + initialDelayInMillis,
97
- repeatIntervalInMillis,
98
- });
99
- }
100
- cancel(tskId) {
101
- this.pending = this.pending.filter(t => t.taskId !== tskId);
102
- }
103
- advance(ms, acc, broadcastReg, window) {
104
- const targetTime = this.virtualNow + ms;
105
- while (true) {
106
- const next = this.pending
107
- .filter(t => t.nextFireAtMillis <= targetTime)
108
- .sort((a, b) => a.nextFireAtMillis - b.nextFireAtMillis)[0];
109
- if (!next)
110
- break;
111
- this.virtualNow = next.nextFireAtMillis;
112
- if (next.repeatIntervalInMillis !== undefined) {
113
- next.nextFireAtMillis = this.virtualNow + next.repeatIntervalInMillis;
114
- }
115
- else {
116
- this.pending = this.pending.filter(t => t !== next);
117
- }
118
- testingInterpreter(next.task, next.taskCallbacks, new TaskRegistry(), acc, broadcastReg, this, window);
119
- }
120
- this.virtualNow = targetTime;
121
- }
122
- }
123
- class BroadcastChannelRegistry {
124
- constructor() {
125
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
- Object.defineProperty(this, "channels", {
127
- enumerable: true,
128
- configurable: true,
129
- writable: true,
130
- value: new Map()
131
- });
132
- }
133
- subscribe(channel,
134
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
- handler, tskId) {
136
- if (!this.channels.has(channel))
137
- this.channels.set(channel, []);
138
- this.channels.get(channel).push({ handler, taskId: tskId });
139
- }
140
- unsubscribe(tskId) {
141
- for (const [ch, subs] of this.channels) {
142
- this.channels.set(ch, subs.filter(s => s.taskId !== tskId));
143
- }
144
- }
145
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
- deliver(channel, message, acc, broadcastReg, scheduler, window) {
147
- const subs = this.channels.get(channel) ?? [];
148
- for (const sub of subs) {
149
- testingInterpreter(sub.handler(message), {
150
- onComplete() { },
151
- onFireEvent() { },
152
- getState() { return undefined; },
153
- setState() { },
154
- }, new TaskRegistry(), acc, broadcastReg, scheduler, window);
155
- }
156
- }
157
- }
158
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
159
- export function makeTestingInterpreter(initialData, window, config) {
160
- const acc = {
161
- window: {
162
- alerts: [...initialData.window.alerts],
163
- confirms: [...initialData.window.confirms],
164
- confirmResponses: [...initialData.window.confirmResponses],
165
- prompts: [...initialData.window.prompts],
166
- promptResponses: [...initialData.window.promptResponses],
167
- },
168
- navigator: {
169
- appBadgeOperations: [...initialData.navigator.appBadgeOperations],
170
- },
171
- clipboard: {
172
- content: initialData.clipboard.content,
173
- writes: [...initialData.clipboard.writes],
174
- },
175
- document: { title: initialData.document.title },
176
- http: { interactions: [...initialData.http.interactions] },
177
- clock: { calls: [...initialData.clock.calls] },
178
- random: { calls: [...initialData.random.calls] },
179
- console: { logs: [...initialData.console.logs] },
180
- storage: {
181
- local: new Map(initialData.storage.local),
182
- session: new Map(initialData.storage.session),
183
- },
184
- navigation: {
185
- locationHistory: [...initialData.navigation.locationHistory],
186
- },
187
- scroll: {
188
- windowScrolls: [...initialData.scroll.windowScrolls],
189
- elementScrolls: [...initialData.scroll.elementScrolls],
190
- },
191
- downloads: [...initialData.downloads],
192
- broadcasts: {
193
- sent: [...initialData.broadcasts.sent],
194
- },
195
- db: {
196
- databases: new Map([...initialData.db._databases.entries()].map(([dbName, dbSnap]) => [
197
- dbName,
198
- {
199
- stores: new Map([...dbSnap.entries()].map(([storeName, storeSnap]) => [
200
- storeName,
201
- {
202
- keyPath: storeSnap.keyPath,
203
- records: new Map(storeSnap.records),
204
- autoIncrementCounter: storeSnap.autoIncrementCounter,
205
- },
206
- ])),
207
- },
208
- ])),
209
- },
210
- uuidSequence: [],
211
- custom: initialData.custom,
212
- };
213
- const scheduler = new TestScheduler();
214
- const broadcastReg = new BroadcastChannelRegistry();
215
- const interpreter = (effect, callbacks, taskRegistry) => {
216
- testingInterpreter(effect, callbacks, taskRegistry, acc, broadcastReg, scheduler, window, config);
217
- };
218
- return {
219
- interpreter,
220
- getData: () => snapshotTestData(acc),
221
- advanceTime: (ms) => scheduler.advance(ms, acc, broadcastReg, window),
222
- fireBroadcast: (channel, message) => broadcastReg.deliver(channel, message, acc, broadcastReg, scheduler, window),
223
- };
224
- }
225
- function testingInterpreter(effect, callbacks, taskRegistry, acc, broadcastReg, scheduler, window, config) {
226
- switch (effect._type) {
227
- case 'Return': {
228
- callbacks.onComplete(effect.value);
229
- return;
230
- }
231
- case 'FlatMap': {
232
- testingInterpreter(effect.effect, {
233
- ...callbacks,
234
- onComplete(result) {
235
- testingInterpreter(effect.next(result), callbacks, taskRegistry, acc, broadcastReg, scheduler, window, config);
236
- },
237
- }, taskRegistry, acc, broadcastReg, scheduler, window, config);
238
- return;
239
- }
240
- case 'Suspend': {
241
- switch (effect.operation._type) {
242
- case 'Alert': {
243
- acc.window.alerts.push(effect.operation.input);
244
- callbacks.onComplete(undefined);
245
- return;
246
- }
247
- case 'Confirm': {
248
- const response = acc.window.confirmResponses.shift() ?? false;
249
- acc.window.confirms.push({
250
- message: effect.operation.input,
251
- response,
252
- });
253
- callbacks.onComplete(response);
254
- return;
255
- }
256
- case 'Prompt': {
257
- const response = acc.window.promptResponses.shift() ?? null;
258
- acc.window.prompts.push({
259
- message: effect.operation.input.message,
260
- default: effect.operation.input.default,
261
- response,
262
- });
263
- callbacks.onComplete(response);
264
- return;
265
- }
266
- case 'GetNotificationPermission': {
267
- callbacks.onComplete(permission('default'));
268
- return;
269
- }
270
- case 'RequestNotificationPermission': {
271
- callbacks.onComplete(permission('granted'));
272
- return;
273
- }
274
- case 'GetLocation': {
275
- const history = acc.navigation.locationHistory;
276
- callbacks.onComplete((history.length > 0
277
- ? history[history.length - 1]
278
- : emptyLocation));
279
- return;
280
- }
281
- case 'Async': {
282
- // run the inner effect synchronously, ignoring the delay
283
- testingInterpreter(effect.operation.input.effect, {
284
- ...callbacks,
285
- onComplete() {
286
- callbacks.onComplete(undefined);
287
- },
288
- }, taskRegistry, acc, broadcastReg, scheduler, window, config);
289
- return;
290
- }
291
- case 'CancelTask': {
292
- scheduler.cancel(effect.operation.input);
293
- broadcastReg.unsubscribe(effect.operation.input);
294
- taskRegistry.remove(effect.operation.input);
295
- callbacks.onComplete(undefined);
296
- return;
297
- }
298
- case 'ClearAppBadge': {
299
- acc.navigator.appBadgeOperations.push({ _type: 'ClearAppBadge' });
300
- callbacks.onComplete(undefined);
301
- return;
302
- }
303
- case 'SetAppBadge': {
304
- acc.navigator.appBadgeOperations.push({
305
- _type: 'SetAppBadge',
306
- count: effect.operation.input,
307
- });
308
- callbacks.onComplete(undefined);
309
- return;
310
- }
311
- case 'SetDocumentTitle': {
312
- acc.document.title = effect.operation.input;
313
- callbacks.onComplete(undefined);
314
- return;
315
- }
316
- case 'ReadClipboard': {
317
- callbacks.onComplete(success(acc.clipboard.content));
318
- return;
319
- }
320
- case 'WriteClipboard': {
321
- acc.clipboard.content = effect.operation.input;
322
- acc.clipboard.writes.push(effect.operation.input);
323
- callbacks.onComplete(success(undefined));
324
- return;
325
- }
326
- case 'Download': {
327
- acc.downloads.push(effect.operation.input);
328
- callbacks.onComplete(undefined);
329
- return;
330
- }
331
- case 'PostBroadcastMessage': {
332
- acc.broadcasts.sent.push(effect.operation.input);
333
- callbacks.onComplete(undefined);
334
- return;
335
- }
336
- case 'SubscribeToBroadcastChannel': {
337
- const { channel, handler, taskId: tskIdInput } = effect.operation.input;
338
- const tskId = tskIdInput ?? taskId(generateUuid());
339
- if (tskIdInput) {
340
- broadcastReg.unsubscribe(tskIdInput);
341
- }
342
- broadcastReg.subscribe(channel, handler, tskId);
343
- callbacks.onComplete(tskId);
344
- return;
345
- }
346
- case 'ScrollWindow': {
347
- acc.scroll.windowScrolls.push(effect.operation.input);
348
- callbacks.onComplete(undefined);
349
- return;
350
- }
351
- case 'ScrollElement': {
352
- const { selector, options } = effect.operation.input;
353
- const el = window.document.querySelector(selector);
354
- if (el === null) {
355
- callbacks.onComplete(failure(scrollElementError(selector)));
356
- }
357
- else {
358
- el.scrollTo(options);
359
- acc.scroll.elementScrolls.push({ selector, options });
360
- callbacks.onComplete(success(undefined));
361
- }
362
- return;
363
- }
364
- case 'FireEvent': {
365
- callbacks.onFireEvent(effect.operation.input);
366
- callbacks.onComplete(undefined);
367
- return;
368
- }
369
- case 'GetState': {
370
- callbacks.onComplete(callbacks.getState());
371
- return;
372
- }
373
- case 'GetTime': {
374
- const callIndex = acc.clock.calls.length;
375
- const scripted = config?.clock.values ?? [];
376
- const value = callIndex < scripted.length ? scripted[callIndex] : new Date(0);
377
- acc.clock.calls.push(value);
378
- callbacks.onComplete(value);
379
- return;
380
- }
381
- case 'GetRandom': {
382
- const callIndex = acc.random.calls.length;
383
- const scripted = config?.random.values ?? [];
384
- const value = callIndex < scripted.length
385
- ? scripted[callIndex]
386
- : seededRandom(callIndex);
387
- acc.random.calls.push(value);
388
- callbacks.onComplete(value);
389
- return;
390
- }
391
- case 'Log': {
392
- acc.console.logs.push(effect.operation.input);
393
- callbacks.onComplete(undefined);
394
- return;
395
- }
396
- case 'MakeHttpRequest': {
397
- // TODO: add HTTP mock support for testing
398
- callbacks.onComplete(undefined);
399
- return;
400
- }
401
- case 'Product': {
402
- let resultA = '__ctrl_fx_unset_result';
403
- let resultB = '__ctrl_fx_unset_result';
404
- testingInterpreter(effect.operation.input[0], {
405
- ...callbacks,
406
- onComplete(a) {
407
- resultA = a;
408
- if (resultB !== '__ctrl_fx_unset_result') {
409
- callbacks.onComplete([resultA, resultB]);
410
- }
411
- },
412
- }, taskRegistry, acc, broadcastReg, scheduler, window, config);
413
- testingInterpreter(effect.operation.input[1], {
414
- ...callbacks,
415
- onComplete(b) {
416
- resultB = b;
417
- if (resultA !== '__ctrl_fx_unset_result') {
418
- callbacks.onComplete([resultA, resultB]);
419
- }
420
- },
421
- }, taskRegistry, acc, broadcastReg, scheduler, window, config);
422
- return;
423
- }
424
- case 'ScheduleTask': {
425
- const tskId = effect.operation.input.taskId ?? taskId(generateUuid());
426
- // rescheduling: cancel any existing task with this id
427
- if (effect.operation.input.taskId) {
428
- scheduler.cancel(effect.operation.input.taskId);
429
- }
430
- scheduler.schedule(
431
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
432
- effect.operation.input.task,
433
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
434
- { ...callbacks, onComplete() { } }, tskId, effect.operation.input.initialDelayInMillis, effect.operation.input.repeatIntervalInMillis);
435
- callbacks.onComplete(tskId);
436
- return;
437
- }
438
- case 'SetTimeout': {
439
- callbacks.onComplete(undefined);
440
- return;
441
- }
442
- case 'PushState': {
443
- acc.navigation.locationHistory.push(effect.operation.input);
444
- callbacks.onComplete(undefined);
445
- return;
446
- }
447
- case 'ReplaceState': {
448
- if (acc.navigation.locationHistory.length > 0) {
449
- acc.navigation.locationHistory[acc.navigation.locationHistory.length - 1] = effect.operation.input;
450
- }
451
- else {
452
- acc.navigation.locationHistory.push(effect.operation.input);
453
- }
454
- callbacks.onComplete(undefined);
455
- return;
456
- }
457
- case 'Go': {
458
- const delta = effect.operation.input;
459
- const newLen = acc.navigation.locationHistory.length + delta;
460
- if (newLen > 0) {
461
- acc.navigation.locationHistory.length = newLen;
462
- }
463
- callbacks.onComplete(undefined);
464
- return;
465
- }
466
- case 'GenerateUuid': {
467
- const uuidLen = acc.uuidSequence.length;
468
- const nextUuid = uuidFromString(`${uuidLen}`);
469
- acc.uuidSequence.push(nextUuid);
470
- callbacks.onComplete(nextUuid);
471
- return;
472
- }
473
- case 'GetStorageItem': {
474
- const store = acc.storage[effect.operation.input.storage];
475
- callbacks.onComplete((store.get(effect.operation.input.key) ?? null));
476
- return;
477
- }
478
- case 'SetStorageItem': {
479
- acc.storage[effect.operation.input.storage].set(effect.operation.input.key, effect.operation.input.value);
480
- callbacks.onComplete(undefined);
481
- return;
482
- }
483
- case 'RemoveStorageItem': {
484
- acc.storage[effect.operation.input.storage].delete(effect.operation.input.key);
485
- callbacks.onComplete(undefined);
486
- return;
487
- }
488
- case 'ClearStorage': {
489
- acc.storage[effect.operation.input.storage].clear();
490
- callbacks.onComplete(undefined);
491
- return;
492
- }
493
- case 'OpenDatabase': {
494
- const { db, setup } = effect.operation.input;
495
- if (!acc.db.databases.has(db)) {
496
- const mutableDb = { stores: new Map() };
497
- acc.db.databases.set(db, mutableDb);
498
- runDbSetupEffect(setup(dbVersion(0)), makeSetupInterpreter(mutableDb), _r => {
499
- callbacks.onComplete(success(undefined));
500
- });
501
- }
502
- else {
503
- callbacks.onComplete(success(undefined));
504
- }
505
- return;
506
- }
507
- case 'RunDbTransaction': {
508
- const { db, effect: dbEffect } = effect.operation.input;
509
- const mutableDb = acc.db.databases.get(db);
510
- if (!mutableDb) {
511
- callbacks.onComplete(failure({
512
- _type: 'UnexpectedError',
513
- message: `Database '${db}' is not open`,
514
- }));
515
- return;
516
- }
517
- runDbEffect(dbEffect, makeDbInterpreter(mutableDb), _r => {
518
- callbacks.onComplete(success(undefined));
519
- });
520
- return;
521
- }
522
- case 'UpdateState': {
523
- callbacks.setState(effect.operation.input(callbacks.getState()));
524
- callbacks.onComplete(undefined);
525
- return;
526
- }
527
- case 'CustomEffect': {
528
- const { args, test } = effect.operation.input;
529
- const result = test(args, acc.custom);
530
- acc.custom = result.data;
531
- callbacks.onComplete(result.out);
532
- return;
533
- }
534
- default: {
535
- exhaustivenessCheck(effect.operation);
536
- }
537
- }
538
- break;
539
- }
540
- default: {
541
- exhaustivenessCheck(effect);
542
- }
543
- }
544
- }
545
- function makeSetupInterpreter(mutableDb) {
546
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
547
- return (op, onComplete) => {
548
- switch (op._type) {
549
- case 'CreateObjectStore': {
550
- mutableDb.stores.set(op.objectStore, {
551
- keyPath: op.keyPath,
552
- records: new Map(),
553
- autoIncrementCounter: 1,
554
- });
555
- onComplete(undefined);
556
- return;
557
- }
558
- case 'CreateIndex': {
559
- onComplete(undefined);
560
- return;
561
- }
562
- case 'DeleteObjectStore': {
563
- mutableDb.stores.delete(op.objectStore);
564
- onComplete(undefined);
565
- return;
566
- }
567
- case 'DeleteIndex': {
568
- onComplete(undefined);
569
- return;
570
- }
571
- case 'GetObjectStoreNames': {
572
- onComplete([...mutableDb.stores.keys()]);
573
- return;
574
- }
575
- case 'GetIndexNames': {
576
- onComplete([]);
577
- return;
578
- }
579
- case 'RunDbEffect': {
580
- runDbEffect(op.effect, makeDbInterpreter(mutableDb), _r => onComplete(undefined));
581
- return;
582
- }
583
- }
584
- };
585
- }
586
- function makeDbInterpreter(mutableDb) {
587
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
588
- return (op, onComplete) => {
589
- switch (op._type) {
590
- case 'Add':
591
- case 'Put': {
592
- const store = requireStore(mutableDb, op.objectStore);
593
- store.records.set(extractKey(op.value, store), op.value);
594
- onComplete(undefined);
595
- return;
596
- }
597
- case 'Get': {
598
- const store = requireStore(mutableDb, op.objectStore);
599
- onComplete(store.records.get(serializeIdbKey(op.key)));
600
- return;
601
- }
602
- case 'GetAll': {
603
- const store = requireStore(mutableDb, op.objectStore);
604
- onComplete([...store.records.values()]);
605
- return;
606
- }
607
- case 'Delete': {
608
- const store = requireStore(mutableDb, op.objectStore);
609
- store.records.delete(serializeIdbKey(op.key));
610
- onComplete(undefined);
611
- return;
612
- }
613
- case 'Count': {
614
- const store = requireStore(mutableDb, op.objectStore);
615
- onComplete(store.records.size);
616
- return;
617
- }
618
- }
619
- };
620
- }
621
- function requireStore(mutableDb, storeName) {
622
- const store = mutableDb.stores.get(storeName);
623
- if (!store)
624
- throw new Error(`Object store '${storeName}' not found`);
625
- return store;
626
- }
627
- function extractKey(value, store) {
628
- const { keyPath } = store;
629
- switch (keyPath._type) {
630
- case 'AutoIncrement':
631
- return String(store.autoIncrementCounter++);
632
- case 'Manual': {
633
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
634
- throw new Error(`Cannot extract key '${keyPath.value}' from non-object`);
635
- }
636
- return JSON.stringify(value[keyPath.value]);
637
- }
638
- case 'Multi': {
639
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
640
- throw new Error('Cannot extract compound key from non-object');
641
- }
642
- const obj = value;
643
- return JSON.stringify([keyPath.first, keyPath.second, ...keyPath.rest].map(f => obj[f]));
644
- }
645
- }
646
- }