@dxos/functions 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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 (184) hide show
  1. package/dist/lib/browser/index.mjs +767 -884
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +767 -884
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/errors.d.ts +44 -60
  8. package/dist/types/src/errors.d.ts.map +1 -1
  9. package/dist/types/src/example/fib.d.ts +1 -1
  10. package/dist/types/src/example/index.d.ts +3 -3
  11. package/dist/types/src/example/index.d.ts.map +1 -1
  12. package/dist/types/src/example/reply.d.ts +1 -1
  13. package/dist/types/src/example/sleep.d.ts +1 -1
  14. package/dist/types/src/index.d.ts +2 -5
  15. package/dist/types/src/index.d.ts.map +1 -1
  16. package/dist/types/src/operation-compatibility.test.d.ts +2 -0
  17. package/dist/types/src/operation-compatibility.test.d.ts.map +1 -0
  18. package/dist/types/src/protocol/functions-ai-http-client.d.ts +12 -0
  19. package/dist/types/src/protocol/functions-ai-http-client.d.ts.map +1 -0
  20. package/dist/types/src/protocol/index.d.ts +2 -0
  21. package/dist/types/src/protocol/index.d.ts.map +1 -0
  22. package/dist/types/src/protocol/protocol.d.ts +7 -0
  23. package/dist/types/src/protocol/protocol.d.ts.map +1 -0
  24. package/dist/types/src/protocol/protocol.test.d.ts +2 -0
  25. package/dist/types/src/protocol/protocol.test.d.ts.map +1 -0
  26. package/dist/types/src/sdk.d.ts +114 -0
  27. package/dist/types/src/sdk.d.ts.map +1 -0
  28. package/dist/types/src/services/credentials.d.ts +6 -4
  29. package/dist/types/src/services/credentials.d.ts.map +1 -1
  30. package/dist/types/src/services/event-logger.d.ts +33 -27
  31. package/dist/types/src/services/event-logger.d.ts.map +1 -1
  32. package/dist/types/src/services/function-invocation-service.d.ts +7 -19
  33. package/dist/types/src/services/function-invocation-service.d.ts.map +1 -1
  34. package/dist/types/src/services/index.d.ts +2 -4
  35. package/dist/types/src/services/index.d.ts.map +1 -1
  36. package/dist/types/src/services/queues.d.ts +4 -4
  37. package/dist/types/src/services/queues.d.ts.map +1 -1
  38. package/dist/types/src/services/tracing.d.ts +41 -12
  39. package/dist/types/src/services/tracing.d.ts.map +1 -1
  40. package/dist/types/src/types/Function.d.ts +40 -35
  41. package/dist/types/src/types/Function.d.ts.map +1 -1
  42. package/dist/types/src/types/Script.d.ts +9 -16
  43. package/dist/types/src/types/Script.d.ts.map +1 -1
  44. package/dist/types/src/types/Trigger.d.ts +58 -76
  45. package/dist/types/src/types/Trigger.d.ts.map +1 -1
  46. package/dist/types/src/types/TriggerEvent.d.ts +43 -13
  47. package/dist/types/src/types/TriggerEvent.d.ts.map +1 -1
  48. package/dist/types/src/types/index.d.ts +1 -0
  49. package/dist/types/src/types/index.d.ts.map +1 -1
  50. package/dist/types/src/types/url.d.ts +13 -0
  51. package/dist/types/src/types/url.d.ts.map +1 -0
  52. package/dist/types/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +23 -58
  54. package/src/errors.ts +4 -4
  55. package/src/example/fib.ts +1 -1
  56. package/src/example/reply.ts +1 -1
  57. package/src/example/sleep.ts +1 -1
  58. package/src/index.ts +2 -5
  59. package/src/operation-compatibility.test.ts +185 -0
  60. package/src/protocol/functions-ai-http-client.ts +67 -0
  61. package/src/{executor → protocol}/index.ts +1 -1
  62. package/src/protocol/protocol.test.ts +59 -0
  63. package/src/protocol/protocol.ts +262 -0
  64. package/src/{handler.ts → sdk.ts} +129 -66
  65. package/src/services/credentials.ts +32 -17
  66. package/src/services/event-logger.ts +10 -4
  67. package/src/services/function-invocation-service.ts +23 -70
  68. package/src/services/index.ts +2 -4
  69. package/src/services/queues.ts +5 -7
  70. package/src/services/tracing.ts +68 -44
  71. package/src/types/Function.ts +39 -8
  72. package/src/types/Script.ts +10 -9
  73. package/src/types/Trigger.ts +18 -14
  74. package/src/types/TriggerEvent.ts +29 -29
  75. package/src/types/index.ts +1 -0
  76. package/src/types/url.ts +32 -0
  77. package/dist/lib/browser/bundler/index.mjs +0 -256
  78. package/dist/lib/browser/bundler/index.mjs.map +0 -7
  79. package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
  80. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
  81. package/dist/lib/browser/chunk-M6EXIREF.mjs +0 -610
  82. package/dist/lib/browser/chunk-M6EXIREF.mjs.map +0 -7
  83. package/dist/lib/browser/edge/index.mjs +0 -83
  84. package/dist/lib/browser/edge/index.mjs.map +0 -7
  85. package/dist/lib/browser/testing/index.mjs +0 -131
  86. package/dist/lib/browser/testing/index.mjs.map +0 -7
  87. package/dist/lib/node-esm/bundler/index.mjs +0 -257
  88. package/dist/lib/node-esm/bundler/index.mjs.map +0 -7
  89. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  90. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  91. package/dist/lib/node-esm/chunk-P3IATZMZ.mjs +0 -612
  92. package/dist/lib/node-esm/chunk-P3IATZMZ.mjs.map +0 -7
  93. package/dist/lib/node-esm/edge/index.mjs +0 -84
  94. package/dist/lib/node-esm/edge/index.mjs.map +0 -7
  95. package/dist/lib/node-esm/testing/index.mjs +0 -132
  96. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  97. package/dist/types/src/bundler/bundler.d.ts +0 -49
  98. package/dist/types/src/bundler/bundler.d.ts.map +0 -1
  99. package/dist/types/src/bundler/bundler.test.d.ts +0 -2
  100. package/dist/types/src/bundler/bundler.test.d.ts.map +0 -1
  101. package/dist/types/src/bundler/index.d.ts +0 -2
  102. package/dist/types/src/bundler/index.d.ts.map +0 -1
  103. package/dist/types/src/e2e/deploy.test.d.ts +0 -2
  104. package/dist/types/src/e2e/deploy.test.d.ts.map +0 -1
  105. package/dist/types/src/edge/functions.d.ts +0 -17
  106. package/dist/types/src/edge/functions.d.ts.map +0 -1
  107. package/dist/types/src/edge/index.d.ts +0 -2
  108. package/dist/types/src/edge/index.d.ts.map +0 -1
  109. package/dist/types/src/executor/executor.d.ts +0 -14
  110. package/dist/types/src/executor/executor.d.ts.map +0 -1
  111. package/dist/types/src/executor/index.d.ts +0 -2
  112. package/dist/types/src/executor/index.d.ts.map +0 -1
  113. package/dist/types/src/handler.d.ts +0 -102
  114. package/dist/types/src/handler.d.ts.map +0 -1
  115. package/dist/types/src/services/database.d.ts +0 -67
  116. package/dist/types/src/services/database.d.ts.map +0 -1
  117. package/dist/types/src/services/function-invocation-service.test.d.ts +0 -2
  118. package/dist/types/src/services/function-invocation-service.test.d.ts.map +0 -1
  119. package/dist/types/src/services/local-function-execution.d.ts +0 -34
  120. package/dist/types/src/services/local-function-execution.d.ts.map +0 -1
  121. package/dist/types/src/services/remote-function-execution-service.d.ts +0 -22
  122. package/dist/types/src/services/remote-function-execution-service.d.ts.map +0 -1
  123. package/dist/types/src/services/service-container.d.ts +0 -57
  124. package/dist/types/src/services/service-container.d.ts.map +0 -1
  125. package/dist/types/src/services/service-registry.d.ts +0 -31
  126. package/dist/types/src/services/service-registry.d.ts.map +0 -1
  127. package/dist/types/src/services/service-registry.test.d.ts +0 -2
  128. package/dist/types/src/services/service-registry.test.d.ts.map +0 -1
  129. package/dist/types/src/testing/index.d.ts +0 -3
  130. package/dist/types/src/testing/index.d.ts.map +0 -1
  131. package/dist/types/src/testing/layer.d.ts +0 -18
  132. package/dist/types/src/testing/layer.d.ts.map +0 -1
  133. package/dist/types/src/testing/logger.d.ts +0 -5
  134. package/dist/types/src/testing/logger.d.ts.map +0 -1
  135. package/dist/types/src/testing/persist-database.test.d.ts +0 -2
  136. package/dist/types/src/testing/persist-database.test.d.ts.map +0 -1
  137. package/dist/types/src/testing/services.d.ts +0 -59
  138. package/dist/types/src/testing/services.d.ts.map +0 -1
  139. package/dist/types/src/trace.d.ts +0 -122
  140. package/dist/types/src/trace.d.ts.map +0 -1
  141. package/dist/types/src/translations.d.ts +0 -12
  142. package/dist/types/src/translations.d.ts.map +0 -1
  143. package/dist/types/src/triggers/index.d.ts +0 -4
  144. package/dist/types/src/triggers/index.d.ts.map +0 -1
  145. package/dist/types/src/triggers/input-builder.d.ts +0 -3
  146. package/dist/types/src/triggers/input-builder.d.ts.map +0 -1
  147. package/dist/types/src/triggers/invocation-tracer.d.ts +0 -37
  148. package/dist/types/src/triggers/invocation-tracer.d.ts.map +0 -1
  149. package/dist/types/src/triggers/trigger-dispatcher.d.ts +0 -78
  150. package/dist/types/src/triggers/trigger-dispatcher.d.ts.map +0 -1
  151. package/dist/types/src/triggers/trigger-dispatcher.test.d.ts +0 -2
  152. package/dist/types/src/triggers/trigger-dispatcher.test.d.ts.map +0 -1
  153. package/dist/types/src/triggers/trigger-state-store.d.ts +0 -28
  154. package/dist/types/src/triggers/trigger-state-store.d.ts.map +0 -1
  155. package/dist/types/src/url.d.ts +0 -21
  156. package/dist/types/src/url.d.ts.map +0 -1
  157. package/src/bundler/bundler.test.ts +0 -58
  158. package/src/bundler/bundler.ts +0 -295
  159. package/src/bundler/index.ts +0 -5
  160. package/src/e2e/deploy.test.ts +0 -69
  161. package/src/edge/functions.ts +0 -67
  162. package/src/edge/index.ts +0 -9
  163. package/src/executor/executor.ts +0 -58
  164. package/src/services/database.ts +0 -175
  165. package/src/services/function-invocation-service.test.ts +0 -81
  166. package/src/services/local-function-execution.ts +0 -153
  167. package/src/services/remote-function-execution-service.ts +0 -63
  168. package/src/services/service-container.ts +0 -115
  169. package/src/services/service-registry.test.ts +0 -45
  170. package/src/services/service-registry.ts +0 -63
  171. package/src/testing/index.ts +0 -6
  172. package/src/testing/layer.ts +0 -114
  173. package/src/testing/logger.ts +0 -17
  174. package/src/testing/persist-database.test.ts +0 -87
  175. package/src/testing/services.ts +0 -115
  176. package/src/trace.ts +0 -178
  177. package/src/translations.ts +0 -20
  178. package/src/triggers/index.ts +0 -7
  179. package/src/triggers/input-builder.ts +0 -35
  180. package/src/triggers/invocation-tracer.ts +0 -101
  181. package/src/triggers/trigger-dispatcher.test.ts +0 -664
  182. package/src/triggers/trigger-dispatcher.ts +0 -521
  183. package/src/triggers/trigger-state-store.ts +0 -61
  184. package/src/url.ts +0 -55
@@ -1,664 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
6
- import { describe, it } from '@effect/vitest';
7
- import * as Duration from 'effect/Duration';
8
- import * as Effect from 'effect/Effect';
9
- import * as Exit from 'effect/Exit';
10
- import * as Fn from 'effect/Function';
11
- import * as Layer from 'effect/Layer';
12
-
13
- import { AiService } from '@dxos/ai';
14
- import { Filter, Obj, Query, Ref } from '@dxos/echo';
15
- import { invariant } from '@dxos/invariant';
16
- import { DataType } from '@dxos/schema';
17
-
18
- import { Example } from '../example';
19
- import { serializeFunction } from '../handler';
20
- import {
21
- ComputeEventLogger,
22
- CredentialsService,
23
- DatabaseService,
24
- FunctionInvocationService,
25
- QueueService,
26
- TracingService,
27
- } from '../services';
28
- import { TestDatabaseLayer } from '../testing';
29
- import { Function, Trigger } from '../types';
30
-
31
- import { InvocationTracer } from './invocation-tracer';
32
- import { TriggerDispatcher } from './trigger-dispatcher';
33
- import { TriggerStateStore } from './trigger-state-store';
34
-
35
- const TestLayer = Fn.pipe(
36
- Layer.mergeAll(ComputeEventLogger.layerFromTracing, InvocationTracer.layerTest, TriggerStateStore.layerMemory),
37
- Layer.provideMerge(
38
- Layer.mergeAll(
39
- AiService.notAvailable,
40
- CredentialsService.layerConfig([]),
41
- FunctionInvocationService.layerTestMocked({ functions: [Example.reply] }).pipe(
42
- Layer.provideMerge(ComputeEventLogger.layerFromTracing),
43
- Layer.provideMerge(TracingService.layerLogInfo()),
44
- ),
45
- FetchHttpClient.layer,
46
- TestDatabaseLayer({
47
- types: [Function.Function, Trigger.Trigger, DataType.Person, DataType.Task],
48
- }),
49
- ),
50
- ),
51
- );
52
-
53
- const TestTriggerDispatcherLayer = Layer.provideMerge(
54
- TriggerDispatcher.layer({ timeControl: 'manual', startingTime: new Date('2025-09-05T15:01:00.000Z') }),
55
- TestLayer,
56
- );
57
-
58
- describe('TriggerDispatcher', () => {
59
- describe('Time Control', () => {
60
- it.effect(
61
- 'should get current time based on time control',
62
- Effect.fnUntraced(function* ({ expect }) {
63
- const dispatcher = yield* TriggerDispatcher;
64
-
65
- const initialTime = dispatcher.getCurrentTime();
66
-
67
- // Advance time by 1 hour
68
- yield* dispatcher.advanceTime(Duration.hours(1));
69
-
70
- const newTime = dispatcher.getCurrentTime();
71
- const timeDiff = newTime.getTime() - initialTime.getTime();
72
-
73
- expect(timeDiff).toBe(Duration.toMillis(Duration.hours(1)));
74
- }, Effect.provide(TestTriggerDispatcherLayer)),
75
- );
76
- });
77
-
78
- describe('Manual Invocation', () => {
79
- it.effect(
80
- 'should manually invoke trigger',
81
- Effect.fnUntraced(function* ({ expect }) {
82
- const functionObj = serializeFunction(Example.reply);
83
- yield* DatabaseService.add(functionObj);
84
- const trigger = Trigger.make({
85
- function: Ref.make(functionObj),
86
- enabled: true,
87
- spec: {
88
- kind: 'timer',
89
- cron: '*/5 * * * *',
90
- },
91
- });
92
- yield* DatabaseService.add(trigger);
93
- const dispatcher = yield* TriggerDispatcher;
94
- const { result } = yield* dispatcher.invokeTrigger({
95
- trigger,
96
- event: { tick: 0 },
97
- });
98
-
99
- expect(result).toEqual(Exit.succeed({ tick: 0 }));
100
- }, Effect.provide(TestTriggerDispatcherLayer)),
101
- );
102
- });
103
-
104
- describe('Timer Triggers', () => {
105
- it.effect(
106
- 'should invoke scheduled timer triggers',
107
- Effect.fnUntraced(function* ({ expect }) {
108
- const functionObj = serializeFunction(Example.reply);
109
- yield* DatabaseService.add(functionObj);
110
- const trigger = Trigger.make({
111
- function: Ref.make(functionObj),
112
- enabled: true,
113
- spec: {
114
- kind: 'timer',
115
- cron: '* * * * *', // Every minute - should trigger immediately
116
- },
117
- });
118
- yield* DatabaseService.add(trigger);
119
-
120
- const dispatcher = yield* TriggerDispatcher;
121
- yield* dispatcher.refreshTriggers();
122
-
123
- // Manually invoke the trigger
124
- yield* dispatcher.advanceTime(Duration.minutes(1));
125
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
126
-
127
- // Should have executed successfully
128
- expect(results.length).toBe(1);
129
- expect(results[0].triggerId).toBe(trigger.id);
130
- expect(Exit.isSuccess(results[0].result)).toBe(true);
131
- }, Effect.provide(TestTriggerDispatcherLayer)),
132
- );
133
-
134
- it.effect(
135
- 'should handle disabled triggers',
136
- Effect.fnUntraced(function* ({ expect }) {
137
- const functionObj = serializeFunction(Example.reply);
138
- yield* DatabaseService.add(functionObj);
139
-
140
- const enabledTrigger = Trigger.make({
141
- function: Ref.make(functionObj),
142
- enabled: true,
143
- spec: {
144
- kind: 'timer',
145
- cron: '* * * * *',
146
- },
147
- });
148
-
149
- const disabledTrigger = Trigger.make({
150
- function: Ref.make(functionObj),
151
- enabled: false,
152
- spec: {
153
- kind: 'timer',
154
- cron: '* * * * *',
155
- },
156
- });
157
-
158
- yield* DatabaseService.add(enabledTrigger);
159
- yield* DatabaseService.add(disabledTrigger);
160
-
161
- const dispatcher = yield* TriggerDispatcher;
162
- yield* dispatcher.refreshTriggers();
163
-
164
- // Manually test invocation of enabled vs disabled
165
- yield* dispatcher.advanceTime(Duration.minutes(1));
166
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
167
-
168
- // Enabled should succeed
169
- expect(results.length).toBe(1);
170
- expect(results[0].triggerId).toBe(enabledTrigger.id);
171
- expect(Exit.isSuccess(results[0].result)).toBe(true);
172
- }, Effect.provide(TestTriggerDispatcherLayer)),
173
- );
174
-
175
- it.effect(
176
- 'cron triggers are invoked periodically on schedule',
177
- Effect.fnUntraced(function* ({ expect }) {
178
- const functionObj = serializeFunction(Example.reply);
179
- yield* DatabaseService.add(functionObj);
180
-
181
- // cron every 5 minutes
182
- const trigger = Trigger.make({
183
- function: Ref.make(functionObj),
184
- enabled: true,
185
- spec: {
186
- kind: 'timer',
187
- cron: '*/5 * * * *',
188
- },
189
- });
190
- yield* DatabaseService.add(trigger);
191
-
192
- // now = 15:01
193
- const dispatcher = yield* TriggerDispatcher;
194
- yield* dispatcher.refreshTriggers(); // next execution = 15:05
195
-
196
- // advance 1 minute; now = 15:02 -- trigger should not be invoked
197
- yield* dispatcher.advanceTime(Duration.minutes(1));
198
- let results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
199
- expect(results.length).toBe(0);
200
-
201
- // advance 4 more minutes; now = 15:06 -- trigger should be invoked
202
- yield* dispatcher.advanceTime(Duration.minutes(4));
203
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
204
- expect(results.length).toBe(1);
205
-
206
- // advance 2 more minutes; now = 15:08 -- trigger should not be invoked
207
- yield* dispatcher.advanceTime(Duration.minutes(2));
208
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
209
- expect(results.length).toBe(0);
210
-
211
- // advance 3 more minutes; now = 15:11 -- trigger should be invoked
212
- yield* dispatcher.advanceTime(Duration.minutes(3));
213
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
214
- expect(results.length).toBe(1);
215
- }, Effect.provide(TestTriggerDispatcherLayer)),
216
- );
217
- });
218
-
219
- describe('Dynamic Trigger Management', () => {
220
- it.effect(
221
- 'should handle trigger updates dynamically',
222
- Effect.fnUntraced(function* ({ expect }) {
223
- const dispatcher = yield* TriggerDispatcher;
224
- yield* dispatcher.refreshTriggers();
225
-
226
- // Initially no triggers in database
227
-
228
- // Add a trigger dynamically
229
- const functionObj = serializeFunction(Example.reply);
230
- yield* DatabaseService.add(functionObj);
231
- const trigger = Trigger.make({
232
- function: Ref.make(functionObj),
233
- enabled: true,
234
- spec: {
235
- kind: 'timer',
236
- cron: '* * * * *', // Every minute
237
- },
238
- });
239
- yield* DatabaseService.add(trigger);
240
-
241
- // Can invoke the trigger
242
- const result = yield* dispatcher.invokeTrigger({ trigger, event: { tick: 0 } });
243
- expect(Exit.isSuccess(result.result)).toBe(true);
244
- }, Effect.provide(TestTriggerDispatcherLayer)),
245
- );
246
- });
247
-
248
- describe('Cron Patterns', () => {
249
- it.effect(
250
- 'should support Effect cron expressions',
251
- Effect.fnUntraced(function* ({ expect }) {
252
- const functionObj = serializeFunction(Example.reply);
253
- yield* DatabaseService.add(functionObj);
254
-
255
- const validPatterns = [
256
- '* * * * *', // Every minute
257
- '0 * * * *', // Every hour
258
- '0 0 * * *', // Daily
259
- '0 0 * * 1', // Every Monday
260
- '0 9-17 * * *', // Every hour from 9 AM to 5 PM
261
- ];
262
-
263
- const dispatcher = yield* TriggerDispatcher;
264
-
265
- // Test that valid patterns can be invoked
266
- for (const cron of validPatterns) {
267
- const trigger = Trigger.make({
268
- function: Ref.make(functionObj),
269
- enabled: true,
270
- spec: {
271
- kind: 'timer',
272
- cron,
273
- },
274
- });
275
- yield* DatabaseService.add(trigger);
276
-
277
- const result = yield* dispatcher.invokeTrigger({ trigger, event: { tick: 0 } });
278
- expect(Exit.isSuccess(result.result)).toBe(true);
279
- }
280
- }, Effect.provide(TestTriggerDispatcherLayer)),
281
- );
282
-
283
- it.effect(
284
- 'should handle invalid cron expressions gracefully',
285
- Effect.fnUntraced(function* ({ expect }) {
286
- const functionObj = serializeFunction(Example.reply);
287
- yield* DatabaseService.add(functionObj);
288
-
289
- // Test with an invalid pattern
290
- const trigger = Trigger.make({
291
- function: Ref.make(functionObj),
292
- enabled: true,
293
- spec: {
294
- kind: 'timer',
295
- cron: 'invalid-cron',
296
- },
297
- });
298
- yield* DatabaseService.add(trigger);
299
-
300
- const dispatcher = yield* TriggerDispatcher;
301
- yield* dispatcher.refreshTriggers();
302
-
303
- // Can still invoke manually even with invalid cron
304
- const result = yield* dispatcher.invokeScheduledTriggers({ kinds: ['timer'] });
305
- expect(result.length).toBe(0);
306
- }, Effect.provide(TestTriggerDispatcherLayer)),
307
- );
308
- });
309
-
310
- describe('Natural Time Control', () => {
311
- it.effect(
312
- 'should start and stop dispatcher',
313
- Effect.fnUntraced(
314
- function* () {
315
- const dispatcher = yield* TriggerDispatcher;
316
- yield* dispatcher.start();
317
- yield* dispatcher.stop();
318
- },
319
- Effect.provide(Layer.provideMerge(TriggerDispatcher.layer({ timeControl: 'natural' }), TestLayer)),
320
- ),
321
- );
322
- });
323
-
324
- describe('Queue Triggers', () => {
325
- it.effect(
326
- 'should invoke scheduled queue triggers',
327
- Effect.fnUntraced(function* ({ expect }) {
328
- const queue = yield* QueueService.createQueue();
329
- const functionObj = serializeFunction(Example.reply);
330
- yield* DatabaseService.add(functionObj);
331
- const trigger = Trigger.make({
332
- function: Ref.make(functionObj),
333
- enabled: true,
334
- spec: {
335
- kind: 'queue',
336
- queue: queue.dxn.toString(),
337
- },
338
- });
339
- yield* DatabaseService.add(trigger);
340
- yield* QueueService.append(queue, [
341
- Obj.make(DataType.Person, {
342
- fullName: 'John Doe',
343
- }),
344
- ]);
345
-
346
- const dispatcher = yield* TriggerDispatcher;
347
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['queue'] });
348
- expect(results.length).toBe(1);
349
- expect(results[0].triggerId).toBe(trigger.id);
350
- expect(Exit.isSuccess(results[0].result)).toBe(true);
351
- }, Effect.provide(TestTriggerDispatcherLayer)),
352
- );
353
-
354
- it.effect(
355
- 'triggers are invoked one by one',
356
- Effect.fnUntraced(function* ({ expect }) {
357
- const queue = yield* QueueService.createQueue();
358
- const functionObj = serializeFunction(Example.reply);
359
- yield* DatabaseService.add(functionObj);
360
- const trigger = Trigger.make({
361
- function: Ref.make(functionObj),
362
- enabled: true,
363
- spec: {
364
- kind: 'queue',
365
- queue: queue.dxn.toString(),
366
- },
367
- });
368
- yield* DatabaseService.add(trigger);
369
- yield* QueueService.append(queue, [
370
- Obj.make(DataType.Person, {
371
- fullName: 'John Doe',
372
- }),
373
- Obj.make(DataType.Person, {
374
- fullName: 'Jane Smith',
375
- }),
376
- ]);
377
-
378
- const dispatcher = yield* TriggerDispatcher;
379
-
380
- {
381
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['queue'] });
382
- expect(results.length).toBe(1);
383
- expect(results[0].triggerId).toBe(trigger.id);
384
- expect(Exit.isSuccess(results[0].result)).toBe(true);
385
- }
386
-
387
- {
388
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['queue'] });
389
- expect(results.length).toBe(1);
390
- expect(results[0].triggerId).toBe(trigger.id);
391
- expect(Exit.isSuccess(results[0].result)).toBe(true);
392
- }
393
-
394
- {
395
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['queue'] });
396
- expect(results.length).toBe(0);
397
- }
398
- }, Effect.provide(TestTriggerDispatcherLayer)),
399
- );
400
-
401
- it.effect(
402
- 'builds input from pattern',
403
- Effect.fnUntraced(function* ({ expect }) {
404
- const queue = yield* QueueService.createQueue();
405
- const functionObj = serializeFunction(Example.reply);
406
- yield* DatabaseService.add(functionObj);
407
- const trigger = Trigger.make({
408
- function: Ref.make(functionObj),
409
- enabled: true,
410
- spec: {
411
- kind: 'queue',
412
- queue: queue.dxn.toString(),
413
- },
414
- input: {
415
- instructions: 'Please process the queue item.',
416
- input: '{{event.item}}',
417
- triggerId: '{{trigger.id}}',
418
- },
419
- });
420
- yield* DatabaseService.add(trigger);
421
- const person = Obj.make(DataType.Person, {
422
- fullName: 'John Doe',
423
- });
424
- yield* QueueService.append(queue, [person]);
425
-
426
- const dispatcher = yield* TriggerDispatcher;
427
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['queue'] });
428
- expect(results.length).toBe(1);
429
- expect(results[0].triggerId).toBe(trigger.id);
430
- const exit = results[0].result;
431
- invariant(Exit.isSuccess(exit));
432
- expect(exit.value).to.deep.include({
433
- instructions: 'Please process the queue item.',
434
- input: {
435
- id: person.id,
436
- fullName: 'John Doe',
437
- },
438
- triggerId: trigger.id,
439
- });
440
- }, Effect.provide(TestTriggerDispatcherLayer)),
441
- );
442
- });
443
-
444
- describe('Database Triggers (Subscription)', () => {
445
- it.effect(
446
- 'should invoke triggers on object creation',
447
- Effect.fnUntraced(function* ({ expect }) {
448
- const functionObj = serializeFunction(Example.reply);
449
- yield* DatabaseService.add(functionObj);
450
-
451
- // Create a subscription trigger that watches for DataType.Person objects
452
- const trigger = Trigger.make({
453
- function: Ref.make(functionObj),
454
- enabled: true,
455
- spec: {
456
- kind: 'subscription',
457
- query: {
458
- ast: Query.select(Filter.type(DataType.Person)).ast,
459
- },
460
- },
461
- });
462
- yield* DatabaseService.add(trigger);
463
-
464
- const dispatcher = yield* TriggerDispatcher;
465
- yield* dispatcher.refreshTriggers();
466
-
467
- // Create a new Person object - this should trigger the subscription
468
- const person = Obj.make(DataType.Person, {
469
- fullName: 'Alice Smith',
470
- });
471
- yield* DatabaseService.add(person);
472
-
473
- // Invoke scheduled triggers to check if subscription fires
474
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
475
-
476
- // Should have triggered for the new person
477
- expect(results.length).toBe(1);
478
- expect(results[0].triggerId).toBe(trigger.id);
479
- expect(Exit.isSuccess(results[0].result)).toBe(true);
480
- }, Effect.provide(TestTriggerDispatcherLayer)),
481
- );
482
-
483
- it.effect(
484
- 'should invoke triggers on object updates',
485
- Effect.fnUntraced(function* ({ expect }) {
486
- const functionObj = serializeFunction(Example.reply);
487
- yield* DatabaseService.add(functionObj);
488
-
489
- // Create a person object first
490
- const person = Obj.make(DataType.Person, {
491
- fullName: 'Bob Jones',
492
- });
493
- yield* DatabaseService.add(person);
494
-
495
- // Create a subscription trigger
496
- const trigger = Trigger.make({
497
- function: Ref.make(functionObj),
498
- enabled: true,
499
- spec: {
500
- kind: 'subscription',
501
- query: {
502
- ast: Query.select(Filter.type(DataType.Person)).ast,
503
- },
504
- },
505
- });
506
- yield* DatabaseService.add(trigger);
507
-
508
- const dispatcher = yield* TriggerDispatcher;
509
- yield* dispatcher.refreshTriggers();
510
-
511
- // Initial check - should trigger for existing object
512
- let results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
513
- expect(results.length).toBe(1);
514
-
515
- // Update the person object
516
- person.fullName = 'Robert Jones';
517
- yield* DatabaseService.flush();
518
-
519
- // Should trigger again for the update
520
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
521
- expect(results.length).toBe(1);
522
- expect(results[0].triggerId).toBe(trigger.id);
523
- expect(Exit.isSuccess(results[0].result)).toBe(true);
524
- }, Effect.provide(TestTriggerDispatcherLayer)),
525
- );
526
-
527
- it.effect(
528
- 'should not invoke triggers for unchanged objects',
529
- Effect.fnUntraced(function* ({ expect }) {
530
- const functionObj = serializeFunction(Example.reply);
531
- yield* DatabaseService.add(functionObj);
532
-
533
- // Create a subscription trigger first
534
- const trigger = Trigger.make({
535
- function: Ref.make(functionObj),
536
- enabled: true,
537
- spec: {
538
- kind: 'subscription',
539
- query: {
540
- ast: Query.select(Filter.type(DataType.Person)).ast,
541
- },
542
- },
543
- });
544
- yield* DatabaseService.add(trigger);
545
-
546
- const dispatcher = yield* TriggerDispatcher;
547
- yield* dispatcher.refreshTriggers();
548
-
549
- // Create a person object
550
- const person = Obj.make(DataType.Person, {
551
- fullName: 'Charlie Brown',
552
- });
553
- yield* DatabaseService.add(person);
554
-
555
- // First invocation - should trigger for new object
556
- let results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
557
- expect(results.length).toBe(1);
558
-
559
- // Second invocation without any changes - should not trigger
560
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
561
- expect(results.length).toBe(0);
562
-
563
- // Update the object
564
- person.fullName = 'Charles Brown';
565
- yield* DatabaseService.flush();
566
-
567
- // Third invocation - should trigger for the update
568
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
569
- expect(results.length).toBe(1);
570
-
571
- // Fourth invocation without changes - should not trigger
572
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
573
- expect(results.length).toBe(0);
574
- }, Effect.provide(TestTriggerDispatcherLayer)),
575
- );
576
-
577
- it.effect(
578
- 'should handle multiple object types with filters',
579
- Effect.fnUntraced(function* ({ expect }) {
580
- const functionObj = serializeFunction(Example.reply);
581
- yield* DatabaseService.add(functionObj);
582
-
583
- // Create a subscription trigger that only watches for DataType.Task objects
584
- const trigger = Trigger.make({
585
- function: Ref.make(functionObj),
586
- enabled: true,
587
- spec: {
588
- kind: 'subscription',
589
- query: {
590
- ast: Query.select(Filter.type(DataType.Task)).ast,
591
- },
592
- },
593
- });
594
- yield* DatabaseService.add(trigger);
595
-
596
- const dispatcher = yield* TriggerDispatcher;
597
- yield* dispatcher.refreshTriggers();
598
-
599
- // Create a Person object - should NOT trigger
600
- const person = Obj.make(DataType.Person, {
601
- fullName: 'David Wilson',
602
- });
603
- yield* DatabaseService.add(person);
604
-
605
- let results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
606
- expect(results.length).toBe(0);
607
-
608
- // Create a Task object - should trigger
609
- const task = Obj.make(DataType.Task, {
610
- title: 'Important task',
611
- });
612
- yield* DatabaseService.add(task);
613
-
614
- results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
615
- expect(results.length).toBe(1);
616
- expect(results[0].triggerId).toBe(trigger.id);
617
- expect(Exit.isSuccess(results[0].result)).toBe(true);
618
- }, Effect.provide(TestTriggerDispatcherLayer)),
619
- );
620
-
621
- it.effect(
622
- 'should pass correct event data to function',
623
- Effect.fnUntraced(function* ({ expect }) {
624
- const functionObj = serializeFunction(Example.reply);
625
- yield* DatabaseService.add(functionObj);
626
-
627
- const person = Obj.make(DataType.Person, {
628
- fullName: 'Eva Martinez',
629
- });
630
- yield* DatabaseService.add(person);
631
-
632
- // Create a subscription trigger with input pattern
633
- const trigger = Trigger.make({
634
- function: Ref.make(functionObj),
635
- enabled: true,
636
- spec: {
637
- kind: 'subscription',
638
- query: {
639
- ast: Query.select(Filter.type(DataType.Person)).ast,
640
- },
641
- },
642
- input: {
643
- objectId: '{{event.changedObjectId}}',
644
- changeType: '{{event.type}}',
645
- triggerId: '{{trigger.id}}',
646
- },
647
- });
648
- yield* DatabaseService.add(trigger);
649
-
650
- const dispatcher = yield* TriggerDispatcher;
651
- const results = yield* dispatcher.invokeScheduledTriggers({ kinds: ['subscription'] });
652
-
653
- expect(results.length).toBe(1);
654
- const exit = results[0].result;
655
- invariant(Exit.isSuccess(exit));
656
- expect(exit.value).to.deep.include({
657
- objectId: person.id,
658
- changeType: 'unknown', // TODO: This should be 'create' or 'update'
659
- triggerId: trigger.id,
660
- });
661
- }, Effect.provide(TestTriggerDispatcherLayer)),
662
- );
663
- });
664
- });