@pikku/core 0.12.39 → 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 (48) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/function/function-runner.d.ts +2 -2
  3. package/dist/function/functions.types.d.ts +6 -2
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +1 -1
  6. package/dist/services/credential-wire-service.d.ts +2 -2
  7. package/dist/services/in-memory-workflow-service.d.ts +1 -0
  8. package/dist/services/in-memory-workflow-service.js +3 -0
  9. package/dist/services/pikku-user-id.d.ts +2 -2
  10. package/dist/services/workflow-service.d.ts +1 -0
  11. package/dist/types/core.types.d.ts +24 -7
  12. package/dist/utils.d.ts +3 -1
  13. package/dist/utils.js +2 -0
  14. package/dist/wirings/channel/channel-middleware-runner.d.ts +2 -2
  15. package/dist/wirings/channel/local/local-eventhub-service.js +7 -4
  16. package/dist/wirings/rpc/rpc-runner.d.ts +4 -4
  17. package/dist/wirings/rpc/rpc-runner.js +0 -16
  18. package/dist/wirings/workflow/pikku-workflow-service.d.ts +5 -2
  19. package/dist/wirings/workflow/pikku-workflow-service.js +17 -17
  20. package/package.json +1 -1
  21. package/src/function/function-runner.ts +3 -2
  22. package/src/function/functions.types.ts +6 -2
  23. package/src/index.ts +7 -1
  24. package/src/services/credential-wire-service.ts +2 -2
  25. package/src/services/in-memory-workflow-service.test.ts +2 -2
  26. package/src/services/in-memory-workflow-service.ts +7 -1
  27. package/src/services/pikku-user-id.ts +2 -2
  28. package/src/services/workflow-service.ts +1 -0
  29. package/src/types/core.types.ts +28 -8
  30. package/src/utils.ts +12 -1
  31. package/src/wirings/ai-agent/ai-agent-prepare.ts +2 -2
  32. package/src/wirings/channel/channel-common.ts +8 -2
  33. package/src/wirings/channel/channel-middleware-runner.ts +3 -3
  34. package/src/wirings/channel/local/local-channel-runner.ts +7 -3
  35. package/src/wirings/channel/local/local-eventhub-service.ts +6 -3
  36. package/src/wirings/channel/serverless/serverless-channel-runner.ts +4 -4
  37. package/src/wirings/cli/cli-runner.ts +2 -2
  38. package/src/wirings/gateway/gateway-runner.ts +8 -2
  39. package/src/wirings/http/http-runner.ts +3 -2
  40. package/src/wirings/mcp/mcp-runner.ts +2 -2
  41. package/src/wirings/queue/queue-runner.ts +2 -2
  42. package/src/wirings/rpc/rpc-runner.ts +12 -24
  43. package/src/wirings/scheduler/scheduler-runner.ts +2 -2
  44. package/src/wirings/trigger/trigger-runner.ts +2 -2
  45. package/src/wirings/workflow/pikku-workflow-service.test.ts +16 -26
  46. package/src/wirings/workflow/pikku-workflow-service.ts +36 -24
  47. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +3 -3
  48. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
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
+
7
+ ## 0.12.40
8
+
9
+ ### Patch Changes
10
+
11
+ - ba1ab08: refactor(workflow): replace `inline: false` with `workflowQueued: true` on function meta
12
+
13
+ The per-function workflow dispatch flag has been renamed from the confusing
14
+ negative `inline: false` to the explicit positive `workflowQueued: true`.
15
+ Two companion fields are also added: `workflowRetries` and `workflowTimeout`
16
+ as function-level equivalents of the per-call-site `NodeOptions` fields.
17
+
18
+ **Breaking change (patch — flag was undocumented):** rename `inline: false`
19
+ to `workflowQueued: true` on any `pikkuSessionlessFunc` / `pikkuFunc` that
20
+ dispatches its workflow steps via the queue.
21
+
22
+ **Behaviour change:** a step marked `workflowQueued: true` now throws if no
23
+ queue service is configured, instead of silently falling back to inline
24
+ execution.
25
+
26
+ **Bug fix:** `post-process.ts` was registering `wf-step-*` queues for every
27
+ workflow step node; it now only registers them for steps that are actually
28
+ `workflowQueued: true`, avoiding spurious queue resource allocation.
29
+
1
30
  ## 0.12.39
2
31
 
3
32
  ### 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;
@@ -149,8 +149,12 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
149
149
  readonly?: boolean;
150
150
  deploy?: 'serverless' | 'server' | 'auto';
151
151
  approvalRequired?: boolean;
152
- /** When false, workflow steps calling this function are dispatched via the queue instead of running inline. Defaults to true (inline). */
153
- inline?: boolean;
152
+ /** When true, workflow steps calling this function are dispatched via the queue. No queue service configured is a hard error. Defaults to false (inline). */
153
+ workflowQueued?: boolean;
154
+ /** Number of retry attempts when this function is used as a workflow step. */
155
+ workflowRetries?: number;
156
+ /** Timeout for this function when used as a workflow step (e.g. '30s', '5m'). */
157
+ workflowTimeout?: string;
154
158
  audit?: boolean | {
155
159
  durability?: 'best-effort' | 'transactional';
156
160
  };
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[];
@@ -82,8 +82,12 @@ export type FunctionRuntimeMeta = {
82
82
  readonly?: boolean;
83
83
  deploy?: 'serverless' | 'server' | 'auto';
84
84
  sessionless?: boolean;
85
- /** When false, workflow steps calling this function are dispatched via the queue. Defaults to true (inline). */
86
- inline?: boolean;
85
+ /** When true, workflow steps calling this function are dispatched via the queue. No queue service configured is a hard error. */
86
+ workflowQueued?: boolean;
87
+ /** Retry count when this function is used as a workflow step. */
88
+ workflowRetries?: number;
89
+ /** Timeout when this function is used as a workflow step (e.g. '30s', '5m'). */
90
+ workflowTimeout?: string;
87
91
  version?: number;
88
92
  approvalRequired?: boolean;
89
93
  approvalDescription?: string;
@@ -122,7 +126,7 @@ export type JSONValue = JSONPrimitive | JSONValue[] | {
122
126
  /**
123
127
  * Utility type for making certain keys required and leaving the rest as optional.
124
128
  */
125
- 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>>;
126
130
  /**
127
131
  * Utility type for making certain keys optional while keeping the rest required.
128
132
  */
@@ -210,7 +214,10 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
210
214
  /**
211
215
  * Represents different forms of wire within Pikku and the outside world.
212
216
  */
213
- 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<{
214
221
  wireType: PikkuWiringTypes;
215
222
  wireId: string;
216
223
  /** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
@@ -219,7 +226,6 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
219
226
  functionId: string;
220
227
  http: PikkuHTTP<In>;
221
228
  mcp: PikkuMCP<MCPTools>;
222
- rpc: TypedRPC;
223
229
  channel: [IsChannel] extends [null] ? PikkuChannel<unknown, Out> : PikkuChannel<unknown, Out> | undefined;
224
230
  scheduledTask: PikkuScheduledTask;
225
231
  queue: PikkuQueue;
@@ -251,10 +257,14 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
251
257
  durability: AuditDurability;
252
258
  };
253
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'>;
254
264
  /**
255
265
  * A function that can wrap an wire and be called before or after
256
266
  */
257
- 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>;
258
268
  /**
259
269
  * Priority levels for middleware execution order.
260
270
  * Lower priority runs first (outermost in the onion model).
@@ -352,11 +362,18 @@ export type CreateSingletonServices<Config extends CoreConfig, SingletonServices
352
362
  /**
353
363
  * Defines a function type for creating session-specific services.
354
364
  */
355
- 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>>;
356
366
  /**
357
367
  * Defines a function type for creating config.
358
368
  */
359
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
+ };
360
377
  /**
361
378
  * Represents the documentation for a route, including summary, description, tags, and errors.
362
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');
@@ -548,26 +555,19 @@ export class PikkuWorkflowService {
548
555
  * through to the inline execution path.
549
556
  */
550
557
  async dispatchStep(runId, stepName, rpcName, data, stepOptions, fromStepName) {
551
- // Step execution is decided purely by the function's `inline` flag (default
552
- // true). Only a function explicitly marked `inline: false` dispatches via
553
- // the queue. The run-level inline state is intentionally NOT consulted
554
- // here: default steps run inline even inside a queue-dispatched run, so a
555
- // normally-started workflow executes its steps in one orchestrator-worker
556
- // pass instead of one queue round-trip per step.
558
+ // Step execution is decided purely by the function's `workflowQueued` flag
559
+ // (default false). Only a function explicitly marked `workflowQueued: true`
560
+ // dispatches via the queue. If the queue service is not configured that is
561
+ // a hard error there is no inline fallback.
557
562
  const functionsMeta = pikkuState(null, 'function', 'meta');
558
563
  const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName];
559
564
  const rpcMeta = typeof rpcFuncId === 'string' ? functionsMeta[rpcFuncId] : undefined;
560
- const forceQueue = rpcMeta?.inline === false;
565
+ const forceQueue = rpcMeta?.workflowQueued === true;
561
566
  if (!forceQueue) {
562
567
  return false;
563
568
  }
564
- // The function opted out of inline execution (`inline: false`) but no queue
565
- // service is configured to dispatch it. Fall back to inline so the workflow
566
- // still progresses, but warn loudly — silently swallowing this hides a real
567
- // misconfiguration (the step won't get its own worker/retry isolation).
568
569
  if (!getSingletonServices()?.queueService) {
569
- getSingletonServices()?.logger.warn(`Workflow step '${stepName}' (function '${rpcName}') is marked 'inline: false' but no queue service is configured — running it inline instead of dispatching to a queue.`);
570
- return false;
570
+ throw new Error(`Workflow step '${stepName}' (function '${rpcName}') is marked 'workflowQueued: true' but no queue service is configured.`);
571
571
  }
572
572
  try {
573
573
  await getSingletonServices().queueService.add(this.getStepWorkerQueueName(rpcName), JSON.parse(JSON.stringify({ runId, stepName, rpcName, data, fromStepName })), this.resolveStepJobOptions(stepOptions));
@@ -690,7 +690,7 @@ export class PikkuWorkflowService {
690
690
  throw new WorkflowRunFailedError(run.error?.message);
691
691
  }
692
692
  if (run.status === 'cancelled') {
693
- throw new Error('Workflow was cancelled');
693
+ throw new WorkflowRunCancelledError();
694
694
  }
695
695
  return run.output;
696
696
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.39",
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
@@ -295,8 +295,12 @@ export type CorePikkuFunctionConfig<
295
295
  readonly?: boolean
296
296
  deploy?: 'serverless' | 'server' | 'auto'
297
297
  approvalRequired?: boolean
298
- /** When false, workflow steps calling this function are dispatched via the queue instead of running inline. Defaults to true (inline). */
299
- inline?: boolean
298
+ /** When true, workflow steps calling this function are dispatched via the queue. No queue service configured is a hard error. Defaults to false (inline). */
299
+ workflowQueued?: boolean
300
+ /** Number of retry attempts when this function is used as a workflow step. */
301
+ workflowRetries?: number
302
+ /** Timeout for this function when used as a workflow step (e.g. '30s', '5m'). */
303
+ workflowTimeout?: string
300
304
  audit?:
301
305
  | boolean
302
306
  | {
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,