@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
@@ -12,6 +12,7 @@ import {
12
12
  PermissionMetadata,
13
13
  CoreSingletonServices,
14
14
  CreateWireServices,
15
+ CoreConfig,
15
16
  } from '../types/core.types.js'
16
17
  import {
17
18
  CorePermissionGroup,
@@ -23,11 +24,55 @@ import { ForbiddenError } from '../errors/errors.js'
23
24
  import { rpcService } from '../wirings/rpc/rpc-runner.js'
24
25
  import { closeWireServices } from '../utils.js'
25
26
 
27
+ /**
28
+ * Get or create singleton services for an external package.
29
+ * Services are cached in pikkuState to avoid recreation on each call.
30
+ *
31
+ * @param packageName - The external package name
32
+ * @param parentServices - The parent/caller's singleton services (used as base)
33
+ * @returns The package's singleton services
34
+ */
35
+ const getOrCreatePackageSingletonServices = async (
36
+ packageName: string,
37
+ parentServices: CoreSingletonServices
38
+ ): Promise<CoreSingletonServices> => {
39
+ // Check if we already have cached singleton services for this package
40
+ const cachedServices = pikkuState(packageName, 'package', 'singletonServices')
41
+ if (cachedServices) {
42
+ return cachedServices
43
+ }
44
+
45
+ // Get the package's service factories
46
+ const factories = pikkuState(packageName, 'package', 'factories')
47
+ if (!factories || !factories.createSingletonServices) {
48
+ // No factories registered, use parent services
49
+ return parentServices
50
+ }
51
+
52
+ // Create config for the package (use parent config if no factory)
53
+ let config: CoreConfig = parentServices.config
54
+ if (factories.createConfig) {
55
+ config = await factories.createConfig(parentServices.variables)
56
+ }
57
+
58
+ // Create singleton services for the package, passing parent services as existing
59
+ const packageServices = await factories.createSingletonServices(
60
+ config,
61
+ parentServices
62
+ )
63
+
64
+ // Cache the services
65
+ pikkuState(packageName, 'package', 'singletonServices', packageServices)
66
+
67
+ return packageServices
68
+ }
69
+
26
70
  export const addFunction = (
27
71
  funcName: string,
28
- funcConfig: CorePikkuFunctionConfig<any, any>
72
+ funcConfig: CorePikkuFunctionConfig<any, any>,
73
+ packageName: string | null = null
29
74
  ) => {
30
- pikkuState('function', 'functions').set(funcName, funcConfig)
75
+ pikkuState(packageName, 'function', 'functions').set(funcName, funcConfig)
31
76
  }
32
77
 
33
78
  export const runPikkuFuncDirectly = async <In, Out>(
@@ -35,9 +80,12 @@ export const runPikkuFuncDirectly = async <In, Out>(
35
80
  allServices: CoreServices,
36
81
  wire: PikkuWire,
37
82
  data: In,
38
- userSession?: SessionService<CoreUserSession>
83
+ userSession?: SessionService<CoreUserSession>,
84
+ packageName: string | null = null
39
85
  ) => {
40
- const funcConfig = pikkuState('function', 'functions').get(funcName)
86
+ const funcConfig = pikkuState(packageName, 'function', 'functions').get(
87
+ funcName
88
+ )
41
89
  if (!funcConfig) {
42
90
  throw new Error(`Function not found: ${funcName}`)
43
91
  }
@@ -65,6 +113,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
65
113
  coerceDataFromSchema,
66
114
  tags = [],
67
115
  wire,
116
+ packageName = null,
68
117
  }: {
69
118
  singletonServices: CoreSingletonServices
70
119
  createWireServices?: CreateWireServices
@@ -77,17 +126,35 @@ export const runPikkuFunc = async <In = any, Out = any>(
77
126
  coerceDataFromSchema?: boolean
78
127
  tags?: string[]
79
128
  wire: PikkuWire
129
+ packageName?: string | null
80
130
  }
81
131
  ): Promise<Out> => {
82
- const funcConfig = pikkuState('function', 'functions').get(funcName)
132
+ const funcConfig = pikkuState(packageName, 'function', 'functions').get(
133
+ funcName
134
+ )
83
135
  if (!funcConfig) {
84
136
  throw new Error(`Function not found: ${funcName}`)
85
137
  }
86
- const funcMeta = pikkuState('function', 'meta')[funcName]
138
+ const allMeta = pikkuState(packageName, 'function', 'meta')
139
+ const funcMeta = allMeta[funcName]
87
140
  if (!funcMeta) {
88
141
  throw new Error(`Function meta not found: ${funcName}`)
89
142
  }
90
143
 
144
+ // For external packages, get or create their singleton services
145
+ const resolvedSingletonServices = packageName
146
+ ? await getOrCreatePackageSingletonServices(packageName, singletonServices)
147
+ : singletonServices
148
+
149
+ // Get the package's createWireServices if available
150
+ let resolvedCreateWireServices = createWireServices
151
+ if (packageName) {
152
+ const factories = pikkuState(packageName, 'package', 'factories')
153
+ if (factories?.createWireServices) {
154
+ resolvedCreateWireServices = factories.createWireServices
155
+ }
156
+ }
157
+
91
158
  // Convert tags to PermissionMetadata and merge with inheritedPermissions
92
159
  const mergedInheritedPermissions: PermissionMetadata[] = [
93
160
  ...(inheritedPermissions || []),
@@ -125,14 +192,15 @@ export const runPikkuFunc = async <In = any, Out = any>(
125
192
  if (inputSchemaName) {
126
193
  // Validate request data against the defined schema, if any
127
194
  await validateSchema(
128
- singletonServices.logger,
129
- singletonServices.schema,
195
+ resolvedSingletonServices.logger,
196
+ resolvedSingletonServices.schema,
130
197
  inputSchemaName,
131
- actualData
198
+ actualData,
199
+ packageName
132
200
  )
133
201
  // Coerce (top level) query string parameters or date objects if specified by the schema
134
202
  if (coerceDataFromSchema) {
135
- coerceTopLevelDataFromSchema(inputSchemaName, actualData)
203
+ coerceTopLevelDataFromSchema(inputSchemaName, actualData, packageName)
136
204
  }
137
205
  }
138
206
 
@@ -141,17 +209,18 @@ export const runPikkuFunc = async <In = any, Out = any>(
141
209
  wirePermissions: wirePermissions,
142
210
  funcInheritedPermissions: funcMeta.permissions,
143
211
  funcPermissions: funcConfig.permissions,
144
- services: singletonServices,
212
+ services: resolvedSingletonServices,
145
213
  wire: { ...wireWithInitialSession, rpc: undefined } as any,
146
214
  data: actualData,
215
+ packageName,
147
216
  })
148
217
 
149
- const wireServices = await createWireServices?.(
150
- singletonServices,
218
+ const wireServices = await resolvedCreateWireServices?.(
219
+ resolvedSingletonServices,
151
220
  wireWithInitialSession
152
221
  )
153
222
  try {
154
- const services = { ...singletonServices, ...wireServices }
223
+ const services = { ...resolvedSingletonServices, ...wireServices }
155
224
  const rpc = rpcService.getContextRPCService(
156
225
  services,
157
226
  wireWithInitialSession
@@ -162,7 +231,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
162
231
  })
163
232
  } finally {
164
233
  if (wireServices) {
165
- await closeWireServices(singletonServices.logger, wireServices)
234
+ await closeWireServices(resolvedSingletonServices.logger, wireServices)
166
235
  }
167
236
  }
168
237
  }
@@ -172,11 +241,12 @@ export const runPikkuFunc = async <In = any, Out = any>(
172
241
  wireMiddleware,
173
242
  funcInheritedMiddleware: funcMeta.middleware,
174
243
  funcMiddleware: funcConfig.middleware,
244
+ packageName,
175
245
  })
176
246
 
177
247
  if (allMiddleware.length > 0) {
178
248
  return (await runMiddleware<CorePikkuMiddleware>(
179
- singletonServices,
249
+ resolvedSingletonServices,
180
250
  wire,
181
251
  allMiddleware,
182
252
  executeFunction
@@ -94,8 +94,8 @@ export type CorePikkuPermissionConfig<
94
94
  > = {
95
95
  /** The permission function */
96
96
  func: CorePikkuPermission<In, Services, Wire>
97
- /** Optional human-readable name for the permission */
98
- name?: string
97
+ /** Optional human-readable title for the permission */
98
+ title?: string
99
99
  /** Optional description of what the permission checks */
100
100
  description?: string
101
101
  }
@@ -116,7 +116,7 @@ export type CorePikkuPermissionConfig<
116
116
  *
117
117
  * // Configuration object syntax with metadata
118
118
  * export const adminPermission = pikkuPermission({
119
- * name: 'Admin Permission',
119
+ * title: 'Admin Permission',
120
120
  * description: 'Checks if user has admin role',
121
121
  * func: async ({ logger }, _data, { session }) => {
122
122
  * const currentSession = await session.get()
@@ -189,6 +189,15 @@ export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> =
189
189
  | Record<string, PikkuPermission | PikkuPermission[]>
190
190
  | undefined
191
191
 
192
+ /**
193
+ * Zod schema type - matches z.ZodType shape for type inference
194
+ * This avoids requiring zod as a dependency while allowing schema inference
195
+ */
196
+ export type ZodLike<T = any> = {
197
+ _input: T
198
+ _output: T
199
+ }
200
+
192
201
  export type CorePikkuFunctionConfig<
193
202
  PikkuFunction extends
194
203
  | CorePikkuFunction<any, any, any, any>
@@ -202,8 +211,14 @@ export type CorePikkuFunctionConfig<
202
211
  any,
203
212
  any
204
213
  >,
214
+ InputSchema extends ZodLike | undefined = undefined,
215
+ OutputSchema extends ZodLike | undefined = undefined,
205
216
  > = {
206
- name?: string
217
+ /** Optional human-readable title for the function */
218
+ title?: string
219
+ /** Optional description of what the function does */
220
+ description?: string
221
+ override?: string
207
222
  tags?: string[]
208
223
  expose?: boolean
209
224
  internal?: boolean
@@ -211,4 +226,111 @@ export type CorePikkuFunctionConfig<
211
226
  auth?: boolean
212
227
  permissions?: CorePermissionGroup<PikkuPermission>
213
228
  middleware?: PikkuMiddleware[]
229
+ input?: InputSchema
230
+ output?: OutputSchema
231
+ }
232
+
233
+ /**
234
+ * A trigger function that sets up a subscription and returns a teardown function.
235
+ * The trigger is fired via wire.trigger.trigger(data).
236
+ *
237
+ * @template TConfig - Configuration type (hardcoded when wired)
238
+ * @template TOutput - Output type produced when trigger fires
239
+ * @template Services - Services available to the trigger
240
+ */
241
+ export type CorePikkuTriggerFunction<
242
+ TConfig = unknown,
243
+ TOutput = unknown,
244
+ Services extends CoreSingletonServices = CoreSingletonServices,
245
+ > = (
246
+ services: Services,
247
+ config: TConfig,
248
+ wire: { trigger: { trigger: (data: TOutput) => void } }
249
+ ) => Promise<() => void | Promise<void>>
250
+
251
+ /**
252
+ * Configuration object for creating a trigger function with metadata
253
+ */
254
+ export type CorePikkuTriggerFunctionConfig<
255
+ TConfig = unknown,
256
+ TOutput = unknown,
257
+ Services extends CoreSingletonServices = CoreSingletonServices,
258
+ ConfigSchema extends ZodLike | undefined = undefined,
259
+ OutputSchema extends ZodLike | undefined = undefined,
260
+ > = {
261
+ /** Optional human-readable title for the trigger */
262
+ title?: string
263
+ /** Optional description of what the trigger does */
264
+ description?: string
265
+ /** Optional tags for categorization */
266
+ tags?: string[]
267
+ /** The trigger function */
268
+ func: CorePikkuTriggerFunction<TConfig, TOutput, Services>
269
+ /** Optional Zod schema for config validation */
270
+ config?: ConfigSchema
271
+ /** Optional Zod schema for output validation */
272
+ output?: OutputSchema
273
+ }
274
+
275
+ /**
276
+ * Factory function for creating trigger functions
277
+ * Supports both direct function and configuration object syntax
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * // Direct function syntax
282
+ * export const redisSubscribeTrigger = pikkuTriggerFunc<
283
+ * { channel: string },
284
+ * { message: string }
285
+ * >(async ({ redis }, { channel }, { trigger }) => {
286
+ * const subscriber = redis.duplicate()
287
+ * await subscriber.subscribe(channel, (msg) => {
288
+ * trigger.trigger({ message: msg })
289
+ * })
290
+ * return () => subscriber.unsubscribe()
291
+ * })
292
+ *
293
+ * // Configuration object syntax with metadata
294
+ * export const redisSubscribeTrigger = pikkuTriggerFunc({
295
+ * title: 'Redis Subscribe Trigger',
296
+ * description: 'Listens to Redis pub/sub channel',
297
+ * config: z.object({ channel: z.string() }),
298
+ * output: z.object({ message: z.string() }),
299
+ * func: async ({ redis }, { channel }, { trigger }) => {
300
+ * const subscriber = redis.duplicate()
301
+ * await subscriber.subscribe(channel, (msg) => {
302
+ * trigger.trigger({ message: msg })
303
+ * })
304
+ * return () => subscriber.unsubscribe()
305
+ * }
306
+ * })
307
+ * ```
308
+ */
309
+ export const pikkuTriggerFunc = <
310
+ TConfig = unknown,
311
+ TOutput = unknown,
312
+ Services extends CoreSingletonServices = CoreSingletonServices,
313
+ ConfigSchema extends ZodLike | undefined = undefined,
314
+ OutputSchema extends ZodLike | undefined = undefined,
315
+ >(
316
+ triggerOrConfig:
317
+ | CorePikkuTriggerFunction<TConfig, TOutput, Services>
318
+ | CorePikkuTriggerFunctionConfig<
319
+ TConfig,
320
+ TOutput,
321
+ Services,
322
+ ConfigSchema,
323
+ OutputSchema
324
+ >
325
+ ): CorePikkuTriggerFunctionConfig<
326
+ TConfig,
327
+ TOutput,
328
+ Services,
329
+ ConfigSchema,
330
+ OutputSchema
331
+ > => {
332
+ if (typeof triggerOrConfig === 'function') {
333
+ return { func: triggerOrConfig }
334
+ }
335
+ return triggerOrConfig
214
336
  }
package/src/index.ts CHANGED
@@ -17,16 +17,21 @@ export * from './wirings/scheduler/index.js'
17
17
  export * from './wirings/rpc/index.js'
18
18
  export * from './wirings/queue/index.js'
19
19
  export * from './wirings/workflow/index.js'
20
+ export * from './wirings/workflow/graph/index.js'
20
21
  export * from './wirings/mcp/index.js'
21
22
  export * from './wirings/cli/index.js'
23
+ export * from './wirings/forge-node/index.js'
24
+ export * from './wirings/trigger/index.js'
22
25
  export * from './errors/index.js'
23
26
  export * from './middleware/index.js'
24
27
  export * from './utils.js'
25
28
  export * from './time-utils.js'
26
- export { pikkuState } from './pikku-state.js'
27
29
  export {
28
- runMiddleware,
29
- addMiddleware,
30
- getMiddlewareByName,
31
- } from './middleware-runner.js'
30
+ pikkuState,
31
+ initializePikkuState,
32
+ resetPikkuState,
33
+ addPackageServiceFactories,
34
+ } from './pikku-state.js'
35
+ export type { PikkuPackageState } from './types/state.types.js'
36
+ export { runMiddleware, addMiddleware } from './middleware-runner.js'
32
37
  export { addPermission } from './permissions.js'
@@ -82,9 +82,10 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
82
82
  */
83
83
  export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
84
84
  tag: string,
85
- middleware: CorePikkuMiddlewareGroup
85
+ middleware: CorePikkuMiddlewareGroup,
86
+ packageName: string | null = null
86
87
  ): CorePikkuMiddlewareGroup => {
87
- const tagGroups = pikkuState('middleware', 'tagGroup')
88
+ const tagGroups = pikkuState(packageName, 'middleware', 'tagGroup')
88
89
  tagGroups[tag] = middleware
89
90
  return middleware
90
91
  }
@@ -100,10 +101,8 @@ export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
100
101
  *
101
102
  * @internal
102
103
  */
103
- export const getMiddlewareByName = (
104
- name: string
105
- ): CorePikkuMiddleware | undefined => {
106
- const middlewareStore = pikkuState('misc', 'middleware')
104
+ const getMiddlewareByName = (name: string): CorePikkuMiddleware | undefined => {
105
+ const middlewareStore = pikkuState(null, 'misc', 'middleware')
107
106
  const middleware = middlewareStore[name]
108
107
  return middleware?.[0]
109
108
  }
@@ -152,11 +151,13 @@ export const combineMiddleware = (
152
151
  wireMiddleware,
153
152
  funcInheritedMiddleware,
154
153
  funcMiddleware,
154
+ packageName = null,
155
155
  }: {
156
156
  wireInheritedMiddleware?: MiddlewareMetadata[]
157
157
  wireMiddleware?: CorePikkuMiddleware[]
158
158
  funcInheritedMiddleware?: MiddlewareMetadata[]
159
159
  funcMiddleware?: CorePikkuMiddleware[]
160
+ packageName?: string | null
160
161
  } = {}
161
162
  ): readonly CorePikkuMiddleware[] => {
162
163
  if (middlewareCache[wireType][uid]) {
@@ -170,14 +171,18 @@ export const combineMiddleware = (
170
171
  for (const meta of wireInheritedMiddleware) {
171
172
  if (meta.type === 'http') {
172
173
  // Look up HTTP middleware group from pikkuState
173
- const group = pikkuState('middleware', 'httpGroup')[meta.route]
174
+ const group = pikkuState(packageName, 'middleware', 'httpGroup')[
175
+ meta.route
176
+ ]
174
177
  if (group) {
175
178
  // At runtime, all factories should be resolved to middleware
176
179
  resolved.push(...(group as CorePikkuMiddleware[]))
177
180
  }
178
181
  } else if (meta.type === 'tag') {
179
182
  // Look up tag middleware group from pikkuState
180
- const group = pikkuState('middleware', 'tagGroup')[meta.tag]
183
+ const group = pikkuState(packageName, 'middleware', 'tagGroup')[
184
+ meta.tag
185
+ ]
181
186
  if (group) {
182
187
  // At runtime, all factories should be resolved to middleware
183
188
  resolved.push(...(group as CorePikkuMiddleware[]))
@@ -202,7 +207,9 @@ export const combineMiddleware = (
202
207
  for (const meta of funcInheritedMiddleware) {
203
208
  if (meta.type === 'tag') {
204
209
  // Look up tag middleware group from pikkuState
205
- const group = pikkuState('middleware', 'tagGroup')[meta.tag]
210
+ const group = pikkuState(packageName, 'middleware', 'tagGroup')[
211
+ meta.tag
212
+ ]
206
213
  if (group) {
207
214
  // At runtime, all factories should be resolved to middleware
208
215
  resolved.push(...(group as CorePikkuMiddleware[]))
@@ -1,18 +1,39 @@
1
1
  import { describe, test, beforeEach } from 'node:test'
2
2
  import * as assert from 'node:assert'
3
- import {
4
- addPermission,
5
- getPermissionsForTags,
6
- runPermissions,
7
- } from './permissions.js'
8
- import { resetPikkuState } from './pikku-state.js'
3
+ import { addPermission, runPermissions } from './permissions.js'
4
+ import { pikkuState, resetPikkuState } from './pikku-state.js'
9
5
  import { CoreServices, CoreUserSession } from './types/core.types.js'
10
- import { CorePermissionGroup } from './function/functions.types.js'
6
+ import {
7
+ CorePermissionGroup,
8
+ CorePikkuPermission,
9
+ } from './function/functions.types.js'
11
10
 
12
11
  beforeEach(() => {
13
12
  resetPikkuState()
14
13
  })
15
14
 
15
+ // Test helper to get permissions for tags (not exported from core to keep it lean)
16
+ const getPermissionsForTags = (
17
+ tags?: string[]
18
+ ): (CorePermissionGroup | CorePikkuPermission)[] => {
19
+ if (!tags || tags.length === 0) {
20
+ return []
21
+ }
22
+ const permissionsStore = pikkuState(null, 'permissions', 'tagGroup')
23
+ const result: (CorePermissionGroup | CorePikkuPermission)[] = []
24
+ for (const tag of new Set(tags)) {
25
+ const tagPermissions = permissionsStore[tag]
26
+ if (tagPermissions) {
27
+ if (Array.isArray(tagPermissions)) {
28
+ result.push(...tagPermissions)
29
+ } else {
30
+ result.push(tagPermissions)
31
+ }
32
+ }
33
+ }
34
+ return result
35
+ }
36
+
16
37
  const mockServices: CoreServices = {
17
38
  logger: {
18
39
  info: () => {},
@@ -78,85 +99,6 @@ describe('addPermission', () => {
78
99
  })
79
100
  })
80
101
 
81
- describe('getPermissionsForTags', () => {
82
- test('should return empty array when no tags provided', () => {
83
- const permissions = getPermissionsForTags([])
84
- assert.deepEqual(permissions, [])
85
- })
86
-
87
- test('should return empty array when tags is undefined', () => {
88
- const permissions = getPermissionsForTags(undefined)
89
- assert.deepEqual(permissions, [])
90
- })
91
-
92
- test('should return permissions for single tag', () => {
93
- const mockPermission = async () => true
94
- addPermission('singleTestTag', [mockPermission])
95
-
96
- const permissions = getPermissionsForTags(['singleTestTag'])
97
- assert.equal(permissions.length, 1)
98
- assert.equal(permissions[0], mockPermission)
99
- })
100
-
101
- test('should return permissions for multiple tags', () => {
102
- const mockPermission1 = async () => true
103
- const mockPermission2 = async () => false
104
-
105
- addPermission('tag1', [mockPermission1])
106
- addPermission('tag2', [mockPermission2])
107
-
108
- const permissions = getPermissionsForTags(['tag1', 'tag2'])
109
- assert.equal(permissions.length, 2)
110
- assert.equal(permissions[0], mockPermission1)
111
- assert.equal(permissions[1], mockPermission2)
112
- })
113
-
114
- test('should handle array permissions correctly', () => {
115
- const mockPermission1 = async () => true
116
- const mockPermission2 = async () => false
117
-
118
- addPermission('arrayHandleTestTag', [mockPermission1, mockPermission2])
119
-
120
- const permissions = getPermissionsForTags(['arrayHandleTestTag'])
121
- assert.equal(permissions.length, 2)
122
- assert.equal(permissions[0], mockPermission1)
123
- assert.equal(permissions[1], mockPermission2)
124
- })
125
-
126
- test('should handle non-array permissions correctly', () => {
127
- const mockPermissionGroup: CorePermissionGroup = {
128
- permissions: [async () => true],
129
- }
130
-
131
- addPermission('objectHandleTestTag', mockPermissionGroup)
132
-
133
- const permissions = getPermissionsForTags(['objectHandleTestTag'])
134
- assert.equal(permissions.length, 1)
135
- assert.equal(permissions[0], mockPermissionGroup)
136
- })
137
-
138
- test('should deduplicate tags', () => {
139
- const mockPermission = async () => true
140
- addPermission('dedupeTestTag', [mockPermission])
141
-
142
- const permissions = getPermissionsForTags([
143
- 'dedupeTestTag',
144
- 'dedupeTestTag',
145
- ])
146
- assert.equal(permissions.length, 1)
147
- assert.equal(permissions[0], mockPermission)
148
- })
149
-
150
- test('should ignore non-existent tags', () => {
151
- const mockPermission = async () => true
152
- addPermission('existingTag', [mockPermission])
153
-
154
- const permissions = getPermissionsForTags(['existingTag', 'nonExistentTag'])
155
- assert.equal(permissions.length, 1)
156
- assert.equal(permissions[0], mockPermission)
157
- })
158
- })
159
-
160
102
  describe('runPermissions', () => {
161
103
  test('should pass when no permissions are defined', async () => {
162
104
  // Should not throw