@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.
- package/CHANGELOG.md +24 -0
- package/dist/errors/error-handler.js +3 -3
- package/dist/function/function-runner.d.ts +4 -3
- package/dist/function/function-runner.js +60 -14
- package/dist/function/functions.types.d.ts +84 -5
- package/dist/function/functions.types.js +41 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -2
- package/dist/middleware/auth-apikey.d.ts +1 -1
- package/dist/middleware/auth-bearer.d.ts +1 -1
- package/dist/middleware/auth-cookie.d.ts +1 -1
- package/dist/middleware/timeout.d.ts +1 -1
- package/dist/middleware-runner.d.ts +3 -14
- package/dist/middleware-runner.js +8 -8
- package/dist/permissions.d.ts +3 -86
- package/dist/permissions.js +11 -67
- package/dist/pikku-state.d.ts +32 -104
- package/dist/pikku-state.js +140 -66
- package/dist/schema.d.ts +8 -6
- package/dist/schema.js +25 -13
- package/dist/services/workflow-service.d.ts +38 -0
- package/dist/services/workflow-service.js +1 -0
- package/dist/types/core.types.d.ts +12 -4
- package/dist/types/core.types.js +1 -1
- package/dist/types/state.types.d.ts +135 -0
- package/dist/types/state.types.js +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +45 -0
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/channel-runner.js +5 -5
- package/dist/wirings/channel/log-channels.js +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
- package/dist/wirings/cli/cli-runner.js +10 -10
- package/dist/wirings/cli/cli.types.d.ts +1 -0
- package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
- package/dist/wirings/forge-node/forge-node.types.js +38 -0
- package/dist/wirings/forge-node/index.d.ts +1 -0
- package/dist/wirings/forge-node/index.js +1 -0
- package/dist/wirings/http/http-runner.d.ts +2 -2
- package/dist/wirings/http/http-runner.js +21 -12
- package/dist/wirings/http/http.types.d.ts +14 -1
- package/dist/wirings/http/log-http-routes.js +1 -1
- package/dist/wirings/http/routers/path-to-regex.js +2 -2
- package/dist/wirings/mcp/mcp-runner.js +21 -21
- package/dist/wirings/queue/queue-runner.js +6 -6
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +7 -8
- package/dist/wirings/rpc/rpc-runner.js +62 -14
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/scheduler/log-schedulers.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +5 -5
- package/dist/wirings/trigger/index.d.ts +2 -0
- package/dist/wirings/trigger/index.js +2 -0
- package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
- package/dist/wirings/trigger/trigger-runner.js +57 -0
- package/dist/wirings/trigger/trigger.types.d.ts +42 -0
- package/dist/wirings/trigger/trigger.types.js +1 -0
- package/dist/wirings/workflow/dsl/index.d.ts +5 -0
- package/dist/wirings/workflow/dsl/index.js +4 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
- package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
- package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
- package/dist/wirings/workflow/graph/graph-node.js +58 -0
- package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
- package/dist/wirings/workflow/graph/graph-runner.js +452 -0
- package/dist/wirings/workflow/graph/index.d.ts +3 -0
- package/dist/wirings/workflow/graph/index.js +3 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
- package/dist/wirings/workflow/index.d.ts +7 -12
- package/dist/wirings/workflow/index.js +6 -11
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
- package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
- package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
- package/dist/wirings/workflow/wire-workflow.js +53 -0
- package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
- package/dist/wirings/workflow/workflow-utils.js +66 -0
- package/dist/wirings/workflow/workflow.types.d.ts +133 -211
- package/package.json +10 -1
- package/src/errors/error-handler.ts +3 -3
- package/src/function/function-runner.test.ts +1 -1
- package/src/function/function-runner.ts +86 -16
- package/src/function/functions.types.ts +126 -4
- package/src/index.ts +10 -5
- package/src/middleware-runner.ts +16 -9
- package/src/permissions.test.ts +28 -86
- package/src/permissions.ts +21 -80
- package/src/pikku-state.ts +156 -201
- package/src/schema.ts +42 -13
- package/src/services/workflow-service.ts +74 -0
- package/src/types/core.types.ts +12 -3
- package/src/types/state.types.ts +195 -0
- package/src/utils.ts +55 -0
- package/src/wirings/channel/channel-handler.ts +1 -1
- package/src/wirings/channel/channel-runner.ts +5 -5
- package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
- package/src/wirings/channel/log-channels.ts +1 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
- package/src/wirings/cli/cli-runner.test.ts +19 -19
- package/src/wirings/cli/cli-runner.ts +10 -9
- package/src/wirings/cli/cli.types.ts +1 -0
- package/src/wirings/forge-node/forge-node.types.ts +135 -0
- package/src/wirings/forge-node/index.ts +1 -0
- package/src/wirings/http/http-runner.test.ts +2 -2
- package/src/wirings/http/http-runner.ts +30 -13
- package/src/wirings/http/http.types.ts +14 -0
- package/src/wirings/http/log-http-routes.ts +1 -1
- package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
- package/src/wirings/http/routers/path-to-regex.ts +2 -2
- package/src/wirings/mcp/mcp-runner.ts +21 -21
- package/src/wirings/queue/queue-runner.test.ts +19 -19
- package/src/wirings/queue/queue-runner.ts +6 -6
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +88 -22
- package/src/wirings/rpc/rpc-types.ts +8 -0
- package/src/wirings/scheduler/log-schedulers.ts +1 -1
- package/src/wirings/scheduler/scheduler-runner.test.ts +27 -27
- package/src/wirings/scheduler/scheduler-runner.ts +5 -5
- package/src/wirings/trigger/index.ts +2 -0
- package/src/wirings/trigger/trigger-runner.ts +96 -0
- package/src/wirings/trigger/trigger.types.ts +56 -0
- package/src/wirings/workflow/dsl/index.ts +30 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
- package/src/wirings/workflow/graph/graph-node.ts +227 -0
- package/src/wirings/workflow/graph/graph-runner.ts +694 -0
- package/src/wirings/workflow/graph/index.ts +3 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
- package/src/wirings/workflow/index.ts +53 -26
- package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
- package/src/wirings/workflow/wire-workflow.ts +94 -0
- package/src/wirings/workflow/workflow-utils.ts +120 -0
- package/src/wirings/workflow/workflow.types.ts +166 -277
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
- package/dist/wirings/workflow/workflow-runner.js +0 -65
- package/src/function/function-runner.ts.bak +0 -188
- 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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
129
|
-
|
|
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:
|
|
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
|
|
150
|
-
|
|
218
|
+
const wireServices = await resolvedCreateWireServices?.(
|
|
219
|
+
resolvedSingletonServices,
|
|
151
220
|
wireWithInitialSession
|
|
152
221
|
)
|
|
153
222
|
try {
|
|
154
|
-
const services = { ...
|
|
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(
|
|
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
|
-
|
|
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
|
|
98
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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'
|
package/src/middleware-runner.ts
CHANGED
|
@@ -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
|
-
|
|
104
|
-
|
|
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')[
|
|
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')[
|
|
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')[
|
|
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[]))
|
package/src/permissions.test.ts
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
import { describe, test, beforeEach } from 'node:test'
|
|
2
2
|
import * as assert from 'node:assert'
|
|
3
|
-
import {
|
|
4
|
-
|
|
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 {
|
|
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
|