@pikku/core 0.12.40 → 0.12.41

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 (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/function/function-runner.d.ts +2 -2
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -1
  5. package/dist/services/credential-wire-service.d.ts +2 -2
  6. package/dist/services/in-memory-workflow-service.d.ts +1 -0
  7. package/dist/services/in-memory-workflow-service.js +3 -0
  8. package/dist/services/pikku-user-id.d.ts +2 -2
  9. package/dist/services/workflow-service.d.ts +1 -0
  10. package/dist/types/core.types.d.ts +18 -5
  11. package/dist/utils.d.ts +3 -1
  12. package/dist/utils.js +2 -0
  13. package/dist/wirings/channel/channel-middleware-runner.d.ts +2 -2
  14. package/dist/wirings/channel/local/local-eventhub-service.js +7 -4
  15. package/dist/wirings/rpc/rpc-runner.d.ts +4 -4
  16. package/dist/wirings/rpc/rpc-runner.js +0 -16
  17. package/dist/wirings/workflow/pikku-workflow-service.d.ts +5 -2
  18. package/dist/wirings/workflow/pikku-workflow-service.js +11 -4
  19. package/package.json +1 -1
  20. package/src/function/function-runner.ts +3 -2
  21. package/src/index.ts +7 -1
  22. package/src/services/credential-wire-service.ts +2 -2
  23. package/src/services/in-memory-workflow-service.test.ts +2 -2
  24. package/src/services/in-memory-workflow-service.ts +7 -1
  25. package/src/services/pikku-user-id.ts +2 -2
  26. package/src/services/workflow-service.ts +1 -0
  27. package/src/types/core.types.ts +22 -6
  28. package/src/utils.ts +12 -1
  29. package/src/wirings/ai-agent/ai-agent-prepare.ts +2 -2
  30. package/src/wirings/channel/channel-common.ts +8 -2
  31. package/src/wirings/channel/channel-middleware-runner.ts +3 -3
  32. package/src/wirings/channel/local/local-channel-runner.ts +7 -3
  33. package/src/wirings/channel/local/local-eventhub-service.ts +6 -3
  34. package/src/wirings/channel/serverless/serverless-channel-runner.ts +4 -4
  35. package/src/wirings/cli/cli-runner.ts +2 -2
  36. package/src/wirings/gateway/gateway-runner.ts +8 -2
  37. package/src/wirings/http/http-runner.ts +3 -2
  38. package/src/wirings/mcp/mcp-runner.ts +2 -2
  39. package/src/wirings/queue/queue-runner.ts +2 -2
  40. package/src/wirings/rpc/rpc-runner.ts +12 -24
  41. package/src/wirings/scheduler/scheduler-runner.ts +2 -2
  42. package/src/wirings/trigger/trigger-runner.ts +2 -2
  43. package/src/wirings/workflow/pikku-workflow-service.test.ts +3 -2
  44. package/src/wirings/workflow/pikku-workflow-service.ts +29 -10
  45. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  CoreSingletonServices,
3
3
  CorePikkuMiddleware,
4
+ PikkuRawWire,
4
5
  PikkuWire,
5
6
  MiddlewareMetadata,
6
7
  } from '../../types/core.types.js'
@@ -73,7 +74,7 @@ export const runChannelLifecycleWithMiddleware = async ({
73
74
  }
74
75
  )
75
76
 
76
- let wire: PikkuWire = { channel }
77
+ let wire: PikkuRawWire = { channel }
77
78
  if (allChannelMiddleware.length > 0) {
78
79
  wire = wrapChannelWithMiddleware(wire, services, allChannelMiddleware)
79
80
  }
@@ -90,7 +91,12 @@ export const runChannelLifecycleWithMiddleware = async ({
90
91
  }
91
92
 
92
93
  if (allMiddleware.length > 0) {
93
- return await runMiddleware(services, wire, allMiddleware, runLifecycle)
94
+ return await runMiddleware(
95
+ services,
96
+ wire as unknown as PikkuWire,
97
+ allMiddleware,
98
+ runLifecycle
99
+ )
94
100
  } else {
95
101
  return await runLifecycle()
96
102
  }
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  CoreSingletonServices,
3
3
  MiddlewareMetadata,
4
- PikkuWire,
4
+ PikkuRawWire,
5
5
  } from '../../types/core.types.js'
6
6
  import type { CorePikkuChannelMiddleware } from './channel.types.js'
7
7
  import { pikkuState } from '../../pikku-state.js'
@@ -87,10 +87,10 @@ export const combineChannelMiddleware = (
87
87
  }
88
88
 
89
89
  export function wrapChannelWithMiddleware<Out>(
90
- wire: PikkuWire,
90
+ wire: PikkuRawWire,
91
91
  services: CoreSingletonServices,
92
92
  middlewares: readonly CorePikkuChannelMiddleware[]
93
- ): PikkuWire {
93
+ ): PikkuRawWire {
94
94
  if (middlewares.length === 0 || !wire.channel) return wire
95
95
 
96
96
  const channel = wire.channel
@@ -4,7 +4,11 @@ import { closeWireServices } from '../../../utils.js'
4
4
  import { processMessageHandlers } from '../channel-handler.js'
5
5
  import type { CoreChannel, RunChannelOptions } from '../channel.types.js'
6
6
  import { PikkuLocalChannelHandler } from './local-channel-handler.js'
7
- import type { PikkuWire, WireServices } from '../../../types/core.types.js'
7
+ import type {
8
+ PikkuRawWire,
9
+ PikkuWire,
10
+ WireServices,
11
+ } from '../../../types/core.types.js'
8
12
  import { isProduction } from '../../../env.js'
9
13
  import { getErrorResponse } from '../../../errors/error-handler.js'
10
14
  import { handleHTTPError } from '../../../handle-error.js'
@@ -46,7 +50,7 @@ const runUpgradeMiddleware = async (
46
50
  {
47
51
  http,
48
52
  ...createMiddlewareSessionWireProps(userSession),
49
- },
53
+ } as unknown as PikkuWire,
50
54
  middleware
51
55
  )
52
56
  }
@@ -123,7 +127,7 @@ export const runLocalChannel = async ({
123
127
  singletonServices.logger
124
128
  )
125
129
  const channel = channelHandler.getChannel()
126
- const wire: PikkuWire = {
130
+ const wire: PikkuRawWire = {
127
131
  channel,
128
132
  ...createMiddlewareSessionWireProps(userSession),
129
133
  }
@@ -69,9 +69,12 @@ export class LocalEventHubService<
69
69
  if (channelId === toChannelId) continue // Skip sending to the sender
70
70
  const channel = this.channels.get(toChannelId)
71
71
  if (channel) {
72
- channel.send(data, isBinary)
73
- } else {
74
- // TODO: Websocket is closed, remove the channel from the topic
72
+ try {
73
+ channel.send(data, isBinary)
74
+ } catch {
75
+ // Channel closed (e.g. SSE ReadableStream controller already closed on browser disconnect) — clean up
76
+ this.onChannelClosed(toChannelId)
77
+ }
75
78
  }
76
79
  }
77
80
  }
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  CoreUserSession,
3
- PikkuWire,
3
+ PikkuRawWire,
4
4
  WireServices,
5
5
  } from '../../../types/core.types.js'
6
6
  import { closeWireServices } from '../../../utils.js'
@@ -120,7 +120,7 @@ export const runChannelConnect = async ({
120
120
  channel.getState = () => channelStore.getState(channelId) as any
121
121
  channel.clearState = () => channelStore.clearState(channelId)
122
122
 
123
- const wire: PikkuWire = {
123
+ const wire: PikkuRawWire = {
124
124
  channel,
125
125
  ...createMiddlewareSessionWireProps(userSession),
126
126
  }
@@ -216,7 +216,7 @@ export const runChannelDisconnect = async ({
216
216
  }
217
217
  }
218
218
 
219
- const wire: PikkuWire = {
219
+ const wire: PikkuRawWire = {
220
220
  channel,
221
221
  ...createMiddlewareSessionWireProps(userSession),
222
222
  }
@@ -288,7 +288,7 @@ export const runChannelMessage = async (
288
288
  }
289
289
  }
290
290
 
291
- const wire: PikkuWire = {
291
+ const wire: PikkuRawWire = {
292
292
  channel,
293
293
  ...createMiddlewareSessionWireProps(userSession),
294
294
  }
@@ -19,7 +19,7 @@ import type {
19
19
  CoreSingletonServices,
20
20
  CoreServices,
21
21
  CreateWireServices,
22
- PikkuWire,
22
+ PikkuRawWire,
23
23
  CreateConfig,
24
24
  CreateSingletonServices,
25
25
  } from '../../types/core.types.js'
@@ -356,7 +356,7 @@ export async function runCLICommand({
356
356
  singletonServices.sessionStore
357
357
  )
358
358
 
359
- const wire: PikkuWire = {
359
+ const wire: PikkuRawWire = {
360
360
  cli: {
361
361
  program,
362
362
  command: commandPath,
@@ -10,6 +10,7 @@ import type {
10
10
  } from './gateway.types.js'
11
11
  import type {
12
12
  PikkuWire,
13
+ PikkuRawWire,
13
14
  CorePikkuMiddleware,
14
15
  CoreSingletonServices,
15
16
  } from '../../types/core.types.js'
@@ -381,7 +382,7 @@ export const createListenerMessageHandler = (
381
382
  const parsed = adapter.parse(rawData)
382
383
  if (!parsed) return
383
384
 
384
- const wire: PikkuWire = {}
385
+ const wire: PikkuRawWire = {}
385
386
  const gateway: PikkuGateway = {
386
387
  gatewayName: name,
387
388
  senderId: parsed.senderId,
@@ -403,7 +404,12 @@ export const createListenerMessageHandler = (
403
404
  }
404
405
 
405
406
  if (allMiddleware.length > 0) {
406
- await runMiddleware(singletonServices, wire, allMiddleware, exec)
407
+ await runMiddleware(
408
+ singletonServices,
409
+ wire as unknown as PikkuWire,
410
+ allMiddleware,
411
+ exec
412
+ )
407
413
  } else {
408
414
  await exec()
409
415
  }
@@ -18,6 +18,7 @@ import type {
18
18
  CorePikkuMiddleware,
19
19
  CorePikkuMiddlewareGroup,
20
20
  WireServices,
21
+ PikkuRawWire,
21
22
  PikkuWire,
22
23
  PikkuWiringTypes,
23
24
  } from '../../types/core.types.js'
@@ -408,7 +409,7 @@ const executeRoute = async (
408
409
  }
409
410
  }
410
411
 
411
- const wire: PikkuWire = {
412
+ const wire: PikkuRawWire = {
412
413
  traceId: requestId,
413
414
  http,
414
415
  channel,
@@ -586,7 +587,7 @@ export const fetchData = async <In, Out>(
586
587
  if (apiType.toLowerCase() === 'options') {
587
588
  const httpGroups = pikkuState(null, 'middleware', 'httpGroup')
588
589
  const globalMiddleware = httpGroups['*']
589
- const wire: PikkuWire = { http: http! }
590
+ const wire = { http: http! } as unknown as PikkuWire
590
591
  if (globalMiddleware) {
591
592
  await runMiddleware(
592
593
  singletonServices,
@@ -1,4 +1,4 @@
1
- import type { PikkuWire } from '../../types/core.types.js'
1
+ import type { PikkuRawWire } from '../../types/core.types.js'
2
2
  import type {
3
3
  CoreMCPResource,
4
4
  CoreMCPPrompt,
@@ -217,7 +217,7 @@ async function runMCPPikkuFunc(
217
217
  const mcpSessionService = new PikkuSessionService(
218
218
  singletonServices.sessionStore
219
219
  )
220
- const wire: PikkuWire = {
220
+ const wire: PikkuRawWire = {
221
221
  mcp: mcpWire,
222
222
  ...createMiddlewareSessionWireProps(mcpSessionService),
223
223
  }
@@ -1,4 +1,4 @@
1
- import type { PikkuWire } from '../../types/core.types.js'
1
+ import type { PikkuRawWire } from '../../types/core.types.js'
2
2
  import type { CoreQueueWorker, QueueJob, PikkuQueue } from './queue.types.js'
3
3
  import type {
4
4
  CorePikkuFunctionConfig,
@@ -156,7 +156,7 @@ export async function runQueueJob({
156
156
  try {
157
157
  logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
158
158
 
159
- const wire: PikkuWire = {
159
+ const wire: PikkuRawWire = {
160
160
  traceId: resolvedTraceId,
161
161
  queue,
162
162
  }
@@ -1,4 +1,8 @@
1
- import type { CoreServices, PikkuWire } from '../../types/core.types.js'
1
+ import type {
2
+ CoreServices,
3
+ PikkuWire,
4
+ PikkuRawWire,
5
+ } from '../../types/core.types.js'
2
6
  import type { SessionService } from '../../services/user-session-service.js'
3
7
  import type { CoreUserSession } from '../../types/core.types.js'
4
8
  import { runPikkuFunc } from '../../function/function-runner.js'
@@ -100,7 +104,7 @@ const resolvePikkuFunction = (
100
104
  export class ContextAwareRPCService {
101
105
  constructor(
102
106
  private services: CoreServices,
103
- private wire: PikkuWire,
107
+ private wire: PikkuRawWire,
104
108
  private options: {
105
109
  requiresAuth?: boolean
106
110
  sessionService?: SessionService<CoreUserSession>
@@ -136,16 +140,8 @@ export class ContextAwareRPCService {
136
140
  funcName: string,
137
141
  data: In
138
142
  ): Promise<Out> {
139
- const rpcDepth = this.wire.rpc?.depth || 0
140
- const updatedWire: PikkuWire = {
143
+ const updatedWire: PikkuRawWire = {
141
144
  ...this.wire,
142
- rpc: this.wire.rpc
143
- ? {
144
- ...this.wire.rpc,
145
- depth: rpcDepth + 1,
146
- global: false,
147
- }
148
- : undefined,
149
145
  }
150
146
 
151
147
  // Check addon namespace first (e.g. 'stripe:createCharge')
@@ -209,7 +205,7 @@ export class ContextAwareRPCService {
209
205
  private async invokeAddonFunction<In = any, Out = any>(
210
206
  namespacedFunction: string,
211
207
  data: In,
212
- wire: PikkuWire
208
+ wire: PikkuRawWire
213
209
  ): Promise<Out> {
214
210
  // Resolve namespace to package name
215
211
  const resolved = resolveNamespace(namespacedFunction)
@@ -248,19 +244,11 @@ export class ContextAwareRPCService {
248
244
  public async rpcWithWire<In = any, Out = any>(
249
245
  rpcName: string,
250
246
  data: In,
251
- wire: PikkuWire
247
+ wire: PikkuRawWire
252
248
  ): Promise<Out> {
253
- const rpcDepth = this.wire.rpc?.depth || 0
254
- const mergedWire: PikkuWire = {
249
+ const mergedWire: PikkuRawWire = {
255
250
  ...this.wire,
256
251
  ...wire,
257
- rpc: this.wire.rpc
258
- ? {
259
- ...this.wire.rpc,
260
- depth: rpcDepth + 1,
261
- global: false,
262
- }
263
- : undefined,
264
252
  }
265
253
 
266
254
  if (rpcName.includes(':')) {
@@ -438,7 +426,7 @@ export class PikkuRPCService<
438
426
  // Convenience function for initializing
439
427
  getContextRPCService(
440
428
  services: Services,
441
- wire: PikkuWire,
429
+ wire: PikkuRawWire,
442
430
  requiresAuthOrOptions?:
443
431
  | boolean
444
432
  | {
@@ -456,7 +444,7 @@ export class PikkuRPCService<
456
444
  : { requiresAuth: requiresAuthOrOptions }
457
445
  const serviceRPC = new ContextAwareRPCService(
458
446
  services,
459
- wire,
447
+ wire as PikkuWire,
460
448
  options,
461
449
  packageName
462
450
  )
@@ -1,4 +1,4 @@
1
- import type { PikkuWire, CoreUserSession } from '../../types/core.types.js'
1
+ import type { PikkuRawWire, CoreUserSession } from '../../types/core.types.js'
2
2
  import type { CoreScheduledTask } from './scheduler.types.js'
3
3
  import { getErrorResponse, PikkuError } from '../../errors/error-handler.js'
4
4
  import {
@@ -97,7 +97,7 @@ export async function runScheduledTask({
97
97
  }
98
98
 
99
99
  // Create the scheduled task wire object
100
- const wire: PikkuWire = {
100
+ const wire: PikkuRawWire = {
101
101
  traceId: resolvedTraceId,
102
102
  scheduledTask: {
103
103
  name,
@@ -1,4 +1,4 @@
1
- import type { PikkuWire } from '../../types/core.types.js'
1
+ import type { PikkuRawWire } from '../../types/core.types.js'
2
2
  import type {
3
3
  CoreTrigger,
4
4
  CoreTriggerSource,
@@ -99,7 +99,7 @@ export async function setupTrigger<TInput = unknown, TOutput = unknown>({
99
99
  )
100
100
  }
101
101
 
102
- const wire: PikkuWire = {
102
+ const wire: PikkuRawWire = {
103
103
  trigger: {
104
104
  invoke: (data: unknown) => {
105
105
  singletonServices.logger.info(`Trigger fired: ${name}`)
@@ -10,10 +10,11 @@ import {
10
10
  } from './pikku-workflow-service.js'
11
11
 
12
12
  describe('pikku-workflow-service worker registration', () => {
13
- test('registers sleeper function metadata', () => {
13
+ test('registers sleeper function metadata after wireQueueWorkers', () => {
14
14
  resetPikkuState()
15
15
 
16
- new InMemoryWorkflowService()
16
+ const ws = new InMemoryWorkflowService()
17
+ ws.wireQueueWorkers()
17
18
 
18
19
  const functionMeta = pikkuState(null, 'function', 'meta')
19
20
  assert.deepEqual(functionMeta.pikkuWorkflowSleeper, {
@@ -144,7 +144,10 @@ export class WorkflowDispatchException extends Error {
144
144
  public readonly stepName: string,
145
145
  options?: { cause?: unknown }
146
146
  ) {
147
- super(`Failed to dispatch workflow step '${stepName}' (run ${runId})`, options)
147
+ super(
148
+ `Failed to dispatch workflow step '${stepName}' (run ${runId})`,
149
+ options
150
+ )
148
151
  this.name = 'WorkflowDispatchException'
149
152
  }
150
153
  }
@@ -184,6 +187,16 @@ addError(WorkflowRunFailedError, {
184
187
  message: 'Workflow run failed.',
185
188
  })
186
189
 
190
+ export class WorkflowRunCancelledError extends PikkuError {
191
+ constructor() {
192
+ super('Workflow was cancelled')
193
+ }
194
+ }
195
+ addError(WorkflowRunCancelledError, {
196
+ status: 409,
197
+ message: 'Workflow was cancelled.',
198
+ })
199
+
187
200
  export class WorkflowServiceNotInitialized extends Error {}
188
201
  export class WorkflowStepNameNotString extends Error {
189
202
  constructor(stepName: any) {
@@ -237,16 +250,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
237
250
  }
238
251
  }
239
252
 
240
- public rewireQueueWorkers(): void {
241
- this.wireQueueWorkers()
242
- }
243
-
244
253
  /**
245
254
  * Wire the queue-based orchestrator/step/sleeper workers.
246
255
  * Subclasses that orchestrate without queues (e.g. Durable Objects) should
247
256
  * pass `wireQueues: false` to the base constructor and skip this entirely.
257
+ * Call this explicitly after adding addons dynamically.
248
258
  */
249
- protected wireQueueWorkers(): void {
259
+ public wireQueueWorkers(): void {
250
260
  const functions = pikkuState(null, 'function', 'functions')
251
261
  const functionsMeta = pikkuState(null, 'function', 'meta')
252
262
 
@@ -426,7 +436,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
426
436
  // Build step summaries from history (latest attempt per step)
427
437
  const stepMap = new Map<
428
438
  string,
429
- { status: StepStatus; startedAt?: Date; completedAt?: Date; attempts: number }
439
+ {
440
+ status: StepStatus
441
+ startedAt?: Date
442
+ completedAt?: Date
443
+ attempts: number
444
+ }
430
445
  >()
431
446
  for (const step of history) {
432
447
  const existing = stepMap.get(step.stepName)
@@ -920,7 +935,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
920
935
  const queueService = this.verifyQueueService()
921
936
  await queueService.add(
922
937
  this.getStepWorkerQueueName(rpcName),
923
- JSON.parse(JSON.stringify({ runId, stepName, rpcName, data, fromStepName })),
938
+ JSON.parse(
939
+ JSON.stringify({ runId, stepName, rpcName, data, fromStepName })
940
+ ),
924
941
  this.resolveStepJobOptions(stepOptions)
925
942
  )
926
943
  }
@@ -1185,7 +1202,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1185
1202
  throw new WorkflowRunFailedError(run.error?.message)
1186
1203
  }
1187
1204
  if (run.status === 'cancelled') {
1188
- throw new Error('Workflow was cancelled')
1205
+ throw new WorkflowRunCancelledError()
1189
1206
  }
1190
1207
  return run.output
1191
1208
  }
@@ -1837,7 +1854,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1837
1854
  }
1838
1855
  if (WORKFLOW_END_STATES.has(childRun.status)) {
1839
1856
  if (childRun.status === 'failed') {
1840
- throw new Error(childRun.error?.message || 'Sub-workflow failed')
1857
+ throw new Error(
1858
+ childRun.error?.message || 'Sub-workflow failed'
1859
+ )
1841
1860
  }
1842
1861
  if (childRun.status === 'cancelled') {
1843
1862
  throw new Error('Sub-workflow was cancelled')