@pikku/core 0.12.19 → 0.12.21

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 (146) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/dev/hot-reload.js +20 -6
  3. package/dist/errors/error-handler.d.ts +1 -1
  4. package/dist/errors/error-handler.js +2 -2
  5. package/dist/function/function-runner.js +53 -3
  6. package/dist/function/functions.types.d.ts +3 -2
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +2 -2
  9. package/dist/middleware/index.d.ts +2 -2
  10. package/dist/middleware/index.js +2 -2
  11. package/dist/middleware/telemetry.d.ts +2 -2
  12. package/dist/middleware/telemetry.js +2 -2
  13. package/dist/middleware-runner.d.ts +15 -27
  14. package/dist/middleware-runner.js +25 -30
  15. package/dist/permissions.d.ts +15 -22
  16. package/dist/permissions.js +30 -25
  17. package/dist/pikku-request.js +1 -1
  18. package/dist/pikku-state.js +2 -3
  19. package/dist/services/ai-agent-runner-service.d.ts +1 -0
  20. package/dist/services/content-service.d.ts +66 -37
  21. package/dist/services/in-memory-queue-service.js +1 -1
  22. package/dist/services/in-memory-workflow-service.d.ts +15 -13
  23. package/dist/services/in-memory-workflow-service.js +31 -13
  24. package/dist/services/index.d.ts +1 -1
  25. package/dist/services/local-content.d.ts +10 -12
  26. package/dist/services/local-content.js +54 -38
  27. package/dist/services/user-session-service.d.ts +1 -1
  28. package/dist/testing/service-tests.js +24 -0
  29. package/dist/types/core.types.d.ts +4 -0
  30. package/dist/types/state.types.d.ts +2 -18
  31. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
  32. package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
  33. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
  34. package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
  35. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
  36. package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
  37. package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
  38. package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
  39. package/dist/wirings/channel/channel-handler.js +1 -1
  40. package/dist/wirings/channel/channel-store.d.ts +13 -0
  41. package/dist/wirings/channel/channel.types.d.ts +3 -0
  42. package/dist/wirings/channel/local/local-channel-runner.js +23 -5
  43. package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
  44. package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
  45. package/dist/wirings/cli/cli-runner.js +24 -5
  46. package/dist/wirings/cli/command-parser.js +19 -4
  47. package/dist/wirings/http/http-runner.js +72 -36
  48. package/dist/wirings/http/http.types.d.ts +1 -0
  49. package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
  50. package/dist/wirings/http/web-request.js +32 -0
  51. package/dist/wirings/rpc/rpc-runner.d.ts +3 -2
  52. package/dist/wirings/rpc/rpc-runner.js +45 -11
  53. package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
  54. package/dist/wirings/workflow/graph/graph-node.js +4 -0
  55. package/dist/wirings/workflow/graph/graph-runner.js +12 -10
  56. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
  57. package/dist/wirings/workflow/index.d.ts +1 -1
  58. package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
  59. package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
  60. package/dist/wirings/workflow/workflow.types.d.ts +34 -0
  61. package/package.json +1 -1
  62. package/run-tests.sh +4 -1
  63. package/src/dev/hot-reload.test.ts +62 -11
  64. package/src/dev/hot-reload.ts +28 -7
  65. package/src/errors/error-handler.ts +8 -2
  66. package/src/errors/error.test.ts +2 -0
  67. package/src/function/function-runner.test.ts +500 -10
  68. package/src/function/function-runner.ts +68 -3
  69. package/src/function/functions.types.ts +3 -2
  70. package/src/index.ts +22 -3
  71. package/src/middleware/index.ts +11 -2
  72. package/src/middleware/telemetry.ts +2 -2
  73. package/src/middleware-runner.test.ts +16 -16
  74. package/src/middleware-runner.ts +42 -30
  75. package/src/permissions.test.ts +27 -24
  76. package/src/permissions.ts +41 -25
  77. package/src/pikku-request.test.ts +35 -0
  78. package/src/pikku-request.ts +1 -1
  79. package/src/pikku-state.ts +2 -3
  80. package/src/run-tests-script.test.ts +18 -0
  81. package/src/services/ai-agent-runner-service.ts +1 -0
  82. package/src/services/content-service.ts +79 -51
  83. package/src/services/in-memory-queue-service.ts +1 -1
  84. package/src/services/in-memory-session-store.ts +1 -2
  85. package/src/services/in-memory-workflow-service.test.ts +33 -11
  86. package/src/services/in-memory-workflow-service.ts +49 -13
  87. package/src/services/index.ts +10 -1
  88. package/src/services/local-content.test.ts +54 -0
  89. package/src/services/local-content.ts +80 -53
  90. package/src/services/typed-credential-service.ts +3 -3
  91. package/src/services/typed-secret-service.ts +3 -3
  92. package/src/services/typed-variables-service.ts +3 -3
  93. package/src/services/user-session-service.ts +4 -4
  94. package/src/testing/service-tests.ts +30 -0
  95. package/src/types/core.types.ts +6 -2
  96. package/src/types/state.types.ts +2 -13
  97. package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
  98. package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
  99. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
  100. package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
  101. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
  102. package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
  103. package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
  104. package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
  105. package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
  106. package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
  107. package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
  108. package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
  109. package/src/wirings/channel/channel-handler.test.ts +272 -0
  110. package/src/wirings/channel/channel-handler.ts +1 -1
  111. package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
  112. package/src/wirings/channel/channel-store.ts +19 -0
  113. package/src/wirings/channel/channel.types.ts +4 -0
  114. package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
  115. package/src/wirings/channel/local/local-channel-runner.ts +41 -5
  116. package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
  117. package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
  118. package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
  119. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
  120. package/src/wirings/cli/cli-runner.test.ts +255 -2
  121. package/src/wirings/cli/cli-runner.ts +31 -10
  122. package/src/wirings/cli/cli.types.ts +4 -8
  123. package/src/wirings/cli/command-parser.test.ts +83 -0
  124. package/src/wirings/cli/command-parser.ts +23 -4
  125. package/src/wirings/http/http-runner.test.ts +296 -1
  126. package/src/wirings/http/http-runner.ts +87 -57
  127. package/src/wirings/http/http.types.ts +11 -26
  128. package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
  129. package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
  130. package/src/wirings/http/web-request.test.ts +115 -0
  131. package/src/wirings/http/web-request.ts +43 -5
  132. package/src/wirings/mcp/mcp-runner.test.ts +367 -0
  133. package/src/wirings/queue/queue.types.ts +2 -5
  134. package/src/wirings/rpc/rpc-runner.test.ts +511 -0
  135. package/src/wirings/rpc/rpc-runner.ts +60 -21
  136. package/src/wirings/rpc/wire-addon.test.ts +57 -0
  137. package/src/wirings/workflow/graph/graph-node.ts +12 -0
  138. package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
  139. package/src/wirings/workflow/graph/graph-runner.ts +28 -10
  140. package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
  141. package/src/wirings/workflow/index.ts +1 -0
  142. package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
  143. package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
  144. package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
  145. package/src/wirings/workflow/workflow.types.ts +68 -5
  146. package/tsconfig.tsbuildinfo +1 -1
@@ -1,81 +1,109 @@
1
- export interface ContentService {
1
+ /**
2
+ * Arguments for signing a content key into a time-limited URL.
3
+ */
4
+ export interface SignContentKeyArgs<TBucket extends string = string> {
5
+ bucket: TBucket
6
+ contentKey: string
7
+ dateLessThan: Date
8
+ dateGreaterThan?: Date
9
+ }
10
+
11
+ /**
12
+ * Arguments for signing an arbitrary URL.
13
+ */
14
+ export interface SignURLArgs {
15
+ url: string
16
+ dateLessThan: Date
17
+ dateGreaterThan?: Date
18
+ }
19
+
20
+ /**
21
+ * Arguments for minting a presigned upload URL.
22
+ */
23
+ export interface GetUploadURLArgs<TBucket extends string = string> {
24
+ bucket: TBucket
25
+ fileKey: string
26
+ contentType: string
27
+ size?: number
28
+ }
29
+
30
+ /**
31
+ * Result of minting a presigned upload URL.
32
+ */
33
+ export interface UploadURLResult {
34
+ uploadUrl: string
35
+ assetKey: string
36
+ uploadHeaders?: Record<string, string>
37
+ uploadMethod?: 'PUT' | 'POST'
38
+ }
39
+
40
+ /**
41
+ * Arguments for an operation that targets a single object by key.
42
+ */
43
+ export interface BucketKeyArgs<TBucket extends string = string> {
44
+ bucket: TBucket
45
+ key: string
46
+ }
47
+
48
+ /**
49
+ * Arguments for writing a stream to storage.
50
+ */
51
+ export interface WriteFileArgs<
52
+ TBucket extends string = string,
53
+ > extends BucketKeyArgs<TBucket> {
54
+ stream: ReadableStream | NodeJS.ReadableStream
55
+ }
56
+
57
+ /**
58
+ * Arguments for copying a local file into storage.
59
+ */
60
+ export interface CopyFileArgs<
61
+ TBucket extends string = string,
62
+ > extends BucketKeyArgs<TBucket> {
63
+ fromAbsolutePath: string
64
+ }
65
+
66
+ export interface ContentService<TBucket extends string = string> {
2
67
  /**
3
68
  * Signs a content key to generate a secure, time-limited access URL.
4
- * @param contentKey - The key representing the content object.
5
- * @param dateLessThan - The expiration time for the signed URL.
6
- * @param dateGreaterThan - (Optional) Start time before which access is denied.
7
69
  */
8
- signContentKey(
9
- contentKey: string,
10
- dateLessThan: Date,
11
- dateGreaterThan?: Date
12
- ): Promise<string>
70
+ signContentKey(args: SignContentKeyArgs<TBucket>): Promise<string>
13
71
 
14
72
  /**
15
73
  * Signs an arbitrary URL to generate a secure, time-limited access URL.
16
- * @param url - The full URL that needs signing.
17
- * @param dateLessThan - The expiration time for the signed URL.
18
- * @param dateGreaterThan - (Optional) Start time before which access is denied.
19
74
  */
20
- signURL(
21
- url: string,
22
- dateLessThan: Date,
23
- dateGreaterThan?: Date
24
- ): Promise<string>
75
+ signURL(args: SignURLArgs): Promise<string>
25
76
 
26
77
  /**
27
78
  * Generates a signed URL for uploading a file directly to storage.
28
- * @param fileKey - The desired key/location of the uploaded file.
29
- * @param contentType - The MIME type of the file.
30
- * @returns A signed upload URL and the finalized asset key.
79
+ * Bucket policy (size limits, MIME allowlist) is enforced by the implementation.
31
80
  */
32
- getUploadURL(
33
- fileKey: string,
34
- contentType: string
35
- ): Promise<{
36
- uploadUrl: string
37
- assetKey: string
38
- uploadHeaders?: Record<string, string>
39
- uploadMethod?: 'PUT' | 'POST'
40
- }>
81
+ getUploadURL(args: GetUploadURLArgs<TBucket>): Promise<UploadURLResult>
41
82
 
42
83
  /**
43
84
  * Deletes a file from the storage backend.
44
- * @param fileName - The name or key of the file to delete.
45
- * @returns A boolean indicating success.
46
85
  */
47
- deleteFile(fileName: string): Promise<boolean>
86
+ deleteFile(args: BucketKeyArgs<TBucket>): Promise<boolean>
48
87
 
49
88
  /**
50
- * Uploads a file stream to storage under a specified asset key.
51
- * @param assetKey - The key where the file will be saved.
52
- * @param stream - A readable stream of the file contents.
53
- * @returns A boolean indicating success.
89
+ * Uploads a file stream to storage under the specified bucket + key.
54
90
  */
55
- writeFile(
56
- assetKey: string,
57
- stream: ReadableStream | NodeJS.ReadableStream
58
- ): Promise<boolean>
91
+ writeFile(args: WriteFileArgs<TBucket>): Promise<boolean>
59
92
 
60
93
  /**
61
- * Copies a file from a local absolute path into storage under a new asset key.
62
- * @param assetKey - The destination key.
63
- * @param fromAbsolutePath - The local absolute file path.
64
- * @returns A boolean indicating success.
94
+ * Copies a file from a local absolute path into storage.
65
95
  */
66
- copyFile(assetKey: string, fromAbsolutePath: string): Promise<boolean>
96
+ copyFile(args: CopyFileArgs<TBucket>): Promise<boolean>
67
97
 
68
98
  /**
69
99
  * Reads a file from storage as a readable stream.
70
- * @param assetKey - The key of the file to read.
71
- * @returns A readable file stream.
72
100
  */
73
- readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>
101
+ readFile(
102
+ args: BucketKeyArgs<TBucket>
103
+ ): Promise<ReadableStream | NodeJS.ReadableStream>
74
104
 
75
105
  /**
76
106
  * Reads an entire file from storage into a Buffer.
77
- * @param assetKey - The key of the file to read.
78
- * @returns The file contents as a Buffer.
79
107
  */
80
- readFileAsBuffer(assetKey: string): Promise<Buffer>
108
+ readFileAsBuffer(args: BucketKeyArgs<TBucket>): Promise<Buffer>
81
109
  }
@@ -24,7 +24,7 @@ export class InMemoryQueueService implements QueueService {
24
24
  pikkuUserId: options?.pikkuUserId,
25
25
  }
26
26
 
27
- const delay = options?.delay ?? 0
27
+ const delay = options?.delay ?? 100 + Math.floor(Math.random() * 201)
28
28
 
29
29
  setTimeout(async () => {
30
30
  try {
@@ -3,8 +3,7 @@ import type { SessionStore } from './session-store.js'
3
3
 
4
4
  export class InMemorySessionStore<
5
5
  UserSession extends CoreUserSession = CoreUserSession,
6
- > implements SessionStore<UserSession>
7
- {
6
+ > implements SessionStore<UserSession> {
8
7
  private sessions = new Map<string, UserSession>()
9
8
 
10
9
  async get(pikkuUserId: string): Promise<UserSession | undefined> {
@@ -1,6 +1,8 @@
1
1
  import { describe, test, beforeEach } from 'node:test'
2
2
  import assert from 'node:assert'
3
3
  import { InMemoryWorkflowService } from './in-memory-workflow-service.js'
4
+ import { getQueueWorkers } from '../wirings/queue/queue-runner.js'
5
+ import { pikkuState } from '../pikku-state.js'
4
6
 
5
7
  let service: InMemoryWorkflowService
6
8
 
@@ -48,17 +50,10 @@ describe('InMemoryWorkflowService', () => {
48
50
  })
49
51
 
50
52
  test('should persist deterministic planned steps metadata', async () => {
51
- const runId = await service.createRun(
52
- 'wf',
53
- {},
54
- true,
55
- 'h',
56
- {} as any,
57
- {
58
- deterministic: true,
59
- plannedSteps: [{ stepName: 'Step 1' }, { stepName: 'Step 2' }],
60
- }
61
- )
53
+ const runId = await service.createRun('wf', {}, true, 'h', {} as any, {
54
+ deterministic: true,
55
+ plannedSteps: [{ stepName: 'Step 1' }, { stepName: 'Step 2' }],
56
+ })
62
57
 
63
58
  const run = await service.getRun(runId)
64
59
  assert.strictEqual(run?.deterministic, true)
@@ -369,4 +364,31 @@ describe('InMemoryWorkflowService', () => {
369
364
  assert.deepStrictEqual(history, [])
370
365
  })
371
366
  })
367
+
368
+ describe('queue worker registration', () => {
369
+ test('rewireQueueWorkers registers workflow queues added after construction', () => {
370
+ const stepQueue = 'wf-step-regression-enrich'
371
+ const orchQueue = 'wf-orchestrator-regression-onboarding'
372
+
373
+ const ws = new InMemoryWorkflowService()
374
+
375
+ const queueMeta = pikkuState(null, 'queue', 'meta')
376
+ queueMeta[stepQueue] = {
377
+ name: stepQueue,
378
+ pikkuFuncId: 'pikkuWorkflowWorker:regressionEnrich',
379
+ }
380
+ queueMeta[orchQueue] = {
381
+ name: orchQueue,
382
+ pikkuFuncId: 'pikkuWorkflowOrchestrator:regressionOnboarding',
383
+ }
384
+
385
+ assert.strictEqual(getQueueWorkers().has(stepQueue), false)
386
+ assert.strictEqual(getQueueWorkers().has(orchQueue), false)
387
+
388
+ ws.rewireQueueWorkers()
389
+
390
+ assert.strictEqual(getQueueWorkers().has(stepQueue), true)
391
+ assert.strictEqual(getQueueWorkers().has(orchQueue), true)
392
+ })
393
+ })
372
394
  })
@@ -37,6 +37,7 @@ export class InMemoryWorkflowService
37
37
  extends PikkuWorkflowService
38
38
  implements WorkflowRunService
39
39
  {
40
+ private sleepTimers = new Set<ReturnType<typeof setTimeout>>()
40
41
  private runs = new Map<string, WorkflowRun>()
41
42
  private steps = new Map<string, StepState>() // keyed by `${runId}:${stepName}`
42
43
  private stepData = new Map<string, InternalStepData>() // keyed by stepId
@@ -51,7 +52,7 @@ export class InMemoryWorkflowService
51
52
  { graph: any; source: string; status: WorkflowVersionStatus }
52
53
  >() // keyed by `${name}:${graphHash}`
53
54
 
54
- async createRun(
55
+ protected async createRunImpl(
55
56
  workflowName: string,
56
57
  input: any,
57
58
  inline: boolean,
@@ -90,6 +91,25 @@ export class InMemoryWorkflowService
90
91
  return runId
91
92
  }
92
93
 
94
+ protected override async scheduleSleep(
95
+ runId: string,
96
+ stepId: string,
97
+ duration: number
98
+ ): Promise<boolean> {
99
+ const timer = setTimeout(async () => {
100
+ this.sleepTimers.delete(timer)
101
+ try {
102
+ await this.executeWorkflowSleepCompleted(runId, stepId)
103
+ } catch (error: any) {
104
+ this.logger?.error(
105
+ `Failed to resume workflow sleep for runId ${runId}: ${error?.message ?? error}`
106
+ )
107
+ }
108
+ }, duration)
109
+ this.sleepTimers.add(timer)
110
+ return true
111
+ }
112
+
93
113
  async getRun(id: string): Promise<WorkflowRun | null> {
94
114
  return this.runs.get(id) || null
95
115
  }
@@ -100,7 +120,7 @@ export class InMemoryWorkflowService
100
120
  return this.stepHistory.get(runId) || []
101
121
  }
102
122
 
103
- async updateRunStatus(
123
+ protected async updateRunStatusImpl(
104
124
  id: string,
105
125
  status: WorkflowStatus,
106
126
  output?: any,
@@ -115,7 +135,7 @@ export class InMemoryWorkflowService
115
135
  }
116
136
  }
117
137
 
118
- async insertStepState(
138
+ protected async insertStepStateImpl(
119
139
  runId: string,
120
140
  stepName: string,
121
141
  rpcName: string | null,
@@ -159,7 +179,7 @@ export class InMemoryWorkflowService
159
179
  return step
160
180
  }
161
181
 
162
- async setStepRunning(stepId: string): Promise<void> {
182
+ protected async setStepRunningImpl(stepId: string): Promise<void> {
163
183
  for (const step of this.steps.values()) {
164
184
  if (step.stepId === stepId) {
165
185
  step.status = 'running'
@@ -170,7 +190,7 @@ export class InMemoryWorkflowService
170
190
  }
171
191
  }
172
192
 
173
- async setStepScheduled(stepId: string): Promise<void> {
193
+ protected async setStepScheduledImpl(stepId: string): Promise<void> {
174
194
  for (const step of this.steps.values()) {
175
195
  if (step.stepId === stepId) {
176
196
  step.status = 'scheduled'
@@ -181,7 +201,10 @@ export class InMemoryWorkflowService
181
201
  }
182
202
  }
183
203
 
184
- async setStepResult(stepId: string, result: any): Promise<void> {
204
+ protected async setStepResultImpl(
205
+ stepId: string,
206
+ result: any
207
+ ): Promise<void> {
185
208
  for (const step of this.steps.values()) {
186
209
  if (step.stepId === stepId) {
187
210
  step.status = 'succeeded'
@@ -193,7 +216,10 @@ export class InMemoryWorkflowService
193
216
  }
194
217
  }
195
218
 
196
- async setStepError(stepId: string, error: Error): Promise<void> {
219
+ protected async setStepErrorImpl(
220
+ stepId: string,
221
+ error: Error
222
+ ): Promise<void> {
197
223
  for (const step of this.steps.values()) {
198
224
  if (step.stepId === stepId) {
199
225
  step.status = 'failed'
@@ -209,7 +235,10 @@ export class InMemoryWorkflowService
209
235
  }
210
236
  }
211
237
 
212
- async setStepChildRunId(stepId: string, childRunId: string): Promise<void> {
238
+ protected async setStepChildRunIdImpl(
239
+ stepId: string,
240
+ childRunId: string
241
+ ): Promise<void> {
213
242
  for (const step of this.steps.values()) {
214
243
  if (step.stepId === stepId) {
215
244
  step.childRunId = childRunId
@@ -219,7 +248,7 @@ export class InMemoryWorkflowService
219
248
  }
220
249
  }
221
250
 
222
- async createRetryAttempt(
251
+ protected async createRetryAttemptImpl(
223
252
  failedStepId: string,
224
253
  status: 'pending' | 'running'
225
254
  ): Promise<StepState> {
@@ -354,6 +383,10 @@ export class InMemoryWorkflowService
354
383
  }
355
384
 
356
385
  async close(): Promise<void> {
386
+ for (const timer of this.sleepTimers) {
387
+ clearTimeout(timer)
388
+ }
389
+ this.sleepTimers.clear()
357
390
  // Clear all in-memory state
358
391
  this.runs.clear()
359
392
  this.steps.clear()
@@ -439,11 +472,14 @@ export class InMemoryWorkflowService
439
472
  }
440
473
  }
441
474
 
442
- async setBranchTaken(stepId: string, branchKey: string): Promise<void> {
475
+ protected async setBranchTakenImpl(
476
+ stepId: string,
477
+ branchKey: string
478
+ ): Promise<void> {
443
479
  this.branchKeys.set(stepId, branchKey)
444
480
  }
445
481
 
446
- async updateRunState(
482
+ protected async updateRunStateImpl(
447
483
  runId: string,
448
484
  name: string,
449
485
  value: unknown
@@ -457,7 +493,7 @@ export class InMemoryWorkflowService
457
493
  return this.runState.get(runId) || {}
458
494
  }
459
495
 
460
- async upsertWorkflowVersion(
496
+ protected async upsertWorkflowVersionImpl(
461
497
  name: string,
462
498
  graphHash: string,
463
499
  graph: any,
@@ -471,7 +507,7 @@ export class InMemoryWorkflowService
471
507
  })
472
508
  }
473
509
 
474
- async updateWorkflowVersionStatus(
510
+ protected async updateWorkflowVersionStatusImpl(
475
511
  name: string,
476
512
  graphHash: string,
477
513
  status: WorkflowVersionStatus
@@ -25,7 +25,16 @@ export { InMemoryQueueService } from './in-memory-queue-service.js'
25
25
  export { InMemoryTriggerService } from './in-memory-trigger-service.js'
26
26
  export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js'
27
27
  export { LocalGatewayService } from './local-gateway-service.js'
28
- export type { ContentService } from './content-service.js'
28
+ export type {
29
+ ContentService,
30
+ SignContentKeyArgs,
31
+ SignURLArgs,
32
+ GetUploadURLArgs,
33
+ UploadURLResult,
34
+ BucketKeyArgs,
35
+ WriteFileArgs,
36
+ CopyFileArgs,
37
+ } from './content-service.js'
29
38
  export type { JWTService } from './jwt-service.js'
30
39
  export type { Logger } from './logger.js'
31
40
  export type { SecretService } from './secret-service.js'
@@ -0,0 +1,54 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, test } from 'node:test'
3
+
4
+ import { LocalContent } from './local-content.js'
5
+
6
+ describe('LocalContent signing', () => {
7
+ test('binds notBefore into the signature payload', async () => {
8
+ let capturedPayload:
9
+ | { signedAt: number; expiresAt: number; notBefore?: number }
10
+ | undefined
11
+
12
+ const content = new LocalContent(
13
+ {
14
+ localFileUploadPath: '/tmp/uploads',
15
+ uploadUrlPrefix: '/reaper',
16
+ assetUrlPrefix: '/assets',
17
+ server: 'http://localhost:3000',
18
+ },
19
+ {
20
+ info: () => {},
21
+ warn: () => {},
22
+ error: () => {},
23
+ debug: () => {},
24
+ setLevel: () => {},
25
+ } as any,
26
+ {
27
+ encode: async (_expiresIn: any, payload: any) => {
28
+ capturedPayload = payload
29
+ return 'signed-token'
30
+ },
31
+ decode: async () => ({}),
32
+ }
33
+ )
34
+
35
+ const dateGreaterThan = new Date(Date.now() + 60_000)
36
+ const dateLessThan = new Date(Date.now() + 120_000)
37
+ const signedUrl = await content.signContentKey({
38
+ bucket: 'avatars',
39
+ contentKey: 'user-1.png',
40
+ dateLessThan,
41
+ dateGreaterThan,
42
+ })
43
+
44
+ const url = new URL(signedUrl)
45
+ assert.equal(url.searchParams.get('signature'), 'signed-token')
46
+ assert.ok(capturedPayload)
47
+ assert.equal(capturedPayload?.expiresAt, dateLessThan.getTime())
48
+ assert.equal(capturedPayload?.notBefore, dateGreaterThan.getTime())
49
+ assert.equal(
50
+ url.searchParams.get('notBefore'),
51
+ String(dateGreaterThan.getTime())
52
+ )
53
+ })
54
+ })