@pikku/inspector 0.12.42 → 0.12.44
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 +266 -0
- package/dist/add/add-addon-bans.js +1 -0
- package/dist/add/add-ai-agent.d.ts +3 -1
- package/dist/add/add-ai-agent.js +82 -0
- package/dist/add/add-credential.js +5 -1
- package/dist/add/add-functions.js +10 -9
- package/dist/add/add-gateway.js +3 -0
- package/dist/add/add-http-route.js +2 -2
- package/dist/add/add-keyed-wiring.js +2 -0
- package/dist/add/add-mcp-prompt.js +0 -1
- package/dist/add/add-mcp-resource.js +0 -1
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +3 -177
- package/dist/add/add-queue-worker.js +3 -0
- package/dist/add/add-schedule.js +3 -0
- package/dist/add/add-scope.d.ts +2 -0
- package/dist/add/add-scope.js +146 -0
- package/dist/add/add-trigger.js +3 -0
- package/dist/add/add-wire-remote-addon.d.ts +10 -0
- package/dist/add/add-wire-remote-addon.js +66 -0
- package/dist/add/add-workflow-graph.js +32 -3
- package/dist/add/add-workflow.js +9 -4
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +4 -0
- package/dist/inspector.js +11 -5
- package/dist/types.d.ts +19 -13
- package/dist/utils/custom-types-generator.js +2 -1
- package/dist/utils/ensure-function-metadata.d.ts +15 -0
- package/dist/utils/ensure-function-metadata.js +24 -0
- package/dist/utils/filter-inspector-state.js +9 -0
- package/dist/utils/get-property-value.d.ts +7 -2
- package/dist/utils/get-property-value.js +48 -1
- package/dist/utils/load-addon-functions-meta.js +81 -6
- package/dist/utils/permissions.d.ts +1 -21
- package/dist/utils/permissions.js +17 -108
- package/dist/utils/post-process.d.ts +30 -1
- package/dist/utils/post-process.js +150 -72
- package/dist/utils/schema-generator.js +19 -4
- package/dist/utils/serialize-inspector-state.d.ts +13 -8
- package/dist/utils/serialize-inspector-state.js +12 -6
- package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
- package/dist/utils/serialize-permissions-groups-meta.js +0 -26
- package/dist/utils/workflow/derive-workflow-plan.js +9 -3
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +373 -91
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +274 -100
- package/dist/utils/workflow/dsl/patterns.d.ts +4 -0
- package/dist/utils/workflow/dsl/patterns.js +11 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +76 -5
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +8 -4
- package/dist/visit.js +4 -0
- package/package.json +3 -3
- package/run-tests.sh +1 -1
- package/src/add/add-addon-bans.ts +1 -0
- package/src/add/add-ai-agent.test.ts +87 -0
- package/src/add/add-ai-agent.ts +122 -0
- package/src/add/add-credential.ts +8 -0
- package/src/add/add-functions.ts +10 -12
- package/src/add/add-gateway.ts +11 -0
- package/src/add/add-http-route.ts +2 -8
- package/src/add/add-keyed-wiring.ts +2 -0
- package/src/add/add-mcp-prompt.ts +0 -1
- package/src/add/add-mcp-resource.ts +0 -1
- package/src/add/add-permission.ts +4 -242
- package/src/add/add-queue-worker.ts +11 -0
- package/src/add/add-schedule.ts +11 -0
- package/src/add/add-scope.test.ts +346 -0
- package/src/add/add-scope.ts +225 -0
- package/src/add/add-trigger.ts +11 -0
- package/src/add/add-wire-remote-addon.ts +77 -0
- package/src/add/add-workflow-edge-cases.test.ts +201 -0
- package/src/add/add-workflow-fanout.test.ts +60 -0
- package/src/add/add-workflow-graph-input.test.ts +94 -0
- package/src/add/add-workflow-graph-notes.test.ts +74 -0
- package/src/add/add-workflow-graph.ts +35 -3
- package/src/add/add-workflow-on-error.test.ts +118 -0
- package/src/add/add-workflow-return-switch.test.ts +121 -0
- package/src/add/add-workflow-silent-drops.test.ts +163 -0
- package/src/add/add-workflow-sleep-expression.test.ts +97 -0
- package/src/add/add-workflow.ts +9 -4
- package/src/add/config-value-metadata.test.ts +169 -0
- package/src/add/inline-wiring-function.test.ts +104 -0
- package/src/add/validate-workflow-graph-addons.test.ts +102 -0
- package/src/error-codes.ts +5 -0
- package/src/inspector.ts +14 -4
- package/src/types.ts +16 -17
- package/src/utils/custom-types-generator.test.ts +26 -1
- package/src/utils/custom-types-generator.ts +2 -1
- package/src/utils/ensure-function-metadata.ts +42 -0
- package/src/utils/filter-inspector-state.test.ts +0 -2
- package/src/utils/filter-inspector-state.ts +9 -0
- package/src/utils/get-property-value.test.ts +141 -0
- package/src/utils/get-property-value.ts +62 -1
- package/src/utils/load-addon-functions-meta.test.ts +157 -0
- package/src/utils/load-addon-functions-meta.ts +94 -6
- package/src/utils/load-addon-scopes.test.ts +138 -0
- package/src/utils/permissions.test.ts +7 -232
- package/src/utils/permissions.ts +20 -160
- package/src/utils/post-process.test.ts +269 -4
- package/src/utils/post-process.ts +216 -95
- package/src/utils/schema-generator.test.ts +164 -0
- package/src/utils/schema-generator.ts +26 -3
- package/src/utils/serialize-inspector-state.ts +22 -25
- package/src/utils/serialize-permissions-groups-meta.ts +0 -28
- package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
- package/src/utils/workflow/derive-workflow-plan.ts +11 -4
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +480 -119
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +71 -0
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +99 -0
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +161 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +121 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +116 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +145 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +311 -105
- package/src/utils/workflow/dsl/patterns.ts +18 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +82 -10
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +105 -0
- package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +11 -2
- package/src/visit.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,34 +1,14 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { extractFunctionName
|
|
2
|
+
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
3
3
|
import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract-services.js';
|
|
4
|
-
import { extractPermissionPikkuNames } from '../utils/permissions.js';
|
|
5
4
|
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
6
5
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
7
|
-
function renameTempDefinitions(state, definitionIds, groupType, groupKey) {
|
|
8
|
-
const tempIndices = definitionIds
|
|
9
|
-
.map((name, i) => (name.startsWith('__temp_') ? i : -1))
|
|
10
|
-
.filter((i) => i >= 0);
|
|
11
|
-
for (const idx of tempIndices) {
|
|
12
|
-
const oldId = definitionIds[idx];
|
|
13
|
-
const newId = tempIndices.length === 1
|
|
14
|
-
? makeContextBasedId(groupType, groupKey)
|
|
15
|
-
: makeContextBasedId(groupType, groupKey, String(idx));
|
|
16
|
-
const existing = state.permissions.definitions[oldId];
|
|
17
|
-
if (existing) {
|
|
18
|
-
delete state.permissions.definitions[oldId];
|
|
19
|
-
state.permissions.definitions[newId] = existing;
|
|
20
|
-
}
|
|
21
|
-
definitionIds[idx] = newId;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
6
|
function isInsidePermissionContainer(node) {
|
|
25
7
|
let current = node.parent;
|
|
26
8
|
while (current) {
|
|
27
9
|
if (ts.isCallExpression(current) &&
|
|
28
10
|
ts.isIdentifier(current.expression) &&
|
|
29
|
-
|
|
30
|
-
current.expression.text === 'addTagPermission' ||
|
|
31
|
-
current.expression.text === 'addHTTPPermission')) {
|
|
11
|
+
current.expression.text === 'pikkuPermissionFactory') {
|
|
32
12
|
return true;
|
|
33
13
|
}
|
|
34
14
|
current = current.parent;
|
|
@@ -36,7 +16,7 @@ function isInsidePermissionContainer(node) {
|
|
|
36
16
|
return false;
|
|
37
17
|
}
|
|
38
18
|
/**
|
|
39
|
-
* Inspect pikkuPermission
|
|
19
|
+
* Inspect pikkuPermission, pikkuAuth, and pikkuPermissionFactory definitions.
|
|
40
20
|
*/
|
|
41
21
|
export const addPermission = (logger, node, checker, state) => {
|
|
42
22
|
if (!ts.isCallExpression(node))
|
|
@@ -249,158 +229,4 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
249
229
|
logger.debug(`• Found permission factory with services: ${services.services.join(', ')}`);
|
|
250
230
|
return;
|
|
251
231
|
}
|
|
252
|
-
// Handle addPermission('tag', [permission1, permission2])
|
|
253
|
-
// Supports two patterns:
|
|
254
|
-
// 1. export const x = () => addTagPermission('tag', [...]) (factory - tree-shakeable)
|
|
255
|
-
// 2. export const x = addTagPermission('tag', [...]) (direct - no tree-shaking)
|
|
256
|
-
if (expression.text === 'addTagPermission') {
|
|
257
|
-
const tagArg = args[0];
|
|
258
|
-
const permissionsArrayArg = args[1];
|
|
259
|
-
if (!tagArg || !permissionsArrayArg)
|
|
260
|
-
return;
|
|
261
|
-
// Extract tag name
|
|
262
|
-
let tag;
|
|
263
|
-
if (ts.isStringLiteral(tagArg)) {
|
|
264
|
-
tag = tagArg.text;
|
|
265
|
-
}
|
|
266
|
-
if (!tag) {
|
|
267
|
-
logger.warn(`• addTagPermission call without valid tag string`);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
// Check if permissions is a literal array or object
|
|
271
|
-
if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
|
|
272
|
-
!ts.isObjectLiteralExpression(permissionsArrayArg)) {
|
|
273
|
-
logger.error(`• addTagPermission('${tag}', ...) must have a literal array or object as second argument`);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
// Extract permission pikkuFuncIds from array
|
|
277
|
-
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
278
|
-
if (permissionNames.length > 0) {
|
|
279
|
-
renameTempDefinitions(state, permissionNames, 'tag', tag);
|
|
280
|
-
}
|
|
281
|
-
const allServices = new Set();
|
|
282
|
-
for (const permissionName of permissionNames) {
|
|
283
|
-
const permissionMeta = state.permissions.definitions[permissionName];
|
|
284
|
-
if (permissionMeta && permissionMeta.services) {
|
|
285
|
-
for (const service of permissionMeta.services.services) {
|
|
286
|
-
allServices.add(service);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
let isFactory = false;
|
|
291
|
-
let exportedName = null;
|
|
292
|
-
let parent = node.parent;
|
|
293
|
-
if (parent && ts.isArrowFunction(parent)) {
|
|
294
|
-
if (parent.parameters.length === 0) {
|
|
295
|
-
isFactory = true;
|
|
296
|
-
const arrowParent = parent.parent;
|
|
297
|
-
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
298
|
-
if (ts.isIdentifier(arrowParent.name)) {
|
|
299
|
-
if (isNamedExport(arrowParent)) {
|
|
300
|
-
exportedName = arrowParent.name.text;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (!isFactory) {
|
|
307
|
-
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
308
|
-
exportedName = extracted.exportedName;
|
|
309
|
-
}
|
|
310
|
-
if (!isFactory && exportedName) {
|
|
311
|
-
logger.warn(`• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
312
|
-
`For tree-shaking, use: export const ${exportedName} = () => addTagPermission('${tag}', [...])`);
|
|
313
|
-
}
|
|
314
|
-
state.permissions.tagPermissions.set(tag, {
|
|
315
|
-
exportName: exportedName,
|
|
316
|
-
sourceFile: node.getSourceFile().fileName,
|
|
317
|
-
position: node.getStart(),
|
|
318
|
-
services: {
|
|
319
|
-
optimized: false,
|
|
320
|
-
services: Array.from(allServices),
|
|
321
|
-
},
|
|
322
|
-
count: permissionNames.length,
|
|
323
|
-
instanceIds: permissionNames,
|
|
324
|
-
isFactory,
|
|
325
|
-
});
|
|
326
|
-
logger.debug(`• Found tag permission group: ${tag} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
// Handle addHTTPPermission(pattern, [permission1, permission2])
|
|
330
|
-
// Supports two patterns:
|
|
331
|
-
// 1. export const x = () => addHTTPPermission('*', [...]) (factory - tree-shakeable)
|
|
332
|
-
// 2. export const x = addHTTPPermission('*', [...]) (direct - no tree-shaking)
|
|
333
|
-
if (expression.text === 'addHTTPPermission') {
|
|
334
|
-
const patternArg = args[0];
|
|
335
|
-
const permissionsArrayArg = args[1];
|
|
336
|
-
if (!patternArg || !permissionsArrayArg)
|
|
337
|
-
return;
|
|
338
|
-
// Extract route pattern
|
|
339
|
-
let pattern;
|
|
340
|
-
if (ts.isStringLiteral(patternArg)) {
|
|
341
|
-
pattern = patternArg.text;
|
|
342
|
-
}
|
|
343
|
-
if (!pattern) {
|
|
344
|
-
logger.warn(`• addHTTPPermission call without valid pattern string`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
// Check if permissions is a literal array or object
|
|
348
|
-
if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
|
|
349
|
-
!ts.isObjectLiteralExpression(permissionsArrayArg)) {
|
|
350
|
-
logger.error(`• addHTTPPermission('${pattern}', ...) must have a literal array or object as second argument`);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
// Extract permission pikkuFuncIds from array
|
|
354
|
-
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
355
|
-
if (permissionNames.length > 0) {
|
|
356
|
-
renameTempDefinitions(state, permissionNames, 'http', pattern);
|
|
357
|
-
}
|
|
358
|
-
const allServices = new Set();
|
|
359
|
-
for (const permissionName of permissionNames) {
|
|
360
|
-
const permissionMeta = state.permissions.definitions[permissionName];
|
|
361
|
-
if (permissionMeta && permissionMeta.services) {
|
|
362
|
-
for (const service of permissionMeta.services.services) {
|
|
363
|
-
allServices.add(service);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
let isFactory = false;
|
|
368
|
-
let exportedName = null;
|
|
369
|
-
let parent = node.parent;
|
|
370
|
-
if (parent && ts.isArrowFunction(parent)) {
|
|
371
|
-
if (parent.parameters.length === 0) {
|
|
372
|
-
isFactory = true;
|
|
373
|
-
const arrowParent = parent.parent;
|
|
374
|
-
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
375
|
-
if (ts.isIdentifier(arrowParent.name)) {
|
|
376
|
-
if (isNamedExport(arrowParent)) {
|
|
377
|
-
exportedName = arrowParent.name.text;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (!isFactory) {
|
|
384
|
-
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
385
|
-
exportedName = extracted.exportedName;
|
|
386
|
-
}
|
|
387
|
-
if (!isFactory && exportedName) {
|
|
388
|
-
logger.warn(`• HTTP permission group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
|
|
389
|
-
`For tree-shaking, use: export const ${exportedName} = () => addHTTPPermission('${pattern}', [...])`);
|
|
390
|
-
}
|
|
391
|
-
state.http.routePermissions.set(pattern, {
|
|
392
|
-
exportName: exportedName,
|
|
393
|
-
sourceFile: node.getSourceFile().fileName,
|
|
394
|
-
position: node.getStart(),
|
|
395
|
-
services: {
|
|
396
|
-
optimized: false,
|
|
397
|
-
services: Array.from(allServices),
|
|
398
|
-
},
|
|
399
|
-
count: permissionNames.length,
|
|
400
|
-
instanceIds: permissionNames,
|
|
401
|
-
isFactory,
|
|
402
|
-
});
|
|
403
|
-
logger.debug(`• Found HTTP route permission group: ${pattern} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
232
|
};
|
|
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
|
+
import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
|
|
5
6
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
7
|
import { extractWireNames } from '../utils/post-process.js';
|
|
7
8
|
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
@@ -44,6 +45,8 @@ export const addQueueWorker = (logger, node, checker, state) => {
|
|
|
44
45
|
logger.critical(ErrorCode.MISSING_QUEUE_NAME, `No 'name' provided for queue processor function '${pikkuFuncId}'.`);
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
48
|
+
// Register metadata for a func inlined into the wiring (see helper).
|
|
49
|
+
ensureInlineWiringFunction(state, pikkuFuncId, name, funcInitializer, checker, extracted.isHelper);
|
|
47
50
|
// --- resolve middleware ---
|
|
48
51
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
49
52
|
// --- track used functions/middleware for service aggregation ---
|
package/dist/add/add-schedule.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
|
+
import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
|
|
5
6
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
7
|
import { extractWireNames } from '../utils/post-process.js';
|
|
7
8
|
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
@@ -43,6 +44,8 @@ export const addSchedule = (logger, node, checker, state, options) => {
|
|
|
43
44
|
if (!nameValue || !scheduleValue) {
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
47
|
+
// Register metadata for a func inlined into the wiring (see helper).
|
|
48
|
+
ensureInlineWiringFunction(state, pikkuFuncId, nameValue, funcInitializer, checker, extracted.isHelper);
|
|
46
49
|
// --- resolve middleware ---
|
|
47
50
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
48
51
|
// --- track used functions/middleware for service aggregation ---
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
const SEPARATOR = ':';
|
|
5
|
+
const WILDCARD = '*';
|
|
6
|
+
/**
|
|
7
|
+
* Validates a single scope segment, reporting rather than throwing so the
|
|
8
|
+
* inspector can surface every problem in one pass.
|
|
9
|
+
*
|
|
10
|
+
* @returns true when the segment is usable.
|
|
11
|
+
*/
|
|
12
|
+
const isValidSegment = (segment, scopeName, logger) => {
|
|
13
|
+
if (segment.length === 0) {
|
|
14
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Scope '${scopeName}' contains an empty segment.`);
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (segment.includes(SEPARATOR)) {
|
|
18
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Scope segment '${segment}' in '${scopeName}' contains the '${SEPARATOR}' separator. ` +
|
|
19
|
+
`Nest scopes with the 'scopes' property instead of embedding '${SEPARATOR}' in a name.`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (segment === WILDCARD) {
|
|
23
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Scope segment '${segment}' in '${scopeName}' is the wildcard. ` +
|
|
24
|
+
`'${WILDCARD}' is reserved for granting a scope and its descendants, and cannot be declared.`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Recursively extracts a `scopes: { ... }` object literal into nested metadata.
|
|
31
|
+
*/
|
|
32
|
+
const extractScopeNodes = (obj, scopeName, logger) => {
|
|
33
|
+
const nodes = {};
|
|
34
|
+
for (const prop of obj.properties) {
|
|
35
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
let segment;
|
|
39
|
+
if (ts.isIdentifier(prop.name)) {
|
|
40
|
+
segment = prop.name.text;
|
|
41
|
+
}
|
|
42
|
+
else if (ts.isStringLiteral(prop.name)) {
|
|
43
|
+
segment = prop.name.text;
|
|
44
|
+
}
|
|
45
|
+
if (segment === undefined) {
|
|
46
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `Scope '${scopeName}' has a nested scope whose key is not a literal.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!isValidSegment(segment, scopeName, logger)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
53
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Nested scope '${segment}' in '${scopeName}' must be an object literal.`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const node = {};
|
|
57
|
+
const displayName = getPropertyValue(prop.initializer, 'displayName');
|
|
58
|
+
if (displayName) {
|
|
59
|
+
node.displayName = displayName;
|
|
60
|
+
}
|
|
61
|
+
const description = getPropertyValue(prop.initializer, 'description');
|
|
62
|
+
if (description) {
|
|
63
|
+
node.description = description;
|
|
64
|
+
}
|
|
65
|
+
const nestedProp = prop.initializer.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
66
|
+
ts.isIdentifier(p.name) &&
|
|
67
|
+
p.name.text === 'scopes');
|
|
68
|
+
if (nestedProp &&
|
|
69
|
+
ts.isPropertyAssignment(nestedProp) &&
|
|
70
|
+
ts.isObjectLiteralExpression(nestedProp.initializer)) {
|
|
71
|
+
const nested = extractScopeNodes(nestedProp.initializer, scopeName, logger);
|
|
72
|
+
if (nested) {
|
|
73
|
+
node.scopes = nested;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
nodes[segment] = node;
|
|
77
|
+
}
|
|
78
|
+
return Object.keys(nodes).length > 0 ? nodes : undefined;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Unwraps `x as const` / `x as any` so a cast declaration is still extracted
|
|
82
|
+
* rather than silently skipped.
|
|
83
|
+
*/
|
|
84
|
+
const unwrapAs = (node) => ts.isAsExpression(node) || ts.isSatisfiesExpression(node)
|
|
85
|
+
? unwrapAs(node.expression)
|
|
86
|
+
: node;
|
|
87
|
+
export const addScope = (logger, node, checker, state, _options) => {
|
|
88
|
+
if (!ts.isCallExpression(node)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const expression = node.expression;
|
|
92
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireScope') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const firstArg = node.arguments[0];
|
|
96
|
+
if (!firstArg) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const unwrapped = unwrapAs(firstArg);
|
|
100
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
104
|
+
// Roots are keyed exactly like the nodes beneath them, so each property of
|
|
105
|
+
// the call's single argument is one tree.
|
|
106
|
+
for (const prop of unwrapped.properties) {
|
|
107
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let name;
|
|
111
|
+
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
|
|
112
|
+
name = prop.name.text;
|
|
113
|
+
}
|
|
114
|
+
if (name === undefined) {
|
|
115
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A scope is declared with a key that is not a literal.');
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!isValidSegment(name, name, logger)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
122
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Scope '${name}' must be an object literal.`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const root = prop.initializer;
|
|
126
|
+
const displayNameValue = getPropertyValue(root, 'displayName');
|
|
127
|
+
const descriptionValue = getPropertyValue(root, 'description');
|
|
128
|
+
let scopes;
|
|
129
|
+
const scopesProp = root.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
130
|
+
ts.isIdentifier(p.name) &&
|
|
131
|
+
p.name.text === 'scopes');
|
|
132
|
+
if (scopesProp &&
|
|
133
|
+
ts.isPropertyAssignment(scopesProp) &&
|
|
134
|
+
ts.isObjectLiteralExpression(scopesProp.initializer)) {
|
|
135
|
+
scopes = extractScopeNodes(scopesProp.initializer, name, logger);
|
|
136
|
+
}
|
|
137
|
+
state.scopes.files.add(sourceFile);
|
|
138
|
+
state.scopes.definitions.push({
|
|
139
|
+
name,
|
|
140
|
+
displayName: displayNameValue || undefined,
|
|
141
|
+
description: descriptionValue || undefined,
|
|
142
|
+
scopes,
|
|
143
|
+
sourceFile,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
package/dist/add/add-trigger.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
|
+
import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
|
|
5
6
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
7
|
import { extractWireNames } from '../utils/post-process.js';
|
|
7
8
|
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
@@ -45,6 +46,8 @@ const addWireTrigger = (logger, node, checker, state, firstArg) => {
|
|
|
45
46
|
if (!nameValue) {
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
49
|
+
// Register metadata for a func inlined into the wiring (see helper).
|
|
50
|
+
ensureInlineWiringFunction(state, pikkuFuncId, nameValue, funcInitializer, checker, extracted.isHelper);
|
|
48
51
|
// --- resolve middleware ---
|
|
49
52
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
50
53
|
// --- track used functions/middleware for service aggregation ---
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detect wireRemoteAddon({ name: '...', package: '...' }) call expressions and
|
|
5
|
+
* record the namespace as a **remote** addon declaration. Only name + package
|
|
6
|
+
* are needed for codegen (serverUrl/auth are runtime closures resolved by the
|
|
7
|
+
* RPC runner); the `remote` marker tells the consumer's RPC-map generator to
|
|
8
|
+
* import the addon's `.remote.gen` map instead of `.internal.gen`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function addWireRemoteAddon(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Detect wireRemoteAddon({ name: '...', package: '...' }) call expressions and
|
|
4
|
+
* record the namespace as a **remote** addon declaration. Only name + package
|
|
5
|
+
* are needed for codegen (serverUrl/auth are runtime closures resolved by the
|
|
6
|
+
* RPC runner); the `remote` marker tells the consumer's RPC-map generator to
|
|
7
|
+
* import the addon's `.remote.gen` map instead of `.internal.gen`.
|
|
8
|
+
*/
|
|
9
|
+
export function addWireRemoteAddon(node, state, logger) {
|
|
10
|
+
if (!ts.isCallExpression(node))
|
|
11
|
+
return;
|
|
12
|
+
const { expression, arguments: args } = node;
|
|
13
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireRemoteAddon')
|
|
14
|
+
return;
|
|
15
|
+
const [firstArg] = args;
|
|
16
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
17
|
+
return;
|
|
18
|
+
let name;
|
|
19
|
+
let pkg;
|
|
20
|
+
// The auth binding is a runtime closure; we only extract the statically
|
|
21
|
+
// knowable slot names so `pikku verify` can check they exist in the
|
|
22
|
+
// consumer's own wirings. `hasAuth` distinguishes "public" (omitted) from
|
|
23
|
+
// "bound via a custom resolve()" (present but no static id).
|
|
24
|
+
let hasAuth = false;
|
|
25
|
+
let authCredentialId;
|
|
26
|
+
let authSecretId;
|
|
27
|
+
for (const prop of firstArg.properties) {
|
|
28
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
29
|
+
continue;
|
|
30
|
+
const key = prop.name.text;
|
|
31
|
+
if (key === 'name' && ts.isStringLiteral(prop.initializer)) {
|
|
32
|
+
name = prop.initializer.text;
|
|
33
|
+
}
|
|
34
|
+
else if (key === 'package' && ts.isStringLiteral(prop.initializer)) {
|
|
35
|
+
pkg = prop.initializer.text;
|
|
36
|
+
}
|
|
37
|
+
else if (key === 'auth' &&
|
|
38
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
39
|
+
hasAuth = true;
|
|
40
|
+
for (const authProp of prop.initializer.properties) {
|
|
41
|
+
if (!ts.isPropertyAssignment(authProp) || !ts.isIdentifier(authProp.name))
|
|
42
|
+
continue;
|
|
43
|
+
if (authProp.name.text === 'credentialId' &&
|
|
44
|
+
ts.isStringLiteral(authProp.initializer)) {
|
|
45
|
+
authCredentialId = authProp.initializer.text;
|
|
46
|
+
}
|
|
47
|
+
else if (authProp.name.text === 'secretId' &&
|
|
48
|
+
ts.isStringLiteral(authProp.initializer)) {
|
|
49
|
+
authSecretId = authProp.initializer.text;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!name || !pkg)
|
|
55
|
+
return;
|
|
56
|
+
logger.debug(`• Found wireRemoteAddon: ${name} → ${pkg} (remote)`);
|
|
57
|
+
state.rpc.wireAddonDeclarations.set(name, {
|
|
58
|
+
package: pkg,
|
|
59
|
+
remote: true,
|
|
60
|
+
hasAuth,
|
|
61
|
+
authCredentialId,
|
|
62
|
+
authSecretId,
|
|
63
|
+
});
|
|
64
|
+
state.rpc.usedAddons.add(name);
|
|
65
|
+
state.rpc.wireAddonFiles.add(node.getSourceFile().fileName);
|
|
66
|
+
}
|
|
@@ -2,6 +2,11 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
3
|
import { extractStringLiteral } from '../utils/extract-node-value.js';
|
|
4
4
|
function extractAstValue(expr, refParamName, templateParamName) {
|
|
5
|
+
// `'GET' as const` / parenthesised values wrap the real initializer — see
|
|
6
|
+
// through them or the whole property is silently dropped from the meta.
|
|
7
|
+
while (ts.isAsExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
8
|
+
expr = expr.expression;
|
|
9
|
+
}
|
|
5
10
|
if (ts.isStringLiteral(expr)) {
|
|
6
11
|
return expr.text;
|
|
7
12
|
}
|
|
@@ -113,9 +118,13 @@ function extractInputMapping(node, _checker) {
|
|
|
113
118
|
const refParamName = node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
|
|
114
119
|
? node.parameters[0].name.text
|
|
115
120
|
: 'ref';
|
|
121
|
+
// The canonical form imports `template` from '@pikku/core/workflow' and calls
|
|
122
|
+
// it inside a single-param `(ref) => (...)` arrow; the older form passes it as
|
|
123
|
+
// a second arrow parameter. Support both — default to the imported name so a
|
|
124
|
+
// `template(...)` value is never dropped just because it isn't a 2nd param.
|
|
116
125
|
const templateParamName = node.parameters.length > 1 && ts.isIdentifier(node.parameters[1].name)
|
|
117
126
|
? node.parameters[1].name.text
|
|
118
|
-
:
|
|
127
|
+
: 'template';
|
|
119
128
|
const input = {};
|
|
120
129
|
for (const prop of bodyObj.properties) {
|
|
121
130
|
if (!ts.isPropertyAssignment(prop))
|
|
@@ -138,6 +147,12 @@ function extractInputMapping(node, _checker) {
|
|
|
138
147
|
* Extract next config (string, array, or record)
|
|
139
148
|
*/
|
|
140
149
|
function extractNextConfig(node, _checker) {
|
|
150
|
+
// Key-based branch `next` (`{ key: [targets] }`) is authored with an `as any`
|
|
151
|
+
// cast because the graph's NextConfig can't narrow record targets to node-id
|
|
152
|
+
// literals; see through that (and parens) so the routing survives into the meta.
|
|
153
|
+
while (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
|
|
154
|
+
node = node.expression;
|
|
155
|
+
}
|
|
141
156
|
if (ts.isStringLiteral(node)) {
|
|
142
157
|
return node.text;
|
|
143
158
|
}
|
|
@@ -207,6 +222,7 @@ function extractWorkflowGraphConfig(configArg, checker) {
|
|
|
207
222
|
let name;
|
|
208
223
|
let description;
|
|
209
224
|
let tags;
|
|
225
|
+
let notes;
|
|
210
226
|
let disabled;
|
|
211
227
|
let nodesNode;
|
|
212
228
|
let configNode;
|
|
@@ -231,6 +247,12 @@ function extractWorkflowGraphConfig(configArg, checker) {
|
|
|
231
247
|
.filter(ts.isStringLiteral)
|
|
232
248
|
.map((el) => el.text);
|
|
233
249
|
}
|
|
250
|
+
else if (propName === 'notes' &&
|
|
251
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
252
|
+
notes = prop.initializer.elements
|
|
253
|
+
.filter(ts.isStringLiteral)
|
|
254
|
+
.map((el) => el.text);
|
|
255
|
+
}
|
|
234
256
|
else if (propName === 'nodes' &&
|
|
235
257
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
236
258
|
nodesNode = prop.initializer;
|
|
@@ -240,7 +262,7 @@ function extractWorkflowGraphConfig(configArg, checker) {
|
|
|
240
262
|
configNode = prop.initializer;
|
|
241
263
|
}
|
|
242
264
|
}
|
|
243
|
-
return { name, description, tags, disabled, nodesNode, configNode };
|
|
265
|
+
return { name, description, tags, notes, disabled, nodesNode, configNode };
|
|
244
266
|
}
|
|
245
267
|
/**
|
|
246
268
|
* Extract graph nodes from the new pikkuWorkflowGraph format
|
|
@@ -283,6 +305,7 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
283
305
|
onError: undefined,
|
|
284
306
|
retries: undefined,
|
|
285
307
|
retryDelay: undefined,
|
|
308
|
+
notes: undefined,
|
|
286
309
|
};
|
|
287
310
|
}
|
|
288
311
|
// Extract config for each node from 'config' property
|
|
@@ -305,6 +328,7 @@ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
|
305
328
|
nodes[nodeId].input = nodeConfig.input;
|
|
306
329
|
nodes[nodeId].retries = nodeConfig.retries;
|
|
307
330
|
nodes[nodeId].retryDelay = nodeConfig.retryDelay;
|
|
331
|
+
nodes[nodeId].notes = nodeConfig.notes;
|
|
308
332
|
}
|
|
309
333
|
}
|
|
310
334
|
}
|
|
@@ -320,6 +344,7 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
320
344
|
let input = {};
|
|
321
345
|
let retries = undefined;
|
|
322
346
|
let retryDelay = undefined;
|
|
347
|
+
let notes = undefined;
|
|
323
348
|
for (const prop of obj.properties) {
|
|
324
349
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
325
350
|
continue;
|
|
@@ -346,8 +371,11 @@ function extractNodeConfigFromObject(obj, checker) {
|
|
|
346
371
|
retryDelay = prop.initializer.text;
|
|
347
372
|
}
|
|
348
373
|
}
|
|
374
|
+
else if (propName === 'notes') {
|
|
375
|
+
notes = extractStringLiteral(prop.initializer, checker);
|
|
376
|
+
}
|
|
349
377
|
}
|
|
350
|
-
return { next, onError, input, retries, retryDelay };
|
|
378
|
+
return { next, onError, input, retries, retryDelay, notes };
|
|
351
379
|
}
|
|
352
380
|
/**
|
|
353
381
|
* Inspector for pikkuWorkflowGraph() calls
|
|
@@ -402,6 +430,7 @@ export const addWorkflowGraph = (logger, node, checker, state) => {
|
|
|
402
430
|
source: 'graph',
|
|
403
431
|
description: graphConfig.description,
|
|
404
432
|
tags: graphConfig.tags,
|
|
433
|
+
notes: graphConfig.notes,
|
|
405
434
|
nodes: graphNodes,
|
|
406
435
|
entryNodeIds,
|
|
407
436
|
};
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -62,8 +62,8 @@ function hasInlineSteps(steps) {
|
|
|
62
62
|
if (step.defaultSteps && hasInlineSteps(step.defaultSteps))
|
|
63
63
|
return true;
|
|
64
64
|
}
|
|
65
|
-
else if (step.type === 'fanout' && step.
|
|
66
|
-
if (hasInlineSteps(
|
|
65
|
+
else if (step.type === 'fanout' && step.body) {
|
|
66
|
+
if (hasInlineSteps(step.body))
|
|
67
67
|
return true;
|
|
68
68
|
}
|
|
69
69
|
else if (step.type === 'parallel' && step.children) {
|
|
@@ -80,6 +80,11 @@ export function collectInvokedRPCs(steps, rpcs) {
|
|
|
80
80
|
for (const step of steps) {
|
|
81
81
|
if (step.type === 'rpc' && step.rpcName) {
|
|
82
82
|
rpcs.add(step.rpcName);
|
|
83
|
+
// The compensation handler is invoked at runtime too, so it has to be
|
|
84
|
+
// wired like any other step.
|
|
85
|
+
if (step.options?.onError) {
|
|
86
|
+
rpcs.add(step.options.onError);
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
else if (step.type === 'branch') {
|
|
85
90
|
for (const branch of step.branches) {
|
|
@@ -96,8 +101,8 @@ export function collectInvokedRPCs(steps, rpcs) {
|
|
|
96
101
|
if (step.defaultSteps)
|
|
97
102
|
collectInvokedRPCs(step.defaultSteps, rpcs);
|
|
98
103
|
}
|
|
99
|
-
else if (step.type === 'fanout' && step.
|
|
100
|
-
collectInvokedRPCs(
|
|
104
|
+
else if (step.type === 'fanout' && step.body) {
|
|
105
|
+
collectInvokedRPCs(step.body, rpcs);
|
|
101
106
|
}
|
|
102
107
|
else if (step.type === 'parallel' && step.children) {
|
|
103
108
|
collectInvokedRPCs(step.children, rpcs);
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export declare enum ErrorCode {
|
|
|
22
22
|
EXPECT_EVENTUALLY_SCENARIO_ONLY = "PKU675",
|
|
23
23
|
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
|
|
24
24
|
INVALID_DSL_WORKFLOW = "PKU641",
|
|
25
|
+
WORKFLOW_GRAPH_ADDON_NOT_WIRED = "PKU642",
|
|
26
|
+
REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
|
|
27
|
+
REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
|
|
25
28
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
26
29
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
27
30
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
package/dist/error-codes.js
CHANGED
|
@@ -24,6 +24,10 @@ export var ErrorCode;
|
|
|
24
24
|
ErrorCode["EXPECT_EVENTUALLY_SCENARIO_ONLY"] = "PKU675";
|
|
25
25
|
ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
|
|
26
26
|
ErrorCode["INVALID_DSL_WORKFLOW"] = "PKU641";
|
|
27
|
+
ErrorCode["WORKFLOW_GRAPH_ADDON_NOT_WIRED"] = "PKU642";
|
|
28
|
+
// Remote addon (wireRemoteAddon) validation
|
|
29
|
+
ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
|
|
30
|
+
ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
|
|
27
31
|
// Database schema codegen warnings
|
|
28
32
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
29
33
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|