@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
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.12.41
2
+
3
+ ### Patch Changes
4
+
5
+ - 04db6a2: Make `rpc` a required property on `PikkuWire`. It is always lazily initialised by the function runner on every invocation regardless of wire type, so marking it optional was misleading.
6
+
1
7
  ## 0.12.40
2
8
 
3
9
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import type { CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes, PikkuWire, MiddlewareMetadata, PermissionMetadata, CoreSingletonServices, CreateWireServices } from '../types/core.types.js';
1
+ import type { CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes, PikkuRawWire, MiddlewareMetadata, PermissionMetadata, CoreSingletonServices, CreateWireServices } from '../types/core.types.js';
2
2
  import type { CorePikkuChannelMiddleware } from '../wirings/channel/channel.types.js';
3
3
  import type { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './functions.types.js';
4
4
  import type { SessionService } from '../services/user-session-service.js';
@@ -19,7 +19,7 @@ export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTy
19
19
  wirePermissions?: CorePermissionGroup | CorePikkuPermission[];
20
20
  coerceDataFromSchema?: boolean;
21
21
  tags?: string[];
22
- wire: PikkuWire;
22
+ wire: PikkuRawWire;
23
23
  sessionService?: SessionService<CoreUserSession>;
24
24
  credentialWireService?: PikkuCredentialWireService;
25
25
  packageName?: string | null;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @module @pikku/core
3
3
  */
4
- export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuWiringTypes, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
4
+ export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, ServerLifecycle, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuRawWire, PikkuWiringTypes, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
5
5
  export { pikkuAIMiddleware, pikkuChannelMiddleware, pikkuChannelMiddlewareFactory, pikkuMiddleware, pikkuMiddlewareFactory, } from './types/core.types.js';
6
6
  export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
7
7
  export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
@@ -47,7 +47,7 @@ export type { WireAddonConfig } from './wirings/rpc/wire-addon.js';
47
47
  export type { PikkuPackageState } from './types/state.types.js';
48
48
  export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
49
49
  export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
50
- export { isSerializable, stopSingletonServices } from './utils.js';
50
+ export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
51
51
  export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
52
52
  export { clearPikkuRuntimeState } from './test-utils.js';
53
53
  export { type ScheduledTaskInfo, type ScheduledTaskSummary, } from './services/scheduler-service.js';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ export { createGraph } from './wirings/workflow/graph/graph-node.js';
17
17
  export { wireAddon } from './wirings/rpc/wire-addon.js';
18
18
  export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
19
19
  export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
20
- export { isSerializable, stopSingletonServices } from './utils.js';
20
+ export { isSerializable, stopSingletonServices, pikkuServerLifecycle, } from './utils.js';
21
21
  export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
22
22
  export { clearPikkuRuntimeState } from './test-utils.js';
23
23
  export { SchedulerService } from './services/scheduler-service.js';
@@ -1,12 +1,12 @@
1
1
  import type { CredentialService } from './credential-service.js';
2
- import type { PikkuWire } from '../types/core.types.js';
2
+ import type { PikkuRawWire } from '../types/core.types.js';
3
3
  export declare class PikkuCredentialWireService {
4
4
  private credentialService?;
5
5
  private wire?;
6
6
  private credentials;
7
7
  private loaded;
8
8
  private loadPromise;
9
- constructor(credentialService?: CredentialService | undefined, wire?: PikkuWire | undefined);
9
+ constructor(credentialService?: CredentialService | undefined, wire?: PikkuRawWire | undefined);
10
10
  set(name: string, value: unknown): void;
11
11
  get<T = unknown>(name: string): T | null | Promise<T | null>;
12
12
  getAll(): Record<string, unknown> | Promise<Record<string, unknown>>;
@@ -17,6 +17,7 @@ import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunW
17
17
  * ```
18
18
  */
19
19
  export declare class InMemoryWorkflowService extends PikkuWorkflowService implements WorkflowRunService {
20
+ constructor();
20
21
  private sleepTimers;
21
22
  private runs;
22
23
  private steps;
@@ -17,6 +17,9 @@ import { isExpectedError } from '../errors/error-handler.js';
17
17
  * ```
18
18
  */
19
19
  export class InMemoryWorkflowService extends PikkuWorkflowService {
20
+ constructor() {
21
+ super({ wireQueues: false });
22
+ }
20
23
  sleepTimers = new Set();
21
24
  runs = new Map();
22
25
  steps = new Map(); // keyed by `${runId}:${stepName}`
@@ -1,3 +1,3 @@
1
- import type { PikkuWire } from '../types/core.types.js';
2
- export type PikkuUserIdResolver = (wire: PikkuWire) => string | undefined;
1
+ import type { PikkuRawWire } from '../types/core.types.js';
2
+ export type PikkuUserIdResolver = (wire: PikkuRawWire) => string | undefined;
3
3
  export declare const defaultPikkuUserIdResolver: PikkuUserIdResolver;
@@ -6,6 +6,7 @@ import type { RunTimeline, ReconstructedRunState } from '../wirings/workflow/run
6
6
  * Handles workflow execution, replay, orchestration logic, and run-level state
7
7
  */
8
8
  export interface WorkflowService {
9
+ wireQueueWorkers?(): void;
9
10
  createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
10
11
  deterministic?: boolean;
11
12
  plannedSteps?: WorkflowPlannedStep[];
@@ -126,7 +126,7 @@ export type JSONValue = JSONPrimitive | JSONValue[] | {
126
126
  /**
127
127
  * Utility type for making certain keys required and leaving the rest as optional.
128
128
  */
129
- export type PickRequired<T, K extends keyof T> = Required<Pick<T, K>> & Partial<T>;
129
+ export type PickRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
130
130
  /**
131
131
  * Utility type for making certain keys optional while keeping the rest required.
132
132
  */
@@ -214,7 +214,10 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
214
214
  /**
215
215
  * Represents different forms of wire within Pikku and the outside world.
216
216
  */
217
- export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown> = Partial<{
217
+ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown> = {
218
+ /** Always present — lazily initialised on first access for every function invocation */
219
+ rpc: TypedRPC;
220
+ } & Partial<{
218
221
  wireType: PikkuWiringTypes;
219
222
  wireId: string;
220
223
  /** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
@@ -223,7 +226,6 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
223
226
  functionId: string;
224
227
  http: PikkuHTTP<In>;
225
228
  mcp: PikkuMCP<MCPTools>;
226
- rpc: TypedRPC;
227
229
  channel: [IsChannel] extends [null] ? PikkuChannel<unknown, Out> : PikkuChannel<unknown, Out> | undefined;
228
230
  scheduledTask: PikkuScheduledTask;
229
231
  queue: PikkuQueue;
@@ -255,10 +257,14 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
255
257
  durability: AuditDurability;
256
258
  };
257
259
  }>;
260
+ /**
261
+ * Wire object as constructed by runners, before rpc is lazily added by the function runner.
262
+ */
263
+ export type PikkuRawWire = Omit<PikkuWire, 'rpc'>;
258
264
  /**
259
265
  * A function that can wrap an wire and be called before or after
260
266
  */
261
- export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wires: PikkuWire<unknown, unknown, false, UserSession>, next: () => Promise<void>) => Promise<void>;
267
+ export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wires: PikkuWire, next: () => Promise<void>) => Promise<void>;
262
268
  /**
263
269
  * Priority levels for middleware execution order.
264
270
  * Lower priority runs first (outermost in the onion model).
@@ -356,11 +362,18 @@ export type CreateSingletonServices<Config extends CoreConfig, SingletonServices
356
362
  /**
357
363
  * Defines a function type for creating session-specific services.
358
364
  */
359
- export type CreateWireServices<SingletonServices extends CoreSingletonServices = CoreSingletonServices, Services extends CoreServices<SingletonServices> = CoreServices<SingletonServices>, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wire: PikkuWire<unknown, unknown, false, UserSession>) => Promise<WireServices<Services, SingletonServices>>;
365
+ export type CreateWireServices<SingletonServices extends CoreSingletonServices = CoreSingletonServices, Services extends CoreServices<SingletonServices> = CoreServices<SingletonServices>, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wire: PikkuRawWire) => Promise<WireServices<Services, SingletonServices>>;
360
366
  /**
361
367
  * Defines a function type for creating config.
362
368
  */
363
369
  export type CreateConfig<Config extends CoreConfig, RemainingArgs extends any[] = unknown[]> = (variables?: VariablesService, ...args: RemainingArgs) => Promise<Config>;
370
+ /** Server lifecycle hooks for startup and shutdown phases. */
371
+ export type ServerLifecycle<SingletonServices extends CoreSingletonServices = CoreSingletonServices> = {
372
+ beforeStart?: (services: SingletonServices) => void | Promise<void>;
373
+ afterStart?: (services: SingletonServices) => void | Promise<void>;
374
+ beforeStop?: (services: SingletonServices) => void | Promise<void>;
375
+ afterStop?: (services: SingletonServices) => void | Promise<void>;
376
+ };
364
377
  /**
365
378
  * Represents the documentation for a route, including summary, description, tags, and errors.
366
379
  */
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from './services/logger.js';
2
- import type { WireServices } from './types/core.types.js';
2
+ import type { CoreSingletonServices, ServerLifecycle, WireServices } from './types/core.types.js';
3
3
  export declare const closeWireServices: (logger: Logger, wireServices: WireServices) => Promise<void>;
4
4
  export declare const createWeakUID: () => string;
5
5
  export declare const isSerializable: (data: any) => boolean;
@@ -12,3 +12,5 @@ export declare const freezeDedupe: <T>(arr?: readonly T[] | T[] | undefined) =>
12
12
  * @param singletonServices - The parent singleton services to stop
13
13
  */
14
14
  export declare const stopSingletonServices: () => Promise<void>;
15
+ /** Wrap server lifecycle hooks so the inspector can discover them. */
16
+ export declare const pikkuServerLifecycle: <SS extends CoreSingletonServices = CoreSingletonServices>(lifecycle: ServerLifecycle<SS>) => ServerLifecycle<SS>;
package/dist/utils.js CHANGED
@@ -105,3 +105,5 @@ export const stopSingletonServices = async () => {
105
105
  await stopService(logger, name, service);
106
106
  }
107
107
  };
108
+ /** Wrap server lifecycle hooks so the inspector can discover them. */
109
+ export const pikkuServerLifecycle = (lifecycle) => lifecycle;
@@ -1,4 +1,4 @@
1
- import type { CoreSingletonServices, MiddlewareMetadata, PikkuWire } from '../../types/core.types.js';
1
+ import type { CoreSingletonServices, MiddlewareMetadata, PikkuRawWire } from '../../types/core.types.js';
2
2
  import type { CorePikkuChannelMiddleware } from './channel.types.js';
3
3
  export declare const addChannelMiddleware: (tag: string, middleware: CorePikkuChannelMiddleware[], packageName?: string | null) => CorePikkuChannelMiddleware[];
4
4
  export declare const clearChannelMiddlewareCache: () => void;
@@ -7,4 +7,4 @@ export declare const combineChannelMiddleware: (wireType: string, uid: string, {
7
7
  wireChannelMiddleware?: CorePikkuChannelMiddleware[];
8
8
  packageName?: string | null;
9
9
  }) => readonly CorePikkuChannelMiddleware[];
10
- export declare function wrapChannelWithMiddleware<Out>(wire: PikkuWire, services: CoreSingletonServices, middlewares: readonly CorePikkuChannelMiddleware[]): PikkuWire;
10
+ export declare function wrapChannelWithMiddleware<Out>(wire: PikkuRawWire, services: CoreSingletonServices, middlewares: readonly CorePikkuChannelMiddleware[]): PikkuRawWire;
@@ -50,10 +50,13 @@ export class LocalEventHubService {
50
50
  continue; // Skip sending to the sender
51
51
  const channel = this.channels.get(toChannelId);
52
52
  if (channel) {
53
- channel.send(data, isBinary);
54
- }
55
- else {
56
- // TODO: Websocket is closed, remove the channel from the topic
53
+ try {
54
+ channel.send(data, isBinary);
55
+ }
56
+ catch {
57
+ // Channel closed (e.g. SSE ReadableStream controller already closed on browser disconnect) — clean up
58
+ this.onChannelClosed(toChannelId);
59
+ }
57
60
  }
58
61
  }
59
62
  }
@@ -1,4 +1,4 @@
1
- import type { CoreServices, PikkuWire } from '../../types/core.types.js';
1
+ import type { CoreServices, PikkuRawWire } from '../../types/core.types.js';
2
2
  import type { SessionService } from '../../services/user-session-service.js';
3
3
  import type { CoreUserSession } from '../../types/core.types.js';
4
4
  import { PikkuError } from '../../errors/error-handler.js';
@@ -19,7 +19,7 @@ export declare class ContextAwareRPCService {
19
19
  private wire;
20
20
  private options;
21
21
  private packageName;
22
- constructor(services: CoreServices, wire: PikkuWire, options: {
22
+ constructor(services: CoreServices, wire: PikkuRawWire, options: {
23
23
  requiresAuth?: boolean;
24
24
  sessionService?: SessionService<CoreUserSession>;
25
25
  }, packageName?: string | null);
@@ -32,7 +32,7 @@ export declare class ContextAwareRPCService {
32
32
  * @private
33
33
  */
34
34
  private invokeAddonFunction;
35
- rpcWithWire<In = any, Out = any>(rpcName: string, data: In, wire: PikkuWire): Promise<Out>;
35
+ rpcWithWire<In = any, Out = any>(rpcName: string, data: In, wire: PikkuRawWire): Promise<Out>;
36
36
  startWorkflow<In = any>(workflowName: string, input: In, options?: {
37
37
  startNode?: string;
38
38
  wire?: {
@@ -94,7 +94,7 @@ export declare class ContextAwareRPCService {
94
94
  remote<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
95
95
  }
96
96
  export declare class PikkuRPCService<Services extends CoreServices, TypedRPC = PikkuRPC> {
97
- getContextRPCService(services: Services, wire: PikkuWire, requiresAuthOrOptions?: boolean | {
97
+ getContextRPCService(services: Services, wire: PikkuRawWire, requiresAuthOrOptions?: boolean | {
98
98
  requiresAuth?: boolean;
99
99
  sessionService?: SessionService<CoreUserSession>;
100
100
  } | undefined, depth?: number, packageName?: string | null): TypedRPC;
@@ -110,16 +110,8 @@ export class ContextAwareRPCService {
110
110
  return await this.rpc(funcName, data);
111
111
  }
112
112
  async rpc(funcName, data) {
113
- const rpcDepth = this.wire.rpc?.depth || 0;
114
113
  const updatedWire = {
115
114
  ...this.wire,
116
- rpc: this.wire.rpc
117
- ? {
118
- ...this.wire.rpc,
119
- depth: rpcDepth + 1,
120
- global: false,
121
- }
122
- : undefined,
123
115
  };
124
116
  // Check addon namespace first (e.g. 'stripe:createCharge')
125
117
  if (funcName.includes(':')) {
@@ -197,17 +189,9 @@ export class ContextAwareRPCService {
197
189
  });
198
190
  }
199
191
  async rpcWithWire(rpcName, data, wire) {
200
- const rpcDepth = this.wire.rpc?.depth || 0;
201
192
  const mergedWire = {
202
193
  ...this.wire,
203
194
  ...wire,
204
- rpc: this.wire.rpc
205
- ? {
206
- ...this.wire.rpc,
207
- depth: rpcDepth + 1,
208
- global: false,
209
- }
210
- : undefined,
211
195
  };
212
196
  if (rpcName.includes(':')) {
213
197
  return this.invokeAddonFunction(rpcName, data, mergedWire);
@@ -66,6 +66,9 @@ export declare class WorkflowRunFailedError extends PikkuError {
66
66
  };
67
67
  constructor(message?: string);
68
68
  }
69
+ export declare class WorkflowRunCancelledError extends PikkuError {
70
+ constructor();
71
+ }
69
72
  export declare class WorkflowServiceNotInitialized extends Error {
70
73
  }
71
74
  export declare class WorkflowStepNameNotString extends Error {
@@ -85,13 +88,13 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
85
88
  mirror?: WorkflowRunMirror;
86
89
  });
87
90
  private safeMirror;
88
- rewireQueueWorkers(): void;
89
91
  /**
90
92
  * Wire the queue-based orchestrator/step/sleeper workers.
91
93
  * Subclasses that orchestrate without queues (e.g. Durable Objects) should
92
94
  * pass `wireQueues: false` to the base constructor and skip this entirely.
95
+ * Call this explicitly after adding addons dynamically.
93
96
  */
94
- protected wireQueueWorkers(): void;
97
+ wireQueueWorkers(): void;
95
98
  /**
96
99
  * Check if a run is executing inline (without queues)
97
100
  */
@@ -131,6 +131,15 @@ addError(WorkflowRunFailedError, {
131
131
  status: 422,
132
132
  message: 'Workflow run failed.',
133
133
  });
134
+ export class WorkflowRunCancelledError extends PikkuError {
135
+ constructor() {
136
+ super('Workflow was cancelled');
137
+ }
138
+ }
139
+ addError(WorkflowRunCancelledError, {
140
+ status: 409,
141
+ message: 'Workflow was cancelled.',
142
+ });
134
143
  export class WorkflowServiceNotInitialized extends Error {
135
144
  }
136
145
  export class WorkflowStepNameNotString extends Error {
@@ -177,13 +186,11 @@ export class PikkuWorkflowService {
177
186
  }
178
187
  }
179
188
  }
180
- rewireQueueWorkers() {
181
- this.wireQueueWorkers();
182
- }
183
189
  /**
184
190
  * Wire the queue-based orchestrator/step/sleeper workers.
185
191
  * Subclasses that orchestrate without queues (e.g. Durable Objects) should
186
192
  * pass `wireQueues: false` to the base constructor and skip this entirely.
193
+ * Call this explicitly after adding addons dynamically.
187
194
  */
188
195
  wireQueueWorkers() {
189
196
  const functions = pikkuState(null, 'function', 'functions');
@@ -683,7 +690,7 @@ export class PikkuWorkflowService {
683
690
  throw new WorkflowRunFailedError(run.error?.message);
684
691
  }
685
692
  if (run.status === 'cancelled') {
686
- throw new Error('Workflow was cancelled');
693
+ throw new WorkflowRunCancelledError();
687
694
  }
688
695
  return run.output;
689
696
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.40",
3
+ "version": "0.12.41",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -11,6 +11,7 @@ import type {
11
11
  CorePikkuMiddleware,
12
12
  PikkuWiringTypes,
13
13
  PikkuWire,
14
+ PikkuRawWire,
14
15
  MiddlewareMetadata,
15
16
  PermissionMetadata,
16
17
  CoreSingletonServices,
@@ -37,7 +38,7 @@ import { rpcService } from '../wirings/rpc/rpc-runner.js'
37
38
  import { closeWireServices } from '../utils.js'
38
39
 
39
40
  async function resolveSession(
40
- wire: PikkuWire,
41
+ wire: PikkuRawWire,
41
42
  singletonServices: CoreSingletonServices,
42
43
  sessionService?: SessionService<CoreUserSession>
43
44
  ): Promise<void> {
@@ -234,7 +235,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
234
235
  wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
235
236
  coerceDataFromSchema?: boolean
236
237
  tags?: string[]
237
- wire: PikkuWire
238
+ wire: PikkuRawWire
238
239
  sessionService?: SessionService<CoreUserSession>
239
240
  credentialWireService?: PikkuCredentialWireService
240
241
  packageName?: string | null
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export type {
12
12
  CoreSingletonServices,
13
13
  CoreUserSession,
14
14
  CreateConfig,
15
+ ServerLifecycle,
15
16
  FunctionMeta,
16
17
  FunctionRuntimeMeta,
17
18
  FunctionServicesMeta,
@@ -28,6 +29,7 @@ export type {
28
29
  PickRequired,
29
30
  PikkuAIMiddlewareHooks,
30
31
  PikkuWire,
32
+ PikkuRawWire,
31
33
  PikkuWiringTypes,
32
34
  RequireAtLeastOne,
33
35
  SerializedError,
@@ -187,7 +189,11 @@ export {
187
189
  addGlobalPermission,
188
190
  checkAuthPermissions,
189
191
  } from './permissions.js'
190
- export { isSerializable, stopSingletonServices } from './utils.js'
192
+ export {
193
+ isSerializable,
194
+ stopSingletonServices,
195
+ pikkuServerLifecycle,
196
+ } from './utils.js'
191
197
  export {
192
198
  getSingletonServices,
193
199
  getCreateWireServices,
@@ -1,6 +1,6 @@
1
1
  import type { CredentialService } from './credential-service.js'
2
2
  import { defaultPikkuUserIdResolver } from './pikku-user-id.js'
3
- import type { PikkuWire } from '../types/core.types.js'
3
+ import type { PikkuRawWire } from '../types/core.types.js'
4
4
 
5
5
  export class PikkuCredentialWireService {
6
6
  private credentials: Record<string, unknown> = {}
@@ -9,7 +9,7 @@ export class PikkuCredentialWireService {
9
9
 
10
10
  constructor(
11
11
  private credentialService?: CredentialService,
12
- private wire?: PikkuWire
12
+ private wire?: PikkuRawWire
13
13
  ) {}
14
14
 
15
15
  set(name: string, value: unknown): void {
@@ -366,7 +366,7 @@ describe('InMemoryWorkflowService', () => {
366
366
  })
367
367
 
368
368
  describe('queue worker registration', () => {
369
- test('rewireQueueWorkers registers workflow queues added after construction', () => {
369
+ test('wireQueueWorkers registers workflow queues added after construction', () => {
370
370
  const stepQueue = 'wf-step-regression-enrich'
371
371
  const orchQueue = 'wf-orchestrator-regression-onboarding'
372
372
 
@@ -385,7 +385,7 @@ describe('InMemoryWorkflowService', () => {
385
385
  assert.strictEqual(getQueueWorkers().has(stepQueue), false)
386
386
  assert.strictEqual(getQueueWorkers().has(orchQueue), false)
387
387
 
388
- ws.rewireQueueWorkers()
388
+ ws.wireQueueWorkers()
389
389
 
390
390
  assert.strictEqual(getQueueWorkers().has(stepQueue), true)
391
391
  assert.strictEqual(getQueueWorkers().has(orchQueue), true)
@@ -39,6 +39,10 @@ export class InMemoryWorkflowService
39
39
  extends PikkuWorkflowService
40
40
  implements WorkflowRunService
41
41
  {
42
+ constructor() {
43
+ super({ wireQueues: false })
44
+ }
45
+
42
46
  private sleepTimers = new Set<ReturnType<typeof setTimeout>>()
43
47
  private runs = new Map<string, WorkflowRun>()
44
48
  private steps = new Map<string, StepState>() // keyed by `${runId}:${stepName}`
@@ -451,7 +455,9 @@ export class InMemoryWorkflowService
451
455
  return nodeIds.filter((id) => !existingSteps.has(id))
452
456
  }
453
457
 
454
- async getStepInstances(runId: string): Promise<
458
+ async getStepInstances(
459
+ runId: string
460
+ ): Promise<
455
461
  Array<{ stepName: string; status: StepStatus; fromStepName?: string }>
456
462
  > {
457
463
  const prefix = `${runId}:`
@@ -1,6 +1,6 @@
1
- import type { PikkuWire } from '../types/core.types.js'
1
+ import type { PikkuRawWire } from '../types/core.types.js'
2
2
 
3
- export type PikkuUserIdResolver = (wire: PikkuWire) => string | undefined
3
+ export type PikkuUserIdResolver = (wire: PikkuRawWire) => string | undefined
4
4
 
5
5
  export const defaultPikkuUserIdResolver: PikkuUserIdResolver = (wire) => {
6
6
  // Explicit pikkuUserId on wire (set by earlier middleware or runner)
@@ -18,6 +18,7 @@ import type {
18
18
  * Handles workflow execution, replay, orchestration logic, and run-level state
19
19
  */
20
20
  export interface WorkflowService {
21
+ wireQueueWorkers?(): void
21
22
  // Run-level state operations
22
23
  createRun(
23
24
  workflowName: string,
@@ -178,8 +178,7 @@ export type JSONValue =
178
178
  /**
179
179
  * Utility type for making certain keys required and leaving the rest as optional.
180
180
  */
181
- export type PickRequired<T, K extends keyof T> = Required<Pick<T, K>> &
182
- Partial<T>
181
+ export type PickRequired<T, K extends keyof T> = T & Required<Pick<T, K>>
183
182
 
184
183
  /**
185
184
  * Utility type for making certain keys optional while keeping the rest required.
@@ -285,7 +284,10 @@ export type PikkuWire<
285
284
  MCPTools extends string | never = never,
286
285
  TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire,
287
286
  TriggerOutput = unknown,
288
- > = Partial<{
287
+ > = {
288
+ /** Always present — lazily initialised on first access for every function invocation */
289
+ rpc: TypedRPC
290
+ } & Partial<{
289
291
  wireType: PikkuWiringTypes
290
292
  wireId: string
291
293
  /** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
@@ -294,7 +296,6 @@ export type PikkuWire<
294
296
  functionId: string
295
297
  http: PikkuHTTP<In>
296
298
  mcp: PikkuMCP<MCPTools>
297
- rpc: TypedRPC
298
299
  channel: [IsChannel] extends [null]
299
300
  ? PikkuChannel<unknown, Out>
300
301
  : PikkuChannel<unknown, Out> | undefined
@@ -333,6 +334,11 @@ export type PikkuWire<
333
334
  }
334
335
  }>
335
336
 
337
+ /**
338
+ * Wire object as constructed by runners, before rpc is lazily added by the function runner.
339
+ */
340
+ export type PikkuRawWire = Omit<PikkuWire, 'rpc'>
341
+
336
342
  /**
337
343
  * A function that can wrap an wire and be called before or after
338
344
  */
@@ -341,7 +347,7 @@ export type CorePikkuMiddleware<
341
347
  UserSession extends CoreUserSession = CoreUserSession,
342
348
  > = (
343
349
  services: SingletonServices,
344
- wires: PikkuWire<unknown, unknown, false, UserSession>,
350
+ wires: PikkuWire,
345
351
  next: () => Promise<void>
346
352
  ) => Promise<void>
347
353
 
@@ -527,7 +533,7 @@ export type CreateWireServices<
527
533
  UserSession extends CoreUserSession = CoreUserSession,
528
534
  > = (
529
535
  services: SingletonServices,
530
- wire: PikkuWire<unknown, unknown, false, UserSession>
536
+ wire: PikkuRawWire
531
537
  ) => Promise<WireServices<Services, SingletonServices>>
532
538
 
533
539
  /**
@@ -538,6 +544,16 @@ export type CreateConfig<
538
544
  RemainingArgs extends any[] = unknown[],
539
545
  > = (variables?: VariablesService, ...args: RemainingArgs) => Promise<Config>
540
546
 
547
+ /** Server lifecycle hooks for startup and shutdown phases. */
548
+ export type ServerLifecycle<
549
+ SingletonServices extends CoreSingletonServices = CoreSingletonServices,
550
+ > = {
551
+ beforeStart?: (services: SingletonServices) => void | Promise<void>
552
+ afterStart?: (services: SingletonServices) => void | Promise<void>
553
+ beforeStop?: (services: SingletonServices) => void | Promise<void>
554
+ afterStop?: (services: SingletonServices) => void | Promise<void>
555
+ }
556
+
541
557
  /**
542
558
  * Represents the documentation for a route, including summary, description, tags, and errors.
543
559
  */
package/src/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { Logger } from './services/logger.js'
2
- import type { WireServices } from './types/core.types.js'
2
+ import type {
3
+ CoreSingletonServices,
4
+ ServerLifecycle,
5
+ WireServices,
6
+ } from './types/core.types.js'
3
7
  import { getSingletonServices, getAllPackageStates } from './pikku-state.js'
4
8
 
5
9
  export const closeWireServices = async (
@@ -127,3 +131,10 @@ export const stopSingletonServices = async (): Promise<void> => {
127
131
  await stopService(logger, name, service)
128
132
  }
129
133
  }
134
+
135
+ /** Wrap server lifecycle hooks so the inspector can discover them. */
136
+ export const pikkuServerLifecycle = <
137
+ SS extends CoreSingletonServices = CoreSingletonServices,
138
+ >(
139
+ lifecycle: ServerLifecycle<SS>
140
+ ): ServerLifecycle<SS> => lifecycle
@@ -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 {
3
3
  CoreAIAgent,
4
4
  AIAgentInput,
@@ -397,7 +397,7 @@ export async function buildToolDefs(
397
397
  needsApproval: needsApproval || undefined,
398
398
  approvalDescriptionFn,
399
399
  execute: async (toolInput: unknown) => {
400
- const wire: PikkuWire = params.sessionService
400
+ const wire: PikkuRawWire = params.sessionService
401
401
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
402
402
  : {}
403
403
  const rpcService = new ContextAwareRPCService(