@pikku/inspector 0.12.10 → 0.12.12
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 +21 -9
- package/dist/add/add-cli.js +10 -3
- package/dist/add/add-credential.js +2 -1
- package/dist/add/add-functions.js +99 -5
- package/dist/add/add-http-route.js +44 -6
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-middleware.js +33 -4
- package/dist/add/add-permission.js +7 -7
- package/dist/add/add-workflow-graph.js +20 -1
- package/dist/error-codes.d.ts +2 -0
- package/dist/error-codes.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +2 -5
- package/dist/types.d.ts +10 -19
- package/dist/utils/extract-function-name.js +6 -0
- package/dist/utils/filter-inspector-state.js +187 -59
- package/dist/utils/filter-utils.js +13 -5
- package/dist/utils/get-property-value.d.ts +10 -0
- package/dist/utils/get-property-value.js +30 -0
- package/dist/utils/post-process.d.ts +2 -3
- package/dist/utils/post-process.js +3 -23
- package/dist/utils/resolve-addon-package.d.ts +4 -5
- package/dist/utils/resolve-addon-package.js +64 -16
- package/dist/utils/resolve-deploy-target.d.ts +28 -0
- package/dist/utils/resolve-deploy-target.js +56 -0
- package/dist/utils/resolve-versions.js +79 -0
- package/dist/utils/schema-generator.js +31 -12
- package/dist/utils/validate-auth-sessionless.d.ts +1 -1
- package/package.json +2 -2
- package/src/add/add-cli.ts +10 -3
- package/src/add/add-credential.ts +3 -0
- package/src/add/add-functions.test.ts +318 -0
- package/src/add/add-functions.ts +164 -6
- package/src/add/add-gateway.ts +5 -1
- package/src/add/add-http-route.ts +54 -7
- package/src/add/add-keyed-wiring.ts +7 -1
- package/src/add/add-mcp-prompt.ts +5 -1
- package/src/add/add-mcp-resource.ts +5 -1
- package/src/add/add-middleware.ts +42 -4
- package/src/add/add-permission.ts +7 -7
- package/src/add/add-schedule.ts +5 -1
- package/src/add/add-workflow-graph.ts +19 -1
- package/src/add/wire-name-literal.test.ts +114 -0
- package/src/error-codes.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +1 -5
- package/src/types.ts +19 -15
- package/src/utils/extract-function-name.ts +8 -0
- package/src/utils/filter-inspector-state.test.ts +168 -64
- package/src/utils/filter-inspector-state.ts +290 -64
- package/src/utils/filter-utils.test.ts +30 -15
- package/src/utils/filter-utils.ts +14 -5
- package/src/utils/get-property-value.ts +40 -0
- package/src/utils/post-process.ts +3 -38
- package/src/utils/resolve-addon-package.ts +65 -14
- package/src/utils/resolve-deploy-target.test.ts +105 -0
- package/src/utils/resolve-deploy-target.ts +63 -0
- package/src/utils/resolve-versions.test.ts +108 -0
- package/src/utils/resolve-versions.ts +86 -0
- package/src/utils/schema-generator.ts +37 -13
- package/src/utils/validate-auth-sessionless.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## 0.12.12
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 9060165: Agents now declare their model directly as `<provider>/<model>` (e.g. `openai/gpt-4o`). The `models`, `agentDefaults`, and `agentOverrides` config blocks have been removed.
|
|
6
|
+
|
|
7
|
+
**Migration:** replace any bare `model: 'alias'` values with the full provider-qualified form and remove those blocks from `pikku.config.json`.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [9060165]
|
|
10
|
+
- Updated dependencies [9060165]
|
|
11
|
+
- Updated dependencies [9060165]
|
|
12
|
+
- @pikku/core@0.12.21
|
|
13
|
+
|
|
14
|
+
## 0.12.11
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 033d172: Log a critical inspector error when multiple functions resolve to the same `pikku` function name, instead of silently allowing routing map collisions. This may cause builds to fail if multiple functions previously resolved to the same `pikku` function name.
|
|
19
|
+
- Updated dependencies [b9ed73e]
|
|
20
|
+
- @pikku/core@0.12.19
|
|
21
|
+
|
|
1
22
|
## 0.12.0
|
|
2
23
|
|
|
3
24
|
## 0.12.10
|
|
@@ -32,7 +53,6 @@
|
|
|
32
53
|
### Patch Changes
|
|
33
54
|
|
|
34
55
|
- 624097e: Add deploy pipeline with provider-agnostic architecture
|
|
35
|
-
|
|
36
56
|
- Add MetaService with explicit typed API, absorb WiringService reads
|
|
37
57
|
- Add deployment service, traceId propagation, scoped logger
|
|
38
58
|
- Rewrite analyzer: one function = one worker, gateways dispatch via RPC
|
|
@@ -79,7 +99,6 @@
|
|
|
79
99
|
|
|
80
100
|
- 5866b66: Add critical error (PKU490) when Zod schemas and wiring calls (wireHTTPRoutes, addPermission, addHTTPMiddleware) coexist in the same file. The CLI uses tsImport to extract Zod schemas at runtime, which executes all top-level code — wiring side-effects crash in this context because pikku state metadata doesn't exist. Schemas and wirings must be in separate files.
|
|
81
101
|
- e412b4d: Optimize CLI codegen performance: 12x faster `pikku all`
|
|
82
|
-
|
|
83
102
|
- Reuse schemas across re-inspections (skip redundant `ts-json-schema-generator` runs)
|
|
84
103
|
- Cache TS schemas to disk (`.pikku/schema-cache.json`) for cross-run reuse
|
|
85
104
|
- Pass `oldProgram` to `ts.createProgram` for incremental TS compilation
|
|
@@ -208,14 +227,12 @@
|
|
|
208
227
|
- 1967172: Update code generation to support channel middleware enhancements
|
|
209
228
|
|
|
210
229
|
**Code Generation Updates:**
|
|
211
|
-
|
|
212
230
|
- Update channel type serialization to include middleware support
|
|
213
231
|
- Improve WebSocket wrapper generation for middleware handling
|
|
214
232
|
- Update CLI channel client generation with better type support
|
|
215
233
|
- Enhance services and schema generation for channel configurations
|
|
216
234
|
|
|
217
235
|
**Inspector Updates:**
|
|
218
|
-
|
|
219
236
|
- Improve channel metadata extraction for middleware
|
|
220
237
|
- Better type analysis for channel lifecycle functions
|
|
221
238
|
- Enhanced post-processing for channel configurations
|
|
@@ -223,19 +240,16 @@
|
|
|
223
240
|
- 753481a: Add bootstrap command, performance optimizations, and CLI improvements
|
|
224
241
|
|
|
225
242
|
**New Features:**
|
|
226
|
-
|
|
227
243
|
- Add `pikku bootstrap` command for type-only generation (~13.5% faster than `pikku all`)
|
|
228
244
|
- Add configurable `ignoreFiles` option to pikku.config.json with sensible defaults (_.gen.ts, _.test.ts, \*.spec.ts)
|
|
229
245
|
- Export pikkuCLIRender helper from serialize-cli-types.ts with JSDoc documentation
|
|
230
246
|
|
|
231
247
|
**Performance Improvements:**
|
|
232
|
-
|
|
233
248
|
- Add aggressive TypeScript compiler options (skipDefaultLibCheck, types: []) - ~37% faster TypeScript setup
|
|
234
249
|
- Add detailed performance timing to inspector phases (--logLevel=debug)
|
|
235
250
|
- Optimize file inspection with ignore patterns - ~10-20% faster overall
|
|
236
251
|
|
|
237
252
|
**Enhancements:**
|
|
238
|
-
|
|
239
253
|
- Fix --logLevel flag to properly apply log level to logger
|
|
240
254
|
- Update middleware logging to use structured log format
|
|
241
255
|
- Improve CLI renderers to consistently use destructured logger service
|
|
@@ -336,7 +350,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
336
350
|
### Patch Changes
|
|
337
351
|
|
|
338
352
|
- 44e3ff4: feat: enhance CLI filtering with type and directory filters
|
|
339
|
-
|
|
340
353
|
- Add --types filter to filter by PikkuEventTypes (http, channel, queue, scheduler, rpc, mcp)
|
|
341
354
|
- Add --directories filter to filter by file paths/directories
|
|
342
355
|
- All filters (tags, types, directories) now work together with AND logic
|
|
@@ -347,7 +360,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
347
360
|
- 7c592b8: feat: support for required services and improved service configuration
|
|
348
361
|
|
|
349
362
|
This release includes several enhancements to service management and configuration:
|
|
350
|
-
|
|
351
363
|
- Added support for required services configuration
|
|
352
364
|
- Improved service discovery and registration
|
|
353
365
|
- Added typed RPC clients for service communication
|
package/dist/add/add-cli.js
CHANGED
|
@@ -295,10 +295,16 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
295
295
|
meta.options = processOptions(logger, optionsNode, typeChecker, inspectorState, options, pikkuFuncId);
|
|
296
296
|
}
|
|
297
297
|
break;
|
|
298
|
-
case 'subcommands':
|
|
299
|
-
|
|
298
|
+
case 'subcommands': {
|
|
299
|
+
let subcommandsNode = prop.initializer;
|
|
300
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
301
|
+
subcommandsNode = resolveIdentifier(prop.initializer, typeChecker, [
|
|
302
|
+
'defineCLICommands',
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
if (subcommandsNode && ts.isObjectLiteralExpression(subcommandsNode)) {
|
|
300
306
|
meta.subcommands = {};
|
|
301
|
-
for (const subProp of
|
|
307
|
+
for (const subProp of subcommandsNode.properties) {
|
|
302
308
|
if (!ts.isPropertyAssignment(subProp))
|
|
303
309
|
continue;
|
|
304
310
|
const subName = getPropertyName(subProp);
|
|
@@ -311,6 +317,7 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
311
317
|
}
|
|
312
318
|
}
|
|
313
319
|
break;
|
|
320
|
+
}
|
|
314
321
|
case 'isDefault':
|
|
315
322
|
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
316
323
|
prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue, getArrayPropertyValue, } from '../utils/get-property-value.js';
|
|
2
|
+
import { getPropertyValue, getArrayPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
|
|
3
3
|
import { ErrorCode } from '../error-codes.js';
|
|
4
4
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
5
5
|
export const addCredential = (logger, node, checker, state, _options) => {
|
|
@@ -17,6 +17,7 @@ export const addCredential = (logger, node, checker, state, _options) => {
|
|
|
17
17
|
}
|
|
18
18
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
19
19
|
const obj = firstArg;
|
|
20
|
+
assertStringLiteralProperty(obj, 'name', 'Credential', logger);
|
|
20
21
|
const nameValue = getPropertyValue(obj, 'name');
|
|
21
22
|
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
22
23
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
@@ -3,8 +3,9 @@ import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
|
3
3
|
import { extractFunctionName, funcIdToTypeName, } from '../utils/extract-function-name.js';
|
|
4
4
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
5
5
|
import { extractUsedWires } from '../utils/extract-services.js';
|
|
6
|
-
import { formatVersionedId } from '@pikku/core';
|
|
6
|
+
import { formatVersionedId, parseVersionedId } from '@pikku/core';
|
|
7
7
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
8
|
+
import { canonicalJSON, hashString } from '../utils/hash.js';
|
|
8
9
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
9
10
|
import { resolvePermissions } from '../utils/permissions.js';
|
|
10
11
|
import { extractWireNames } from '../utils/post-process.js';
|
|
@@ -209,6 +210,33 @@ function unwrapPromise(checker, type) {
|
|
|
209
210
|
}
|
|
210
211
|
return type;
|
|
211
212
|
}
|
|
213
|
+
const resolveExistingFunctionSource = (state, pikkuFuncId) => {
|
|
214
|
+
return (state.functions.meta[pikkuFuncId]?.sourceFile ||
|
|
215
|
+
state.rpc.internalFiles.get(pikkuFuncId)?.path ||
|
|
216
|
+
null);
|
|
217
|
+
};
|
|
218
|
+
const areCompatibleFunctionIds = (existingId, incomingId) => {
|
|
219
|
+
if (existingId === incomingId) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
const existingParsed = parseVersionedId(existingId);
|
|
223
|
+
const incomingParsed = parseVersionedId(incomingId);
|
|
224
|
+
return existingParsed.baseName === incomingParsed.baseName;
|
|
225
|
+
};
|
|
226
|
+
function printNode(node) {
|
|
227
|
+
return ts
|
|
228
|
+
.createPrinter({ removeComments: true })
|
|
229
|
+
.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile());
|
|
230
|
+
}
|
|
231
|
+
function computeImplementationHash(args) {
|
|
232
|
+
const { wrapper, handler, objectNode, isDirectFunction } = args;
|
|
233
|
+
return hashString(canonicalJSON({
|
|
234
|
+
wrapper,
|
|
235
|
+
isDirectFunction,
|
|
236
|
+
handler: printNode(handler),
|
|
237
|
+
config: objectNode ? printNode(objectNode) : null,
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
212
240
|
/**
|
|
213
241
|
* Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
|
|
214
242
|
* then push into state.functions.meta.
|
|
@@ -396,10 +424,16 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
396
424
|
}
|
|
397
425
|
}
|
|
398
426
|
if (version !== undefined) {
|
|
399
|
-
|
|
427
|
+
let baseName = explicitName || exportedName || pikkuFuncId;
|
|
428
|
+
// Strip trailing VN suffix if it matches the version (e.g. getDataV1 + version:1 → getData@v1)
|
|
429
|
+
const vSuffix = `V${version}`;
|
|
430
|
+
if (baseName.endsWith(vSuffix) && baseName.length > vSuffix.length) {
|
|
431
|
+
baseName = baseName.slice(0, -vSuffix.length);
|
|
432
|
+
}
|
|
400
433
|
pikkuFuncId = formatVersionedId(baseName, version);
|
|
401
434
|
}
|
|
402
435
|
const isMCPToolFunc = expression.text === 'pikkuMCPToolFunc';
|
|
436
|
+
const isListFunc = expression.text === 'pikkuListFunc';
|
|
403
437
|
const mcpEnabled = mcp || isMCPToolFunc;
|
|
404
438
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
405
439
|
const handler = resolvedFunc &&
|
|
@@ -461,7 +495,22 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
461
495
|
state.schemaLookup.set(schemaName, inputSchemaRef);
|
|
462
496
|
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
463
497
|
}
|
|
464
|
-
else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
498
|
+
else if (isListFunc && genericTypes.length >= 1 && genericTypes[0]) {
|
|
499
|
+
const inputAliasName = `${capitalizedName}Input`;
|
|
500
|
+
const filterType = genericTypes[0];
|
|
501
|
+
const filterTypeText = checker.typeToString(filterType, undefined, ts.TypeFormatFlags.NoTruncation);
|
|
502
|
+
const refs = resolveTypeImports(filterType, state.functions.typesMap, true, checker);
|
|
503
|
+
state.functions.typesMap.addCustomType(inputAliasName, `{ cursor?: string; limit?: number; sort?: Array<{ column: string; direction: 'asc' | 'desc' }>; filter?: unknown; search?: string; } & ${filterTypeText}`, [...new Set(refs)]);
|
|
504
|
+
inputNames = [inputAliasName];
|
|
505
|
+
const secondParam = handler.parameters[1];
|
|
506
|
+
if (secondParam) {
|
|
507
|
+
inputTypes = [checker.getTypeAtLocation(secondParam)];
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
inputTypes = [filterType];
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else if (!isListFunc && genericTypes.length >= 1 && genericTypes[0]) {
|
|
465
514
|
// Fall back to extracting from generic type arguments
|
|
466
515
|
const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
|
|
467
516
|
inputNames = result.names;
|
|
@@ -485,7 +534,15 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
485
534
|
state.schemaLookup.set(schemaName, outputSchemaRef);
|
|
486
535
|
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
487
536
|
}
|
|
488
|
-
else if (genericTypes.length >= 2) {
|
|
537
|
+
else if (isListFunc && genericTypes.length >= 2 && genericTypes[1]) {
|
|
538
|
+
const outputAliasName = `${capitalizedName}Output`;
|
|
539
|
+
const rowType = genericTypes[1];
|
|
540
|
+
const rowTypeText = checker.typeToString(rowType, undefined, ts.TypeFormatFlags.NoTruncation);
|
|
541
|
+
const refs = resolveTypeImports(rowType, state.functions.typesMap, true, checker);
|
|
542
|
+
state.functions.typesMap.addCustomType(outputAliasName, `{ rows: Array<${rowTypeText}>; nextCursor: string | null; totalCount?: number; }`, [...new Set(refs)]);
|
|
543
|
+
outputNames = [outputAliasName];
|
|
544
|
+
}
|
|
545
|
+
else if (!isListFunc && genericTypes.length >= 2) {
|
|
489
546
|
outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
|
|
490
547
|
}
|
|
491
548
|
else {
|
|
@@ -539,6 +596,36 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
539
596
|
if (inputTypes.length > 0) {
|
|
540
597
|
state.typesLookup.set(pikkuFuncId, inputTypes);
|
|
541
598
|
}
|
|
599
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
600
|
+
const existingFunction = state.functions.meta[pikkuFuncId];
|
|
601
|
+
if (existingFunction &&
|
|
602
|
+
existingFunction.sourceFile &&
|
|
603
|
+
existingFunction.sourceFile !== sourceFile) {
|
|
604
|
+
logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Function name '${name}' is not unique. ` +
|
|
605
|
+
`'${pikkuFuncId}' is already defined in '${existingFunction.sourceFile}' and cannot be redefined in '${sourceFile}'.`);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (exportedName || explicitName) {
|
|
609
|
+
const existingInternal = state.rpc.internalMeta[name];
|
|
610
|
+
if (existingInternal &&
|
|
611
|
+
!areCompatibleFunctionIds(existingInternal, pikkuFuncId)) {
|
|
612
|
+
const existingSource = resolveExistingFunctionSource(state, existingInternal) || 'unknown file';
|
|
613
|
+
logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Function name '${name}' is not unique. ` +
|
|
614
|
+
`It already points to '${existingInternal}' in '${existingSource}', but '${pikkuFuncId}' in '${sourceFile}' tried to use the same name.`);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (expose) {
|
|
618
|
+
const existingExposed = state.rpc.exposedMeta[name];
|
|
619
|
+
if (existingExposed &&
|
|
620
|
+
!areCompatibleFunctionIds(existingExposed, pikkuFuncId)) {
|
|
621
|
+
const existingSource = resolveExistingFunctionSource(state, existingExposed) ||
|
|
622
|
+
'unknown file';
|
|
623
|
+
logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Exposed function name '${name}' is not unique. ` +
|
|
624
|
+
`It already points to '${existingExposed}' in '${existingSource}', but '${pikkuFuncId}' in '${sourceFile}' tried to use the same name.`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
542
629
|
// --- resolve middleware ---
|
|
543
630
|
let middleware = objectNode
|
|
544
631
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
@@ -569,6 +656,12 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
569
656
|
}
|
|
570
657
|
}
|
|
571
658
|
const sessionless = expression.text !== 'pikkuFunc';
|
|
659
|
+
const implementationHash = computeImplementationHash({
|
|
660
|
+
wrapper: expression.text,
|
|
661
|
+
handler,
|
|
662
|
+
objectNode,
|
|
663
|
+
isDirectFunction,
|
|
664
|
+
});
|
|
572
665
|
state.functions.meta[pikkuFuncId] = {
|
|
573
666
|
pikkuFuncId,
|
|
574
667
|
functionType: 'user',
|
|
@@ -588,6 +681,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
588
681
|
deploy: deploy || undefined,
|
|
589
682
|
approvalRequired: approvalRequired || undefined,
|
|
590
683
|
approvalDescription: approvalDescription || undefined,
|
|
684
|
+
implementationHash,
|
|
591
685
|
version,
|
|
592
686
|
title,
|
|
593
687
|
tags: tags || undefined,
|
|
@@ -597,7 +691,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
597
691
|
middleware,
|
|
598
692
|
permissions,
|
|
599
693
|
isDirectFunction,
|
|
600
|
-
sourceFile
|
|
694
|
+
sourceFile,
|
|
601
695
|
exportedName: exportedName || undefined,
|
|
602
696
|
};
|
|
603
697
|
// Populate node metadata if node config is present
|
|
@@ -7,6 +7,7 @@ import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
|
|
|
7
7
|
import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
|
|
8
8
|
import { extractWireNames } from '../utils/post-process.js';
|
|
9
9
|
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
10
|
+
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
|
|
10
11
|
import { ErrorCode } from '../error-codes.js';
|
|
11
12
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
|
|
12
13
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
@@ -113,12 +114,30 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
113
114
|
if (funcName.startsWith('__temp_')) {
|
|
114
115
|
funcName = makeContextBasedId('http', method, fullRoute);
|
|
115
116
|
}
|
|
117
|
+
let refAddonTarget = null;
|
|
118
|
+
if (ts.isCallExpression(funcInitializer) &&
|
|
119
|
+
ts.isIdentifier(funcInitializer.expression) &&
|
|
120
|
+
funcInitializer.expression.text === 'ref') {
|
|
121
|
+
const [firstArg] = funcInitializer.arguments;
|
|
122
|
+
if (firstArg &&
|
|
123
|
+
ts.isStringLiteral(firstArg) &&
|
|
124
|
+
firstArg.text.includes(':')) {
|
|
125
|
+
refAddonTarget = firstArg.text;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
116
128
|
const packageName = ts.isIdentifier(funcInitializer)
|
|
117
129
|
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
118
130
|
: null;
|
|
131
|
+
if (refAddonTarget) {
|
|
132
|
+
const targetMeta = resolveFunctionMeta(state, refAddonTarget);
|
|
133
|
+
if (!targetMeta) {
|
|
134
|
+
logger.warn(`Skipping route '${fullRoute}': addon function metadata for '${refAddonTarget}' is not available yet.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
119
138
|
ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
|
|
120
139
|
// Lookup existing function metadata
|
|
121
|
-
const fnMeta = state
|
|
140
|
+
const fnMeta = resolveFunctionMeta(state, funcName);
|
|
122
141
|
if (!fnMeta) {
|
|
123
142
|
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
|
|
124
143
|
return;
|
|
@@ -127,17 +146,36 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
127
146
|
return;
|
|
128
147
|
}
|
|
129
148
|
const input = fnMeta.inputs?.[0] || null;
|
|
149
|
+
const getRouteInputKeys = () => {
|
|
150
|
+
const targetFuncName = refAddonTarget ?? funcName;
|
|
151
|
+
const inputTypes = state.typesLookup.get(targetFuncName);
|
|
152
|
+
if (inputTypes && inputTypes.length > 0) {
|
|
153
|
+
return extractTypeKeys(inputTypes[0]);
|
|
154
|
+
}
|
|
155
|
+
const targetMeta = resolveFunctionMeta(state, targetFuncName);
|
|
156
|
+
if (targetMeta?.inputSchemaName) {
|
|
157
|
+
const schema = state.schemas[targetMeta.inputSchemaName];
|
|
158
|
+
const properties = schema?.properties;
|
|
159
|
+
if (properties && typeof properties === 'object') {
|
|
160
|
+
return Object.keys(properties);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
130
165
|
// Validate that route params and query params exist in function input type
|
|
131
166
|
if (params.length > 0 || query.length > 0) {
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
|
|
167
|
+
const inputKeys = getRouteInputKeys();
|
|
168
|
+
if (!inputKeys) {
|
|
169
|
+
// Input shape isn't inspectable at this phase (e.g. addon ref or opaque handler).
|
|
170
|
+
// Skip param/query validation rather than emitting a false positive.
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
135
173
|
// Check path params
|
|
136
174
|
if (params.length > 0) {
|
|
137
175
|
const missingParams = params.filter((p) => !inputKeys.includes(p));
|
|
138
176
|
if (missingParams.length > 0) {
|
|
139
177
|
logger.critical(ErrorCode.ROUTE_PARAM_MISMATCH, `Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
|
|
140
|
-
`not found in function '${funcName}' input type. ` +
|
|
178
|
+
`not found in function '${refAddonTarget ?? funcName}' input type. ` +
|
|
141
179
|
`Input type has: [${inputKeys.join(', ')}]`);
|
|
142
180
|
return;
|
|
143
181
|
}
|
|
@@ -147,7 +185,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
147
185
|
const missingQuery = query.filter((q) => !inputKeys.includes(q));
|
|
148
186
|
if (missingQuery.length > 0) {
|
|
149
187
|
logger.critical(ErrorCode.ROUTE_QUERY_MISMATCH, `Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
|
|
150
|
-
`not found in function '${funcName}' input type. ` +
|
|
188
|
+
`not found in function '${refAddonTarget ?? funcName}' input type. ` +
|
|
151
189
|
`Input type has: [${inputKeys.join(', ')}]`);
|
|
152
190
|
return;
|
|
153
191
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
2
|
+
import { getPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
|
|
3
3
|
import { ErrorCode } from '../error-codes.js';
|
|
4
4
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
5
5
|
export const createAddKeyedWiring = (config) => {
|
|
@@ -19,6 +19,8 @@ export const createAddKeyedWiring = (config) => {
|
|
|
19
19
|
}
|
|
20
20
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
21
21
|
const obj = firstArg;
|
|
22
|
+
assertStringLiteralProperty(obj, 'name', config.label, logger);
|
|
23
|
+
assertStringLiteralProperty(obj, config.idField, config.label, logger);
|
|
22
24
|
const nameValue = getPropertyValue(obj, 'name');
|
|
23
25
|
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
24
26
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
@@ -171,7 +171,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
171
171
|
logger.debug(`• Found middleware factory with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
|
-
if (expression.text === '
|
|
174
|
+
if (expression.text === 'addTagMiddleware') {
|
|
175
175
|
const tagArg = args[0];
|
|
176
176
|
const middlewareArrayArg = args[1];
|
|
177
177
|
if (!tagArg || !middlewareArrayArg)
|
|
@@ -181,11 +181,11 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
181
181
|
tag = tagArg.text;
|
|
182
182
|
}
|
|
183
183
|
if (!tag) {
|
|
184
|
-
logger.warn(`•
|
|
184
|
+
logger.warn(`• addTagMiddleware call without valid tag string`);
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
187
|
if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
|
|
188
|
-
logger.error(`•
|
|
188
|
+
logger.error(`• addTagMiddleware('${tag}', ...) must have a literal array as second argument`);
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
@@ -236,7 +236,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
236
236
|
}
|
|
237
237
|
if (!isFactory && exportedName) {
|
|
238
238
|
logger.warn(`• Middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
239
|
-
`For tree-shaking, use: export const ${exportedName} = () =>
|
|
239
|
+
`For tree-shaking, use: export const ${exportedName} = () => addTagMiddleware('${tag}', [...])`);
|
|
240
240
|
}
|
|
241
241
|
state.middleware.tagMiddleware.set(tag, {
|
|
242
242
|
exportName: exportedName,
|
|
@@ -253,6 +253,34 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
253
253
|
logger.debug(`• Found tag middleware group: ${tag} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
254
254
|
return;
|
|
255
255
|
}
|
|
256
|
+
if (expression.text === 'addGlobalMiddleware') {
|
|
257
|
+
const middlewareArrayArg = args[0];
|
|
258
|
+
if (!middlewareArrayArg ||
|
|
259
|
+
!ts.isArrayLiteralExpression(middlewareArrayArg)) {
|
|
260
|
+
logger.error(`• addGlobalMiddleware(...) must have a literal array as its only argument`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
264
|
+
const definitionIds = refs.map((r) => r.definitionId);
|
|
265
|
+
if (definitionIds.length > 0) {
|
|
266
|
+
renameTempDefinitions(state, definitionIds, 'global', 'middleware');
|
|
267
|
+
}
|
|
268
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
269
|
+
for (let i = 0; i < refs.length; i++) {
|
|
270
|
+
const instanceId = makeContextBasedId('global', 'middleware', String(i));
|
|
271
|
+
state.middleware.instances[instanceId] = {
|
|
272
|
+
definitionId: definitionIds[i],
|
|
273
|
+
sourceFile,
|
|
274
|
+
position: node.getStart(),
|
|
275
|
+
isFactoryCall: refs[i].isFactoryCall,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Without this, bootstrap codegen's "import every file with a wire-call"
|
|
279
|
+
// pass skips middleware-only files and the registration never runs.
|
|
280
|
+
state.http.files.add(sourceFile);
|
|
281
|
+
logger.debug(`• Found global middleware group with ${refs.length} entries`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
256
284
|
if (expression.text === 'addHTTPMiddleware') {
|
|
257
285
|
const patternArg = args[0];
|
|
258
286
|
const middlewareArrayArg = args[1];
|
|
@@ -332,6 +360,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
332
360
|
instanceIds,
|
|
333
361
|
isFactory,
|
|
334
362
|
});
|
|
363
|
+
state.http.files.add(sourceFile);
|
|
335
364
|
logger.debug(`• Found HTTP route middleware group: ${pattern} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
336
365
|
return;
|
|
337
366
|
}
|
|
@@ -27,7 +27,7 @@ function isInsidePermissionContainer(node) {
|
|
|
27
27
|
if (ts.isCallExpression(current) &&
|
|
28
28
|
ts.isIdentifier(current.expression) &&
|
|
29
29
|
(current.expression.text === 'pikkuPermissionFactory' ||
|
|
30
|
-
current.expression.text === '
|
|
30
|
+
current.expression.text === 'addTagPermission' ||
|
|
31
31
|
current.expression.text === 'addHTTPPermission')) {
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
@@ -247,9 +247,9 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
247
247
|
}
|
|
248
248
|
// Handle addPermission('tag', [permission1, permission2])
|
|
249
249
|
// Supports two patterns:
|
|
250
|
-
// 1. export const x = () =>
|
|
251
|
-
// 2. export const x =
|
|
252
|
-
if (expression.text === '
|
|
250
|
+
// 1. export const x = () => addTagPermission('tag', [...]) (factory - tree-shakeable)
|
|
251
|
+
// 2. export const x = addTagPermission('tag', [...]) (direct - no tree-shaking)
|
|
252
|
+
if (expression.text === 'addTagPermission') {
|
|
253
253
|
const tagArg = args[0];
|
|
254
254
|
const permissionsArrayArg = args[1];
|
|
255
255
|
if (!tagArg || !permissionsArrayArg)
|
|
@@ -260,13 +260,13 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
260
260
|
tag = tagArg.text;
|
|
261
261
|
}
|
|
262
262
|
if (!tag) {
|
|
263
|
-
logger.warn(`•
|
|
263
|
+
logger.warn(`• addTagPermission call without valid tag string`);
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
266
266
|
// Check if permissions is a literal array or object
|
|
267
267
|
if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
|
|
268
268
|
!ts.isObjectLiteralExpression(permissionsArrayArg)) {
|
|
269
|
-
logger.error(`•
|
|
269
|
+
logger.error(`• addTagPermission('${tag}', ...) must have a literal array or object as second argument`);
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
272
|
// Extract permission pikkuFuncIds from array
|
|
@@ -305,7 +305,7 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
305
305
|
}
|
|
306
306
|
if (!isFactory && exportedName) {
|
|
307
307
|
logger.warn(`• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
308
|
-
`For tree-shaking, use: export const ${exportedName} = () =>
|
|
308
|
+
`For tree-shaking, use: export const ${exportedName} = () => addTagPermission('${tag}', [...])`);
|
|
309
309
|
}
|
|
310
310
|
state.permissions.tagPermissions.set(tag, {
|
|
311
311
|
exportName: exportedName,
|
|
@@ -281,6 +281,8 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
281
281
|
input: {},
|
|
282
282
|
next: undefined,
|
|
283
283
|
onError: undefined,
|
|
284
|
+
retries: undefined,
|
|
285
|
+
retryDelay: undefined,
|
|
284
286
|
};
|
|
285
287
|
}
|
|
286
288
|
// Extract config for each node from 'config' property
|
|
@@ -301,6 +303,8 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
301
303
|
nodes[nodeId].next = nodeConfig.next;
|
|
302
304
|
nodes[nodeId].onError = nodeConfig.onError;
|
|
303
305
|
nodes[nodeId].input = nodeConfig.input;
|
|
306
|
+
nodes[nodeId].retries = nodeConfig.retries;
|
|
307
|
+
nodes[nodeId].retryDelay = nodeConfig.retryDelay;
|
|
304
308
|
}
|
|
305
309
|
}
|
|
306
310
|
}
|
|
@@ -314,6 +318,8 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
314
318
|
let next = undefined;
|
|
315
319
|
let onError = undefined;
|
|
316
320
|
let input = {};
|
|
321
|
+
let retries = undefined;
|
|
322
|
+
let retryDelay = undefined;
|
|
317
323
|
for (const prop of obj.properties) {
|
|
318
324
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
319
325
|
continue;
|
|
@@ -327,8 +333,21 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
327
333
|
else if (propName === 'input') {
|
|
328
334
|
input = extractInputMapping(prop.initializer, checker);
|
|
329
335
|
}
|
|
336
|
+
else if (propName === 'retries') {
|
|
337
|
+
if (ts.isNumericLiteral(prop.initializer)) {
|
|
338
|
+
retries = Number(prop.initializer.text);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else if (propName === 'retryDelay') {
|
|
342
|
+
if (ts.isNumericLiteral(prop.initializer)) {
|
|
343
|
+
retryDelay = Number(prop.initializer.text);
|
|
344
|
+
}
|
|
345
|
+
else if (ts.isStringLiteral(prop.initializer)) {
|
|
346
|
+
retryDelay = prop.initializer.text;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
330
349
|
}
|
|
331
|
-
return { next, onError, input };
|
|
350
|
+
return { next, onError, input, retries, retryDelay };
|
|
332
351
|
}
|
|
333
352
|
/**
|
|
334
353
|
* Inspector for pikkuWorkflowGraph() calls
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare enum ErrorCode {
|
|
10
10
|
MISSING_NAME = "PKU111",
|
|
11
|
+
NON_LITERAL_WIRE_NAME = "PKU118",
|
|
11
12
|
MISSING_DESCRIPTION = "PKU123",
|
|
12
13
|
INVALID_VALUE = "PKU124",
|
|
13
14
|
MISSING_URI = "PKU220",
|
|
@@ -41,6 +42,7 @@ export declare enum ErrorCode {
|
|
|
41
42
|
PERMISSION_EMPTY_ARRAY = "PKU937",
|
|
42
43
|
PERMISSION_PATTERN_INVALID = "PKU975",
|
|
43
44
|
DUPLICATE_FUNCTION_VERSION = "PKU850",
|
|
45
|
+
DUPLICATE_FUNCTION_NAME = "PKU851",
|
|
44
46
|
MANIFEST_MISSING = "PKU860",
|
|
45
47
|
FUNCTION_VERSION_MODIFIED = "PKU861",
|
|
46
48
|
CONTRACT_CHANGED_REQUIRES_BUMP = "PKU862",
|
package/dist/error-codes.js
CHANGED
|
@@ -10,6 +10,7 @@ export var ErrorCode;
|
|
|
10
10
|
(function (ErrorCode) {
|
|
11
11
|
// Validation errors
|
|
12
12
|
ErrorCode["MISSING_NAME"] = "PKU111";
|
|
13
|
+
ErrorCode["NON_LITERAL_WIRE_NAME"] = "PKU118";
|
|
13
14
|
ErrorCode["MISSING_DESCRIPTION"] = "PKU123";
|
|
14
15
|
ErrorCode["INVALID_VALUE"] = "PKU124";
|
|
15
16
|
ErrorCode["MISSING_URI"] = "PKU220";
|
|
@@ -48,6 +49,7 @@ export var ErrorCode;
|
|
|
48
49
|
ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
|
|
49
50
|
// Versioning errors
|
|
50
51
|
ErrorCode["DUPLICATE_FUNCTION_VERSION"] = "PKU850";
|
|
52
|
+
ErrorCode["DUPLICATE_FUNCTION_NAME"] = "PKU851";
|
|
51
53
|
// Contract versioning errors
|
|
52
54
|
ErrorCode["MANIFEST_MISSING"] = "PKU860";
|
|
53
55
|
ErrorCode["FUNCTION_VERSION_MODIFIED"] = "PKU861";
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { ErrorCode } from './error-codes.js';
|
|
|
6
6
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
7
7
|
export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
|
|
8
8
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
9
|
+
export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
9
10
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
10
11
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
11
12
|
export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { inspect, getInitialInspectorState } from './inspector.js';
|
|
|
2
2
|
export { ErrorCode } from './error-codes.js';
|
|
3
3
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
4
4
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
5
|
+
export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
5
6
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
6
7
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
7
8
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|