@pikku/core 0.12.19 → 0.12.20
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 +66 -0
- package/dist/function/function-runner.js +53 -3
- package/dist/index.d.ts +1 -1
- package/dist/services/content-service.d.ts +66 -37
- package/dist/services/index.d.ts +1 -1
- package/dist/services/local-content.d.ts +10 -12
- package/dist/services/local-content.js +46 -37
- package/dist/wirings/rpc/rpc-runner.d.ts +3 -2
- package/dist/wirings/rpc/rpc-runner.js +32 -8
- package/package.json +1 -1
- package/src/function/function-runner.ts +68 -3
- package/src/index.ts +10 -1
- package/src/services/content-service.ts +79 -51
- package/src/services/index.ts +10 -1
- package/src/services/local-content.ts +68 -52
- package/src/wirings/rpc/rpc-runner.ts +45 -18
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -72,6 +72,54 @@ async function resolveSession(
|
|
|
72
72
|
* @param parentServices - The parent/caller's singleton services (used as base)
|
|
73
73
|
* @returns The package's singleton services
|
|
74
74
|
*/
|
|
75
|
+
/**
|
|
76
|
+
* Find the consumer-defined namespace (from wireAddon) for a given addon package.
|
|
77
|
+
* Returns null if the package isn't registered as an addon.
|
|
78
|
+
*/
|
|
79
|
+
const findAddonNamespaceForPackage = (packageName: string): string | null => {
|
|
80
|
+
const addons = pikkuState(null, 'addons', 'packages')
|
|
81
|
+
if (!addons) return null
|
|
82
|
+
for (const [namespace, cfg] of addons.entries()) {
|
|
83
|
+
if (cfg?.package === packageName) return namespace
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wrap a workflow service so that bare workflow names passed from inside an
|
|
90
|
+
* addon function are auto-prefixed with the addon's consumer-facing namespace.
|
|
91
|
+
* Without this, `runToCompletion('myWorkflow')` from inside an addon misses
|
|
92
|
+
* the workflow registered under the addon's package scope and throws
|
|
93
|
+
* WorkflowNotFoundError — forcing addons to hardcode their consumer-defined
|
|
94
|
+
* namespace, which couples the addon to its caller.
|
|
95
|
+
*
|
|
96
|
+
* Explicit `'ns:name'` and bare names that already exist in root meta are
|
|
97
|
+
* unaffected; only bare names that would otherwise miss resolution get
|
|
98
|
+
* prefixed.
|
|
99
|
+
*/
|
|
100
|
+
const wrapWorkflowServiceForPackage = <T extends object>(
|
|
101
|
+
service: T,
|
|
102
|
+
packageName: string
|
|
103
|
+
): T => {
|
|
104
|
+
return new Proxy(service, {
|
|
105
|
+
get(target, prop, receiver) {
|
|
106
|
+
if (prop === 'startWorkflow' || prop === 'runToCompletion') {
|
|
107
|
+
const original = Reflect.get(target, prop, receiver) as Function
|
|
108
|
+
return function (this: any, name: string, ...rest: any[]) {
|
|
109
|
+
if (typeof name === 'string' && !name.includes(':')) {
|
|
110
|
+
const namespace = findAddonNamespaceForPackage(packageName)
|
|
111
|
+
if (namespace) {
|
|
112
|
+
name = `${namespace}:${name}`
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return original.call(this, name, ...rest)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Reflect.get(target, prop, receiver)
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
75
123
|
const getOrCreatePackageSingletonServices = async (
|
|
76
124
|
packageName: string,
|
|
77
125
|
parentServices: CoreSingletonServices
|
|
@@ -101,6 +149,18 @@ const getOrCreatePackageSingletonServices = async (
|
|
|
101
149
|
parentServices
|
|
102
150
|
)
|
|
103
151
|
|
|
152
|
+
// Wrap workflowService so that bare names used inside the addon's functions
|
|
153
|
+
// resolve to workflows registered under the addon's package scope.
|
|
154
|
+
if (
|
|
155
|
+
packageServices.workflowService &&
|
|
156
|
+
typeof packageServices.workflowService === 'object'
|
|
157
|
+
) {
|
|
158
|
+
packageServices.workflowService = wrapWorkflowServiceForPackage(
|
|
159
|
+
packageServices.workflowService as object,
|
|
160
|
+
packageName
|
|
161
|
+
) as typeof packageServices.workflowService
|
|
162
|
+
}
|
|
163
|
+
|
|
104
164
|
// Cache the services
|
|
105
165
|
pikkuState(packageName, 'package', 'singletonServices', packageServices)
|
|
106
166
|
|
|
@@ -378,11 +438,16 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
378
438
|
wireServices && Object.keys(wireServices).length > 0
|
|
379
439
|
? { ...resolvedSingletonServices, ...wireServices }
|
|
380
440
|
: resolvedSingletonServices
|
|
441
|
+
const callerPackageName = packageName
|
|
381
442
|
Object.defineProperty(resolvedWire, 'rpc', {
|
|
382
443
|
get() {
|
|
383
|
-
const rpc = rpcService.getContextRPCService(
|
|
384
|
-
|
|
385
|
-
|
|
444
|
+
const rpc = rpcService.getContextRPCService(
|
|
445
|
+
services,
|
|
446
|
+
resolvedWire,
|
|
447
|
+
{ sessionService },
|
|
448
|
+
0,
|
|
449
|
+
callerPackageName
|
|
450
|
+
)
|
|
386
451
|
Object.defineProperty(resolvedWire, 'rpc', {
|
|
387
452
|
value: rpc,
|
|
388
453
|
writable: true,
|
package/src/index.ts
CHANGED
|
@@ -98,7 +98,16 @@ export type { QueueService } from './wirings/queue/queue.types.js'
|
|
|
98
98
|
export type { JWTService } from './services/jwt-service.js'
|
|
99
99
|
export type { SecretService } from './services/secret-service.js'
|
|
100
100
|
export type { VariablesService } from './services/variables-service.js'
|
|
101
|
-
export type {
|
|
101
|
+
export type {
|
|
102
|
+
ContentService,
|
|
103
|
+
SignContentKeyArgs,
|
|
104
|
+
SignURLArgs,
|
|
105
|
+
GetUploadURLArgs,
|
|
106
|
+
UploadURLResult,
|
|
107
|
+
BucketKeyArgs,
|
|
108
|
+
WriteFileArgs,
|
|
109
|
+
CopyFileArgs,
|
|
110
|
+
} from './services/content-service.js'
|
|
102
111
|
export type { DeploymentService } from './services/deployment-service.js'
|
|
103
112
|
export type { WorkflowService } from './services/workflow-service.js'
|
|
104
113
|
export type { GatewayService } from './services/gateway-service.js'
|
|
@@ -1,81 +1,109 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Arguments for signing a content key into a time-limited URL.
|
|
3
|
+
*/
|
|
4
|
+
export interface SignContentKeyArgs<TBucket extends string = string> {
|
|
5
|
+
bucket: TBucket
|
|
6
|
+
contentKey: string
|
|
7
|
+
dateLessThan: Date
|
|
8
|
+
dateGreaterThan?: Date
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Arguments for signing an arbitrary URL.
|
|
13
|
+
*/
|
|
14
|
+
export interface SignURLArgs {
|
|
15
|
+
url: string
|
|
16
|
+
dateLessThan: Date
|
|
17
|
+
dateGreaterThan?: Date
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Arguments for minting a presigned upload URL.
|
|
22
|
+
*/
|
|
23
|
+
export interface GetUploadURLArgs<TBucket extends string = string> {
|
|
24
|
+
bucket: TBucket
|
|
25
|
+
fileKey: string
|
|
26
|
+
contentType: string
|
|
27
|
+
size?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result of minting a presigned upload URL.
|
|
32
|
+
*/
|
|
33
|
+
export interface UploadURLResult {
|
|
34
|
+
uploadUrl: string
|
|
35
|
+
assetKey: string
|
|
36
|
+
uploadHeaders?: Record<string, string>
|
|
37
|
+
uploadMethod?: 'PUT' | 'POST'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Arguments for an operation that targets a single object by key.
|
|
42
|
+
*/
|
|
43
|
+
export interface BucketKeyArgs<TBucket extends string = string> {
|
|
44
|
+
bucket: TBucket
|
|
45
|
+
key: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Arguments for writing a stream to storage.
|
|
50
|
+
*/
|
|
51
|
+
export interface WriteFileArgs<
|
|
52
|
+
TBucket extends string = string,
|
|
53
|
+
> extends BucketKeyArgs<TBucket> {
|
|
54
|
+
stream: ReadableStream | NodeJS.ReadableStream
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Arguments for copying a local file into storage.
|
|
59
|
+
*/
|
|
60
|
+
export interface CopyFileArgs<
|
|
61
|
+
TBucket extends string = string,
|
|
62
|
+
> extends BucketKeyArgs<TBucket> {
|
|
63
|
+
fromAbsolutePath: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ContentService<TBucket extends string = string> {
|
|
2
67
|
/**
|
|
3
68
|
* Signs a content key to generate a secure, time-limited access URL.
|
|
4
|
-
* @param contentKey - The key representing the content object.
|
|
5
|
-
* @param dateLessThan - The expiration time for the signed URL.
|
|
6
|
-
* @param dateGreaterThan - (Optional) Start time before which access is denied.
|
|
7
69
|
*/
|
|
8
|
-
signContentKey(
|
|
9
|
-
contentKey: string,
|
|
10
|
-
dateLessThan: Date,
|
|
11
|
-
dateGreaterThan?: Date
|
|
12
|
-
): Promise<string>
|
|
70
|
+
signContentKey(args: SignContentKeyArgs<TBucket>): Promise<string>
|
|
13
71
|
|
|
14
72
|
/**
|
|
15
73
|
* Signs an arbitrary URL to generate a secure, time-limited access URL.
|
|
16
|
-
* @param url - The full URL that needs signing.
|
|
17
|
-
* @param dateLessThan - The expiration time for the signed URL.
|
|
18
|
-
* @param dateGreaterThan - (Optional) Start time before which access is denied.
|
|
19
74
|
*/
|
|
20
|
-
signURL(
|
|
21
|
-
url: string,
|
|
22
|
-
dateLessThan: Date,
|
|
23
|
-
dateGreaterThan?: Date
|
|
24
|
-
): Promise<string>
|
|
75
|
+
signURL(args: SignURLArgs): Promise<string>
|
|
25
76
|
|
|
26
77
|
/**
|
|
27
78
|
* Generates a signed URL for uploading a file directly to storage.
|
|
28
|
-
*
|
|
29
|
-
* @param contentType - The MIME type of the file.
|
|
30
|
-
* @returns A signed upload URL and the finalized asset key.
|
|
79
|
+
* Bucket policy (size limits, MIME allowlist) is enforced by the implementation.
|
|
31
80
|
*/
|
|
32
|
-
getUploadURL(
|
|
33
|
-
fileKey: string,
|
|
34
|
-
contentType: string
|
|
35
|
-
): Promise<{
|
|
36
|
-
uploadUrl: string
|
|
37
|
-
assetKey: string
|
|
38
|
-
uploadHeaders?: Record<string, string>
|
|
39
|
-
uploadMethod?: 'PUT' | 'POST'
|
|
40
|
-
}>
|
|
81
|
+
getUploadURL(args: GetUploadURLArgs<TBucket>): Promise<UploadURLResult>
|
|
41
82
|
|
|
42
83
|
/**
|
|
43
84
|
* Deletes a file from the storage backend.
|
|
44
|
-
* @param fileName - The name or key of the file to delete.
|
|
45
|
-
* @returns A boolean indicating success.
|
|
46
85
|
*/
|
|
47
|
-
deleteFile(
|
|
86
|
+
deleteFile(args: BucketKeyArgs<TBucket>): Promise<boolean>
|
|
48
87
|
|
|
49
88
|
/**
|
|
50
|
-
* Uploads a file stream to storage under
|
|
51
|
-
* @param assetKey - The key where the file will be saved.
|
|
52
|
-
* @param stream - A readable stream of the file contents.
|
|
53
|
-
* @returns A boolean indicating success.
|
|
89
|
+
* Uploads a file stream to storage under the specified bucket + key.
|
|
54
90
|
*/
|
|
55
|
-
writeFile(
|
|
56
|
-
assetKey: string,
|
|
57
|
-
stream: ReadableStream | NodeJS.ReadableStream
|
|
58
|
-
): Promise<boolean>
|
|
91
|
+
writeFile(args: WriteFileArgs<TBucket>): Promise<boolean>
|
|
59
92
|
|
|
60
93
|
/**
|
|
61
|
-
* Copies a file from a local absolute path into storage
|
|
62
|
-
* @param assetKey - The destination key.
|
|
63
|
-
* @param fromAbsolutePath - The local absolute file path.
|
|
64
|
-
* @returns A boolean indicating success.
|
|
94
|
+
* Copies a file from a local absolute path into storage.
|
|
65
95
|
*/
|
|
66
|
-
copyFile(
|
|
96
|
+
copyFile(args: CopyFileArgs<TBucket>): Promise<boolean>
|
|
67
97
|
|
|
68
98
|
/**
|
|
69
99
|
* Reads a file from storage as a readable stream.
|
|
70
|
-
* @param assetKey - The key of the file to read.
|
|
71
|
-
* @returns A readable file stream.
|
|
72
100
|
*/
|
|
73
|
-
readFile(
|
|
101
|
+
readFile(
|
|
102
|
+
args: BucketKeyArgs<TBucket>
|
|
103
|
+
): Promise<ReadableStream | NodeJS.ReadableStream>
|
|
74
104
|
|
|
75
105
|
/**
|
|
76
106
|
* Reads an entire file from storage into a Buffer.
|
|
77
|
-
* @param assetKey - The key of the file to read.
|
|
78
|
-
* @returns The file contents as a Buffer.
|
|
79
107
|
*/
|
|
80
|
-
readFileAsBuffer(
|
|
108
|
+
readFileAsBuffer(args: BucketKeyArgs<TBucket>): Promise<Buffer>
|
|
81
109
|
}
|
package/src/services/index.ts
CHANGED
|
@@ -25,7 +25,16 @@ export { InMemoryQueueService } from './in-memory-queue-service.js'
|
|
|
25
25
|
export { InMemoryTriggerService } from './in-memory-trigger-service.js'
|
|
26
26
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js'
|
|
27
27
|
export { LocalGatewayService } from './local-gateway-service.js'
|
|
28
|
-
export type {
|
|
28
|
+
export type {
|
|
29
|
+
ContentService,
|
|
30
|
+
SignContentKeyArgs,
|
|
31
|
+
SignURLArgs,
|
|
32
|
+
GetUploadURLArgs,
|
|
33
|
+
UploadURLResult,
|
|
34
|
+
BucketKeyArgs,
|
|
35
|
+
WriteFileArgs,
|
|
36
|
+
CopyFileArgs,
|
|
37
|
+
} from './content-service.js'
|
|
29
38
|
export type { JWTService } from './jwt-service.js'
|
|
30
39
|
export type { Logger } from './logger.js'
|
|
31
40
|
export type { SecretService } from './secret-service.js'
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { createReadStream, createWriteStream, promises } from 'fs'
|
|
2
2
|
import { mkdir, readFile } from 'fs/promises'
|
|
3
3
|
import { resolve, normalize } from 'path'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
BucketKeyArgs,
|
|
6
|
+
ContentService,
|
|
7
|
+
CopyFileArgs,
|
|
8
|
+
GetUploadURLArgs,
|
|
9
|
+
JWTService,
|
|
10
|
+
Logger,
|
|
11
|
+
SignContentKeyArgs,
|
|
12
|
+
SignURLArgs,
|
|
13
|
+
UploadURLResult,
|
|
14
|
+
WriteFileArgs,
|
|
15
|
+
} from '@pikku/core/services'
|
|
5
16
|
import { pipeline } from 'stream/promises'
|
|
6
17
|
import type { Readable } from 'stream'
|
|
7
18
|
|
|
@@ -20,15 +31,20 @@ export class LocalContent implements ContentService {
|
|
|
20
31
|
private jwt?: JWTService
|
|
21
32
|
) {}
|
|
22
33
|
|
|
23
|
-
private safePath(
|
|
34
|
+
private safePath(bucket: string, key: string): string {
|
|
24
35
|
const base = resolve(this.config.localFileUploadPath)
|
|
25
|
-
const
|
|
36
|
+
const scoped = resolve(base, normalize(bucket))
|
|
37
|
+
const target = resolve(scoped, normalize(key))
|
|
26
38
|
if (!target.startsWith(base + '/') && target !== base) {
|
|
27
39
|
throw new Error('Invalid asset key')
|
|
28
40
|
}
|
|
29
41
|
return target
|
|
30
42
|
}
|
|
31
43
|
|
|
44
|
+
private joinKey(bucket: string, key: string): string {
|
|
45
|
+
return `${bucket}/${key}`
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
public async init() {}
|
|
33
49
|
|
|
34
50
|
private async signParams(
|
|
@@ -58,105 +74,105 @@ export class LocalContent implements ContentService {
|
|
|
58
74
|
return params.toString()
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
public async signURL(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return `${url}?${params}`
|
|
77
|
+
public async signURL(args: SignURLArgs): Promise<string> {
|
|
78
|
+
const params = await this.signParams(
|
|
79
|
+
args.dateLessThan,
|
|
80
|
+
args.dateGreaterThan
|
|
81
|
+
)
|
|
82
|
+
return `${args.url}?${params}`
|
|
68
83
|
}
|
|
69
84
|
|
|
70
|
-
public async signContentKey(
|
|
71
|
-
|
|
72
|
-
dateLessThan: Date,
|
|
73
|
-
dateGreaterThan?: Date
|
|
74
|
-
): Promise<string> {
|
|
85
|
+
public async signContentKey(args: SignContentKeyArgs): Promise<string> {
|
|
86
|
+
const fullKey = this.joinKey(args.bucket, args.contentKey)
|
|
75
87
|
const base = this.config.server
|
|
76
|
-
? `${this.config.server}${this.config.assetUrlPrefix}/${
|
|
77
|
-
: `${this.config.assetUrlPrefix}/${
|
|
78
|
-
return this.signURL(
|
|
88
|
+
? `${this.config.server}${this.config.assetUrlPrefix}/${fullKey}`
|
|
89
|
+
: `${this.config.assetUrlPrefix}/${fullKey}`
|
|
90
|
+
return this.signURL({
|
|
91
|
+
url: base,
|
|
92
|
+
dateLessThan: args.dateLessThan,
|
|
93
|
+
dateGreaterThan: args.dateGreaterThan,
|
|
94
|
+
})
|
|
79
95
|
}
|
|
80
96
|
|
|
81
|
-
public async getUploadURL(
|
|
82
|
-
this.
|
|
97
|
+
public async getUploadURL(args: GetUploadURLArgs): Promise<UploadURLResult> {
|
|
98
|
+
const fullKey = this.joinKey(args.bucket, args.fileKey)
|
|
99
|
+
this.logger.debug(`Going to upload with key: ${fullKey}`)
|
|
83
100
|
return {
|
|
84
|
-
uploadUrl: `${this.config.uploadUrlPrefix}/${
|
|
85
|
-
assetKey,
|
|
101
|
+
uploadUrl: `${this.config.uploadUrlPrefix}/${fullKey}`,
|
|
102
|
+
assetKey: fullKey,
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
public async writeFile(
|
|
90
|
-
|
|
91
|
-
stream: ReadableStream | NodeJS.ReadableStream
|
|
92
|
-
): Promise<boolean> {
|
|
93
|
-
this.logger.debug(`Writing file: ${assetKey}`)
|
|
106
|
+
public async writeFile(args: WriteFileArgs): Promise<boolean> {
|
|
107
|
+
this.logger.debug(`Writing file: ${args.bucket}/${args.key}`)
|
|
94
108
|
|
|
95
|
-
const path = this.safePath(
|
|
109
|
+
const path = this.safePath(args.bucket, args.key)
|
|
96
110
|
|
|
97
111
|
try {
|
|
98
112
|
await this.createDirectoryForFile(path)
|
|
99
113
|
const fileStream = createWriteStream(path)
|
|
100
|
-
|
|
101
|
-
await pipeline(stream as Readable, fileStream)
|
|
114
|
+
await pipeline(args.stream as Readable, fileStream)
|
|
102
115
|
return true
|
|
103
116
|
} catch (e) {
|
|
104
117
|
console.error(e)
|
|
105
|
-
this.logger.error(`Error writing content ${
|
|
118
|
+
this.logger.error(`Error writing content ${args.bucket}/${args.key}`, e)
|
|
106
119
|
return false
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
public async copyFile(
|
|
111
|
-
|
|
112
|
-
fromAbsolutePath: string
|
|
113
|
-
): Promise<boolean> {
|
|
114
|
-
this.logger.debug(`Writing file: ${assetKey}`)
|
|
123
|
+
public async copyFile(args: CopyFileArgs): Promise<boolean> {
|
|
124
|
+
this.logger.debug(`Writing file: ${args.bucket}/${args.key}`)
|
|
115
125
|
try {
|
|
116
|
-
const path = this.safePath(
|
|
126
|
+
const path = this.safePath(args.bucket, args.key)
|
|
117
127
|
await this.createDirectoryForFile(path)
|
|
118
|
-
await promises.copyFile(fromAbsolutePath, path)
|
|
128
|
+
await promises.copyFile(args.fromAbsolutePath, path)
|
|
129
|
+
return true
|
|
119
130
|
} catch (e) {
|
|
120
131
|
console.error(e)
|
|
121
|
-
this.logger.error(`Error inserting content ${
|
|
132
|
+
this.logger.error(`Error inserting content ${args.bucket}/${args.key}`, e)
|
|
122
133
|
}
|
|
123
134
|
return false
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
public async readFile(
|
|
127
|
-
|
|
138
|
+
args: BucketKeyArgs
|
|
128
139
|
): Promise<ReadableStream | NodeJS.ReadableStream> {
|
|
129
|
-
this.logger.debug(`Getting key: ${
|
|
140
|
+
this.logger.debug(`Getting key: ${args.bucket}/${args.key}`)
|
|
130
141
|
|
|
131
|
-
const filePath = this.safePath(
|
|
142
|
+
const filePath = this.safePath(args.bucket, args.key)
|
|
132
143
|
|
|
133
144
|
try {
|
|
134
145
|
const stream = createReadStream(filePath)
|
|
135
|
-
// Handle early stream errors (like file not found, permission denied, etc.)
|
|
136
146
|
stream.on('error', (err) => {
|
|
137
|
-
this.logger.error(
|
|
147
|
+
this.logger.error(
|
|
148
|
+
`Error getting content ${args.bucket}/${args.key}`,
|
|
149
|
+
err
|
|
150
|
+
)
|
|
138
151
|
})
|
|
139
152
|
|
|
140
153
|
return stream
|
|
141
154
|
} catch (e) {
|
|
142
|
-
this.logger.error(
|
|
155
|
+
this.logger.error(
|
|
156
|
+
`Error setting up stream for ${args.bucket}/${args.key}`,
|
|
157
|
+
e
|
|
158
|
+
)
|
|
143
159
|
throw e
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
162
|
|
|
147
|
-
public async readFileAsBuffer(
|
|
148
|
-
const filePath = this.safePath(
|
|
149
|
-
this.logger.debug(`Reading file as buffer: ${
|
|
163
|
+
public async readFileAsBuffer(args: BucketKeyArgs): Promise<Buffer> {
|
|
164
|
+
const filePath = this.safePath(args.bucket, args.key)
|
|
165
|
+
this.logger.debug(`Reading file as buffer: ${args.bucket}/${args.key}`)
|
|
150
166
|
return readFile(filePath)
|
|
151
167
|
}
|
|
152
168
|
|
|
153
|
-
public async deleteFile(
|
|
154
|
-
this.logger.debug(`deleting key: ${
|
|
169
|
+
public async deleteFile(args: BucketKeyArgs): Promise<boolean> {
|
|
170
|
+
this.logger.debug(`deleting key: ${args.bucket}/${args.key}`)
|
|
155
171
|
try {
|
|
156
|
-
await promises.unlink(this.safePath(
|
|
172
|
+
await promises.unlink(this.safePath(args.bucket, args.key))
|
|
157
173
|
return true
|
|
158
174
|
} catch (e: any) {
|
|
159
|
-
this.logger.error(`Error deleting content ${
|
|
175
|
+
this.logger.error(`Error deleting content ${args.bucket}/${args.key}`, e)
|
|
160
176
|
}
|
|
161
177
|
return false
|
|
162
178
|
}
|
|
@@ -54,7 +54,25 @@ export const resolveNamespace = (
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a bare (non-namespaced) RPC name to its pikkuFuncId, preferring
|
|
59
|
+
* the caller's addon package when provided. Returns the function name plus
|
|
60
|
+
* the package scope that resolved it (null = root), so callers can thread
|
|
61
|
+
* the scope into runPikkuFunc without a second lookup.
|
|
62
|
+
*/
|
|
63
|
+
const resolvePikkuFunction = (
|
|
64
|
+
rpcName: string,
|
|
65
|
+
packageName: string | null = null
|
|
66
|
+
): { pikkuFuncId: string; packageName: string | null } => {
|
|
67
|
+
// Addon-scoped calls: try the caller's package function meta first.
|
|
68
|
+
// (RPC meta only lives in root; addon functions are registered under their package.)
|
|
69
|
+
if (packageName) {
|
|
70
|
+
const pkgFunctions = pikkuState(packageName, 'function', 'meta')
|
|
71
|
+
const pkgMeta = pkgFunctions?.[rpcName]
|
|
72
|
+
if (pkgMeta) {
|
|
73
|
+
return { pikkuFuncId: pkgMeta.pikkuFuncId || rpcName, packageName }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
58
76
|
const rpc = pikkuState(null, 'rpc', 'meta')
|
|
59
77
|
let rpcMeta = rpc[rpcName]
|
|
60
78
|
if (!rpcMeta) {
|
|
@@ -66,7 +84,7 @@ const getPikkuFunctionName = (rpcName: string): string => {
|
|
|
66
84
|
if (!rpcMeta) {
|
|
67
85
|
throw new RPCNotFoundError(rpcName)
|
|
68
86
|
}
|
|
69
|
-
return rpcMeta
|
|
87
|
+
return { pikkuFuncId: rpcMeta, packageName: null }
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
// Context-aware RPC client for use within services
|
|
@@ -77,7 +95,8 @@ export class ContextAwareRPCService {
|
|
|
77
95
|
private options: {
|
|
78
96
|
requiresAuth?: boolean
|
|
79
97
|
sessionService?: SessionService<CoreUserSession>
|
|
80
|
-
}
|
|
98
|
+
},
|
|
99
|
+
private packageName: string | null = null
|
|
81
100
|
) {}
|
|
82
101
|
|
|
83
102
|
public async rpcExposed(funcName: string, data: any): Promise<any> {
|
|
@@ -131,17 +150,22 @@ export class ContextAwareRPCService {
|
|
|
131
150
|
}
|
|
132
151
|
}
|
|
133
152
|
|
|
134
|
-
//
|
|
153
|
+
// Bare name: resolve via caller's package scope first (if any), then root.
|
|
154
|
+
// Note: intra-addon bare calls do NOT re-apply the addon's external
|
|
155
|
+
// addonConfig.auth/tags — those gates are only applied on the external
|
|
156
|
+
// 'namespace:func' boundary via invokeAddonFunction.
|
|
135
157
|
try {
|
|
158
|
+
const resolved = resolvePikkuFunction(funcName, this.packageName)
|
|
136
159
|
return await runPikkuFunc<In, Out>(
|
|
137
160
|
'rpc',
|
|
138
161
|
funcName,
|
|
139
|
-
|
|
162
|
+
resolved.pikkuFuncId,
|
|
140
163
|
{
|
|
141
164
|
auth: this.options.requiresAuth,
|
|
142
165
|
singletonServices: this.services,
|
|
143
166
|
data: () => data,
|
|
144
167
|
wire: updatedWire,
|
|
168
|
+
packageName: resolved.packageName,
|
|
145
169
|
}
|
|
146
170
|
)
|
|
147
171
|
} catch (e) {
|
|
@@ -232,17 +256,14 @@ export class ContextAwareRPCService {
|
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
wire: mergedWire,
|
|
244
|
-
}
|
|
245
|
-
)
|
|
259
|
+
const resolved = resolvePikkuFunction(rpcName, this.packageName)
|
|
260
|
+
return await runPikkuFunc<In, Out>('rpc', rpcName, resolved.pikkuFuncId, {
|
|
261
|
+
auth: this.options.requiresAuth,
|
|
262
|
+
singletonServices: this.services,
|
|
263
|
+
data: () => data,
|
|
264
|
+
wire: mergedWire,
|
|
265
|
+
packageName: resolved.packageName,
|
|
266
|
+
})
|
|
246
267
|
} catch (e) {
|
|
247
268
|
if (e instanceof RPCNotFoundError && this.services.deploymentService) {
|
|
248
269
|
const session =
|
|
@@ -406,14 +427,20 @@ export class PikkuRPCService<
|
|
|
406
427
|
sessionService?: SessionService<CoreUserSession>
|
|
407
428
|
}
|
|
408
429
|
| undefined,
|
|
409
|
-
depth: number = 0
|
|
430
|
+
depth: number = 0,
|
|
431
|
+
packageName: string | null = null
|
|
410
432
|
): TypedRPC {
|
|
411
433
|
const options =
|
|
412
434
|
typeof requiresAuthOrOptions === 'object' &&
|
|
413
435
|
requiresAuthOrOptions !== null
|
|
414
436
|
? requiresAuthOrOptions
|
|
415
437
|
: { requiresAuth: requiresAuthOrOptions }
|
|
416
|
-
const serviceRPC = new ContextAwareRPCService(
|
|
438
|
+
const serviceRPC = new ContextAwareRPCService(
|
|
439
|
+
services,
|
|
440
|
+
wire,
|
|
441
|
+
options,
|
|
442
|
+
packageName
|
|
443
|
+
)
|
|
417
444
|
return {
|
|
418
445
|
depth,
|
|
419
446
|
global: false,
|