@pikku/inspector 0.12.16 → 0.12.17

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 CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.12.17
2
+
3
+ ### Patch Changes
4
+
5
+ - 2cf67be: Add inline option to pikkuFunc/pikkuSessionlessFunc for workflow step dispatch
6
+
7
+ By default, workflow steps now run inline (no queue hop). Set inline: false on a function to force dispatch through the queue for that step.
8
+
9
+ - Updated dependencies [2cf67be]
10
+ - @pikku/core@0.12.28
11
+
1
12
  ## 0.12.16
2
13
 
3
14
  ### Patch Changes
@@ -277,6 +277,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
277
277
  let deploy;
278
278
  let approvalRequired;
279
279
  let approvalDescription;
280
+ let inline;
280
281
  let version;
281
282
  let objectNode;
282
283
  let nodeDisplayName = null;
@@ -344,6 +345,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
344
345
  readonly_ = getPropertyValue(firstArg, 'readonly');
345
346
  deploy = getPropertyValue(firstArg, 'deploy');
346
347
  approvalRequired = getPropertyValue(firstArg, 'approvalRequired');
348
+ inline = getPropertyValue(firstArg, 'inline');
347
349
  // Extract approvalDescription identifier reference
348
350
  for (const prop of firstArg.properties) {
349
351
  if (ts.isPropertyAssignment(prop) &&
@@ -716,6 +718,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
716
718
  deploy: deploy || undefined,
717
719
  approvalRequired: approvalRequired || undefined,
718
720
  approvalDescription: approvalDescription || undefined,
721
+ inline: inline === false ? false : undefined,
719
722
  implementationHash,
720
723
  version,
721
724
  title,
@@ -7,6 +7,53 @@
7
7
  export function sanitizeTypeName(name) {
8
8
  return name.replace(/[^a-zA-Z0-9_$]/g, '_');
9
9
  }
10
+ const CLASSIFICATION_WRAPPERS = new Set(['Private', 'Pii', 'Secret']);
11
+ function findMatchingAngleBracket(type, startIndex) {
12
+ let depth = 0;
13
+ for (let i = startIndex; i < type.length; i += 1) {
14
+ const char = type[i];
15
+ if (char === '<') {
16
+ depth += 1;
17
+ continue;
18
+ }
19
+ if (char === '>' && (i === 0 || type[i - 1] !== '=')) {
20
+ depth -= 1;
21
+ if (depth === 0) {
22
+ return i;
23
+ }
24
+ }
25
+ }
26
+ return -1;
27
+ }
28
+ function stripClassificationWrappers(type) {
29
+ let output = '';
30
+ let index = 0;
31
+ while (index < type.length) {
32
+ const char = type[index];
33
+ if (!/[A-Za-z_$]/.test(char)) {
34
+ output += char;
35
+ index += 1;
36
+ continue;
37
+ }
38
+ let end = index + 1;
39
+ while (end < type.length && /[A-Za-z0-9_$]/.test(type[end])) {
40
+ end += 1;
41
+ }
42
+ const identifier = type.slice(index, end);
43
+ if (CLASSIFICATION_WRAPPERS.has(identifier) && type[end] === '<') {
44
+ const closingIndex = findMatchingAngleBracket(type, end);
45
+ if (closingIndex !== -1) {
46
+ const inner = type.slice(end + 1, closingIndex);
47
+ output += stripClassificationWrappers(inner);
48
+ index = closingIndex + 1;
49
+ continue;
50
+ }
51
+ }
52
+ output += identifier;
53
+ index = end;
54
+ }
55
+ return output;
56
+ }
10
57
  export function generateCustomTypes(typesMap, requiredTypes) {
11
58
  const typeDeclarations = Array.from(typesMap.customTypes.entries())
12
59
  .sort(([a], [b]) => a.localeCompare(b))
@@ -17,11 +64,13 @@ export function generateCustomTypes(typesMap, requiredTypes) {
17
64
  .map(([originalName, { type, references }]) => {
18
65
  const name = sanitizeTypeName(originalName);
19
66
  references.forEach((refName) => {
20
- if (refName !== '__object' && !refName.startsWith('__object_')) {
67
+ if (refName !== '__object' &&
68
+ !refName.startsWith('__object_') &&
69
+ !CLASSIFICATION_WRAPPERS.has(refName)) {
21
70
  requiredTypes.add(refName);
22
71
  }
23
72
  });
24
- const typeString = type;
73
+ const typeString = stripClassificationWrappers(type);
25
74
  const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g;
26
75
  const potentialTypes = typeString.match(typeNameRegex) || [];
27
76
  potentialTypes.forEach((typeName) => {
@@ -46,12 +95,14 @@ export function generateCustomTypes(typesMap, requiredTypes) {
46
95
  // Type not found in map (ambient/builtin type)
47
96
  }
48
97
  });
49
- if (name === type)
98
+ if (name === typeString)
50
99
  return null;
51
- return `export type ${name} = ${type}`;
100
+ return `export type ${name} = ${typeString}`;
52
101
  });
53
102
  const importsByPath = new Map();
54
103
  for (const typeName of requiredTypes) {
104
+ if (CLASSIFICATION_WRAPPERS.has(typeName))
105
+ continue;
55
106
  try {
56
107
  const typeMeta = typesMap.getTypeMeta(typeName);
57
108
  if (typeMeta.path) {
@@ -212,7 +212,9 @@ async function batchImportWithRegister(logger, sourceFiles) {
212
212
  return null;
213
213
  }
214
214
  finally {
215
- await unregister?.();
215
+ void Promise.resolve(unregister?.()).catch((e) => {
216
+ logger.debug(`tsx unregister() failed: ${e.message}`);
217
+ });
216
218
  }
217
219
  }
218
220
  async function importWithRegister(sourceFile) {
@@ -221,7 +223,7 @@ async function importWithRegister(sourceFile) {
221
223
  return await import(sourceFile);
222
224
  }
223
225
  finally {
224
- await unregister();
226
+ void Promise.resolve(unregister()).catch(() => { });
225
227
  }
226
228
  }
227
229
  function processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTypeStore, printer, fakeSourceFile, logger) {
@@ -1040,6 +1040,29 @@ function extractReturn(statement, context) {
1040
1040
  if (!statement.expression) {
1041
1041
  return null;
1042
1042
  }
1043
+ if (ts.isAwaitExpression(statement.expression) &&
1044
+ ts.isCallExpression(statement.expression.expression)) {
1045
+ const call = statement.expression.expression;
1046
+ if (isWorkflowDoCall(call, context.checker)) {
1047
+ return isInlineDoCall(call)
1048
+ ? extractInlineStep(call, context)
1049
+ : extractRpcStep(call, context);
1050
+ }
1051
+ if (isWorkflowSleepCall(call, context.checker)) {
1052
+ return extractSleepStep(call, context);
1053
+ }
1054
+ }
1055
+ if (ts.isCallExpression(statement.expression)) {
1056
+ const call = statement.expression;
1057
+ if (isWorkflowDoCall(call, context.checker)) {
1058
+ return isInlineDoCall(call)
1059
+ ? extractInlineStep(call, context)
1060
+ : extractRpcStep(call, context);
1061
+ }
1062
+ if (isWorkflowSleepCall(call, context.checker)) {
1063
+ return extractSleepStep(call, context);
1064
+ }
1065
+ }
1043
1066
  if (!ts.isObjectLiteralExpression(statement.expression)) {
1044
1067
  return null;
1045
1068
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.16",
3
+ "version": "0.12.17",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
38
- "@pikku/core": "^0.12.27",
38
+ "@pikku/core": "^0.12.28",
39
39
  "path-to-regexp": "^8.3.0",
40
40
  "ts-json-schema-generator": "^2.5.0",
41
41
  "tsx": "^4.21.0",
@@ -392,6 +392,7 @@ export const addFunctions: AddWiring = (
392
392
  let deploy: 'serverless' | 'server' | 'auto' | undefined
393
393
  let approvalRequired: boolean | undefined
394
394
  let approvalDescription: string | undefined
395
+ let inline: boolean | undefined
395
396
  let version: number | undefined
396
397
  let objectNode: ts.ObjectLiteralExpression | undefined
397
398
  let nodeDisplayName: string | null = null
@@ -487,6 +488,7 @@ export const addFunctions: AddWiring = (
487
488
  approvalRequired = getPropertyValue(firstArg, 'approvalRequired') as
488
489
  | boolean
489
490
  | undefined
491
+ inline = getPropertyValue(firstArg, 'inline') as boolean | undefined
490
492
 
491
493
  // Extract approvalDescription identifier reference
492
494
  for (const prop of firstArg.properties) {
@@ -1004,6 +1006,7 @@ export const addFunctions: AddWiring = (
1004
1006
  deploy: deploy || undefined,
1005
1007
  approvalRequired: approvalRequired || undefined,
1006
1008
  approvalDescription: approvalDescription || undefined,
1009
+ inline: inline === false ? false : undefined,
1007
1010
  implementationHash,
1008
1011
  version,
1009
1012
  title,
@@ -0,0 +1,99 @@
1
+ import { test, describe } from 'node:test'
2
+ import { strict as assert } from 'node:assert'
3
+ import { generateCustomTypes } from './custom-types-generator.js'
4
+ import { TypesMap } from '../types-map.js'
5
+
6
+ function makeTypesMap(
7
+ entries: Record<string, { type: string; references: string[] }>
8
+ ): TypesMap {
9
+ const tm = new TypesMap()
10
+ for (const [name, { type, references }] of Object.entries(entries)) {
11
+ tm.addCustomType(name, type, references)
12
+ }
13
+ return tm
14
+ }
15
+
16
+ describe('generateCustomTypes — classification wrapper stripping', () => {
17
+ test('strips Private<T> wrapper from type alias', () => {
18
+ const tm = makeTypesMap({
19
+ UserEmail: { type: 'Private<string>', references: ['Private'] },
20
+ })
21
+ const result = generateCustomTypes(tm, new Set())
22
+ assert.match(result, /UserEmail/, 'should emit UserEmail alias')
23
+ assert.match(
24
+ result,
25
+ /= string/,
26
+ 'Private<string> should be stripped to string'
27
+ )
28
+ assert.doesNotMatch(result, /Private/, 'Private wrapper must be removed')
29
+ })
30
+
31
+ test('strips Secret<T> wrapper from type alias', () => {
32
+ const tm = makeTypesMap({
33
+ HashedPw: { type: 'Secret<string>', references: ['Secret'] },
34
+ })
35
+ const result = generateCustomTypes(tm, new Set())
36
+ assert.match(result, /HashedPw/)
37
+ assert.match(
38
+ result,
39
+ /= string/,
40
+ 'Secret<string> should be stripped to string'
41
+ )
42
+ assert.doesNotMatch(result, /Secret/, 'Secret wrapper must be removed')
43
+ })
44
+
45
+ test('strips Pii<T> wrapper from type alias', () => {
46
+ const tm = makeTypesMap({
47
+ UserPhone: { type: 'Pii<string>', references: ['Pii'] },
48
+ })
49
+ const result = generateCustomTypes(tm, new Set())
50
+ assert.match(result, /UserPhone/)
51
+ assert.match(result, /= string/, 'Pii<string> should be stripped to string')
52
+ assert.doesNotMatch(result, /Pii/, 'Pii wrapper must be removed')
53
+ })
54
+
55
+ test('strips nested classification wrappers', () => {
56
+ const tm = makeTypesMap({
57
+ Combo: {
58
+ type: 'Private<Secret<string>>',
59
+ references: ['Private', 'Secret'],
60
+ },
61
+ })
62
+ const result = generateCustomTypes(tm, new Set())
63
+ assert.match(result, /Combo/)
64
+ assert.match(
65
+ result,
66
+ /= string/,
67
+ 'nested wrappers should resolve to inner type'
68
+ )
69
+ assert.doesNotMatch(
70
+ result,
71
+ /Private|Secret/,
72
+ 'all wrappers must be removed'
73
+ )
74
+ })
75
+
76
+ test('does not strip non-classification type names', () => {
77
+ const tm = makeTypesMap({
78
+ MyType: { type: 'SomeOtherType', references: ['SomeOtherType'] },
79
+ })
80
+ const required = new Set<string>()
81
+ generateCustomTypes(tm, required)
82
+ assert.ok(
83
+ required.has('SomeOtherType'),
84
+ 'non-classification references must remain in requiredTypes'
85
+ )
86
+ })
87
+
88
+ test('classification wrapper references are not added to requiredTypes', () => {
89
+ const tm = makeTypesMap({
90
+ SensitiveField: { type: 'Private<string>', references: ['Private'] },
91
+ })
92
+ const required = new Set<string>()
93
+ generateCustomTypes(tm, required)
94
+ assert.ok(
95
+ !required.has('Private'),
96
+ 'Private must not be added to requiredTypes'
97
+ )
98
+ })
99
+ })
@@ -11,6 +11,61 @@ export function sanitizeTypeName(name: string): string {
11
11
  return name.replace(/[^a-zA-Z0-9_$]/g, '_')
12
12
  }
13
13
 
14
+ const CLASSIFICATION_WRAPPERS = new Set(['Private', 'Pii', 'Secret'])
15
+
16
+ function findMatchingAngleBracket(type: string, startIndex: number): number {
17
+ let depth = 0
18
+ for (let i = startIndex; i < type.length; i += 1) {
19
+ const char = type[i]
20
+ if (char === '<') {
21
+ depth += 1
22
+ continue
23
+ }
24
+ if (char === '>' && (i === 0 || type[i - 1] !== '=')) {
25
+ depth -= 1
26
+ if (depth === 0) {
27
+ return i
28
+ }
29
+ }
30
+ }
31
+ return -1
32
+ }
33
+
34
+ function stripClassificationWrappers(type: string): string {
35
+ let output = ''
36
+ let index = 0
37
+
38
+ while (index < type.length) {
39
+ const char = type[index]
40
+ if (!/[A-Za-z_$]/.test(char)) {
41
+ output += char
42
+ index += 1
43
+ continue
44
+ }
45
+
46
+ let end = index + 1
47
+ while (end < type.length && /[A-Za-z0-9_$]/.test(type[end])) {
48
+ end += 1
49
+ }
50
+
51
+ const identifier = type.slice(index, end)
52
+ if (CLASSIFICATION_WRAPPERS.has(identifier) && type[end] === '<') {
53
+ const closingIndex = findMatchingAngleBracket(type, end)
54
+ if (closingIndex !== -1) {
55
+ const inner = type.slice(end + 1, closingIndex)
56
+ output += stripClassificationWrappers(inner)
57
+ index = closingIndex + 1
58
+ continue
59
+ }
60
+ }
61
+
62
+ output += identifier
63
+ index = end
64
+ }
65
+
66
+ return output
67
+ }
68
+
14
69
  export function generateCustomTypes(
15
70
  typesMap: TypesMap,
16
71
  requiredTypes: Set<string>
@@ -25,12 +80,16 @@ export function generateCustomTypes(
25
80
  .map(([originalName, { type, references }]) => {
26
81
  const name = sanitizeTypeName(originalName)
27
82
  references.forEach((refName) => {
28
- if (refName !== '__object' && !refName.startsWith('__object_')) {
83
+ if (
84
+ refName !== '__object' &&
85
+ !refName.startsWith('__object_') &&
86
+ !CLASSIFICATION_WRAPPERS.has(refName)
87
+ ) {
29
88
  requiredTypes.add(refName)
30
89
  }
31
90
  })
32
91
 
33
- const typeString = type
92
+ const typeString = stripClassificationWrappers(type)
34
93
  const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g
35
94
  const potentialTypes = typeString.match(typeNameRegex) || []
36
95
 
@@ -59,12 +118,13 @@ export function generateCustomTypes(
59
118
  }
60
119
  })
61
120
 
62
- if (name === type) return null
63
- return `export type ${name} = ${type}`
121
+ if (name === typeString) return null
122
+ return `export type ${name} = ${typeString}`
64
123
  })
65
124
 
66
125
  const importsByPath = new Map<string, Set<string>>()
67
126
  for (const typeName of requiredTypes) {
127
+ if (CLASSIFICATION_WRAPPERS.has(typeName)) continue
68
128
  try {
69
129
  const typeMeta = typesMap.getTypeMeta(typeName)
70
130
  if (typeMeta.path) {
@@ -297,7 +297,9 @@ async function batchImportWithRegister(
297
297
  logger.debug(`tsx register() batch import failed: ${(e as Error).message}`)
298
298
  return null
299
299
  } finally {
300
- await unregister?.()
300
+ void Promise.resolve(unregister?.()).catch((e) => {
301
+ logger.debug(`tsx unregister() failed: ${(e as Error).message}`)
302
+ })
301
303
  }
302
304
  }
303
305
 
@@ -308,7 +310,7 @@ async function importWithRegister(
308
310
  try {
309
311
  return await import(sourceFile)
310
312
  } finally {
311
- await unregister()
313
+ void Promise.resolve(unregister()).catch(() => {})
312
314
  }
313
315
  }
314
316
 
@@ -6,7 +6,6 @@ import type {
6
6
  BranchStepMeta,
7
7
  ParallelGroupStepMeta,
8
8
  FanoutStepMeta,
9
- ReturnStepMeta,
10
9
  CancelStepMeta,
11
10
  SetStepMeta,
12
11
  SwitchStepMeta,
@@ -1340,11 +1339,38 @@ function extractOutputBinding(
1340
1339
  function extractReturn(
1341
1340
  statement: ts.ReturnStatement,
1342
1341
  context: ExtractionContext
1343
- ): ReturnStepMeta | null {
1342
+ ): WorkflowStepMeta | null {
1344
1343
  if (!statement.expression) {
1345
1344
  return null
1346
1345
  }
1347
1346
 
1347
+ if (
1348
+ ts.isAwaitExpression(statement.expression) &&
1349
+ ts.isCallExpression(statement.expression.expression)
1350
+ ) {
1351
+ const call = statement.expression.expression
1352
+ if (isWorkflowDoCall(call, context.checker)) {
1353
+ return isInlineDoCall(call)
1354
+ ? extractInlineStep(call, context)
1355
+ : extractRpcStep(call, context)
1356
+ }
1357
+ if (isWorkflowSleepCall(call, context.checker)) {
1358
+ return extractSleepStep(call, context)
1359
+ }
1360
+ }
1361
+
1362
+ if (ts.isCallExpression(statement.expression)) {
1363
+ const call = statement.expression
1364
+ if (isWorkflowDoCall(call, context.checker)) {
1365
+ return isInlineDoCall(call)
1366
+ ? extractInlineStep(call, context)
1367
+ : extractRpcStep(call, context)
1368
+ }
1369
+ if (isWorkflowSleepCall(call, context.checker)) {
1370
+ return extractSleepStep(call, context)
1371
+ }
1372
+ }
1373
+
1348
1374
  if (!ts.isObjectLiteralExpression(statement.expression)) {
1349
1375
  return null
1350
1376
  }