@pikku/core 0.11.1 → 0.11.2

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 (143) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/error-handler.js +3 -3
  3. package/dist/function/function-runner.d.ts +4 -3
  4. package/dist/function/function-runner.js +60 -14
  5. package/dist/function/functions.types.d.ts +84 -5
  6. package/dist/function/functions.types.js +41 -1
  7. package/dist/index.d.ts +6 -2
  8. package/dist/index.js +5 -2
  9. package/dist/middleware/auth-apikey.d.ts +1 -1
  10. package/dist/middleware/auth-bearer.d.ts +1 -1
  11. package/dist/middleware/auth-cookie.d.ts +1 -1
  12. package/dist/middleware/timeout.d.ts +1 -1
  13. package/dist/middleware-runner.d.ts +3 -14
  14. package/dist/middleware-runner.js +8 -8
  15. package/dist/permissions.d.ts +3 -86
  16. package/dist/permissions.js +11 -67
  17. package/dist/pikku-state.d.ts +32 -104
  18. package/dist/pikku-state.js +140 -66
  19. package/dist/schema.d.ts +8 -6
  20. package/dist/schema.js +25 -13
  21. package/dist/services/workflow-service.d.ts +38 -0
  22. package/dist/services/workflow-service.js +1 -0
  23. package/dist/types/core.types.d.ts +12 -4
  24. package/dist/types/core.types.js +1 -1
  25. package/dist/types/state.types.d.ts +135 -0
  26. package/dist/types/state.types.js +1 -0
  27. package/dist/utils.d.ts +8 -0
  28. package/dist/utils.js +45 -0
  29. package/dist/wirings/channel/channel-handler.js +1 -1
  30. package/dist/wirings/channel/channel-runner.js +5 -5
  31. package/dist/wirings/channel/log-channels.js +1 -1
  32. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
  33. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  34. package/dist/wirings/cli/cli-runner.js +10 -10
  35. package/dist/wirings/cli/cli.types.d.ts +1 -0
  36. package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
  37. package/dist/wirings/forge-node/forge-node.types.js +38 -0
  38. package/dist/wirings/forge-node/index.d.ts +1 -0
  39. package/dist/wirings/forge-node/index.js +1 -0
  40. package/dist/wirings/http/http-runner.d.ts +2 -2
  41. package/dist/wirings/http/http-runner.js +21 -12
  42. package/dist/wirings/http/http.types.d.ts +14 -1
  43. package/dist/wirings/http/log-http-routes.js +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  45. package/dist/wirings/mcp/mcp-runner.js +21 -21
  46. package/dist/wirings/queue/queue-runner.js +6 -6
  47. package/dist/wirings/rpc/index.d.ts +1 -1
  48. package/dist/wirings/rpc/index.js +1 -1
  49. package/dist/wirings/rpc/rpc-runner.d.ts +7 -8
  50. package/dist/wirings/rpc/rpc-runner.js +62 -14
  51. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  52. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  53. package/dist/wirings/scheduler/scheduler-runner.js +5 -5
  54. package/dist/wirings/trigger/index.d.ts +2 -0
  55. package/dist/wirings/trigger/index.js +2 -0
  56. package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
  57. package/dist/wirings/trigger/trigger-runner.js +57 -0
  58. package/dist/wirings/trigger/trigger.types.d.ts +42 -0
  59. package/dist/wirings/trigger/trigger.types.js +1 -0
  60. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  61. package/dist/wirings/workflow/dsl/index.js +4 -0
  62. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
  63. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  64. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  65. package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
  66. package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
  67. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  68. package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
  69. package/dist/wirings/workflow/graph/graph-runner.js +452 -0
  70. package/dist/wirings/workflow/graph/index.d.ts +3 -0
  71. package/dist/wirings/workflow/graph/index.js +3 -0
  72. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
  73. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  74. package/dist/wirings/workflow/index.d.ts +7 -12
  75. package/dist/wirings/workflow/index.js +6 -11
  76. package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
  77. package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
  78. package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
  79. package/dist/wirings/workflow/wire-workflow.js +53 -0
  80. package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
  81. package/dist/wirings/workflow/workflow-utils.js +66 -0
  82. package/dist/wirings/workflow/workflow.types.d.ts +133 -211
  83. package/package.json +10 -1
  84. package/src/errors/error-handler.ts +3 -3
  85. package/src/function/function-runner.test.ts +1 -1
  86. package/src/function/function-runner.ts +86 -16
  87. package/src/function/functions.types.ts +126 -4
  88. package/src/index.ts +10 -5
  89. package/src/middleware-runner.ts +16 -9
  90. package/src/permissions.test.ts +28 -86
  91. package/src/permissions.ts +21 -80
  92. package/src/pikku-state.ts +156 -201
  93. package/src/schema.ts +42 -13
  94. package/src/services/workflow-service.ts +74 -0
  95. package/src/types/core.types.ts +12 -3
  96. package/src/types/state.types.ts +195 -0
  97. package/src/utils.ts +55 -0
  98. package/src/wirings/channel/channel-handler.ts +1 -1
  99. package/src/wirings/channel/channel-runner.ts +5 -5
  100. package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
  101. package/src/wirings/channel/log-channels.ts +1 -1
  102. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
  103. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  104. package/src/wirings/cli/cli-runner.test.ts +19 -19
  105. package/src/wirings/cli/cli-runner.ts +10 -9
  106. package/src/wirings/cli/cli.types.ts +1 -0
  107. package/src/wirings/forge-node/forge-node.types.ts +135 -0
  108. package/src/wirings/forge-node/index.ts +1 -0
  109. package/src/wirings/http/http-runner.test.ts +2 -2
  110. package/src/wirings/http/http-runner.ts +30 -13
  111. package/src/wirings/http/http.types.ts +14 -0
  112. package/src/wirings/http/log-http-routes.ts +1 -1
  113. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  114. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  115. package/src/wirings/mcp/mcp-runner.ts +21 -21
  116. package/src/wirings/queue/queue-runner.test.ts +19 -19
  117. package/src/wirings/queue/queue-runner.ts +6 -6
  118. package/src/wirings/rpc/index.ts +1 -1
  119. package/src/wirings/rpc/rpc-runner.ts +88 -22
  120. package/src/wirings/rpc/rpc-types.ts +8 -0
  121. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  122. package/src/wirings/scheduler/scheduler-runner.test.ts +27 -27
  123. package/src/wirings/scheduler/scheduler-runner.ts +5 -5
  124. package/src/wirings/trigger/index.ts +2 -0
  125. package/src/wirings/trigger/trigger-runner.ts +96 -0
  126. package/src/wirings/trigger/trigger.types.ts +56 -0
  127. package/src/wirings/workflow/dsl/index.ts +30 -0
  128. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
  129. package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
  130. package/src/wirings/workflow/graph/graph-node.ts +227 -0
  131. package/src/wirings/workflow/graph/graph-runner.ts +694 -0
  132. package/src/wirings/workflow/graph/index.ts +3 -0
  133. package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
  134. package/src/wirings/workflow/index.ts +53 -26
  135. package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
  136. package/src/wirings/workflow/wire-workflow.ts +94 -0
  137. package/src/wirings/workflow/workflow-utils.ts +120 -0
  138. package/src/wirings/workflow/workflow.types.ts +166 -277
  139. package/tsconfig.tsbuildinfo +1 -1
  140. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  141. package/dist/wirings/workflow/workflow-runner.js +0 -65
  142. package/src/function/function-runner.ts.bak +0 -188
  143. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -0,0 +1,74 @@
1
+ import {
2
+ SerializedError,
3
+ CoreSingletonServices,
4
+ CreateWireServices,
5
+ CoreConfig,
6
+ } from '../types/core.types.js'
7
+ import {
8
+ WorkflowRun,
9
+ StepState,
10
+ WorkflowStatus,
11
+ } from '../wirings/workflow/workflow.types.js'
12
+
13
+ /**
14
+ * Interface for workflow orchestration
15
+ * Handles workflow execution, replay, orchestration logic, and run-level state
16
+ */
17
+ export interface WorkflowService {
18
+ // Run-level state operations
19
+ createRun(workflowName: string, input: any, inline?: boolean): Promise<string>
20
+ getRun(id: string): Promise<WorkflowRun | null>
21
+ getRunHistory(runId: string): Promise<Array<StepState & { stepName: string }>>
22
+ updateRunStatus(
23
+ id: string,
24
+ status: WorkflowStatus,
25
+ output?: any,
26
+ error?: SerializedError
27
+ ): Promise<void>
28
+ withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>
29
+ close(): Promise<void>
30
+
31
+ // Orchestration operations
32
+ resumeWorkflow(runId: string): Promise<void>
33
+ setServices(
34
+ singletonServices: CoreSingletonServices,
35
+ createWireServices: CreateWireServices,
36
+ config: CoreConfig
37
+ ): void
38
+ startWorkflow<I>(
39
+ name: string,
40
+ input: I,
41
+ rpcService: any,
42
+ options?: { inline?: boolean; startNode?: string }
43
+ ): Promise<{ runId: string }>
44
+ runWorkflowJob(runId: string, rpcService: any): Promise<void>
45
+ orchestrateWorkflow(runId: string, rpcService: any): Promise<void>
46
+ executeWorkflowSleepCompleted(runId: string, stepId: string): Promise<void>
47
+
48
+ // Step-level state operations
49
+ insertStepState(
50
+ runId: string,
51
+ stepName: string,
52
+ rpcName: string,
53
+ data: any,
54
+ stepOptions?: { retries?: number; retryDelay?: string | number }
55
+ ): Promise<StepState>
56
+ getStepState(runId: string, stepName: string): Promise<StepState>
57
+ setStepRunning(stepId: string): Promise<void>
58
+ setStepScheduled(stepId: string): Promise<void>
59
+ setStepResult(stepId: string, result: any): Promise<void>
60
+ setStepError(stepId: string, error: Error): Promise<void>
61
+ createRetryAttempt(
62
+ stepId: string,
63
+ status: 'pending' | 'running'
64
+ ): Promise<StepState>
65
+
66
+ // Step execution
67
+ executeWorkflowStep(
68
+ runId: string,
69
+ stepName: string,
70
+ rpcName: string | null,
71
+ data: any,
72
+ rpcService: any
73
+ ): Promise<void>
74
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Logger, LogLevel } from '../services/logger.js'
2
2
  import { VariablesService } from '../services/variables-service.js'
3
+ import { SecretService } from '../services/secret-service.js'
3
4
  import { SchemaService } from '../services/schema-service.js'
4
5
  import { JWTService } from '../services/jwt-service.js'
5
6
  import { PikkuHTTP } from '../wirings/http/http.types.js'
@@ -16,6 +17,8 @@ import {
16
17
  WorkflowServiceConfig,
17
18
  WorkflowStepWire,
18
19
  } from '../wirings/workflow/workflow.types.js'
20
+ import type { PikkuGraphWire } from '../wirings/workflow/graph/workflow-graph.types.js'
21
+ import { PikkuTrigger } from '../wirings/trigger/trigger.types.js'
19
22
  import { SchedulerService } from '../services/scheduler-service.js'
20
23
 
21
24
  export type PikkuWiringTypes =
@@ -169,6 +172,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
169
172
  logger: Logger
170
173
  /** The variable service to be used */
171
174
  variables: VariablesService
175
+ /** The secrets service to retrieve secrets */
176
+ secrets: SecretService
172
177
  /** The workflow orchestrator service */
173
178
  workflowService?: WorkflowService
174
179
  /** The queue service */
@@ -189,6 +194,7 @@ export type PikkuWire<
189
194
  IsChannel extends true | null = null,
190
195
  MCPTools extends string | never = never,
191
196
  TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire,
197
+ TriggerOutput = unknown,
192
198
  > = Partial<{
193
199
  http: PikkuHTTP<In>
194
200
  mcp: PikkuMCP<MCPTools>
@@ -201,6 +207,8 @@ export type PikkuWire<
201
207
  cli: PikkuCLI
202
208
  workflow: TypedWorkflow
203
209
  workflowStep: WorkflowStepWire
210
+ graph: PikkuGraphWire
211
+ trigger: PikkuTrigger<TriggerOutput>
204
212
  initialSession: HasInitialSession extends true
205
213
  ? UserSession
206
214
  : UserSession | undefined
@@ -231,8 +239,8 @@ export type CorePikkuMiddlewareConfig<
231
239
  > = {
232
240
  /** The middleware function */
233
241
  func: CorePikkuMiddleware<SingletonServices, UserSession>
234
- /** Optional human-readable name for the middleware */
235
- name?: string
242
+ /** Optional human-readable title for the middleware */
243
+ title?: string
236
244
  /** Optional description of what the middleware does */
237
245
  description?: string
238
246
  }
@@ -275,7 +283,7 @@ export type CorePikkuMiddlewareGroup<
275
283
  *
276
284
  * // Configuration object syntax with metadata
277
285
  * export const logMiddleware = pikkuMiddleware({
278
- * name: 'Request Logger',
286
+ * title: 'Request Logger',
279
287
  * description: 'Logs request information',
280
288
  * func: async ({ logger }, next) => {
281
289
  * logger.info('Request started')
@@ -367,6 +375,7 @@ export type CreateConfig<
367
375
  export type CommonWireMeta = {
368
376
  pikkuFuncName: string
369
377
 
378
+ title?: string
370
379
  tags?: string[]
371
380
  summary?: string
372
381
  description?: string
@@ -0,0 +1,195 @@
1
+ import { PikkuError, ErrorDetails } from '../errors/error-handler.js'
2
+ import {
3
+ CorePikkuFunctionConfig,
4
+ CorePermissionGroup,
5
+ CorePikkuPermission,
6
+ } from '../function/functions.types.js'
7
+ import { CoreChannel, ChannelsMeta } from '../wirings/channel/channel.types.js'
8
+ import { CLIMeta, CLIProgramState } from '../wirings/cli/cli.types.js'
9
+ import {
10
+ HTTPMethod,
11
+ CoreHTTPFunctionWiring,
12
+ HTTPWiringsMeta,
13
+ } from '../wirings/http/http.types.js'
14
+ import {
15
+ CoreMCPResource,
16
+ MCPResourceMeta,
17
+ CoreMCPTool,
18
+ MCPToolMeta,
19
+ CoreMCPPrompt,
20
+ MCPPromptMeta,
21
+ } from '../wirings/mcp/mcp.types.js'
22
+ import {
23
+ CoreQueueWorker,
24
+ QueueWorkersMeta,
25
+ } from '../wirings/queue/queue.types.js'
26
+ import {
27
+ CoreScheduledTask,
28
+ ScheduledTasksMeta,
29
+ } from '../wirings/scheduler/scheduler.types.js'
30
+ import {
31
+ CoreWorkflow,
32
+ WorkflowsRuntimeMeta,
33
+ WorkflowWires,
34
+ } from '../wirings/workflow/workflow.types.js'
35
+ import type {
36
+ WorkflowGraphDefinition,
37
+ GraphNodeConfig,
38
+ } from '../wirings/workflow/graph/workflow-graph.types.js'
39
+ import { CoreTrigger, TriggerMeta } from '../wirings/trigger/trigger.types.js'
40
+ import {
41
+ FunctionsMeta,
42
+ CorePikkuMiddleware,
43
+ CorePikkuMiddlewareGroup,
44
+ FunctionServicesMeta,
45
+ CreateConfig,
46
+ CreateSingletonServices,
47
+ CreateWireServices,
48
+ CoreConfig,
49
+ CoreSingletonServices,
50
+ CoreServices,
51
+ CoreUserSession,
52
+ } from './core.types.js'
53
+
54
+ /**
55
+ * State structure for an individual package
56
+ */
57
+ export interface PikkuPackageState {
58
+ function: {
59
+ meta: FunctionsMeta
60
+ functions: Map<string, CorePikkuFunctionConfig<any, any>>
61
+ }
62
+ rpc: {
63
+ meta: Record<string, string>
64
+ files: Map<
65
+ string,
66
+ {
67
+ exportedName: string
68
+ path: string
69
+ }
70
+ >
71
+ /** Maps namespace aliases to package names (e.g., 'ext' -> '@pikku/templates-function-external') */
72
+ externalPackages: Map<string, string>
73
+ }
74
+ http: {
75
+ middleware: Map<string, CorePikkuMiddleware<any, any>[]>
76
+ permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>
77
+ routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>
78
+ meta: HTTPWiringsMeta
79
+ }
80
+ channel: {
81
+ channels: Map<string, CoreChannel<any, any>>
82
+ meta: ChannelsMeta
83
+ }
84
+ scheduler: {
85
+ tasks: Map<string, CoreScheduledTask>
86
+ meta: ScheduledTasksMeta
87
+ }
88
+ queue: {
89
+ registrations: Map<string, CoreQueueWorker>
90
+ meta: QueueWorkersMeta
91
+ }
92
+ workflows: {
93
+ registrations: Map<string, CoreWorkflow>
94
+ graphRegistrations: Map<string, WorkflowGraphDefinition<any>>
95
+ /** DSL workflow wirings (from wireWorkflow({ func: ... })) */
96
+ wirings: Map<any, { wires: WorkflowWires; func: any }>
97
+ /** Graph workflow wirings (from wireWorkflow({ graph: ... })) */
98
+ graphWirings: Map<
99
+ any,
100
+ { wires: WorkflowWires; graph: Record<string, GraphNodeConfig<string>> }
101
+ >
102
+ meta: WorkflowsRuntimeMeta
103
+ }
104
+ trigger: {
105
+ triggers: Map<string, CoreTrigger>
106
+ meta: TriggerMeta
107
+ }
108
+ mcp: {
109
+ resources: Map<string, CoreMCPResource>
110
+ resourcesMeta: MCPResourceMeta
111
+ tools: Map<string, CoreMCPTool>
112
+ toolsMeta: MCPToolMeta
113
+ prompts: Map<string, CoreMCPPrompt>
114
+ promptsMeta: MCPPromptMeta
115
+ }
116
+ cli: {
117
+ meta: CLIMeta | Record<string, any> // Backward compatible with old published CLI format
118
+ programs: Record<string, CLIProgramState>
119
+ }
120
+ middleware: {
121
+ tagGroup: Record<string, CorePikkuMiddlewareGroup>
122
+ httpGroup: Record<string, CorePikkuMiddlewareGroup>
123
+ tagGroupMeta: Record<
124
+ string,
125
+ {
126
+ exportName: string | null
127
+ sourceFile: string
128
+ position: number
129
+ services: FunctionServicesMeta
130
+ middlewareCount: number
131
+ isFactory: boolean
132
+ }
133
+ >
134
+ httpGroupMeta: Record<
135
+ string,
136
+ {
137
+ exportName: string | null
138
+ sourceFile: string
139
+ position: number
140
+ services: FunctionServicesMeta
141
+ middlewareCount: number
142
+ isFactory: boolean
143
+ }
144
+ >
145
+ }
146
+ permissions: {
147
+ tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
148
+ httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
149
+ tagGroupMeta: Record<
150
+ string,
151
+ {
152
+ exportName: string | null
153
+ sourceFile: string
154
+ position: number
155
+ services: FunctionServicesMeta
156
+ permissionCount: number
157
+ isFactory: boolean
158
+ }
159
+ >
160
+ httpGroupMeta: Record<
161
+ string,
162
+ {
163
+ exportName: string | null
164
+ sourceFile: string
165
+ position: number
166
+ services: FunctionServicesMeta
167
+ permissionCount: number
168
+ isFactory: boolean
169
+ }
170
+ >
171
+ }
172
+ misc: {
173
+ errors: Map<PikkuError, ErrorDetails>
174
+ schemas: Map<string, any>
175
+ middleware: Record<string, CorePikkuMiddleware[]>
176
+ permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>
177
+ }
178
+ package: {
179
+ /** Service factory functions for external packages */
180
+ factories: {
181
+ createConfig?: CreateConfig<CoreConfig>
182
+ createSingletonServices?: CreateSingletonServices<
183
+ CoreConfig,
184
+ CoreSingletonServices
185
+ >
186
+ createWireServices?: CreateWireServices<
187
+ CoreSingletonServices,
188
+ CoreServices,
189
+ CoreUserSession
190
+ >
191
+ } | null
192
+ /** Cached singleton services for this package */
193
+ singletonServices: CoreSingletonServices | null
194
+ }
195
+ }
package/src/utils.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Logger } from './services/logger.js'
2
+ import { CoreSingletonServices } from './types/core.types.js'
2
3
 
3
4
  // TODO: WireServices probably needs it's own type
4
5
  // but is an issue for the future and will be tackled
@@ -57,3 +58,57 @@ export const freezeDedupe = <T>(
57
58
  }
58
59
  return Object.freeze(out)
59
60
  }
61
+
62
+ /**
63
+ * Stop a single service by calling its stop method if it exists
64
+ */
65
+ const stopService = async (
66
+ logger: Logger,
67
+ name: string,
68
+ service: any
69
+ ): Promise<void> => {
70
+ const stop = service?.stop
71
+ if (stop) {
72
+ logger.info(`Stopping singleton service: ${name}`)
73
+ try {
74
+ await stop.call(service)
75
+ } catch (e: any) {
76
+ logger.error(`Error stopping service ${name}:`, e)
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Stop all singleton services, including external package services.
83
+ * External package services are stopped first, then the parent services.
84
+ *
85
+ * @param singletonServices - The parent singleton services to stop
86
+ */
87
+ export const stopSingletonServices = async (
88
+ singletonServices: CoreSingletonServices
89
+ ): Promise<void> => {
90
+ const logger = singletonServices.logger
91
+
92
+ // First, stop all external package singleton services
93
+ if (globalThis.pikkuState) {
94
+ for (const [packageName, packageState] of globalThis.pikkuState) {
95
+ // Skip main package - we handle it separately
96
+ if (packageName === '__main__') continue
97
+
98
+ const packageServices = packageState.package?.singletonServices
99
+ if (packageServices) {
100
+ logger.info(`Stopping singleton services for package: ${packageName}`)
101
+ for (const [name, service] of Object.entries(packageServices)) {
102
+ await stopService(logger, `${packageName}/${name}`, service)
103
+ }
104
+ // Clear the cached services
105
+ packageState.package.singletonServices = null
106
+ }
107
+ }
108
+ }
109
+
110
+ // Then stop the parent singleton services
111
+ for (const [name, service] of Object.entries(singletonServices)) {
112
+ await stopService(logger, name, service)
113
+ }
114
+ }
@@ -17,7 +17,7 @@ const getRouteMeta = (
17
17
  routingProperty?: string,
18
18
  routerValue?: string
19
19
  ): ChannelMessageMeta => {
20
- const channelMeta = pikkuState('channel', 'meta')[channelName]
20
+ const channelMeta = pikkuState(null, 'channel', 'meta')[channelName]
21
21
  if (!channelMeta) {
22
22
  throw new Error(`Channel ${channelName} not found`)
23
23
  }
@@ -33,13 +33,13 @@ export const wireChannel = <
33
33
  >
34
34
  ) => {
35
35
  // Get the channel metadata
36
- const channelsMeta = pikkuState('channel', 'meta')
36
+ const channelsMeta = pikkuState(null, 'channel', 'meta')
37
37
  const channelMeta = channelsMeta[channel.name]
38
38
  if (!channelMeta) {
39
39
  throw new Error(`Channel metadata not found for channel: ${channel.name}`)
40
40
  }
41
41
 
42
- pikkuState('channel', 'channels').set(channel.name, channel as any)
42
+ pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
43
43
 
44
44
  // Register onConnect function if provided
45
45
  if (channel.onConnect && channelMeta.connect) {
@@ -89,7 +89,7 @@ export const wireChannel = <
89
89
  }
90
90
 
91
91
  // Store the channel configuration
92
- pikkuState('channel', 'channels').set(channel.name, channel as any)
92
+ pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
93
93
  }
94
94
 
95
95
  const getMatchingChannelConfig = (path: string) => {
@@ -98,7 +98,7 @@ const getMatchingChannelConfig = (path: string) => {
98
98
  return null
99
99
  }
100
100
 
101
- const meta = pikkuState('channel', 'meta')
101
+ const meta = pikkuState(null, 'channel', 'meta')
102
102
  const channelMeta = Object.values(meta).find(
103
103
  (channelConfig) => channelConfig.route === matchedPath.route
104
104
  )
@@ -106,7 +106,7 @@ const getMatchingChannelConfig = (path: string) => {
106
106
  return null
107
107
  }
108
108
 
109
- const channels = pikkuState('channel', 'channels')
109
+ const channels = pikkuState(null, 'channel', 'channels')
110
110
  const channelConfig = channels.get(channelMeta.name)
111
111
  if (!channelConfig) {
112
112
  return null
@@ -101,10 +101,12 @@ const mockCreateWireServices = async () => ({
101
101
 
102
102
  beforeEach(() => {
103
103
  resetPikkuState()
104
+ httpRouter.reset()
104
105
  })
105
106
 
106
107
  afterEach(() => {
107
108
  resetPikkuState()
109
+ httpRouter.reset()
108
110
  })
109
111
 
110
112
  test('runChannel should return undefined and 404 if no matching channel is found', async () => {
@@ -130,7 +132,7 @@ test('runChannel should return undefined and 404 if no matching channel is found
130
132
  test('runChannel should return a channel handler if channel matches and no auth required', async () => {
131
133
  resetPikkuState()
132
134
 
133
- pikkuState('channel', 'meta', {
135
+ pikkuState(null, 'channel', 'meta', {
134
136
  test: {
135
137
  name: 'test',
136
138
  route: '/test-channel',
@@ -6,7 +6,7 @@ import { Logger } from '../../services/index.js'
6
6
  * @param logger - A logger for logging information.
7
7
  */
8
8
  export const logChannels = (logger: Logger) => {
9
- const channels = pikkuState('channel', 'channels')
9
+ const channels = pikkuState(null, 'channel', 'channels')
10
10
  if (channels.size === 0) {
11
11
  logger.info('No channels added')
12
12
  return
@@ -35,9 +35,9 @@ const getVariablesForChannel = ({
35
35
  channelHandlerFactory: PikkuChannelHandlerFactory
36
36
  openingData?: unknown
37
37
  }) => {
38
- const channels = pikkuState('channel', 'channels')
38
+ const channels = pikkuState(null, 'channel', 'channels')
39
39
  const channelConfig = channels.get(channelName)
40
- const channelsMeta = pikkuState('channel', 'meta')
40
+ const channelsMeta = pikkuState(null, 'channel', 'meta')
41
41
  const meta = channelsMeta[channelName]
42
42
  if (!channelConfig) {
43
43
  throw new Error(`Channel not found: ${channelName}`)
@@ -1,5 +1,5 @@
1
1
  import { pikkuState } from '../../../pikku-state.js'
2
- import { CorePikkuCLIRender, CLIMeta } from '../cli.types.js'
2
+ import { CorePikkuCLIRender, type CLIMeta } from '../cli.types.js'
3
3
  import { generateCommandHelp, parseCLIArguments } from '../command-parser.js'
4
4
 
5
5
  /**
@@ -27,12 +27,13 @@ export async function executeCLIViaChannel({
27
27
  defaultRenderer?: CorePikkuCLIRender<any>
28
28
  }): Promise<void> {
29
29
  // Get CLI metadata from state
30
- const allCLIMeta = pikkuState('cli', 'meta') as unknown as CLIMeta | undefined
31
- if (!allCLIMeta) {
30
+ const cliMeta = pikkuState(null, 'cli', 'meta')
31
+ if (!cliMeta || !('programs' in cliMeta)) {
32
32
  throw new Error(
33
33
  '[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/docs/pikku-cli/errors/pku342 for more information.'
34
34
  )
35
35
  }
36
+ const allCLIMeta = cliMeta as CLIMeta
36
37
  const programMeta = allCLIMeta.programs[programName]
37
38
 
38
39
  if (!programMeta) {
@@ -30,7 +30,7 @@ describe('CLI Runner', () => {
30
30
 
31
31
  describe('runCLICommand', () => {
32
32
  test('should throw NotFoundError when program not found', async () => {
33
- pikkuState('cli', 'meta', { programs: {}, renderers: {} })
33
+ pikkuState(null, 'cli', 'meta', { programs: {}, renderers: {} })
34
34
 
35
35
  await assert.rejects(
36
36
  async () =>
@@ -45,7 +45,7 @@ describe('CLI Runner', () => {
45
45
  })
46
46
 
47
47
  test('should throw NotFoundError when command not found', async () => {
48
- pikkuState('cli', 'meta', {
48
+ pikkuState(null, 'cli', 'meta', {
49
49
  programs: {
50
50
  'test-cli': {
51
51
  program: 'test-cli',
@@ -56,7 +56,7 @@ describe('CLI Runner', () => {
56
56
  renderers: {},
57
57
  })
58
58
 
59
- pikkuState('cli', 'programs', {
59
+ pikkuState(null, 'cli', 'programs', {
60
60
  'test-cli': {
61
61
  defaultRenderer: undefined,
62
62
  middleware: [],
@@ -84,7 +84,7 @@ describe('CLI Runner', () => {
84
84
  }
85
85
 
86
86
  // Setup metadata
87
- pikkuState('cli', 'meta', {
87
+ pikkuState(null, 'cli', 'meta', {
88
88
  programs: {
89
89
  'test-cli': {
90
90
  program: 'test-cli',
@@ -102,7 +102,7 @@ describe('CLI Runner', () => {
102
102
  renderers: {},
103
103
  })
104
104
 
105
- pikkuState('cli', 'programs', {
105
+ pikkuState(null, 'cli', 'programs', {
106
106
  'test-cli': {
107
107
  defaultRenderer: undefined,
108
108
  middleware: [],
@@ -110,7 +110,7 @@ describe('CLI Runner', () => {
110
110
  },
111
111
  })
112
112
 
113
- pikkuState('function', 'meta', {
113
+ pikkuState(null, 'function', 'meta', {
114
114
  greetFunc: {
115
115
  pikkuFuncName: 'greetFunc',
116
116
  inputSchemaName: null,
@@ -148,7 +148,7 @@ describe('CLI Runner', () => {
148
148
  return { success: true }
149
149
  }
150
150
 
151
- pikkuState('cli', 'meta', {
151
+ pikkuState(null, 'cli', 'meta', {
152
152
  programs: {
153
153
  'test-cli': {
154
154
  program: 'test-cli',
@@ -166,7 +166,7 @@ describe('CLI Runner', () => {
166
166
  renderers: {},
167
167
  })
168
168
 
169
- pikkuState('cli', 'programs', {
169
+ pikkuState(null, 'cli', 'programs', {
170
170
  'test-cli': {
171
171
  defaultRenderer: undefined,
172
172
  middleware: [testMiddleware],
@@ -174,7 +174,7 @@ describe('CLI Runner', () => {
174
174
  },
175
175
  })
176
176
 
177
- pikkuState('function', 'meta', {
177
+ pikkuState(null, 'function', 'meta', {
178
178
  testFunc: {
179
179
  pikkuFuncName: 'testFunc',
180
180
  inputSchemaName: null,
@@ -208,7 +208,7 @@ describe('CLI Runner', () => {
208
208
 
209
209
  const testFunc = async () => ({ success: true })
210
210
 
211
- pikkuState('cli', 'meta', {
211
+ pikkuState(null, 'cli', 'meta', {
212
212
  programs: {
213
213
  'test-cli': {
214
214
  program: 'test-cli',
@@ -226,7 +226,7 @@ describe('CLI Runner', () => {
226
226
  renderers: {},
227
227
  })
228
228
 
229
- pikkuState('cli', 'programs', {
229
+ pikkuState(null, 'cli', 'programs', {
230
230
  'test-cli': {
231
231
  defaultRenderer: undefined,
232
232
  middleware: [testMiddleware],
@@ -234,7 +234,7 @@ describe('CLI Runner', () => {
234
234
  },
235
235
  })
236
236
 
237
- pikkuState('function', 'meta', {
237
+ pikkuState(null, 'function', 'meta', {
238
238
  greetFunc: {
239
239
  pikkuFuncName: 'greetFunc',
240
240
  inputSchemaName: null,
@@ -260,7 +260,7 @@ describe('CLI Runner', () => {
260
260
  test('should throw error when auth required but no session', async () => {
261
261
  const testFunc = async () => ({ success: true })
262
262
 
263
- pikkuState('cli', 'meta', {
263
+ pikkuState(null, 'cli', 'meta', {
264
264
  programs: {
265
265
  'test-cli': {
266
266
  program: 'test-cli',
@@ -278,7 +278,7 @@ describe('CLI Runner', () => {
278
278
  renderers: {},
279
279
  })
280
280
 
281
- pikkuState('function', 'meta', {
281
+ pikkuState(null, 'function', 'meta', {
282
282
  secureFunc: {
283
283
  pikkuFuncName: 'secureFunc',
284
284
  inputSchemaName: null,
@@ -306,7 +306,7 @@ describe('CLI Runner', () => {
306
306
  test('should register CLI program and commands', () => {
307
307
  const greetFunc = async () => 'Hello'
308
308
 
309
- pikkuState('cli', 'meta', {
309
+ pikkuState(null, 'cli', 'meta', {
310
310
  programs: {
311
311
  'my-cli': {
312
312
  program: 'my-cli',
@@ -337,7 +337,7 @@ describe('CLI Runner', () => {
337
337
  },
338
338
  })
339
339
 
340
- const programs = pikkuState('cli', 'programs')
340
+ const programs = pikkuState(null, 'cli', 'programs')
341
341
  assert.ok(programs['my-cli'])
342
342
  assert.strictEqual(programs['my-cli'].middleware.length, 0)
343
343
  })
@@ -347,7 +347,7 @@ describe('CLI Runner', () => {
347
347
  await next()
348
348
  }
349
349
 
350
- pikkuState('cli', 'meta', {
350
+ pikkuState(null, 'cli', 'meta', {
351
351
  programs: {
352
352
  'my-cli': {
353
353
  program: 'my-cli',
@@ -364,13 +364,13 @@ describe('CLI Runner', () => {
364
364
  middleware: [middleware],
365
365
  })
366
366
 
367
- const programs = pikkuState('cli', 'programs')
367
+ const programs = pikkuState(null, 'cli', 'programs')
368
368
  assert.strictEqual(programs['my-cli'].middleware.length, 1)
369
369
  assert.strictEqual(programs['my-cli'].middleware[0], middleware)
370
370
  })
371
371
 
372
372
  test('should throw error when CLI metadata not found', () => {
373
- pikkuState('cli', 'meta', { programs: {}, renderers: {} })
373
+ pikkuState(null, 'cli', 'meta', { programs: {}, renderers: {} })
374
374
 
375
375
  assert.throws(() => {
376
376
  wireCLI({