@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
@@ -19,7 +19,7 @@ import { freezeDedupe } from './utils.js'
19
19
  * @param wire - The wire object containing request/response context and session.
20
20
  * @returns A promise that resolves to void.
21
21
  */
22
- export const verifyPermissions = async <Out = any>(
22
+ const verifyPermissions = async <Out = any>(
23
23
  permissions: CorePermissionGroup,
24
24
  services: CoreServices,
25
25
  data: any,
@@ -53,31 +53,6 @@ export const verifyPermissions = async <Out = any>(
53
53
  return false
54
54
  }
55
55
 
56
- /**
57
- * Registers a single permission function by name in the global permission store.
58
- *
59
- * This function is used by CLI-generated code to register permission functions
60
- * that can be referenced by name in metadata. It stores permissions in a special
61
- * namespace to avoid conflicts with tag-based permissions.
62
- *
63
- * @param {string} name - The unique name (pikkuFuncName) of the permission function.
64
- * @param {CorePikkuPermission} permission - The permission function to register.
65
- *
66
- * @example
67
- * ```typescript
68
- * // Called by CLI-generated pikku-permissions.gen.ts
69
- * registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
70
- * registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
71
- * ```
72
- */
73
- export const registerPermission = (
74
- name: string,
75
- permission: CorePikkuPermission<any>
76
- ) => {
77
- const permissionStore = pikkuState('misc', 'permissions')
78
- permissionStore[name] = [permission]
79
- }
80
-
81
56
  /**
82
57
  * Retrieves a registered permission function by its name.
83
58
  *
@@ -89,10 +64,8 @@ export const registerPermission = (
89
64
  *
90
65
  * @internal
91
66
  */
92
- export const getPermissionByName = (
93
- name: string
94
- ): CorePikkuPermission | undefined => {
95
- const permissionStore = pikkuState('misc', 'permissions')
67
+ const getPermissionByName = (name: string): CorePikkuPermission | undefined => {
68
+ const permissionStore = pikkuState(null, 'misc', 'permissions')
96
69
  const permission = permissionStore[name]
97
70
  if (Array.isArray(permission) && permission.length === 1) {
98
71
  return permission[0]
@@ -130,9 +103,10 @@ export const getPermissionByName = (
130
103
  */
131
104
  export const addPermission = (
132
105
  tag: string,
133
- permissions: CorePermissionGroup | CorePikkuPermission[]
106
+ permissions: CorePermissionGroup | CorePikkuPermission[],
107
+ packageName: string | null = null
134
108
  ): CorePermissionGroup | CorePikkuPermission[] => {
135
- const tagGroups = pikkuState('permissions', 'tagGroup')
109
+ const tagGroups = pikkuState(packageName, 'permissions', 'tagGroup')
136
110
  if (tagGroups[tag]) {
137
111
  throw new Error(
138
112
  `Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
@@ -142,50 +116,6 @@ export const addPermission = (
142
116
  return permissions
143
117
  }
144
118
 
145
- const EMPTY: readonly (CorePermissionGroup | CorePikkuPermission)[] = []
146
-
147
- /**
148
- * Retrieves permissions for a given set of tags.
149
- *
150
- * This function looks up all permissions registered for any of the provided tags
151
- * and returns them as a flattened array.
152
- *
153
- * @param {string[]} tags - Array of tags to look up permissions for.
154
- * @returns {any[]} Array of permission functions that apply to the given tags.
155
- *
156
- * @example
157
- * ```typescript
158
- * // Get all permissions for tags 'api' and 'auth'
159
- * const permissions = getPermissionsForTags(['api', 'auth'])
160
- * ```
161
- */
162
- export const getPermissionsForTags = (
163
- tags?: string[]
164
- ): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
165
- if (!tags || tags.length === 0) {
166
- return EMPTY
167
- }
168
-
169
- const permissionsStore = pikkuState('permissions', 'tagGroup')
170
- const applicablePermissions: Array<
171
- CorePermissionGroup | CorePikkuPermission
172
- > = []
173
-
174
- // Collect permissions for all matching tags
175
- for (const tag of new Set(tags)) {
176
- const tagPermissions = permissionsStore[tag]
177
- if (tagPermissions) {
178
- if (Array.isArray(tagPermissions)) {
179
- applicablePermissions.push(...tagPermissions)
180
- } else {
181
- applicablePermissions.push(tagPermissions)
182
- }
183
- }
184
- }
185
-
186
- return applicablePermissions
187
- }
188
-
189
119
  const combinedPermissionsCache: Record<
190
120
  PikkuWiringTypes,
191
121
  Record<string, readonly (CorePermissionGroup | CorePikkuPermission)[]>
@@ -224,7 +154,7 @@ const combinedPermissionsCache: Record<
224
154
  * })
225
155
  * ```
226
156
  */
227
- export const combinePermissions = (
157
+ const combinePermissions = (
228
158
  wireType: PikkuWiringTypes,
229
159
  uid: string,
230
160
  {
@@ -232,11 +162,13 @@ export const combinePermissions = (
232
162
  wirePermissions,
233
163
  funcInheritedPermissions,
234
164
  funcPermissions,
165
+ packageName = null,
235
166
  }: {
236
167
  wireInheritedPermissions?: PermissionMetadata[]
237
168
  wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
238
169
  funcInheritedPermissions?: PermissionMetadata[]
239
170
  funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
171
+ packageName?: string | null
240
172
  } = {}
241
173
  ): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
242
174
  if (combinedPermissionsCache[wireType][uid]) {
@@ -250,7 +182,9 @@ export const combinePermissions = (
250
182
  for (const meta of wireInheritedPermissions) {
251
183
  if (meta.type === 'http') {
252
184
  // Look up HTTP permission group from pikkuState
253
- const group = pikkuState('permissions', 'httpGroup')[meta.route]
185
+ const group = pikkuState(packageName, 'permissions', 'httpGroup')[
186
+ meta.route
187
+ ]
254
188
  if (group) {
255
189
  if (Array.isArray(group)) {
256
190
  resolved.push(...group)
@@ -260,7 +194,9 @@ export const combinePermissions = (
260
194
  }
261
195
  } else if (meta.type === 'tag') {
262
196
  // Look up tag permission group from pikkuState
263
- const group = pikkuState('permissions', 'tagGroup')[meta.tag]
197
+ const group = pikkuState(packageName, 'permissions', 'tagGroup')[
198
+ meta.tag
199
+ ]
264
200
  if (group) {
265
201
  if (Array.isArray(group)) {
266
202
  resolved.push(...group)
@@ -292,7 +228,9 @@ export const combinePermissions = (
292
228
  for (const meta of funcInheritedPermissions) {
293
229
  if (meta.type === 'tag') {
294
230
  // Look up tag permission group from pikkuState
295
- const group = pikkuState('permissions', 'tagGroup')[meta.tag]
231
+ const group = pikkuState(packageName, 'permissions', 'tagGroup')[
232
+ meta.tag
233
+ ]
296
234
  if (group) {
297
235
  if (Array.isArray(group)) {
298
236
  resolved.push(...group)
@@ -338,6 +276,7 @@ export const runPermissions = async (
338
276
  services,
339
277
  wire,
340
278
  data,
279
+ packageName = null,
341
280
  }: {
342
281
  wireInheritedPermissions?: PermissionMetadata[]
343
282
  wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
@@ -346,6 +285,7 @@ export const runPermissions = async (
346
285
  services: CoreServices
347
286
  wire: PikkuWire<any, never, any, never, never, never>
348
287
  data: any
288
+ packageName?: string | null
349
289
  }
350
290
  ) => {
351
291
  // Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
@@ -354,6 +294,7 @@ export const runPermissions = async (
354
294
  wirePermissions,
355
295
  funcInheritedPermissions,
356
296
  funcPermissions,
297
+ packageName,
357
298
  })
358
299
 
359
300
  // Check all combined permissions - at least one must pass if any exist
@@ -1,230 +1,185 @@
1
- import { ChannelsMeta, CoreChannel } from './wirings/channel/channel.types.js'
1
+ import { PikkuPackageState } from './index.js'
2
+ import { HTTPWiringsMeta } from './wirings/http/http.types.js'
2
3
  import {
3
- CoreHTTPFunctionWiring,
4
- HTTPMethod,
5
- HTTPWiringsMeta,
6
- } from './wirings/http/http.types.js'
7
- import {
8
- FunctionsMeta,
9
- CorePikkuMiddleware,
10
- CorePikkuMiddlewareGroup,
11
- FunctionServicesMeta,
12
- } from './types/core.types.js'
13
- import {
14
- CoreScheduledTask,
15
- ScheduledTasksMeta,
16
- } from './wirings/scheduler/scheduler.types.js'
17
- import { ErrorDetails, PikkuError } from './errors/error-handler.js'
18
- import {
19
- CorePermissionGroup,
20
- CorePikkuFunctionConfig,
21
- CorePikkuPermission,
22
- } from './function/functions.types.js'
23
- import {
24
- QueueWorkersMeta,
25
- CoreQueueWorker,
26
- } from './wirings/queue/queue.types.js'
27
- import {
28
- CoreMCPResource,
29
- CoreMCPTool,
30
- CoreMCPPrompt,
31
4
  MCPResourceMeta,
32
5
  MCPToolMeta,
33
6
  MCPPromptMeta,
34
7
  } from './wirings/mcp/mcp.types.js'
35
- import { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js'
36
- import {
37
- CoreWorkflow,
38
- WorkflowsMeta,
39
- } from './wirings/workflow/workflow.types.js'
8
+ import { ScheduledTasksMeta } from './wirings/scheduler/scheduler.types.js'
9
+ import { TriggerMeta } from './wirings/trigger/trigger.types.js'
40
10
 
41
- interface PikkuState {
42
- function: {
43
- meta: FunctionsMeta
44
- functions: Map<string, CorePikkuFunctionConfig<any, any>>
11
+ declare global {
12
+ // eslint-disable-next-line no-var
13
+ var pikkuState: Map<string, PikkuPackageState> | undefined
14
+ }
15
+
16
+ /**
17
+ * Get or set package-scoped pikku state
18
+ *
19
+ * @param packageName - Package name (null for main package, '@scope/package' for external packages)
20
+ * @param type - State category (function, rpc, http, etc.)
21
+ * @param content - Content key within the category
22
+ * @param value - Optional value to set
23
+ * @returns The current value of the state
24
+ *
25
+ * @example
26
+ * // Main package
27
+ * pikkuState(null, 'function', 'functions').get(funcName)
28
+ *
29
+ * // External package
30
+ * pikkuState('@acme/stripe-functions', 'rpc', 'meta')
31
+ */
32
+ export const pikkuState = <
33
+ Type extends keyof PikkuPackageState,
34
+ Content extends keyof PikkuPackageState[Type],
35
+ >(
36
+ packageName: string | null,
37
+ type: Type,
38
+ content: Content,
39
+ value?: PikkuPackageState[Type][Content]
40
+ ): PikkuPackageState[Type][Content] => {
41
+ const resolvedPackageName = packageName ?? '__main__'
42
+
43
+ // Initialize package state if it doesn't exist
44
+ if (
45
+ !globalThis.pikkuState ||
46
+ !globalThis.pikkuState.has(resolvedPackageName)
47
+ ) {
48
+ initializePikkuState(resolvedPackageName)
45
49
  }
46
- rpc: {
47
- meta: Record<string, string>
48
- files: Map<
49
- string,
50
- {
51
- exportedName: string
52
- path: string
53
- }
54
- >
50
+
51
+ const packageState = globalThis.pikkuState!.get(resolvedPackageName)!
52
+
53
+ if (value !== undefined) {
54
+ packageState[type][content] = value
55
55
  }
56
+
57
+ return packageState[type][content]
58
+ }
59
+
60
+ const createEmptyPackageState = (): PikkuPackageState => ({
61
+ function: {
62
+ meta: {},
63
+ functions: new Map(),
64
+ },
65
+ rpc: {
66
+ meta: {},
67
+ files: new Map(),
68
+ externalPackages: new Map(),
69
+ },
56
70
  http: {
57
- middleware: Map<string, CorePikkuMiddleware<any, any>[]>
58
- permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>
59
- routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>
60
- meta: HTTPWiringsMeta
61
- }
71
+ middleware: new Map(),
72
+ permissions: new Map(),
73
+ routes: new Map(),
74
+ meta: {
75
+ post: {},
76
+ get: {},
77
+ delete: {},
78
+ patch: {},
79
+ head: {},
80
+ put: {},
81
+ options: {},
82
+ } as HTTPWiringsMeta,
83
+ },
62
84
  channel: {
63
- channels: Map<string, CoreChannel<any, any>>
64
- meta: ChannelsMeta
65
- }
85
+ channels: new Map(),
86
+ meta: {},
87
+ },
66
88
  scheduler: {
67
- tasks: Map<string, CoreScheduledTask>
68
- meta: ScheduledTasksMeta
69
- }
89
+ tasks: new Map(),
90
+ meta: [] as unknown as ScheduledTasksMeta,
91
+ },
70
92
  queue: {
71
- registrations: Map<string, CoreQueueWorker>
72
- meta: QueueWorkersMeta
73
- }
93
+ registrations: new Map(),
94
+ meta: {},
95
+ },
74
96
  workflows: {
75
- registrations: Map<string, CoreWorkflow>
76
- meta: WorkflowsMeta
77
- }
97
+ registrations: new Map(),
98
+ graphRegistrations: new Map(),
99
+ wirings: new Map(),
100
+ graphWirings: new Map(),
101
+ meta: {},
102
+ },
103
+ trigger: {
104
+ triggers: new Map(),
105
+ meta: {} as TriggerMeta,
106
+ },
78
107
  mcp: {
79
- resources: Map<string, CoreMCPResource>
80
- resourcesMeta: MCPResourceMeta
81
- tools: Map<string, CoreMCPTool>
82
- toolsMeta: MCPToolMeta
83
- prompts: Map<string, CoreMCPPrompt>
84
- promptsMeta: MCPPromptMeta
85
- }
108
+ resources: new Map(),
109
+ resourcesMeta: {} as MCPResourceMeta,
110
+ tools: new Map(),
111
+ toolsMeta: {} as MCPToolMeta,
112
+ prompts: new Map(),
113
+ promptsMeta: {} as MCPPromptMeta,
114
+ },
86
115
  cli: {
87
- meta: CLIMeta | Record<string, any> // Backward compatible with old published CLI format
88
- programs: Record<string, CLIProgramState>
89
- }
116
+ meta: { programs: {}, renderers: {} },
117
+ programs: {},
118
+ },
90
119
  middleware: {
91
- tagGroup: Record<string, CorePikkuMiddlewareGroup>
92
- httpGroup: Record<string, CorePikkuMiddlewareGroup>
93
- tagGroupMeta: Record<
94
- string,
95
- {
96
- exportName: string | null
97
- sourceFile: string
98
- position: number
99
- services: FunctionServicesMeta
100
- middlewareCount: number
101
- isFactory: boolean
102
- }
103
- >
104
- httpGroupMeta: Record<
105
- string,
106
- {
107
- exportName: string | null
108
- sourceFile: string
109
- position: number
110
- services: FunctionServicesMeta
111
- middlewareCount: number
112
- isFactory: boolean
113
- }
114
- >
115
- }
120
+ tagGroup: {},
121
+ httpGroup: {},
122
+ tagGroupMeta: {},
123
+ httpGroupMeta: {},
124
+ },
116
125
  permissions: {
117
- tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
118
- httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
119
- tagGroupMeta: Record<
120
- string,
121
- {
122
- exportName: string | null
123
- sourceFile: string
124
- position: number
125
- services: FunctionServicesMeta
126
- permissionCount: number
127
- isFactory: boolean
128
- }
129
- >
130
- httpGroupMeta: Record<
131
- string,
132
- {
133
- exportName: string | null
134
- sourceFile: string
135
- position: number
136
- services: FunctionServicesMeta
137
- permissionCount: number
138
- isFactory: boolean
139
- }
140
- >
141
- }
126
+ tagGroup: {},
127
+ httpGroup: {},
128
+ tagGroupMeta: {},
129
+ httpGroupMeta: {},
130
+ },
142
131
  misc: {
143
- errors: Map<PikkuError, ErrorDetails>
144
- schemas: Map<string, any>
145
- middleware: Record<string, CorePikkuMiddleware[]>
146
- permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>
132
+ errors: new Map(),
133
+ schemas: new Map(),
134
+ middleware: {},
135
+ permissions: {},
136
+ },
137
+ package: {
138
+ factories: null,
139
+ singletonServices: null,
140
+ },
141
+ })
142
+
143
+ /**
144
+ * Initialize state for a new package
145
+ */
146
+ export const initializePikkuState = (packageName: string): void => {
147
+ if (!globalThis.pikkuState) {
148
+ globalThis.pikkuState = new Map()
149
+ }
150
+ if (!globalThis.pikkuState.has(packageName)) {
151
+ globalThis.pikkuState.set(packageName, createEmptyPackageState())
147
152
  }
148
153
  }
149
154
 
150
155
  export const resetPikkuState = () => {
151
- globalThis.pikkuState = {
152
- function: {
153
- meta: {},
154
- functions: new Map(),
155
- },
156
- rpc: {
157
- meta: {},
158
- files: new Map(),
159
- },
160
- http: {
161
- permissions: new Map(),
162
- routes: new Map(),
163
- meta: {},
164
- },
165
- channel: {
166
- channels: new Map(),
167
- meta: {},
168
- },
169
- scheduler: {
170
- tasks: new Map(),
171
- meta: [] as unknown as ScheduledTasksMeta,
172
- },
173
- queue: {
174
- registrations: new Map(),
175
- meta: {},
176
- },
177
- workflows: {
178
- registrations: new Map(),
179
- meta: {},
180
- },
181
- mcp: {
182
- resources: new Map(),
183
- resourcesMeta: {} as MCPResourceMeta,
184
- tools: new Map(),
185
- toolsMeta: {} as MCPToolMeta,
186
- prompts: new Map(),
187
- promptsMeta: {} as MCPPromptMeta,
188
- },
189
- cli: {
190
- meta: { programs: {}, renderers: {} },
191
- programs: {},
192
- },
193
- middleware: {
194
- tagGroup: {},
195
- httpGroup: {},
196
- tagGroupMeta: {},
197
- httpGroupMeta: {},
198
- },
199
- permissions: {
200
- tagGroup: {},
201
- httpGroup: {},
202
- tagGroupMeta: {},
203
- httpGroupMeta: {},
204
- },
205
- misc: {
206
- errors: globalThis.pikkuState?.misc?.errors || new Map(),
207
- schemas: new Map(),
208
- middleware: {},
209
- permissions: {},
210
- },
211
- } as PikkuState
156
+ // Preserve the errors map before resetting
157
+ const existingErrors = globalThis.pikkuState?.get('__main__')?.misc.errors
158
+
159
+ globalThis.pikkuState = new Map()
160
+ initializePikkuState('__main__')
161
+
162
+ // Restore the errors map if it existed
163
+ if (existingErrors) {
164
+ const mainState = globalThis.pikkuState.get('__main__')!
165
+ mainState.misc.errors = existingErrors
166
+ }
212
167
  }
213
168
 
214
169
  if (!globalThis.pikkuState) {
215
170
  resetPikkuState()
216
171
  }
217
172
 
218
- export const pikkuState = <
219
- Type extends keyof PikkuState,
220
- Content extends keyof PikkuState[Type],
221
- >(
222
- type: Type,
223
- content: Content,
224
- value?: PikkuState[Type][Content]
225
- ): PikkuState[Type][Content] => {
226
- if (value) {
227
- globalThis.pikkuState[type][content] = value
228
- }
229
- return globalThis.pikkuState[type][content]
173
+ /**
174
+ * Register service factories for an external package.
175
+ * These factories are used to create services when the package's functions are invoked.
176
+ *
177
+ * @param packageName - The package name (e.g., '@pikku/templates-function-external')
178
+ * @param factories - The service factory functions
179
+ */
180
+ export const addPackageServiceFactories = (
181
+ packageName: string,
182
+ factories: NonNullable<PikkuPackageState['package']['factories']>
183
+ ): void => {
184
+ pikkuState(packageName, 'package', 'factories', factories)
230
185
  }
package/src/schema.ts CHANGED
@@ -4,25 +4,32 @@ import { UnprocessableContentError } from './errors/errors.js'
4
4
  import { pikkuState } from './pikku-state.js'
5
5
 
6
6
  /**
7
- * Adds a schema to the global schemas map.
7
+ * Adds a schema to the schemas map for a specific package.
8
8
  * @param name - The name of the schema.
9
9
  * @param value - The schema value.
10
+ * @param packageName - The package name (null for main package, '@scope/package' for external packages).
10
11
  * @ignore
11
12
  */
12
- export const addSchema = (name: string, value: any) => {
13
- pikkuState('misc', 'schemas').set(name, value)
13
+ export const addSchema = (
14
+ name: string,
15
+ value: any,
16
+ packageName: string | null = null
17
+ ) => {
18
+ pikkuState(packageName, 'misc', 'schemas').set(name, value)
14
19
  }
15
20
 
16
21
  /**
17
- * Retrieves a schema from the global schemas map.
22
+ * Retrieves a schema from the schemas map for a specific package.
18
23
  * @param name - The name of the schema.
24
+ * @param packageName - The package name (null for main package, '@scope/package' for external packages).
19
25
  * @returns The schema value or undefined if not found.
20
26
  * @ignore
21
27
  */
22
28
  export const getSchema = (
23
- name: string
29
+ name: string,
30
+ packageName: string | null = null
24
31
  ): Record<string, unknown> | undefined => {
25
- return pikkuState('misc', 'schemas').get(name)
32
+ return pikkuState(packageName, 'misc', 'schemas').get(name)
26
33
  }
27
34
 
28
35
  /**
@@ -36,7 +43,7 @@ export const compileAllSchemas = (
36
43
  if (!schemaService) {
37
44
  throw new Error('SchemaService needs to be defined to load schemas')
38
45
  }
39
- const schemas = pikkuState('misc', 'schemas')
46
+ const schemas = pikkuState(null, 'misc', 'schemas')
40
47
  for (const [name, schema] of schemas) {
41
48
  schemaService.compileSchema(name, schema)
42
49
  }
@@ -47,14 +54,15 @@ const validateAllSchemasLoaded = (
47
54
  logger: Logger,
48
55
  schemaService: SchemaService
49
56
  ) => {
50
- const routesMeta = pikkuState('http', 'meta')
57
+ const routesMeta = pikkuState(null, 'http', 'meta')
51
58
  const validators = schemaService.getSchemaNames()
52
59
 
53
60
  const missingSchemas: string[] = []
54
61
 
55
62
  for (const routePaths of Object.values(routesMeta)) {
56
63
  for (const meta of Object.values(routePaths)) {
57
- const inputs = pikkuState('function', 'meta')[meta.pikkuFuncName]?.inputs
64
+ const inputs = pikkuState(null, 'function', 'meta')[meta.pikkuFuncName]
65
+ ?.inputs
58
66
  const input = inputs?.[0]
59
67
  if (!input || validators.has(input)) {
60
68
  continue
@@ -74,8 +82,12 @@ const validateAllSchemasLoaded = (
74
82
  }
75
83
  }
76
84
 
77
- export const coerceTopLevelDataFromSchema = (schemaName: string, data: any) => {
78
- const schema = pikkuState('misc', 'schemas').get(schemaName)
85
+ export const coerceTopLevelDataFromSchema = (
86
+ schemaName: string,
87
+ data: any,
88
+ packageName: string | null = null
89
+ ) => {
90
+ const schema = pikkuState(packageName, 'misc', 'schemas').get(schemaName)
79
91
  for (const key in schema.properties) {
80
92
  const property = schema.properties[key]
81
93
  if (typeof property === 'boolean') {
@@ -97,7 +109,8 @@ export const validateSchema = async (
97
109
  logger: Logger,
98
110
  schemaService: SchemaService | undefined,
99
111
  schemaName: string | undefined | null,
100
- data: any
112
+ data: any,
113
+ packageName: string | null = null
101
114
  ) => {
102
115
  if (schemaService) {
103
116
  if (!schemaName) {
@@ -108,7 +121,23 @@ export const validateSchema = async (
108
121
  return
109
122
  }
110
123
  }
111
- const schema = pikkuState('misc', 'schemas').get(schemaName)
124
+ logger.debug(
125
+ `[validateSchema] Validating schema '${schemaName}' for package '${packageName ?? 'main'}' with data:`,
126
+ JSON.stringify(data)
127
+ )
128
+ const schemas = pikkuState(packageName, 'misc', 'schemas')
129
+ const schema = schemas.get(schemaName)
130
+ if (schema === undefined) {
131
+ const availableSchemas = Array.from(schemas.keys())
132
+ logger.error(
133
+ `Schema '${schemaName}' not found for package '${packageName ?? 'main'}'. Available schemas: ${availableSchemas.join(', ') || '(none)'}`
134
+ )
135
+ } else {
136
+ logger.debug(
137
+ `[validateSchema] Found schema '${schemaName}':`,
138
+ JSON.stringify(schema)
139
+ )
140
+ }
112
141
  await schemaService.compileSchema(schemaName, schema)
113
142
  await schemaService.validateSchema(schemaName, data)
114
143
  }