@pikku/core 0.7.2 → 0.7.3
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 +6 -0
- package/dist/channel/channel-handler.js +0 -1
- package/dist/channel/channel-runner.js +16 -4
- package/dist/channel/local/local-channel-runner.js +3 -2
- package/dist/function/function-runner.d.ts +4 -5
- package/dist/function/function-runner.js +53 -13
- package/dist/function/functions.types.d.ts +5 -0
- package/dist/http/http-runner.js +8 -4
- package/dist/pikku-state.d.ts +7 -3
- package/dist/pikku-state.js +5 -2
- package/dist/rpc/index.d.ts +1 -0
- package/dist/rpc/index.js +1 -0
- package/dist/rpc/rpc-runner.d.ts +31 -0
- package/dist/rpc/rpc-runner.js +101 -0
- package/dist/rpc/rpc-types.d.ts +9 -0
- package/dist/rpc/rpc-types.js +1 -0
- package/dist/scheduler/scheduler-runner.js +8 -3
- package/dist/types/core.types.d.ts +2 -0
- package/lcov.info +684 -409
- package/package.json +2 -1
- package/src/channel/channel-handler.ts +0 -1
- package/src/channel/channel-runner.ts +18 -8
- package/src/channel/local/local-channel-runner.ts +3 -2
- package/src/function/function-runner.ts +81 -24
- package/src/function/functions.types.ts +11 -0
- package/src/http/http-runner.test.ts +3 -3
- package/src/http/http-runner.ts +9 -4
- package/src/pikku-state.ts +14 -13
- package/src/rpc/index.ts +1 -0
- package/src/rpc/rpc-runner.ts +149 -0
- package/src/rpc/rpc-types.ts +10 -0
- package/src/scheduler/scheduler-runner.ts +8 -3
- package/src/types/core.types.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./channel/local": "./dist/channel/local/index.js",
|
|
25
25
|
"./channel/serverless": "./dist/channel/serverless/index.js",
|
|
26
26
|
"./http": "./dist/http/index.js",
|
|
27
|
+
"./rpc": "./dist/rpc/index.js",
|
|
27
28
|
"./errors": "./dist/errors/index.js",
|
|
28
29
|
"./scheduler": "./dist/scheduler/index.js",
|
|
29
30
|
"./services": "./dist/services/index.js",
|
|
@@ -96,7 +96,6 @@ export const processMessageHandlers = (
|
|
|
96
96
|
typeof onMessage === 'function' ? {} : onMessage.permissions
|
|
97
97
|
|
|
98
98
|
return await runPikkuFunc(pikkuFuncName, {
|
|
99
|
-
singletonServices: services,
|
|
100
99
|
getAllServices: () => ({
|
|
101
100
|
...services,
|
|
102
101
|
channel: channelHandler.getChannel(),
|
|
@@ -35,15 +35,16 @@ export const addChannel = <
|
|
|
35
35
|
|
|
36
36
|
// Register onConnect function if provided
|
|
37
37
|
if (channel.onConnect && channelMeta.connectPikkuFuncName) {
|
|
38
|
-
addFunction(channelMeta.connectPikkuFuncName,
|
|
38
|
+
addFunction(channelMeta.connectPikkuFuncName, {
|
|
39
|
+
func: channel.onConnect as any,
|
|
40
|
+
})
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Register onDisconnect function if provided
|
|
42
44
|
if (channel.onDisconnect && channelMeta.disconnectPikkuFuncName) {
|
|
43
|
-
addFunction(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
45
|
+
addFunction(channelMeta.disconnectPikkuFuncName, {
|
|
46
|
+
func: channel.onDisconnect,
|
|
47
|
+
})
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// Register onMessage function if provided
|
|
@@ -52,7 +53,9 @@ export const addChannel = <
|
|
|
52
53
|
typeof channel.onMessage === 'function'
|
|
53
54
|
? channel.onMessage
|
|
54
55
|
: channel.onMessage.func
|
|
55
|
-
addFunction(channelMeta.message.pikkuFuncName,
|
|
56
|
+
addFunction(channelMeta.message.pikkuFuncName, {
|
|
57
|
+
func: messageFunc,
|
|
58
|
+
})
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
// Register functions in onMessageRoute
|
|
@@ -68,10 +71,17 @@ export const addChannel = <
|
|
|
68
71
|
if (!routeMeta) return
|
|
69
72
|
|
|
70
73
|
// Extract the function from the handler
|
|
71
|
-
const routeFunc =
|
|
74
|
+
const routeFunc =
|
|
75
|
+
typeof handler === 'function'
|
|
76
|
+
? { func: handler }
|
|
77
|
+
: {
|
|
78
|
+
func: handler.func,
|
|
79
|
+
auth: handler.auth,
|
|
80
|
+
permissions: handler.permissions,
|
|
81
|
+
}
|
|
72
82
|
|
|
73
83
|
// Register the function using the pikku name from metadata
|
|
74
|
-
addFunction(routeMeta.pikkuFuncName, routeFunc
|
|
84
|
+
addFunction(routeMeta.pikkuFuncName, routeFunc)
|
|
75
85
|
})
|
|
76
86
|
})
|
|
77
87
|
}
|
|
@@ -14,6 +14,7 @@ import { runMiddleware } from '../../middleware-runner.js'
|
|
|
14
14
|
import { PikkuUserSessionService } from '../../services/user-session-service.js'
|
|
15
15
|
import { PikkuHTTP } from '../../http/http.types.js'
|
|
16
16
|
import { runPikkuFuncDirectly } from '../../function/function-runner.js'
|
|
17
|
+
import { rpcService } from '../../rpc/rpc-runner.js'
|
|
17
18
|
|
|
18
19
|
export const runLocalChannel = async ({
|
|
19
20
|
singletonServices,
|
|
@@ -71,11 +72,11 @@ export const runLocalChannel = async ({
|
|
|
71
72
|
)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
const allServices = {
|
|
75
|
+
const allServices = rpcService.injectRPCService({
|
|
75
76
|
...singletonServices,
|
|
76
77
|
...sessionServices,
|
|
77
78
|
userSession: userSession,
|
|
78
|
-
}
|
|
79
|
+
})
|
|
79
80
|
|
|
80
81
|
channelHandler.registerOnOpen(() => {
|
|
81
82
|
if (channelConfig.onConnect && meta.connectPikkuFuncName) {
|
|
@@ -2,22 +2,68 @@ import { ForbiddenError } from '../errors/errors.js'
|
|
|
2
2
|
import { verifyPermissions } from '../permissions.js'
|
|
3
3
|
import { pikkuState } from '../pikku-state.js'
|
|
4
4
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
5
|
+
import { Logger } from '../services/logger.js'
|
|
6
|
+
import { CoreServices, CoreUserSession } from '../types/core.types.js'
|
|
5
7
|
import {
|
|
6
|
-
CoreServices,
|
|
7
|
-
CoreSingletonServices,
|
|
8
|
-
CoreUserSession,
|
|
9
|
-
} from '../types/core.types.js'
|
|
10
|
-
import {
|
|
11
|
-
CoreAPIFunction,
|
|
12
|
-
CoreAPIFunctionSessionless,
|
|
13
8
|
CorePermissionGroup,
|
|
9
|
+
CorePikkuFunctionConfig,
|
|
14
10
|
} from './functions.types.js'
|
|
15
11
|
|
|
12
|
+
// Permission merging function
|
|
13
|
+
function mergePermissions(
|
|
14
|
+
logger: Logger,
|
|
15
|
+
funcName: string,
|
|
16
|
+
funcPermissions?: CorePermissionGroup,
|
|
17
|
+
transportPermissions?: CorePermissionGroup
|
|
18
|
+
): CorePermissionGroup | undefined {
|
|
19
|
+
if (!funcPermissions && !transportPermissions) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!funcPermissions) {
|
|
24
|
+
return transportPermissions
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!transportPermissions) {
|
|
28
|
+
return funcPermissions
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Start with a copy of function permissions
|
|
32
|
+
const mergedPermissions = { ...funcPermissions }
|
|
33
|
+
|
|
34
|
+
// Merge in transport permissions
|
|
35
|
+
for (const [key, transportValue] of Object.entries(transportPermissions)) {
|
|
36
|
+
if (key in mergedPermissions) {
|
|
37
|
+
// For permission arrays, concatenate and deduplicate values
|
|
38
|
+
if (
|
|
39
|
+
Array.isArray(mergedPermissions[key]) &&
|
|
40
|
+
Array.isArray(transportValue)
|
|
41
|
+
) {
|
|
42
|
+
mergedPermissions[key] = [
|
|
43
|
+
...new Set([...mergedPermissions[key], ...transportValue]),
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
// For other types, warn about conflict and use the more restrictive (transport-level) value
|
|
47
|
+
else {
|
|
48
|
+
logger.warn(
|
|
49
|
+
`Permission conflict on key "${key}" for function "${funcName}". Using transport-level permission.`
|
|
50
|
+
)
|
|
51
|
+
mergedPermissions[key] = transportValue
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// If key doesn't exist in function permissions, add it
|
|
55
|
+
mergedPermissions[key] = transportValue
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return mergedPermissions
|
|
60
|
+
}
|
|
61
|
+
|
|
16
62
|
export const addFunction = (
|
|
17
63
|
funcName: string,
|
|
18
|
-
|
|
64
|
+
funcConfig: CorePikkuFunctionConfig<any, any>
|
|
19
65
|
) => {
|
|
20
|
-
pikkuState('
|
|
66
|
+
pikkuState('function', 'functions').set(funcName, funcConfig)
|
|
21
67
|
}
|
|
22
68
|
|
|
23
69
|
export const runPikkuFuncDirectly = async <In, Out>(
|
|
@@ -26,24 +72,22 @@ export const runPikkuFuncDirectly = async <In, Out>(
|
|
|
26
72
|
data: In,
|
|
27
73
|
session?: CoreUserSession
|
|
28
74
|
) => {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
75
|
+
const funcConfig = pikkuState('function', 'functions').get(funcName)
|
|
76
|
+
if (!funcConfig) {
|
|
31
77
|
throw new Error(`Function not found: ${funcName}`)
|
|
32
78
|
}
|
|
33
|
-
return (await func(allServices, data, session!)) as Out
|
|
79
|
+
return (await funcConfig.func(allServices, data, session!)) as Out
|
|
34
80
|
}
|
|
35
81
|
|
|
36
82
|
export const runPikkuFunc = async <In = any, Out = any>(
|
|
37
83
|
funcName: string,
|
|
38
84
|
{
|
|
39
|
-
singletonServices,
|
|
40
85
|
getAllServices,
|
|
41
86
|
data,
|
|
42
87
|
session,
|
|
43
|
-
permissions,
|
|
88
|
+
permissions: transportPermissions,
|
|
44
89
|
coerceDataFromSchema,
|
|
45
90
|
}: {
|
|
46
|
-
singletonServices: CoreSingletonServices
|
|
47
91
|
getAllServices: () => Promise<CoreServices> | CoreServices
|
|
48
92
|
data: In
|
|
49
93
|
session?: CoreUserSession
|
|
@@ -51,20 +95,29 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
51
95
|
coerceDataFromSchema?: boolean
|
|
52
96
|
}
|
|
53
97
|
): Promise<Out> => {
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
98
|
+
const funcConfig = pikkuState('function', 'functions').get(funcName)
|
|
99
|
+
if (!funcConfig) {
|
|
56
100
|
throw new Error(`Function not found: ${funcName}`)
|
|
57
101
|
}
|
|
58
|
-
const funcMeta = pikkuState('
|
|
102
|
+
const funcMeta = pikkuState('function', 'meta')[funcName]
|
|
59
103
|
if (!funcMeta) {
|
|
60
104
|
throw new Error(`Function meta not found: ${funcName}`)
|
|
61
105
|
}
|
|
106
|
+
|
|
107
|
+
if (funcConfig.auth && !session) {
|
|
108
|
+
throw new ForbiddenError(
|
|
109
|
+
`Function ${funcName} requires authentication even though transport does not`
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const allServices = await getAllServices()
|
|
114
|
+
|
|
62
115
|
const schemaName = funcMeta.schemaName
|
|
63
116
|
if (schemaName) {
|
|
64
117
|
// Validate request data against the defined schema, if any
|
|
65
118
|
await validateSchema(
|
|
66
|
-
|
|
67
|
-
|
|
119
|
+
allServices.logger,
|
|
120
|
+
allServices.schema,
|
|
68
121
|
schemaName,
|
|
69
122
|
data
|
|
70
123
|
)
|
|
@@ -74,11 +127,15 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
74
127
|
}
|
|
75
128
|
}
|
|
76
129
|
|
|
77
|
-
const allServices = await getAllServices()
|
|
78
|
-
|
|
79
130
|
// Execute permission checks
|
|
131
|
+
const mergedPermissions = mergePermissions(
|
|
132
|
+
allServices.logger,
|
|
133
|
+
funcName,
|
|
134
|
+
funcConfig.permissions,
|
|
135
|
+
transportPermissions
|
|
136
|
+
)
|
|
80
137
|
const permissioned = await verifyPermissions(
|
|
81
|
-
|
|
138
|
+
mergedPermissions,
|
|
82
139
|
allServices,
|
|
83
140
|
data,
|
|
84
141
|
session
|
|
@@ -88,5 +145,5 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
88
145
|
throw new ForbiddenError('Permission denied')
|
|
89
146
|
}
|
|
90
147
|
|
|
91
|
-
return (await func(allServices, data, session!)) as Out
|
|
148
|
+
return (await funcConfig.func(allServices, data, session!)) as Out
|
|
92
149
|
}
|
|
@@ -75,3 +75,14 @@ export type CoreAPIPermission<
|
|
|
75
75
|
export type CorePermissionGroup<APIPermission = CoreAPIPermission<any>> =
|
|
76
76
|
| Record<string, APIPermission | APIPermission[]>
|
|
77
77
|
| undefined
|
|
78
|
+
|
|
79
|
+
export type CorePikkuFunctionConfig<
|
|
80
|
+
APIFunction extends
|
|
81
|
+
| CoreAPIFunction<any, any>
|
|
82
|
+
| CoreAPIFunctionSessionless<any, any>,
|
|
83
|
+
APIPermission extends CoreAPIPermission<any> = CoreAPIPermission<any>,
|
|
84
|
+
> = {
|
|
85
|
+
func: APIFunction
|
|
86
|
+
auth?: boolean
|
|
87
|
+
permissions?: CorePermissionGroup<APIPermission>
|
|
88
|
+
}
|
|
@@ -15,8 +15,8 @@ const sessionMiddleware: PikkuMiddleware = async (services, _, next) => {
|
|
|
15
15
|
await next()
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const setHTTPFunctionMap = (
|
|
19
|
-
pikkuState('
|
|
18
|
+
const setHTTPFunctionMap = (func: any) => {
|
|
19
|
+
pikkuState('function', 'meta', {
|
|
20
20
|
pikku_func_name: {
|
|
21
21
|
pikkuFuncName: 'pikku_func_name',
|
|
22
22
|
services: ['userSession'],
|
|
@@ -31,7 +31,7 @@ const setHTTPFunctionMap = (fun: any) => {
|
|
|
31
31
|
output: null,
|
|
32
32
|
},
|
|
33
33
|
])
|
|
34
|
-
addFunction('pikku_func_name',
|
|
34
|
+
addFunction('pikku_func_name', { func })
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
describe('fetch', () => {
|
package/src/http/http-runner.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
|
31
31
|
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
|
|
32
32
|
import { PikkuChannel } from '../channel/channel.types.js'
|
|
33
33
|
import { addFunction, runPikkuFunc } from '../function/function-runner.js'
|
|
34
|
+
import { rpcService } from '../rpc/rpc-runner.js'
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Registers middleware either globally or for a specific route.
|
|
@@ -101,7 +102,11 @@ export const addHTTPRoute = <
|
|
|
101
102
|
if (!routeMeta) {
|
|
102
103
|
throw new Error('Route metadata not found')
|
|
103
104
|
}
|
|
104
|
-
addFunction(routeMeta.pikkuFuncName,
|
|
105
|
+
addFunction(routeMeta.pikkuFuncName, {
|
|
106
|
+
func: httpRoute.func,
|
|
107
|
+
auth: httpRoute.auth,
|
|
108
|
+
permissions: httpRoute.permissions,
|
|
109
|
+
})
|
|
105
110
|
const routes = pikkuState('http', 'routes')
|
|
106
111
|
if (!routes.has(httpRoute.method)) {
|
|
107
112
|
routes.set(httpRoute.method, new Map())
|
|
@@ -309,17 +314,17 @@ const executeRouteWithMiddleware = async (
|
|
|
309
314
|
{ http },
|
|
310
315
|
session
|
|
311
316
|
)
|
|
312
|
-
|
|
317
|
+
|
|
318
|
+
return rpcService.injectRPCService({
|
|
313
319
|
...singletonServices,
|
|
314
320
|
...sessionServices,
|
|
315
321
|
http,
|
|
316
322
|
userSession,
|
|
317
323
|
channel,
|
|
318
|
-
}
|
|
324
|
+
})
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
const result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
322
|
-
singletonServices,
|
|
323
328
|
getAllServices,
|
|
324
329
|
session,
|
|
325
330
|
data,
|
package/src/pikku-state.ts
CHANGED
|
@@ -10,18 +10,16 @@ import {
|
|
|
10
10
|
ScheduledTasksMeta,
|
|
11
11
|
} from './scheduler/scheduler.types.js'
|
|
12
12
|
import { ErrorDetails, PikkuError } from './errors/error-handler.js'
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
CoreAPIFunctionSessionless,
|
|
16
|
-
} from './function/functions.types.js'
|
|
13
|
+
import { CorePikkuFunctionConfig } from './function/functions.types.js'
|
|
14
|
+
import { RPCMeta } from './rpc/rpc-types.js'
|
|
17
15
|
|
|
18
16
|
interface PikkuState {
|
|
19
|
-
|
|
17
|
+
function: {
|
|
20
18
|
meta: FunctionsMeta
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
>
|
|
19
|
+
functions: Map<string, CorePikkuFunctionConfig<any, any>>
|
|
20
|
+
}
|
|
21
|
+
rpc: {
|
|
22
|
+
meta: Record<string, RPCMeta>
|
|
25
23
|
}
|
|
26
24
|
http: {
|
|
27
25
|
middleware: Array<{ route: string; middleware: PikkuMiddleware[] }>
|
|
@@ -44,9 +42,12 @@ interface PikkuState {
|
|
|
44
42
|
|
|
45
43
|
export const resetPikkuState = () => {
|
|
46
44
|
globalThis.pikkuState = {
|
|
47
|
-
|
|
45
|
+
function: {
|
|
46
|
+
meta: {},
|
|
47
|
+
functions: new Map(),
|
|
48
|
+
},
|
|
49
|
+
rpc: {
|
|
48
50
|
meta: {},
|
|
49
|
-
nameToFunction: new Map(),
|
|
50
51
|
},
|
|
51
52
|
http: {
|
|
52
53
|
middleware: [],
|
|
@@ -59,13 +60,13 @@ export const resetPikkuState = () => {
|
|
|
59
60
|
},
|
|
60
61
|
scheduler: {
|
|
61
62
|
tasks: new Map(),
|
|
62
|
-
meta: [],
|
|
63
|
+
meta: [] as unknown as ScheduledTasksMeta,
|
|
63
64
|
},
|
|
64
65
|
misc: {
|
|
65
66
|
errors: globalThis.pikkuState?.misc?.errors || new Map(),
|
|
66
67
|
schemas: globalThis.pikkuState?.misc?.schema || new Map(),
|
|
67
68
|
},
|
|
68
|
-
}
|
|
69
|
+
} as PikkuState
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
if (!globalThis.pikkuState) {
|
package/src/rpc/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initialize } from './rpc-runner.js'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CoreSingletonServices,
|
|
3
|
+
CoreServices,
|
|
4
|
+
CreateSessionServices,
|
|
5
|
+
CoreUserSession,
|
|
6
|
+
} from '../types/core.types.js'
|
|
7
|
+
import { runPikkuFunc } from '../function/function-runner.js'
|
|
8
|
+
import { pikkuState } from '../pikku-state.js'
|
|
9
|
+
import { RPCMeta } from './rpc-types.js'
|
|
10
|
+
import { PikkuUserSessionService } from '../services/user-session-service.js'
|
|
11
|
+
|
|
12
|
+
// Type for the RPC service configuration
|
|
13
|
+
type RPCServiceConfig = {
|
|
14
|
+
singletonServices: CoreSingletonServices
|
|
15
|
+
createSessionServices: CreateSessionServices
|
|
16
|
+
coerceDataFromSchema: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getRPCMeta = (rpcName: string): RPCMeta => {
|
|
20
|
+
const rpc = pikkuState('rpc', 'meta')
|
|
21
|
+
const rpcMeta = rpc[rpcName]
|
|
22
|
+
if (!rpcMeta) {
|
|
23
|
+
throw new Error(`RPC function not found: ${rpcName}`)
|
|
24
|
+
}
|
|
25
|
+
return rpcMeta
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Context-aware RPC client for use within services
|
|
29
|
+
class ServiceRPC {
|
|
30
|
+
constructor(
|
|
31
|
+
private services: CoreServices,
|
|
32
|
+
private options: {
|
|
33
|
+
coerceDataFromSchema?: boolean
|
|
34
|
+
}
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
public async rpc<In = any, Out = any>(
|
|
38
|
+
funcName: string,
|
|
39
|
+
data: In
|
|
40
|
+
): Promise<Out> {
|
|
41
|
+
const session = await this.services.userSession?.get()
|
|
42
|
+
const rpcDepth = this.services.rpc?.depth || 0
|
|
43
|
+
const rpcMeta = getRPCMeta(funcName)
|
|
44
|
+
return runPikkuFunc<In, Out>(rpcMeta.pikkuFuncName, {
|
|
45
|
+
getAllServices: () => {
|
|
46
|
+
this.services.rpc = this.services.rpc
|
|
47
|
+
? {
|
|
48
|
+
...this.services.rpc,
|
|
49
|
+
depth: rpcDepth + 1,
|
|
50
|
+
global: false,
|
|
51
|
+
}
|
|
52
|
+
: undefined
|
|
53
|
+
return this.services
|
|
54
|
+
},
|
|
55
|
+
data,
|
|
56
|
+
session,
|
|
57
|
+
coerceDataFromSchema: this.options.coerceDataFromSchema,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// RPC Service class for the global interface
|
|
63
|
+
export class PikkuRPCService {
|
|
64
|
+
private config?: RPCServiceConfig
|
|
65
|
+
|
|
66
|
+
// Initialize the RPC service with configuration
|
|
67
|
+
initialize(config: RPCServiceConfig) {
|
|
68
|
+
this.config = config
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Convenience function for initializing
|
|
72
|
+
injectRPCService(coreServices: CoreServices, depth: number = 0) {
|
|
73
|
+
const serviceCopy = {
|
|
74
|
+
...coreServices,
|
|
75
|
+
}
|
|
76
|
+
const serviceRPC = new ServiceRPC(serviceCopy, {
|
|
77
|
+
coerceDataFromSchema: this.config?.coerceDataFromSchema,
|
|
78
|
+
})
|
|
79
|
+
serviceCopy.rpc = {
|
|
80
|
+
depth,
|
|
81
|
+
global: false,
|
|
82
|
+
invoke: serviceRPC.rpc.bind(serviceRPC),
|
|
83
|
+
}
|
|
84
|
+
return serviceCopy
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Global RPC method to call functions by name
|
|
88
|
+
async rpc<In = any, Out = any>(
|
|
89
|
+
funcName: string,
|
|
90
|
+
data: In,
|
|
91
|
+
session?: CoreUserSession
|
|
92
|
+
): Promise<Out> {
|
|
93
|
+
if (!this.config) {
|
|
94
|
+
throw new Error('RPC service not initialized')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const rpcMeta = getRPCMeta(funcName)
|
|
98
|
+
const { singletonServices, createSessionServices, coerceDataFromSchema } =
|
|
99
|
+
this.config
|
|
100
|
+
|
|
101
|
+
// Define the getAllServices function for runPikkuFunc
|
|
102
|
+
const getAllServices = async () => {
|
|
103
|
+
const userSession = new PikkuUserSessionService()
|
|
104
|
+
if (session) {
|
|
105
|
+
userSession.set(session)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sessionServices = await createSessionServices(
|
|
109
|
+
singletonServices,
|
|
110
|
+
{},
|
|
111
|
+
session
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return this.injectRPCService(
|
|
115
|
+
{
|
|
116
|
+
...singletonServices,
|
|
117
|
+
...sessionServices,
|
|
118
|
+
userSession,
|
|
119
|
+
},
|
|
120
|
+
0
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Call the function using runPikkuFunc - it will handle permission merging
|
|
125
|
+
return runPikkuFunc<In, Out>(rpcMeta.pikkuFuncName, {
|
|
126
|
+
getAllServices,
|
|
127
|
+
data,
|
|
128
|
+
session,
|
|
129
|
+
coerceDataFromSchema: coerceDataFromSchema ?? true,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Create a singleton instance
|
|
135
|
+
export const rpcService = new PikkuRPCService()
|
|
136
|
+
|
|
137
|
+
// Convenience function for initializing
|
|
138
|
+
export const initialize = (config: RPCServiceConfig) => {
|
|
139
|
+
rpcService.initialize(config)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Convenience function for making standalone RPC calls
|
|
143
|
+
export const pikkuRPC = <In = any, Out = any, session = CoreUserSession>(
|
|
144
|
+
funcName: string,
|
|
145
|
+
data: In,
|
|
146
|
+
session?: CoreUserSession
|
|
147
|
+
): Promise<Out> => {
|
|
148
|
+
return rpcService.rpc(funcName, data, session)
|
|
149
|
+
}
|
|
@@ -10,6 +10,7 @@ import { getErrorResponse } from '../errors/error-handler.js'
|
|
|
10
10
|
import { closeSessionServices } from '../utils.js'
|
|
11
11
|
import { pikkuState } from '../pikku-state.js'
|
|
12
12
|
import { addFunction, runPikkuFunc } from '../function/function-runner.js'
|
|
13
|
+
import { rpcService } from '../rpc/rpc-runner.js'
|
|
13
14
|
|
|
14
15
|
export type RunScheduledTasksParams = {
|
|
15
16
|
name: string
|
|
@@ -32,7 +33,9 @@ export const addScheduledTask = <
|
|
|
32
33
|
if (!taskMeta) {
|
|
33
34
|
throw new Error('Task metadata not found')
|
|
34
35
|
}
|
|
35
|
-
addFunction(taskMeta.pikkuFuncName,
|
|
36
|
+
addFunction(taskMeta.pikkuFuncName, {
|
|
37
|
+
func: scheduledTask.func,
|
|
38
|
+
})
|
|
36
39
|
|
|
37
40
|
const tasks = pikkuState('scheduler', 'tasks')
|
|
38
41
|
if (tasks.has(scheduledTask.name)) {
|
|
@@ -80,13 +83,15 @@ export async function runScheduledTask<
|
|
|
80
83
|
{},
|
|
81
84
|
session
|
|
82
85
|
)
|
|
83
|
-
return {
|
|
86
|
+
return rpcService.injectRPCService({
|
|
87
|
+
...singletonServices,
|
|
88
|
+
...sessionServices,
|
|
89
|
+
})
|
|
84
90
|
}
|
|
85
91
|
return singletonServices
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
return await runPikkuFunc(meta.pikkuFuncName, {
|
|
89
|
-
singletonServices,
|
|
90
95
|
getAllServices,
|
|
91
96
|
session,
|
|
92
97
|
data: undefined,
|
package/src/types/core.types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { UserSessionService } from '../services/user-session-service.js'
|
|
|
7
7
|
import { JWTService } from '../services/jwt-service.js'
|
|
8
8
|
import { SecretService } from '../services/secret-service.js'
|
|
9
9
|
import { PikkuChannel } from '../channel/channel.types.js'
|
|
10
|
+
import { PikkuRPC } from '../rpc/rpc-types.js'
|
|
10
11
|
|
|
11
12
|
export interface FunctionServicesMeta {
|
|
12
13
|
optimized: boolean
|
|
@@ -126,6 +127,7 @@ export type CoreServices<
|
|
|
126
127
|
CoreServices extends Record<string, unknown> = {},
|
|
127
128
|
> = SingletonServices &
|
|
128
129
|
PikkuInteraction & {
|
|
130
|
+
rpc?: PikkuRPC
|
|
129
131
|
userSession?: UserSessionService<UserSession>
|
|
130
132
|
channel?: PikkuChannel<unknown, unknown>
|
|
131
133
|
} & CoreServices
|