@pikku/inspector 0.12.89 → 0.12.91
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 +33 -0
- package/dist/add/add-analytics.d.ts +23 -0
- package/dist/add/add-analytics.js +7 -8
- package/dist/add/add-outgoing-webhook.d.ts +3 -0
- package/dist/add/add-outgoing-webhook.js +81 -0
- package/dist/add/add-trigger.d.ts +5 -0
- package/dist/add/add-trigger.js +104 -0
- package/dist/inspector.js +3 -0
- package/dist/types.d.ts +10 -1
- package/dist/utils/find-zod-transform.d.ts +3 -3
- package/dist/utils/find-zod-transform.js +3 -3
- package/dist/utils/post-process.js +9 -0
- package/dist/utils/resolve-versions.js +6 -0
- package/dist/utils/schema-generator.js +5 -10
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/zod-to-type-text.d.ts +25 -0
- package/dist/utils/zod-to-type-text.js +228 -0
- package/dist/visit.js +2 -0
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## 0.12.91
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 5e93f30: Add `defineOutgoingWebhook({ event, title, description?, payload })` in `@pikku/core/webhook`. The CLI collects every exported declaration into `.pikku/webhooks/pikku-outgoing-webhooks-meta.gen.json` and `pikku-outgoing-webhooks.gen.ts`, which exports `OutgoingWebhooksMap`, `TypedWebhookService` and `typedWebhookService(service)`: `send` checks `data` against the declared payload for a declared event and accepts any other event unchanged. `MetaService.getOutgoingWebhooksMeta()` and the console addon's `outgoingWebhooksMeta` serve the declarations.
|
|
6
|
+
- 5e93f30: Add `wireTriggerWebhookSource({ name, method?, route?, secret?, events, receive?, check?, setup?, teardown? })` in `#pikku/trigger`. Each source becomes a `POST /webhooks/<name>` route whose events are validated against their schemas and queued on `pikku-incoming-webhooks` through `IncomingWebhookService`; a generated worker runs the matching `wireTrigger({ name: '<source>:<event>' })` and the queue retries it on failure. `pikku webhooks status | setup | teardown --url --labelPrefix [--previous]` registers the routes with the provider and prints one JSON line per source.
|
|
7
|
+
|
|
8
|
+
`KyselyIncomingWebhookService` (with the `incoming-webhook` schema) records a receipt per event, drops a provider's redelivery of an event it already accepted, and keeps each dispatch's attempts and last error. `pikku dev` and `pikku serve` use it when a Kysely database is configured.
|
|
9
|
+
|
|
10
|
+
- Updated dependencies [5e93f30]
|
|
11
|
+
- Updated dependencies [5e93f30]
|
|
12
|
+
- @pikku/core@0.12.125
|
|
13
|
+
|
|
14
|
+
## 0.12.90
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 4e05a10: Print the TypeScript text for a Zod schema directly, dropping `zod-to-ts`.
|
|
19
|
+
|
|
20
|
+
`processZodSchema` built a TypeScript AST only to print it straight back to a
|
|
21
|
+
string. `zodToTypeText` walks Zod's own definitions instead, which removes
|
|
22
|
+
`zod-to-ts`, `ts.createPrinter`, `ts.EmitHint` and `ts.createSourceFile` from
|
|
23
|
+
the Zod path — it no longer touches the compiler API at all. `@pikku/cli`
|
|
24
|
+
declared `zod-to-ts` without importing it; that is dropped too.
|
|
25
|
+
|
|
26
|
+
A defaulted field is now optional in the generated type. `processZodSchema`
|
|
27
|
+
already strips defaulted fields out of the JSON Schema's `required`, so the two
|
|
28
|
+
halves of the same contract disagreed: the validator accepted a payload that
|
|
29
|
+
omitted the field, while the type said a caller had to pass it.
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [1394385]
|
|
32
|
+
- @pikku/core@0.12.124
|
|
33
|
+
|
|
1
34
|
## 0.12.89
|
|
2
35
|
|
|
3
36
|
### Patch Changes
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import type { InspectorLogger, InspectorState } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A call is the pikku one when the symbol it resolves to is, whatever the file
|
|
5
|
+
* chose to call it locally — `import { defineAnalyticsEvents as declare }` is
|
|
6
|
+
* an ordinary thing to write, and matching on the callee's text alone both
|
|
7
|
+
* misses that and claims a same-named local helper.
|
|
8
|
+
*/
|
|
9
|
+
export declare const callsImportedDefiner: (expression: ts.Expression, checker: ts.TypeChecker, definer: string) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The name the generated ingest has to import the declaration by — which is
|
|
12
|
+
* the name the module exports it under, not the name of the const. A module
|
|
13
|
+
* that keeps the declaration to itself has no such name, so it is refused here
|
|
14
|
+
* rather than emitted into an ingest that cannot compile.
|
|
15
|
+
*/
|
|
16
|
+
export declare const exportedName: (declaration: ts.VariableDeclaration, name: ts.Identifier, checker: ts.TypeChecker) => string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* An event's props, as source text rather than a resolved type — `z.string()`
|
|
19
|
+
* is what the declaration says and what a reader recognises, and resolving it
|
|
20
|
+
* would mean type-checking a third-party generic to render one table cell.
|
|
21
|
+
*
|
|
22
|
+
* Absent for a schema pikku cannot read the shape off (a shared const, a union,
|
|
23
|
+
* a vendor that is not zod). The catalog renders nothing rather than guessing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const readEventShape: (initializer: ts.Expression) => Record<string, string> | undefined;
|
|
3
26
|
/**
|
|
4
27
|
* Record where each `defineAnalyticsEvents` declaration is, which names it
|
|
5
28
|
* declares, and the shape each name validates — the names drive the generated
|
|
@@ -7,18 +7,17 @@ const DEFINE_ANALYTICS_EVENTS = 'defineAnalyticsEvents';
|
|
|
7
7
|
* an ordinary thing to write, and matching on the callee's text alone both
|
|
8
8
|
* misses that and claims a same-named local helper.
|
|
9
9
|
*/
|
|
10
|
-
const
|
|
10
|
+
export const callsImportedDefiner = (expression, checker, definer) => {
|
|
11
11
|
if (!ts.isIdentifier(expression))
|
|
12
12
|
return false;
|
|
13
13
|
const symbol = checker.getSymbolAtLocation(expression);
|
|
14
14
|
if (!symbol)
|
|
15
|
-
return expression.text ===
|
|
15
|
+
return expression.text === definer;
|
|
16
16
|
// An import specifier names what it imported even when the module itself
|
|
17
17
|
// does not resolve, which is every project whose deps are not installed yet.
|
|
18
18
|
const declaration = symbol.declarations?.[0];
|
|
19
19
|
if (declaration && ts.isImportSpecifier(declaration)) {
|
|
20
|
-
return (
|
|
21
|
-
DEFINE_ANALYTICS_EVENTS);
|
|
20
|
+
return (declaration.propertyName ?? declaration.name).text === definer;
|
|
22
21
|
}
|
|
23
22
|
// Only an import can be pikku's. A file's own helper of the same name
|
|
24
23
|
// resolves to a symbol whose `.name` matches, so resolving without this
|
|
@@ -26,7 +25,7 @@ const callsDefineAnalyticsEvents = (expression, checker) => {
|
|
|
26
25
|
if (!(symbol.flags & ts.SymbolFlags.Alias))
|
|
27
26
|
return false;
|
|
28
27
|
const resolved = checker.getAliasedSymbol(symbol) ?? symbol;
|
|
29
|
-
return resolved.name ===
|
|
28
|
+
return resolved.name === definer;
|
|
30
29
|
};
|
|
31
30
|
/**
|
|
32
31
|
* The name the generated ingest has to import the declaration by — which is
|
|
@@ -34,7 +33,7 @@ const callsDefineAnalyticsEvents = (expression, checker) => {
|
|
|
34
33
|
* that keeps the declaration to itself has no such name, so it is refused here
|
|
35
34
|
* rather than emitted into an ingest that cannot compile.
|
|
36
35
|
*/
|
|
37
|
-
const exportedName = (declaration, name, checker) => {
|
|
36
|
+
export const exportedName = (declaration, name, checker) => {
|
|
38
37
|
const moduleSymbol = checker.getSymbolAtLocation(declaration.getSourceFile());
|
|
39
38
|
const declarationSymbol = checker.getSymbolAtLocation(name);
|
|
40
39
|
if (!moduleSymbol || !declarationSymbol) {
|
|
@@ -78,7 +77,7 @@ const findObjectShape = (expression) => {
|
|
|
78
77
|
* Absent for a schema pikku cannot read the shape off (a shared const, a union,
|
|
79
78
|
* a vendor that is not zod). The catalog renders nothing rather than guessing.
|
|
80
79
|
*/
|
|
81
|
-
const readEventShape = (initializer) => {
|
|
80
|
+
export const readEventShape = (initializer) => {
|
|
82
81
|
const shape = findObjectShape(initializer);
|
|
83
82
|
if (!shape)
|
|
84
83
|
return undefined;
|
|
@@ -110,7 +109,7 @@ export const addAnalytics = (logger, node, checker, state) => {
|
|
|
110
109
|
const { initializer, name } = node;
|
|
111
110
|
if (!initializer || !ts.isCallExpression(initializer))
|
|
112
111
|
return;
|
|
113
|
-
if (!
|
|
112
|
+
if (!callsImportedDefiner(initializer.expression, checker, DEFINE_ANALYTICS_EVENTS))
|
|
114
113
|
return;
|
|
115
114
|
if (!ts.isIdentifier(name))
|
|
116
115
|
return;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { callsImportedDefiner, exportedName, readEventShape, } from './add-analytics.js';
|
|
3
|
+
const DEFINE_OUTGOING_WEBHOOK = 'defineOutgoingWebhook';
|
|
4
|
+
const stringProperty = (object, key) => {
|
|
5
|
+
for (const property of object.properties) {
|
|
6
|
+
if (!ts.isPropertyAssignment(property))
|
|
7
|
+
continue;
|
|
8
|
+
const name = property.name;
|
|
9
|
+
const text = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
|
|
10
|
+
if (text !== key)
|
|
11
|
+
continue;
|
|
12
|
+
const value = property.initializer;
|
|
13
|
+
if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
|
|
14
|
+
return value.text;
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
const propertyInitializer = (object, key) => {
|
|
20
|
+
for (const property of object.properties) {
|
|
21
|
+
if (!ts.isPropertyAssignment(property))
|
|
22
|
+
continue;
|
|
23
|
+
const name = property.name;
|
|
24
|
+
if ((ts.isIdentifier(name) || ts.isStringLiteral(name)) &&
|
|
25
|
+
name.text === key)
|
|
26
|
+
return property.initializer;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
30
|
+
export const addOutgoingWebhook = (logger, node, checker, state) => {
|
|
31
|
+
if (!ts.isVariableDeclaration(node))
|
|
32
|
+
return;
|
|
33
|
+
const { initializer, name } = node;
|
|
34
|
+
if (!initializer || !ts.isCallExpression(initializer))
|
|
35
|
+
return;
|
|
36
|
+
if (!callsImportedDefiner(initializer.expression, checker, DEFINE_OUTGOING_WEBHOOK))
|
|
37
|
+
return;
|
|
38
|
+
if (!ts.isIdentifier(name))
|
|
39
|
+
return;
|
|
40
|
+
const file = node.getSourceFile().fileName;
|
|
41
|
+
const [argument] = initializer.arguments;
|
|
42
|
+
if (!argument || !ts.isObjectLiteralExpression(argument)) {
|
|
43
|
+
logger.error(`defineOutgoingWebhook in ${file} must be called with an object literal: { event, title, payload }.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const event = stringProperty(argument, 'event');
|
|
47
|
+
const title = stringProperty(argument, 'title');
|
|
48
|
+
if (!event || !title) {
|
|
49
|
+
logger.error(`defineOutgoingWebhook '${name.text}' in ${file} needs 'event' and 'title' as string literals, so they can be listed without running the app.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const variable = exportedName(node, name, checker);
|
|
53
|
+
if (!variable) {
|
|
54
|
+
logger.error(`defineOutgoingWebhook '${event}' in ${file} is assigned to '${name.text}', which the module does not export. The generated webhook types import it by name, so export it.`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const description = stringProperty(argument, 'description');
|
|
58
|
+
const payloadExpression = propertyInitializer(argument, 'payload');
|
|
59
|
+
const payload = payloadExpression
|
|
60
|
+
? readEventShape(payloadExpression)
|
|
61
|
+
: undefined;
|
|
62
|
+
const webhooks = (state.outgoingWebhooks ??= []);
|
|
63
|
+
const existing = webhooks.findIndex((webhook) => webhook.file === file && webhook.variable === variable);
|
|
64
|
+
const declared = webhooks.find((webhook, index) => webhook.event === event && index !== existing);
|
|
65
|
+
if (declared) {
|
|
66
|
+
logger.error(`Webhook event '${event}' is declared twice: ${declared.file} and ${file}. An event belongs to one defineOutgoingWebhook.`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const entry = {
|
|
70
|
+
file,
|
|
71
|
+
variable,
|
|
72
|
+
event,
|
|
73
|
+
title,
|
|
74
|
+
...(description !== undefined ? { description } : {}),
|
|
75
|
+
...(payload ? { payload } : {}),
|
|
76
|
+
};
|
|
77
|
+
if (existing >= 0)
|
|
78
|
+
webhooks[existing] = entry;
|
|
79
|
+
else
|
|
80
|
+
webhooks.push(entry);
|
|
81
|
+
};
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { AddWiring } from '../types.js';
|
|
2
2
|
export declare const addTrigger: AddWiring;
|
|
3
|
+
/**
|
|
4
|
+
* A trigger named `<source>:<event>` must name an event its webhook source
|
|
5
|
+
* declares, or it would never fire.
|
|
6
|
+
*/
|
|
7
|
+
export declare const validateWebhookSourceTriggers: (logger: Parameters<AddWiring>[0], state: Parameters<AddWiring>[3]) => void;
|
package/dist/add/add-trigger.js
CHANGED
|
@@ -6,7 +6,9 @@ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js
|
|
|
6
6
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
7
7
|
import { extractWireNames } from '../utils/post-process.js';
|
|
8
8
|
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
9
|
+
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
|
|
9
10
|
import { ErrorCode } from '../error-codes.js';
|
|
11
|
+
const WEBHOOK_SOURCE_STEPS = ['receive', 'check', 'setup', 'teardown'];
|
|
10
12
|
export const addTrigger = (logger, node, checker, state, options) => {
|
|
11
13
|
if (!ts.isCallExpression(node)) {
|
|
12
14
|
return;
|
|
@@ -23,6 +25,9 @@ export const addTrigger = (logger, node, checker, state, options) => {
|
|
|
23
25
|
else if (expression.text === 'wireTriggerSource') {
|
|
24
26
|
addWireTriggerSource(logger, node, checker, state, options, firstArg);
|
|
25
27
|
}
|
|
28
|
+
else if (expression.text === 'wireTriggerWebhookSource') {
|
|
29
|
+
addWireTriggerWebhookSource(logger, node, checker, state, firstArg);
|
|
30
|
+
}
|
|
26
31
|
};
|
|
27
32
|
const addWireTrigger = (logger, node, checker, state, firstArg) => {
|
|
28
33
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
@@ -88,3 +93,102 @@ const addWireTriggerSource = (logger, node, checker, state, options, firstArg) =
|
|
|
88
93
|
}
|
|
89
94
|
state.triggers.files.add(node.getSourceFile().fileName);
|
|
90
95
|
};
|
|
96
|
+
const refTarget = (initializer) => {
|
|
97
|
+
if (!ts.isCallExpression(initializer) ||
|
|
98
|
+
!ts.isIdentifier(initializer.expression) ||
|
|
99
|
+
initializer.expression.text !== 'ref') {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const [target] = initializer.arguments;
|
|
103
|
+
return target && ts.isStringLiteral(target) && target.text.includes(':')
|
|
104
|
+
? target.text
|
|
105
|
+
: null;
|
|
106
|
+
};
|
|
107
|
+
const addWireTriggerWebhookSource = (logger, node, checker, state, firstArg) => {
|
|
108
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const obj = firstArg;
|
|
112
|
+
const name = getPropertyValue(obj, 'name');
|
|
113
|
+
if (!name) {
|
|
114
|
+
logger.critical(ErrorCode.MISSING_NAME, `wireTriggerWebhookSource in ${node.getSourceFile().fileName} needs 'name' as a string literal.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const method = getPropertyValue(obj, 'method') ?? 'post';
|
|
118
|
+
const route = getPropertyValue(obj, 'route') ?? `/webhooks/${name}`;
|
|
119
|
+
let secret = getPropertyValue(obj, 'secret');
|
|
120
|
+
const eventsInitializer = getPropertyAssignmentInitializer(obj, 'events', false, checker);
|
|
121
|
+
const events = eventsInitializer
|
|
122
|
+
? checker
|
|
123
|
+
.getTypeAtLocation(eventsInitializer)
|
|
124
|
+
.getProperties()
|
|
125
|
+
.map((property) => property.getName())
|
|
126
|
+
.sort()
|
|
127
|
+
: [];
|
|
128
|
+
const steps = {};
|
|
129
|
+
for (const step of WEBHOOK_SOURCE_STEPS) {
|
|
130
|
+
const initializer = getPropertyAssignmentInitializer(obj, step, true, checker);
|
|
131
|
+
if (!initializer)
|
|
132
|
+
continue;
|
|
133
|
+
const target = refTarget(initializer);
|
|
134
|
+
if (target) {
|
|
135
|
+
if (!resolveFunctionMeta(state, target)) {
|
|
136
|
+
logger.warn(`Webhook source '${name}': addon function metadata for '${target}' is not available yet.`);
|
|
137
|
+
}
|
|
138
|
+
steps[step] = target;
|
|
139
|
+
state.serviceAggregation.usedFunctions.add(target);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const extracted = extractFunctionName(initializer, checker, state.rootDir);
|
|
143
|
+
let pikkuFuncId = extracted.pikkuFuncId;
|
|
144
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
145
|
+
pikkuFuncId = makeContextBasedId('trigger-webhook', name, step);
|
|
146
|
+
}
|
|
147
|
+
ensureInlineWiringFunction(state, pikkuFuncId, `${name}:${step}`, initializer, checker, extracted.isHelper);
|
|
148
|
+
const meta = state.functions.meta[pikkuFuncId];
|
|
149
|
+
if (meta && meta.sessionless === undefined)
|
|
150
|
+
meta.sessionless = true;
|
|
151
|
+
steps[step] = pikkuFuncId;
|
|
152
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
153
|
+
}
|
|
154
|
+
// An addon's receive reads the secret by its own name; the app may have
|
|
155
|
+
// renamed it for this instance.
|
|
156
|
+
const namespace = steps.receive?.includes(':')
|
|
157
|
+
? steps.receive.slice(0, steps.receive.indexOf(':'))
|
|
158
|
+
: null;
|
|
159
|
+
const addon = namespace
|
|
160
|
+
? state.rpc.wireAddonDeclarations.get(namespace)
|
|
161
|
+
: undefined;
|
|
162
|
+
if (secret && addon?.secretOverrides?.[secret]) {
|
|
163
|
+
secret = addon.secretOverrides[secret];
|
|
164
|
+
}
|
|
165
|
+
const meta = {
|
|
166
|
+
name,
|
|
167
|
+
method: method,
|
|
168
|
+
route,
|
|
169
|
+
events,
|
|
170
|
+
...(secret ? { secret } : {}),
|
|
171
|
+
...steps,
|
|
172
|
+
};
|
|
173
|
+
state.triggers.webhookSourceMeta[name] = meta;
|
|
174
|
+
state.triggers.files.add(node.getSourceFile().fileName);
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* A trigger named `<source>:<event>` must name an event its webhook source
|
|
178
|
+
* declares, or it would never fire.
|
|
179
|
+
*/
|
|
180
|
+
export const validateWebhookSourceTriggers = (logger, state) => {
|
|
181
|
+
const sources = state.triggers.webhookSourceMeta;
|
|
182
|
+
for (const trigger of Object.keys(state.triggers.meta)) {
|
|
183
|
+
const colon = trigger.indexOf(':');
|
|
184
|
+
if (colon === -1)
|
|
185
|
+
continue;
|
|
186
|
+
const source = sources[trigger.slice(0, colon)];
|
|
187
|
+
if (!source || source.events.length === 0)
|
|
188
|
+
continue;
|
|
189
|
+
const event = trigger.slice(colon + 1);
|
|
190
|
+
if (!source.events.includes(event)) {
|
|
191
|
+
logger.error(`Trigger '${trigger}' listens for '${event}', which webhook source '${source.name}' does not declare. Declared: ${source.events.join(', ')}.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
package/dist/inspector.js
CHANGED
|
@@ -14,6 +14,7 @@ import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
|
14
14
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
15
15
|
import { pikkuState } from '@pikku/core/state';
|
|
16
16
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
17
|
+
import { validateWebhookSourceTriggers } from './add/add-trigger.js';
|
|
17
18
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
|
|
18
19
|
import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
|
|
19
20
|
import { generateAllSchemas } from './utils/schema-generator.js';
|
|
@@ -79,6 +80,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
79
80
|
triggers: {
|
|
80
81
|
meta: {},
|
|
81
82
|
sourceMeta: {},
|
|
83
|
+
webhookSourceMeta: {},
|
|
82
84
|
files: new Set(),
|
|
83
85
|
},
|
|
84
86
|
scheduledTasks: {
|
|
@@ -353,6 +355,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
353
355
|
ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, sourceOptions));
|
|
354
356
|
}
|
|
355
357
|
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`);
|
|
358
|
+
validateWebhookSourceTriggers(logger, state);
|
|
356
359
|
resolveLatestVersions(state, logger);
|
|
357
360
|
if (options.schemaConfig) {
|
|
358
361
|
const startSchemas = performance.now();
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { ChannelMessageMeta, ChannelsMeta } from '@pikku/core/channel';
|
|
|
5
5
|
import type { GatewaysMeta } from '@pikku/core/gateway';
|
|
6
6
|
import type { HTTPWiringsMeta } from '@pikku/core/http';
|
|
7
7
|
import type { ScheduledTasksMeta } from '@pikku/core/scheduler';
|
|
8
|
-
import type { TriggerMeta, TriggerSourceMeta } from '@pikku/core/trigger';
|
|
8
|
+
import type { TriggerMeta, TriggerSourceMeta, WebhookSourcesMeta } from '@pikku/core/trigger';
|
|
9
9
|
import type { QueueWorkersMeta } from '@pikku/core/queue';
|
|
10
10
|
import type { WorkflowsMeta } from '@pikku/core/workflow';
|
|
11
11
|
import type { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
|
|
@@ -515,6 +515,14 @@ export interface InspectorState {
|
|
|
515
515
|
* inspector cannot read off the declaration. */
|
|
516
516
|
props?: Record<string, Record<string, string>>;
|
|
517
517
|
}>;
|
|
518
|
+
outgoingWebhooks?: Array<{
|
|
519
|
+
file: string;
|
|
520
|
+
variable: string;
|
|
521
|
+
event: string;
|
|
522
|
+
title: string;
|
|
523
|
+
description?: string;
|
|
524
|
+
payload?: Record<string, string>;
|
|
525
|
+
}>;
|
|
518
526
|
addonServerlessIncompatible: Map<string, string[]>;
|
|
519
527
|
configFactories: PathToNameAndType;
|
|
520
528
|
serverLifecycleFactories: PathToNameAndType;
|
|
@@ -539,6 +547,7 @@ export interface InspectorState {
|
|
|
539
547
|
triggers: {
|
|
540
548
|
meta: TriggerMeta;
|
|
541
549
|
sourceMeta: TriggerSourceMeta;
|
|
550
|
+
webhookSourceMeta: WebhookSourcesMeta;
|
|
542
551
|
files: Set<string>;
|
|
543
552
|
};
|
|
544
553
|
scheduledTasks: {
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* contract has to describe one shape. Zod says so twice, in two different
|
|
7
7
|
* places, and neither says it usefully on its own — `toJSONSchema` with
|
|
8
8
|
* `unrepresentable: 'any'` quietly yields `{}`, a schema that accepts
|
|
9
|
-
* everything, and `
|
|
10
|
-
*
|
|
11
|
-
* and the call.
|
|
9
|
+
* everything, and `zodToTypeText` prints the input side of the pipe, which is
|
|
10
|
+
* not the shape the function receives. Finding it up front lets the diagnostic
|
|
11
|
+
* name the schema, the path, and the call.
|
|
12
12
|
*
|
|
13
13
|
* Zod 4 models `x.transform(fn)` as a `pipe` whose `out` is a `transform`, so
|
|
14
14
|
* the walk looks for that node rather than for the method.
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* contract has to describe one shape. Zod says so twice, in two different
|
|
7
7
|
* places, and neither says it usefully on its own — `toJSONSchema` with
|
|
8
8
|
* `unrepresentable: 'any'` quietly yields `{}`, a schema that accepts
|
|
9
|
-
* everything, and `
|
|
10
|
-
*
|
|
11
|
-
* and the call.
|
|
9
|
+
* everything, and `zodToTypeText` prints the input side of the pipe, which is
|
|
10
|
+
* not the shape the function receives. Finding it up front lets the diagnostic
|
|
11
|
+
* name the schema, the path, and the call.
|
|
12
12
|
*
|
|
13
13
|
* Zod 4 models `x.transform(fn)` as a `pipe` whose `out` is a `transform`, so
|
|
14
14
|
* the walk looks for that node rather than for the method.
|
|
@@ -208,6 +208,15 @@ export function aggregateRequiredServices(state) {
|
|
|
208
208
|
requiredServices.add('schedulerService');
|
|
209
209
|
requiredServices.add('queueService');
|
|
210
210
|
}
|
|
211
|
+
// Webhook source routes and their worker are generated and destructure
|
|
212
|
+
// nothing, so they are found by id: the route accepts into the queue through
|
|
213
|
+
// incomingWebhookService and the worker records attempts through it.
|
|
214
|
+
const hasWebhookSources = Object.values(state.triggers?.webhookSourceMeta ?? {}).some(({ method, route }) => state.functions.meta[`http:${method}:${route}`] !== undefined);
|
|
215
|
+
if (hasWebhookSources ||
|
|
216
|
+
state.functions.meta['queue:pikku-incoming-webhooks'] !== undefined) {
|
|
217
|
+
requiredServices.add('incomingWebhookService');
|
|
218
|
+
requiredServices.add('queueService');
|
|
219
|
+
}
|
|
211
220
|
// 6b. Inject synthetic queue workers for workflow graph steps.
|
|
212
221
|
// Each workflow gets an orchestrator queue and per-step queues.
|
|
213
222
|
// Without these, the PikkuWorkflowService constructor can't find
|
|
@@ -169,6 +169,12 @@ function updateWiringReferences(state, oldId, newId) {
|
|
|
169
169
|
source.pikkuFuncId = newId;
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
for (const source of Object.values(state.triggers.webhookSourceMeta ?? {})) {
|
|
173
|
+
for (const step of ['receive', 'check', 'setup', 'teardown']) {
|
|
174
|
+
if (source[step] === oldId)
|
|
175
|
+
source[step] = newId;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
172
178
|
}
|
|
173
179
|
// MCP resources, tools, and prompts
|
|
174
180
|
if (state.mcpEndpoints) {
|
|
@@ -5,8 +5,8 @@ import { dirname, join, resolve } from 'path';
|
|
|
5
5
|
import { createGenerator, RootlessError } from 'ts-json-schema-generator';
|
|
6
6
|
import { register } from 'tsx/esm/api';
|
|
7
7
|
import * as z from 'zod';
|
|
8
|
-
import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts';
|
|
9
8
|
import { findZodTransform } from './find-zod-transform.js';
|
|
9
|
+
import { zodToTypeText } from './zod-to-type-text.js';
|
|
10
10
|
import { ErrorCode } from '../error-codes.js';
|
|
11
11
|
import { generateCustomTypes } from './custom-types-generator.js';
|
|
12
12
|
const PRIMITIVE_TYPES = new Set([
|
|
@@ -382,7 +382,7 @@ async function importWithRegister(sourceFile) {
|
|
|
382
382
|
void Promise.resolve(unregister()).catch(() => { });
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
|
-
function processZodSchema(schemaName, zodSchema, schemas, typesMap,
|
|
385
|
+
function processZodSchema(schemaName, zodSchema, schemas, typesMap, logger) {
|
|
386
386
|
const transformPath = findZodTransform(zodSchema);
|
|
387
387
|
if (transformPath !== undefined) {
|
|
388
388
|
const where = transformPath === '' ? 'the schema itself' : transformPath;
|
|
@@ -412,18 +412,13 @@ function processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTyp
|
|
|
412
412
|
delete schema.required;
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
-
|
|
416
|
-
const typeText = printer.printNode(ts.EmitHint.Unspecified, tsType, fakeSourceFile);
|
|
417
|
-
typesMap.addCustomType(schemaName, typeText, []);
|
|
415
|
+
typesMap.addCustomType(schemaName, zodToTypeText(zodSchema), []);
|
|
418
416
|
schemas[schemaName] = schema;
|
|
419
417
|
logger.debug(`• Generated schema from Zod: ${schemaName}`);
|
|
420
418
|
}
|
|
421
419
|
async function generateZodSchemas(logger, schemaLookup, typesMap) {
|
|
422
420
|
const schemas = {};
|
|
423
421
|
const errors = [];
|
|
424
|
-
const auxiliaryTypeStore = createAuxiliaryTypeStore();
|
|
425
|
-
const printer = ts.createPrinter();
|
|
426
|
-
const fakeSourceFile = ts.createSourceFile('zod-types.ts', '', ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
|
|
427
422
|
// Validate all schemas are zod (or unspecified vendor)
|
|
428
423
|
for (const [schemaName, ref] of schemaLookup.entries()) {
|
|
429
424
|
if (ref.vendor && ref.vendor !== 'zod') {
|
|
@@ -453,7 +448,7 @@ async function generateZodSchemas(logger, schemaLookup, typesMap) {
|
|
|
453
448
|
continue;
|
|
454
449
|
}
|
|
455
450
|
try {
|
|
456
|
-
processZodSchema(schemaName, zodSchema, schemas, typesMap,
|
|
451
|
+
processZodSchema(schemaName, zodSchema, schemas, typesMap, logger);
|
|
457
452
|
}
|
|
458
453
|
catch (e) {
|
|
459
454
|
errors.push(`Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`);
|
|
@@ -478,7 +473,7 @@ async function generateZodSchemas(logger, schemaLookup, typesMap) {
|
|
|
478
473
|
errors.push(`Could not find exported schema '${ref.variableName}' in ${runtimeFile} for ${schemaName}. Available exports: ${Object.keys(module).join(', ')}`);
|
|
479
474
|
continue;
|
|
480
475
|
}
|
|
481
|
-
processZodSchema(schemaName, zodSchema, schemas, typesMap,
|
|
476
|
+
processZodSchema(schemaName, zodSchema, schemas, typesMap, logger);
|
|
482
477
|
}
|
|
483
478
|
catch (e) {
|
|
484
479
|
errors.push(`Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`);
|
|
@@ -143,6 +143,7 @@ export interface SerializableInspectorState {
|
|
|
143
143
|
triggers: {
|
|
144
144
|
meta: InspectorState['triggers']['meta'];
|
|
145
145
|
sourceMeta: InspectorState['triggers']['sourceMeta'];
|
|
146
|
+
webhookSourceMeta?: InspectorState['triggers']['webhookSourceMeta'];
|
|
146
147
|
files: string[];
|
|
147
148
|
};
|
|
148
149
|
scheduledTasks: {
|
|
@@ -56,6 +56,7 @@ export function serializeInspectorState(state) {
|
|
|
56
56
|
triggers: {
|
|
57
57
|
meta: state.triggers.meta,
|
|
58
58
|
sourceMeta: state.triggers.sourceMeta,
|
|
59
|
+
webhookSourceMeta: state.triggers.webhookSourceMeta,
|
|
59
60
|
files: Array.from(state.triggers.files),
|
|
60
61
|
},
|
|
61
62
|
scheduledTasks: {
|
|
@@ -243,6 +244,7 @@ export function deserializeInspectorState(data) {
|
|
|
243
244
|
triggers: {
|
|
244
245
|
meta: data.triggers?.meta ?? {},
|
|
245
246
|
sourceMeta: data.triggers?.sourceMeta ?? {},
|
|
247
|
+
webhookSourceMeta: data.triggers?.webhookSourceMeta ?? {},
|
|
246
248
|
files: new Set(data.triggers?.files ?? []),
|
|
247
249
|
},
|
|
248
250
|
scheduledTasks: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print the TypeScript type text for a Zod schema.
|
|
3
|
+
*
|
|
4
|
+
* `processZodSchema` needs two things from a schema: the JSON Schema, which
|
|
5
|
+
* `z.toJSONSchema` already gives it, and the TypeScript source text that goes
|
|
6
|
+
* into the generated types. Only the second needed a library, and only to build
|
|
7
|
+
* a TypeScript AST that was immediately printed back to a string — so this walks
|
|
8
|
+
* Zod's own definitions and writes the text directly.
|
|
9
|
+
*
|
|
10
|
+
* It prints from the Zod tree rather than from the JSON Schema on purpose. The
|
|
11
|
+
* JSON Schema has already lost what the text needs: `z.date()` is rewritten to
|
|
12
|
+
* `string`/`date-time` before anything can read it, and `z.any()` and
|
|
13
|
+
* `z.unknown()` both flatten to `{}`. The Zod tree still says `Date`, `any` and
|
|
14
|
+
* `unknown`.
|
|
15
|
+
*
|
|
16
|
+
* The walk mirrors {@link findZodTransform} — same `_zod.def` shapes, same
|
|
17
|
+
* seen-set and node budget, for the same reason.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The TypeScript type text for `schema`, as it would be written by hand.
|
|
21
|
+
*
|
|
22
|
+
* A schema that refers to itself cannot be written inline, so a cycle prints as
|
|
23
|
+
* `any` rather than recursing forever.
|
|
24
|
+
*/
|
|
25
|
+
export declare function zodToTypeText(schema: unknown): string;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print the TypeScript type text for a Zod schema.
|
|
3
|
+
*
|
|
4
|
+
* `processZodSchema` needs two things from a schema: the JSON Schema, which
|
|
5
|
+
* `z.toJSONSchema` already gives it, and the TypeScript source text that goes
|
|
6
|
+
* into the generated types. Only the second needed a library, and only to build
|
|
7
|
+
* a TypeScript AST that was immediately printed back to a string — so this walks
|
|
8
|
+
* Zod's own definitions and writes the text directly.
|
|
9
|
+
*
|
|
10
|
+
* It prints from the Zod tree rather than from the JSON Schema on purpose. The
|
|
11
|
+
* JSON Schema has already lost what the text needs: `z.date()` is rewritten to
|
|
12
|
+
* `string`/`date-time` before anything can read it, and `z.any()` and
|
|
13
|
+
* `z.unknown()` both flatten to `{}`. The Zod tree still says `Date`, `any` and
|
|
14
|
+
* `unknown`.
|
|
15
|
+
*
|
|
16
|
+
* The walk mirrors {@link findZodTransform} — same `_zod.def` shapes, same
|
|
17
|
+
* seen-set and node budget, for the same reason.
|
|
18
|
+
*/
|
|
19
|
+
/** Max nodes visited, so a pathological or cyclic schema cannot hang the walk. */
|
|
20
|
+
const MAX_NODES = 10_000;
|
|
21
|
+
const INDENT = ' ';
|
|
22
|
+
const isNode = (value) => typeof value === 'object' &&
|
|
23
|
+
value !== null &&
|
|
24
|
+
typeof value._zod?.def?.type === 'string';
|
|
25
|
+
const tight = (text) => ({ text, loose: false });
|
|
26
|
+
const loose = (text) => ({ text, loose: true });
|
|
27
|
+
const parenthesized = ({ text, loose }) => loose ? `(${text})` : text;
|
|
28
|
+
/** A JS value as a TypeScript literal type. */
|
|
29
|
+
const literalOf = (value) => {
|
|
30
|
+
if (typeof value === 'string')
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
if (typeof value === 'bigint')
|
|
33
|
+
return `${value}n`;
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return 'undefined';
|
|
36
|
+
if (value === null)
|
|
37
|
+
return 'null';
|
|
38
|
+
return String(value);
|
|
39
|
+
};
|
|
40
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
41
|
+
const propertyKey = (name) => IDENTIFIER.test(name) ? name : JSON.stringify(name);
|
|
42
|
+
/** A `.describe()` string as a JSDoc block at the given indentation. */
|
|
43
|
+
const jsDoc = (description, indent) => {
|
|
44
|
+
if (!description)
|
|
45
|
+
return '';
|
|
46
|
+
const lines = description.split('\n');
|
|
47
|
+
if (lines.length === 1)
|
|
48
|
+
return `${indent}/** ${lines[0]} */\n`;
|
|
49
|
+
return `${indent}/**\n${lines.map((line) => `${indent} * ${line}`).join('\n')}\n${indent} */\n`;
|
|
50
|
+
};
|
|
51
|
+
const union = (parts) => {
|
|
52
|
+
const texts = [...new Set(parts.map(parenthesized))];
|
|
53
|
+
if (texts.length === 0)
|
|
54
|
+
return tight('never');
|
|
55
|
+
if (texts.length === 1)
|
|
56
|
+
return tight(texts[0]);
|
|
57
|
+
return loose(texts.join(' | '));
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The TypeScript type text for `schema`, as it would be written by hand.
|
|
61
|
+
*
|
|
62
|
+
* A schema that refers to itself cannot be written inline, so a cycle prints as
|
|
63
|
+
* `any` rather than recursing forever.
|
|
64
|
+
*/
|
|
65
|
+
export function zodToTypeText(schema) {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
let budget = MAX_NODES;
|
|
68
|
+
const print = (node, indent) => {
|
|
69
|
+
if (budget-- <= 0 || !isNode(node))
|
|
70
|
+
return tight('any');
|
|
71
|
+
if (seen.has(node))
|
|
72
|
+
return tight('any');
|
|
73
|
+
seen.add(node);
|
|
74
|
+
try {
|
|
75
|
+
return printDef(node, indent);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
seen.delete(node);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const printDef = (node, indent) => {
|
|
82
|
+
const def = node._zod.def;
|
|
83
|
+
const inner = indent + INDENT;
|
|
84
|
+
switch (def.type) {
|
|
85
|
+
case 'string':
|
|
86
|
+
case 'email':
|
|
87
|
+
case 'url':
|
|
88
|
+
case 'uuid':
|
|
89
|
+
case 'guid':
|
|
90
|
+
case 'cuid':
|
|
91
|
+
case 'cuid2':
|
|
92
|
+
case 'ulid':
|
|
93
|
+
case 'emoji':
|
|
94
|
+
case 'nanoid':
|
|
95
|
+
case 'jwt':
|
|
96
|
+
case 'base64':
|
|
97
|
+
case 'base64url':
|
|
98
|
+
case 'ipv4':
|
|
99
|
+
case 'ipv6':
|
|
100
|
+
case 'cidrv4':
|
|
101
|
+
case 'cidrv6':
|
|
102
|
+
case 'iso_date':
|
|
103
|
+
case 'iso_time':
|
|
104
|
+
case 'iso_datetime':
|
|
105
|
+
case 'iso_duration':
|
|
106
|
+
return tight('string');
|
|
107
|
+
case 'number':
|
|
108
|
+
case 'int':
|
|
109
|
+
case 'nan':
|
|
110
|
+
return tight('number');
|
|
111
|
+
case 'bigint':
|
|
112
|
+
return tight('bigint');
|
|
113
|
+
case 'boolean':
|
|
114
|
+
return tight('boolean');
|
|
115
|
+
case 'date':
|
|
116
|
+
return tight('Date');
|
|
117
|
+
case 'null':
|
|
118
|
+
return tight('null');
|
|
119
|
+
case 'undefined':
|
|
120
|
+
case 'void':
|
|
121
|
+
return tight('undefined');
|
|
122
|
+
case 'symbol':
|
|
123
|
+
return tight('symbol');
|
|
124
|
+
case 'file':
|
|
125
|
+
return tight('File');
|
|
126
|
+
case 'never':
|
|
127
|
+
return tight('never');
|
|
128
|
+
case 'any':
|
|
129
|
+
return tight('any');
|
|
130
|
+
case 'unknown':
|
|
131
|
+
return tight('unknown');
|
|
132
|
+
case 'literal':
|
|
133
|
+
return union((def.values ?? []).map((v) => tight(literalOf(v))));
|
|
134
|
+
case 'enum':
|
|
135
|
+
return union(Object.values(def.entries ?? {}).map((v) => tight(literalOf(v))));
|
|
136
|
+
case 'template_literal': {
|
|
137
|
+
const body = (def.parts ?? [])
|
|
138
|
+
.map((part) => isNode(part) ? `\${${print(part, indent).text}}` : String(part))
|
|
139
|
+
.join('');
|
|
140
|
+
return tight(`\`${body}\``);
|
|
141
|
+
}
|
|
142
|
+
case 'array':
|
|
143
|
+
return tight(`${parenthesized(print(def.element, indent))}[]`);
|
|
144
|
+
case 'set':
|
|
145
|
+
return tight(`Set<${print(def.valueType, indent).text}>`);
|
|
146
|
+
case 'map':
|
|
147
|
+
return tight(`Map<${print(def.keyType, indent).text}, ${print(def.valueType, indent).text}>`);
|
|
148
|
+
case 'promise':
|
|
149
|
+
return tight(`Promise<${print(def.innerType, indent).text}>`);
|
|
150
|
+
case 'tuple': {
|
|
151
|
+
const items = (def.items ?? []).map((item) => print(item, inner).text);
|
|
152
|
+
if (def.rest) {
|
|
153
|
+
items.push(`...${parenthesized(print(def.rest, inner))}[]`);
|
|
154
|
+
}
|
|
155
|
+
if (items.length === 0)
|
|
156
|
+
return tight('[]');
|
|
157
|
+
return tight(`[\n${items.map((item) => `${inner}${item}`).join(',\n')}\n${indent}]`);
|
|
158
|
+
}
|
|
159
|
+
case 'union':
|
|
160
|
+
return union((def.options ?? []).map((option) => print(option, indent)));
|
|
161
|
+
case 'intersection':
|
|
162
|
+
return loose(`${parenthesized(print(def.left, indent))} & ${parenthesized(print(def.right, indent))}`);
|
|
163
|
+
case 'record':
|
|
164
|
+
return tight(`{\n${inner}[key: ${print(def.keyType, inner).text}]: ${print(def.valueType, inner).text};\n${indent}}`);
|
|
165
|
+
case 'object':
|
|
166
|
+
case 'interface': {
|
|
167
|
+
const shape = def.shape ?? {};
|
|
168
|
+
const entries = Object.entries(shape);
|
|
169
|
+
if (entries.length === 0 && !def.catchall)
|
|
170
|
+
return tight('{}');
|
|
171
|
+
const members = entries.map(([name, property]) => {
|
|
172
|
+
const optional = isNode(property) && isOptional(property);
|
|
173
|
+
const printed = print(property, inner);
|
|
174
|
+
const type = optional
|
|
175
|
+
? `${parenthesized(printed)} | undefined`
|
|
176
|
+
: printed.text;
|
|
177
|
+
const doc = jsDoc(isNode(property) ? property.description : undefined, inner);
|
|
178
|
+
return `${doc}${inner}${propertyKey(name)}${optional ? '?' : ''}: ${type};`;
|
|
179
|
+
});
|
|
180
|
+
if (def.catchall) {
|
|
181
|
+
members.push(`${inner}[key: string]: ${print(def.catchall, inner).text};`);
|
|
182
|
+
}
|
|
183
|
+
return tight(`{\n${members.join('\n')}\n${indent}}`);
|
|
184
|
+
}
|
|
185
|
+
case 'nullable':
|
|
186
|
+
return union([print(def.innerType, indent), tight('null')]);
|
|
187
|
+
case 'lazy': {
|
|
188
|
+
try {
|
|
189
|
+
return print(def.getter(), indent);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// A lazy schema that cannot be forced tells us nothing about its type.
|
|
193
|
+
return tight('any');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
default:
|
|
197
|
+
// optional, default, prefault, nonoptional, readonly, catch, success,
|
|
198
|
+
// pipe — every wrapper Zod spells `innerType`, and a pipe's input is the
|
|
199
|
+
// shape a caller writes.
|
|
200
|
+
if (def.innerType)
|
|
201
|
+
return print(def.innerType, indent);
|
|
202
|
+
if (def.in)
|
|
203
|
+
return print(def.in, indent);
|
|
204
|
+
return tight('any');
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Whether a property may be omitted.
|
|
209
|
+
*
|
|
210
|
+
* `.default()` counts. `processZodSchema` strips every defaulted field out of
|
|
211
|
+
* the JSON Schema's `required`, because a caller who omits one gets the
|
|
212
|
+
* default rather than a validation error — so the text has to say the same
|
|
213
|
+
* thing the validator does.
|
|
214
|
+
*/
|
|
215
|
+
const isOptional = (node) => {
|
|
216
|
+
const def = node._zod.def;
|
|
217
|
+
if (def.type === 'optional')
|
|
218
|
+
return true;
|
|
219
|
+
if (def.type === 'default' || def.type === 'prefault')
|
|
220
|
+
return true;
|
|
221
|
+
if (def.type === 'nonoptional')
|
|
222
|
+
return false;
|
|
223
|
+
if (def.innerType && isNode(def.innerType))
|
|
224
|
+
return isOptional(def.innerType);
|
|
225
|
+
return false;
|
|
226
|
+
};
|
|
227
|
+
return print(schema, '').text;
|
|
228
|
+
}
|
package/dist/visit.js
CHANGED
|
@@ -31,6 +31,7 @@ import { addWorkflowGraph } from './add/add-workflow-graph.js';
|
|
|
31
31
|
import { addFeature } from './add/add-feature.js';
|
|
32
32
|
import { addAgent } from './add/add-agent.js';
|
|
33
33
|
import { addAnalytics } from './add/add-analytics.js';
|
|
34
|
+
import { addOutgoingWebhook } from './add/add-outgoing-webhook.js';
|
|
34
35
|
import { addAgentScorer } from './add/add-agent-scorer.js';
|
|
35
36
|
import { addApprovalDescription } from './add/add-approval-description.js';
|
|
36
37
|
export const visitSetup = (logger, checker, node, state, options) => {
|
|
@@ -43,6 +44,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
43
44
|
addFileWithFactory(node, checker, state.configFactories, 'CreateConfig');
|
|
44
45
|
addFileWithFactory(node, checker, state.serverLifecycleFactories, 'ServerLifecycle');
|
|
45
46
|
addAnalytics(logger, node, checker, state);
|
|
47
|
+
addOutgoingWebhook(logger, node, checker, state);
|
|
46
48
|
addRPCInvocations(node, state, logger);
|
|
47
49
|
addWireAddon(node, state, logger);
|
|
48
50
|
addWireRemoteAddon(node, state, logger);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.91",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -51,14 +51,13 @@
|
|
|
51
51
|
"ts-json-schema-generator": "2.9.1-next.18",
|
|
52
52
|
"tsx": "^4.23.12",
|
|
53
53
|
"typescript": "^6.0.3",
|
|
54
|
-
"zod": "^4.4.3"
|
|
55
|
-
"zod-to-ts": "^2.1.0"
|
|
54
|
+
"zod": "^4.4.3"
|
|
56
55
|
},
|
|
57
56
|
"peerDependencies": {
|
|
58
|
-
"@pikku/core": "^0.12.
|
|
57
|
+
"@pikku/core": "^0.12.125"
|
|
59
58
|
},
|
|
60
59
|
"devDependencies": {
|
|
61
|
-
"@pikku/core": "^0.12.
|
|
60
|
+
"@pikku/core": "^0.12.125",
|
|
62
61
|
"@types/node": "^24.13.3"
|
|
63
62
|
},
|
|
64
63
|
"engines": {
|