@dxos/functions 0.8.4-main.ead640a → 0.8.4-main.ef1bc66f44

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