quidproquo-actionprocessor-node 0.1.7 → 0.1.9

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.
Files changed (83) hide show
  1. package/lib/commonjs/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.js +4 -0
  2. package/lib/commonjs/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.js.map +1 -1
  3. package/lib/commonjs/dynamicActionProcessor/file/getFileDeleteActionProcessor.js +10 -2
  4. package/lib/commonjs/dynamicActionProcessor/file/getFileDeleteActionProcessor.js.map +1 -1
  5. package/lib/commonjs/dynamicActionProcessor/file/getFileExistsActionProcessor.js +7 -3
  6. package/lib/commonjs/dynamicActionProcessor/file/getFileExistsActionProcessor.js.map +1 -1
  7. package/lib/commonjs/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.js +13 -3
  8. package/lib/commonjs/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.js.map +1 -1
  9. package/lib/commonjs/dynamicActionProcessor/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js +13 -3
  10. package/lib/commonjs/dynamicActionProcessor/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js.map +1 -1
  11. package/lib/commonjs/dynamicActionProcessor/file/getFileListDirectoryActionProcessor.js +3 -2
  12. package/lib/commonjs/dynamicActionProcessor/file/getFileListDirectoryActionProcessor.js.map +1 -1
  13. package/lib/commonjs/dynamicActionProcessor/file/getFileReadBinaryContentsActionProcessor.js +3 -2
  14. package/lib/commonjs/dynamicActionProcessor/file/getFileReadBinaryContentsActionProcessor.js.map +1 -1
  15. package/lib/commonjs/dynamicActionProcessor/file/getFileReadObjectJsonActionProcessor.js +5 -4
  16. package/lib/commonjs/dynamicActionProcessor/file/getFileReadObjectJsonActionProcessor.js.map +1 -1
  17. package/lib/commonjs/dynamicActionProcessor/file/getFileReadTextContentsActionProcessor.js +4 -3
  18. package/lib/commonjs/dynamicActionProcessor/file/getFileReadTextContentsActionProcessor.js.map +1 -1
  19. package/lib/commonjs/dynamicActionProcessor/file/getFileStreamOpenActionProcessor.js +3 -2
  20. package/lib/commonjs/dynamicActionProcessor/file/getFileStreamOpenActionProcessor.js.map +1 -1
  21. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteBinaryContentsActionProcessor.js +3 -2
  22. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteBinaryContentsActionProcessor.js.map +1 -1
  23. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteObjectJsonActionProcessor.js +3 -2
  24. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteObjectJsonActionProcessor.js.map +1 -1
  25. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.js +3 -2
  26. package/lib/commonjs/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.js.map +1 -1
  27. package/lib/commonjs/dynamicActionProcessor/file/utils.d.ts +1 -1
  28. package/lib/commonjs/dynamicActionProcessor/file/utils.js +9 -2
  29. package/lib/commonjs/dynamicActionProcessor/file/utils.js.map +1 -1
  30. package/lib/commonjs/traceStoryExecution/getSystemTraceStoryActionProcessor.d.ts +3 -2
  31. package/lib/commonjs/traceStoryExecution/getSystemTraceStoryActionProcessor.js +4 -3
  32. package/lib/commonjs/traceStoryExecution/getSystemTraceStoryActionProcessor.js.map +1 -1
  33. package/lib/commonjs/traceStoryExecution/index.d.ts +1 -0
  34. package/lib/commonjs/traceStoryExecution/index.js +1 -0
  35. package/lib/commonjs/traceStoryExecution/index.js.map +1 -1
  36. package/lib/commonjs/traceStoryExecution/resolveSourceMaps.d.ts +2 -1
  37. package/lib/commonjs/traceStoryExecution/resolveSourceMaps.js +24 -3
  38. package/lib/commonjs/traceStoryExecution/resolveSourceMaps.js.map +1 -1
  39. package/lib/commonjs/traceStoryExecution/traceStoryExecution.d.ts +1 -0
  40. package/lib/commonjs/traceStoryExecution/traceStoryExecution.js +1 -1
  41. package/lib/commonjs/traceStoryExecution/traceStoryExecution.js.map +1 -1
  42. package/lib/esm/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.js +4 -0
  43. package/lib/esm/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.js.map +1 -1
  44. package/lib/esm/dynamicActionProcessor/file/getFileDeleteActionProcessor.js +10 -2
  45. package/lib/esm/dynamicActionProcessor/file/getFileDeleteActionProcessor.js.map +1 -1
  46. package/lib/esm/dynamicActionProcessor/file/getFileExistsActionProcessor.js +8 -4
  47. package/lib/esm/dynamicActionProcessor/file/getFileExistsActionProcessor.js.map +1 -1
  48. package/lib/esm/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.js +14 -4
  49. package/lib/esm/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.js.map +1 -1
  50. package/lib/esm/dynamicActionProcessor/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js +14 -4
  51. package/lib/esm/dynamicActionProcessor/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js.map +1 -1
  52. package/lib/esm/dynamicActionProcessor/file/getFileListDirectoryActionProcessor.js +3 -2
  53. package/lib/esm/dynamicActionProcessor/file/getFileListDirectoryActionProcessor.js.map +1 -1
  54. package/lib/esm/dynamicActionProcessor/file/getFileReadBinaryContentsActionProcessor.js +3 -2
  55. package/lib/esm/dynamicActionProcessor/file/getFileReadBinaryContentsActionProcessor.js.map +1 -1
  56. package/lib/esm/dynamicActionProcessor/file/getFileReadObjectJsonActionProcessor.js +6 -5
  57. package/lib/esm/dynamicActionProcessor/file/getFileReadObjectJsonActionProcessor.js.map +1 -1
  58. package/lib/esm/dynamicActionProcessor/file/getFileReadTextContentsActionProcessor.js +5 -4
  59. package/lib/esm/dynamicActionProcessor/file/getFileReadTextContentsActionProcessor.js.map +1 -1
  60. package/lib/esm/dynamicActionProcessor/file/getFileStreamOpenActionProcessor.js +3 -2
  61. package/lib/esm/dynamicActionProcessor/file/getFileStreamOpenActionProcessor.js.map +1 -1
  62. package/lib/esm/dynamicActionProcessor/file/getFileWriteBinaryContentsActionProcessor.js +3 -2
  63. package/lib/esm/dynamicActionProcessor/file/getFileWriteBinaryContentsActionProcessor.js.map +1 -1
  64. package/lib/esm/dynamicActionProcessor/file/getFileWriteObjectJsonActionProcessor.js +3 -2
  65. package/lib/esm/dynamicActionProcessor/file/getFileWriteObjectJsonActionProcessor.js.map +1 -1
  66. package/lib/esm/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.js +3 -2
  67. package/lib/esm/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.js.map +1 -1
  68. package/lib/esm/dynamicActionProcessor/file/utils.d.ts +1 -1
  69. package/lib/esm/dynamicActionProcessor/file/utils.js +10 -3
  70. package/lib/esm/dynamicActionProcessor/file/utils.js.map +1 -1
  71. package/lib/esm/traceStoryExecution/getSystemTraceStoryActionProcessor.d.ts +3 -2
  72. package/lib/esm/traceStoryExecution/getSystemTraceStoryActionProcessor.js +4 -3
  73. package/lib/esm/traceStoryExecution/getSystemTraceStoryActionProcessor.js.map +1 -1
  74. package/lib/esm/traceStoryExecution/index.d.ts +1 -0
  75. package/lib/esm/traceStoryExecution/index.js +1 -0
  76. package/lib/esm/traceStoryExecution/index.js.map +1 -1
  77. package/lib/esm/traceStoryExecution/resolveSourceMaps.d.ts +2 -1
  78. package/lib/esm/traceStoryExecution/resolveSourceMaps.js +22 -2
  79. package/lib/esm/traceStoryExecution/resolveSourceMaps.js.map +1 -1
  80. package/lib/esm/traceStoryExecution/traceStoryExecution.d.ts +1 -0
  81. package/lib/esm/traceStoryExecution/traceStoryExecution.js +1 -1
  82. package/lib/esm/traceStoryExecution/traceStoryExecution.js.map +1 -1
  83. package/package.json +6 -5
@@ -1 +1 @@
1
- {"version":3,"file":"getFileWriteTextContentsActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAUyB;AAEzB,gDAAkC;AAGlC,mCAAuE;AAEvE,MAAM,+BAA+B,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAAwC,EAAE;IAChI,OAAO,KAAkC,EAAE,4CAA7B,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAA,uBAAe,EAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;YACrE,MAAM,IAAA,mCAA2B,EAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC3C,OAAO,IAAA,8BAAY,EAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,IAAA,kDAAgC,EAAC,KAAK,EAAE;gBAC7C,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,oDAAkC,CAAC,YAAY,EAAE,+BAA+B,QAAQ,EAAE,CAAC,EAAE,eAAe;aAC7I,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAA,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,uCAAuC,GAClD,CAAC,MAAyB,EAA+B,EAAE,CAC3D,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAC7D,CAAC,gCAAc,CAAC,iBAAiB,CAAC,EAAE,+BAA+B,CAAC,SAAS,EAAE,MAAM,CAAC;KACvF,CAAC,CAAA;EAAA,CAAC;AAJQ,QAAA,uCAAuC,2CAI/C","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n FileActionType,\n FileWriteTextContentsActionProcessor,\n FileWriteTextContentsErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { ensureParentDirectoryExists, resolveFilePath } from './utils';\n\nconst getProcessFileWriteTextContents = (qpqConfig: QPQConfig, config: FileStorageConfig): FileWriteTextContentsActionProcessor => {\n return async ({ drive, filepath, data }) => {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath);\n await ensureParentDirectoryExists(fullPath);\n await fs.writeFile(fullPath, data, 'utf8');\n return actionResult(void 0);\n } catch (error: unknown) {\n return actionResultErrorFromCaughtError(error, {\n EACCES: () => actionResultError(FileWriteTextContentsErrorTypeEnum.AccessDenied, `Access denied writing file: ${filepath}`), // node fs code\n });\n }\n };\n};\n\nexport const getFileWriteTextContentsActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.WriteTextContents]: getProcessFileWriteTextContents(qpqConfig, config),\n });\n"]}
1
+ {"version":3,"file":"getFileWriteTextContentsActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileWriteTextContentsActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qDAUyB;AAEzB,gDAAkC;AAGlC,mCAAuE;AAEvE,MAAM,+BAA+B,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAAwC,EAAE;IAChI,OAAO,KAAyC,EAAE,4CAApC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE;QAC5C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAA,uBAAe,EAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC5E,MAAM,IAAA,mCAA2B,EAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC3C,OAAO,IAAA,8BAAY,EAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,IAAA,kDAAgC,EAAC,KAAK,EAAE;gBAC7C,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAA,mCAAiB,EAAC,oDAAkC,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;gBAC/G,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,oDAAkC,CAAC,YAAY,EAAE,+BAA+B,QAAQ,EAAE,CAAC,EAAE,eAAe;aAC7I,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAA,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,uCAAuC,GAClD,CAAC,MAAyB,EAA+B,EAAE,CAC3D,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAC7D,CAAC,gCAAc,CAAC,iBAAiB,CAAC,EAAE,+BAA+B,CAAC,SAAS,EAAE,MAAM,CAAC;KACvF,CAAC,CAAA;EAAA,CAAC;AAJQ,QAAA,uCAAuC,2CAI/C","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n FileActionType,\n FileWriteTextContentsActionProcessor,\n FileWriteTextContentsErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { ensureParentDirectoryExists, resolveFilePath } from './utils';\n\nconst getProcessFileWriteTextContents = (qpqConfig: QPQConfig, config: FileStorageConfig): FileWriteTextContentsActionProcessor => {\n return async ({ drive, filepath, data, scope }) => {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath, scope);\n await ensureParentDirectoryExists(fullPath);\n await fs.writeFile(fullPath, data, 'utf8');\n return actionResult(void 0);\n } catch (error: unknown) {\n return actionResultErrorFromCaughtError(error, {\n InvalidScopeError: (error) => actionResultError(FileWriteTextContentsErrorTypeEnum.InvalidScope, error.message),\n EACCES: () => actionResultError(FileWriteTextContentsErrorTypeEnum.AccessDenied, `Access denied writing file: ${filepath}`), // node fs code\n });\n }\n };\n};\n\nexport const getFileWriteTextContentsActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.WriteTextContents]: getProcessFileWriteTextContents(qpqConfig, config),\n });\n"]}
@@ -2,6 +2,6 @@ import { QPQConfig } from 'quidproquo-core';
2
2
  import { FileStorageConfig } from './types';
3
3
  export declare const isStorageDriveNameValid: (drive: string, qpqConfig: QPQConfig) => boolean;
4
4
  export declare function resolveDriveServiceName(drive: string, qpqConfig: QPQConfig): string;
5
- export declare function resolveFilePath(fileStorageConfig: FileStorageConfig, qpqConfig: QPQConfig, drive: string, filepath: string): string;
5
+ export declare function resolveFilePath(fileStorageConfig: FileStorageConfig, qpqConfig: QPQConfig, drive: string, filepath: string, scope?: string): string;
6
6
  export declare const ensureDirectoryExists: (dirPath: string) => Promise<void>;
7
7
  export declare const ensureParentDirectoryExists: (filePath: string) => Promise<void>;
@@ -65,9 +65,16 @@ function resolveDriveServiceName(drive, qpqConfig) {
65
65
  const storageDrive = quidproquo_core_1.qpqCoreUtils.getStorageDriveByName(drive, qpqConfig);
66
66
  return (_b = (_a = storageDrive === null || storageDrive === void 0 ? void 0 : storageDrive.owner) === null || _a === void 0 ? void 0 : _a.module) !== null && _b !== void 0 ? _b : quidproquo_core_1.qpqCoreUtils.getApplicationModuleName(qpqConfig);
67
67
  }
68
- function resolveFilePath(fileStorageConfig, qpqConfig, drive, filepath) {
68
+ function resolveFilePath(fileStorageConfig, qpqConfig, drive, filepath, scope) {
69
69
  const service = resolveDriveServiceName(drive, qpqConfig);
70
- const root = path.resolve(fileStorageConfig.storagePath, service, drive);
70
+ // A scope becomes part of the root, so the escape check below also guarantees
71
+ // the filepath cannot traverse out of its scope, not just out of the drive.
72
+ if (scope !== undefined) {
73
+ (0, quidproquo_core_1.validateScopeSegment)(scope);
74
+ }
75
+ const root = scope
76
+ ? path.resolve(fileStorageConfig.storagePath, service, drive, scope)
77
+ : path.resolve(fileStorageConfig.storagePath, service, drive);
71
78
  // Block absolute paths and null bytes outright
72
79
  if (path.isAbsolute(filepath)) {
73
80
  throw new Error('Absolute paths are not allowed.');
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,0DAGC;AAED,0CA2BC;AArDD,qDAA0D;AAE1D,gDAAkC;AAClC,2CAA6B;AAI7B,4CAA4C;AACrC,MAAM,uBAAuB,GAAG,CAAC,KAAa,EAAE,SAAoB,EAAW,EAAE;IACtF,MAAM,YAAY,GAAG,8BAAY,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC;IAEpG,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AARW,QAAA,uBAAuB,2BAQlC;AAEF,iFAAiF;AACjF,8EAA8E;AAC9E,6EAA6E;AAC7E,SAAgB,uBAAuB,CAAC,KAAa,EAAE,SAAoB;;IACzE,MAAM,YAAY,GAAG,8BAAY,CAAC,qBAAqB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC1E,OAAO,MAAA,MAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,KAAK,0CAAE,MAAM,mCAAI,8BAAY,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC;AACzF,CAAC;AAED,SAAgB,eAAe,CAAC,iBAAoC,EAAE,SAAoB,EAAE,KAAa,EAAE,QAAgB;IACzH,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAEzE,+CAA+C;IAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,uEAAuE;IACvE,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,kDAAkD;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEzC,2BAA2B;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3C,kCAAkC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACnE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAED,4BAA4B;AACrB,MAAM,qBAAqB,GAAG,CAAO,OAAe,EAAiB,EAAE;IAC5E,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC,CAAA,CAAC;AAFW,QAAA,qBAAqB,yBAEhC;AAEF,+CAA+C;AACxC,MAAM,2BAA2B,GAAG,CAAO,QAAgB,EAAiB,EAAE;IACnF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,IAAA,6BAAqB,EAAC,SAAS,CAAC,CAAC;AACzC,CAAC,CAAA,CAAC;AAHW,QAAA,2BAA2B,+BAGtC","sourcesContent":["import { QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nimport { FileStorageConfig } from './types';\n\n// Resolve a drive name to a filesystem path\nexport const isStorageDriveNameValid = (drive: string, qpqConfig: QPQConfig): boolean => {\n const storageDrive = qpqCoreUtils.getStorageDrives(qpqConfig).find((d) => d.storageDrive === drive);\n\n if (!storageDrive) {\n return false;\n }\n\n return true;\n};\n\n// Resolve the service that owns a drive's on-disk location. Drives declared with\n// `owner.module` are shared across services, so every caller reads and writes\n// under the owner's folder — mirroring how AWS resolves the bucket by owner.\nexport function resolveDriveServiceName(drive: string, qpqConfig: QPQConfig): string {\n const storageDrive = qpqCoreUtils.getStorageDriveByName(drive, qpqConfig);\n return storageDrive?.owner?.module ?? qpqCoreUtils.getApplicationModuleName(qpqConfig);\n}\n\nexport function resolveFilePath(fileStorageConfig: FileStorageConfig, qpqConfig: QPQConfig, drive: string, filepath: string): string {\n const service = resolveDriveServiceName(drive, qpqConfig);\n const root = path.resolve(fileStorageConfig.storagePath, service, drive);\n\n // Block absolute paths and null bytes outright\n if (path.isAbsolute(filepath)) {\n throw new Error('Absolute paths are not allowed.');\n }\n\n // Check for null to prevent directory traversal attacks in native libs\n if (filepath.includes('\\0')) {\n throw new Error('Invalid path.');\n }\n\n // Normalize user input (collapses ., .., slashes)\n const cleaned = path.normalize(filepath);\n\n // Resolve against the root\n const target = path.resolve(root, cleaned);\n\n // Ensure target stays within root\n const rel = path.relative(root, target);\n if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {\n return target;\n }\n\n throw new Error('Invalid file path: escapes drive root.');\n}\n\n// Ensure a directory exists\nexport const ensureDirectoryExists = async (dirPath: string): Promise<void> => {\n await fs.mkdir(dirPath, { recursive: true });\n};\n\n// Ensure the parent directory of a file exists\nexport const ensureParentDirectoryExists = async (filePath: string): Promise<void> => {\n const parentDir = path.dirname(filePath);\n await ensureDirectoryExists(parentDir);\n};\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { QPQConfig, qpqCoreUtils, validateScopeSegment } from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nimport { FileStorageConfig } from './types';\n\n// Resolve a drive name to a filesystem path\nexport const isStorageDriveNameValid = (drive: string, qpqConfig: QPQConfig): boolean => {\n const storageDrive = qpqCoreUtils.getStorageDrives(qpqConfig).find((d) => d.storageDrive === drive);\n\n if (!storageDrive) {\n return false;\n }\n\n return true;\n};\n\n// Resolve the service that owns a drive's on-disk location. Drives declared with\n// `owner.module` are shared across services, so every caller reads and writes\n// under the owner's folder — mirroring how AWS resolves the bucket by owner.\nexport function resolveDriveServiceName(drive: string, qpqConfig: QPQConfig): string {\n const storageDrive = qpqCoreUtils.getStorageDriveByName(drive, qpqConfig);\n return storageDrive?.owner?.module ?? qpqCoreUtils.getApplicationModuleName(qpqConfig);\n}\n\nexport function resolveFilePath(fileStorageConfig: FileStorageConfig, qpqConfig: QPQConfig, drive: string, filepath: string, scope?: string): string {\n const service = resolveDriveServiceName(drive, qpqConfig);\n\n // A scope becomes part of the root, so the escape check below also guarantees\n // the filepath cannot traverse out of its scope, not just out of the drive.\n if (scope !== undefined) {\n validateScopeSegment(scope);\n }\n\n const root = scope\n ? path.resolve(fileStorageConfig.storagePath, service, drive, scope)\n : path.resolve(fileStorageConfig.storagePath, service, drive);\n\n // Block absolute paths and null bytes outright\n if (path.isAbsolute(filepath)) {\n throw new Error('Absolute paths are not allowed.');\n }\n\n // Check for null to prevent directory traversal attacks in native libs\n if (filepath.includes('\\0')) {\n throw new Error('Invalid path.');\n }\n\n // Normalize user input (collapses ., .., slashes)\n const cleaned = path.normalize(filepath);\n\n // Resolve against the root\n const target = path.resolve(root, cleaned);\n\n // Ensure target stays within root\n const rel = path.relative(root, target);\n if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {\n return target;\n }\n\n throw new Error('Invalid file path: escapes drive root.');\n}\n\n// Ensure a directory exists\nexport const ensureDirectoryExists = async (dirPath: string): Promise<void> => {\n await fs.mkdir(dirPath, { recursive: true });\n};\n\n// Ensure the parent directory of a file exists\nexport const ensureParentDirectoryExists = async (filePath: string): Promise<void> => {\n const parentDir = path.dirname(filePath);\n await ensureDirectoryExists(parentDir);\n};\n"]}
@@ -1,2 +1,3 @@
1
- import { ActionProcessorListResolver } from 'quidproquo-core';
2
- export declare const getSystemTraceStoryActionProcessor: (defaultScriptPatterns?: string[]) => ActionProcessorListResolver;
1
+ import { ActionProcessorListResolver, QPQConfig, QpqFunctionRuntime } from 'quidproquo-core';
2
+ export type OwnCodeMarkersResolver = (qpqFunctionRuntimeInfo: QpqFunctionRuntime | undefined, qpqConfig: QPQConfig) => string[] | undefined;
3
+ export declare const getSystemTraceStoryActionProcessor: (defaultScriptPatterns?: string[], resolveOwnCodeMarkers?: OwnCodeMarkersResolver) => ActionProcessorListResolver;
@@ -16,7 +16,7 @@ const traceStoryExecution_1 = require("./traceStoryExecution");
16
16
  // Loads the recorded story's real code through the runtime's dynamicModuleLoader (which
17
17
  // federates on lambda), then replays it under the tracer. defaultScriptPatterns lets the
18
18
  // host add environment-specific scripts to trace (e.g. the lambda federated code cache).
19
- const getProcessTraceStory = (qpqConfig, defaultScriptPatterns) => {
19
+ const getProcessTraceStory = (qpqConfig, defaultScriptPatterns, resolveOwnCodeMarkers) => {
20
20
  return (payload, session, actionProcessors, logger, updateSession, dynamicModuleLoader) => __awaiter(void 0, void 0, void 0, function* () {
21
21
  const { storyResult, scriptPatterns, onlyOwnCode } = payload;
22
22
  if (!storyResult.qpqFunctionRuntimeInfo) {
@@ -30,6 +30,7 @@ const getProcessTraceStory = (qpqConfig, defaultScriptPatterns) => {
30
30
  const { trace } = yield (0, traceStoryExecution_1.traceStoryExecution)(storyResult, story, {
31
31
  scriptPatterns: [...defaultScriptPatterns, ...(scriptPatterns || [])],
32
32
  onlyOwnCode,
33
+ ownCodeMarkers: resolveOwnCodeMarkers === null || resolveOwnCodeMarkers === void 0 ? void 0 : resolveOwnCodeMarkers(storyResult.qpqFunctionRuntimeInfo, qpqConfig),
33
34
  });
34
35
  return (0, quidproquo_core_1.actionResult)(trace);
35
36
  }
@@ -38,9 +39,9 @@ const getProcessTraceStory = (qpqConfig, defaultScriptPatterns) => {
38
39
  }
39
40
  });
40
41
  };
41
- const getSystemTraceStoryActionProcessor = (defaultScriptPatterns = []) => (qpqConfig) => __awaiter(void 0, void 0, void 0, function* () {
42
+ const getSystemTraceStoryActionProcessor = (defaultScriptPatterns = [], resolveOwnCodeMarkers) => (qpqConfig) => __awaiter(void 0, void 0, void 0, function* () {
42
43
  return ({
43
- [quidproquo_core_1.SystemActionType.TraceStory]: getProcessTraceStory(qpqConfig, defaultScriptPatterns),
44
+ [quidproquo_core_1.SystemActionType.TraceStory]: getProcessTraceStory(qpqConfig, defaultScriptPatterns, resolveOwnCodeMarkers),
44
45
  });
45
46
  });
46
47
  exports.getSystemTraceStoryActionProcessor = getSystemTraceStoryActionProcessor;
@@ -1 +1 @@
1
- {"version":3,"file":"getSystemTraceStoryActionProcessor.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/getSystemTraceStoryActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAcyB;AAEzB,+DAA4D;AAE5D,qFAAqF;AACrF,wFAAwF;AACxF,yFAAyF;AACzF,yFAAyF;AACzF,MAAM,oBAAoB,GAAG,CAAC,SAAoB,EAAE,qBAA+B,EAAmC,EAAE;IACtH,OAAO,CACL,OAAsC,EACtC,OAAqB,EACrB,gBAAqC,EACrC,MAAiB,EACjB,aAAkC,EAClC,mBAAwC,EACxC,EAAE;QACF,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAE7D,IAAI,CAAC,WAAW,CAAC,sBAAsB,EAAE,CAAC;YACxC,OAAO,IAAA,mCAAiB,EACtB,+BAAa,CAAC,UAAU,EACxB,UAAU,WAAW,CAAC,WAAW,mEAAmE,CACrG,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAA,mCAAiB,EAAC,+BAAa,CAAC,QAAQ,EAAE,yCAAyC,WAAW,CAAC,WAAW,GAAG,CAAC,CAAC;QACxH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAA,yCAAmB,EAAC,WAAW,EAAE,KAAK,EAAE;gBAC9D,cAAc,EAAE,CAAC,GAAG,qBAAqB,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;gBACrE,WAAW;aACZ,CAAC,CAAC;YAEH,OAAO,IAAA,8BAAY,EAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,mCAAiB,EAAC,+BAAa,CAAC,YAAY,EAAE,qBAAqB,WAAW,CAAC,WAAW,MAAO,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACrI,CAAC;IACH,CAAC,CAAA,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,kCAAkC,GAC7C,CAAC,wBAAkC,EAAE,EAA+B,EAAE,CACtE,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAC7D,CAAC,kCAAgB,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,qBAAqB,CAAC;KACtF,CAAC,CAAA;EAAA,CAAC;AAJQ,QAAA,kCAAkC,sCAI1C","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n DynamicModuleLoader,\n ErrorTypeEnum,\n QPQConfig,\n QpqLogger,\n StorySession,\n StorySessionUpdater,\n SystemActionType,\n SystemTraceStoryActionPayload,\n SystemTraceStoryActionProcessor,\n} from 'quidproquo-core';\n\nimport { traceStoryExecution } from './traceStoryExecution';\n\n// Shared System::TraceStory processor for node runtimes (lambda and the dev server).\n// Loads the recorded story's real code through the runtime's dynamicModuleLoader (which\n// federates on lambda), then replays it under the tracer. defaultScriptPatterns lets the\n// host add environment-specific scripts to trace (e.g. the lambda federated code cache).\nconst getProcessTraceStory = (qpqConfig: QPQConfig, defaultScriptPatterns: string[]): SystemTraceStoryActionProcessor => {\n return async (\n payload: SystemTraceStoryActionPayload,\n session: StorySession,\n actionProcessors: ActionProcessorList,\n logger: QpqLogger,\n updateSession: StorySessionUpdater,\n dynamicModuleLoader: DynamicModuleLoader,\n ) => {\n const { storyResult, scriptPatterns, onlyOwnCode } = payload;\n\n if (!storyResult.qpqFunctionRuntimeInfo) {\n return actionResultError(\n ErrorTypeEnum.BadRequest,\n `Story [${storyResult.correlation}] has no qpqFunctionRuntimeInfo - cannot locate its code to trace`,\n );\n }\n\n const story = await dynamicModuleLoader(storyResult.qpqFunctionRuntimeInfo);\n if (!story) {\n return actionResultError(ErrorTypeEnum.NotFound, `Unable to dynamically load story for [${storyResult.correlation}]`);\n }\n\n try {\n const { trace } = await traceStoryExecution(storyResult, story, {\n scriptPatterns: [...defaultScriptPatterns, ...(scriptPatterns || [])],\n onlyOwnCode,\n });\n\n return actionResult(trace);\n } catch (error) {\n return actionResultError(ErrorTypeEnum.GenericError, `Trace failed for [${storyResult.correlation}]: ${(error as Error).message}`);\n }\n };\n};\n\nexport const getSystemTraceStoryActionProcessor =\n (defaultScriptPatterns: string[] = []): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [SystemActionType.TraceStory]: getProcessTraceStory(qpqConfig, defaultScriptPatterns),\n });\n"]}
1
+ {"version":3,"file":"getSystemTraceStoryActionProcessor.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/getSystemTraceStoryActionProcessor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAeyB;AAEzB,+DAA4D;AAS5D,qFAAqF;AACrF,wFAAwF;AACxF,yFAAyF;AACzF,yFAAyF;AACzF,MAAM,oBAAoB,GAAG,CAC3B,SAAoB,EACpB,qBAA+B,EAC/B,qBAA8C,EACb,EAAE;IACnC,OAAO,CACL,OAAsC,EACtC,OAAqB,EACrB,gBAAqC,EACrC,MAAiB,EACjB,aAAkC,EAClC,mBAAwC,EACxC,EAAE;QACF,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAE7D,IAAI,CAAC,WAAW,CAAC,sBAAsB,EAAE,CAAC;YACxC,OAAO,IAAA,mCAAiB,EACtB,+BAAa,CAAC,UAAU,EACxB,UAAU,WAAW,CAAC,WAAW,mEAAmE,CACrG,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAA,mCAAiB,EAAC,+BAAa,CAAC,QAAQ,EAAE,yCAAyC,WAAW,CAAC,WAAW,GAAG,CAAC,CAAC;QACxH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAA,yCAAmB,EAAC,WAAW,EAAE,KAAK,EAAE;gBAC9D,cAAc,EAAE,CAAC,GAAG,qBAAqB,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;gBACrE,WAAW;gBACX,cAAc,EAAE,qBAAqB,aAArB,qBAAqB,uBAArB,qBAAqB,CAAG,WAAW,CAAC,sBAAsB,EAAE,SAAS,CAAC;aACvF,CAAC,CAAC;YAEH,OAAO,IAAA,8BAAY,EAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,mCAAiB,EAAC,+BAAa,CAAC,YAAY,EAAE,qBAAqB,WAAW,CAAC,WAAW,MAAO,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACrI,CAAC;IACH,CAAC,CAAA,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,kCAAkC,GAC7C,CAAC,wBAAkC,EAAE,EAAE,qBAA8C,EAA+B,EAAE,CACtH,CAAO,SAAoB,EAAgC,EAAE;IAAC,OAAA,CAAC;QAC7D,CAAC,kCAAgB,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,qBAAqB,EAAE,qBAAqB,CAAC;KAC7G,CAAC,CAAA;EAAA,CAAC;AAJQ,QAAA,kCAAkC,sCAI1C","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n DynamicModuleLoader,\n ErrorTypeEnum,\n QPQConfig,\n QpqFunctionRuntime,\n QpqLogger,\n StorySession,\n StorySessionUpdater,\n SystemActionType,\n SystemTraceStoryActionPayload,\n SystemTraceStoryActionProcessor,\n} from 'quidproquo-core';\n\nimport { traceStoryExecution } from './traceStoryExecution';\n\n// Resolves extra own-code markers (see resolveSourceMaps.filterOwnCodeLocations) for the\n// story being traced. Opt-in: lambda's federated remotes stay on the plain \"not\n// node_modules\" heuristic that already works for them; hosts whose bundle layout defeats\n// that heuristic (the dev server bundles every hosted service plus the whole framework\n// into one script) pass a resolver to narrow onlyOwnCode to the traced service.\nexport type OwnCodeMarkersResolver = (qpqFunctionRuntimeInfo: QpqFunctionRuntime | undefined, qpqConfig: QPQConfig) => string[] | undefined;\n\n// Shared System::TraceStory processor for node runtimes (lambda and the dev server).\n// Loads the recorded story's real code through the runtime's dynamicModuleLoader (which\n// federates on lambda), then replays it under the tracer. defaultScriptPatterns lets the\n// host add environment-specific scripts to trace (e.g. the lambda federated code cache).\nconst getProcessTraceStory = (\n qpqConfig: QPQConfig,\n defaultScriptPatterns: string[],\n resolveOwnCodeMarkers?: OwnCodeMarkersResolver,\n): SystemTraceStoryActionProcessor => {\n return async (\n payload: SystemTraceStoryActionPayload,\n session: StorySession,\n actionProcessors: ActionProcessorList,\n logger: QpqLogger,\n updateSession: StorySessionUpdater,\n dynamicModuleLoader: DynamicModuleLoader,\n ) => {\n const { storyResult, scriptPatterns, onlyOwnCode } = payload;\n\n if (!storyResult.qpqFunctionRuntimeInfo) {\n return actionResultError(\n ErrorTypeEnum.BadRequest,\n `Story [${storyResult.correlation}] has no qpqFunctionRuntimeInfo - cannot locate its code to trace`,\n );\n }\n\n const story = await dynamicModuleLoader(storyResult.qpqFunctionRuntimeInfo);\n if (!story) {\n return actionResultError(ErrorTypeEnum.NotFound, `Unable to dynamically load story for [${storyResult.correlation}]`);\n }\n\n try {\n const { trace } = await traceStoryExecution(storyResult, story, {\n scriptPatterns: [...defaultScriptPatterns, ...(scriptPatterns || [])],\n onlyOwnCode,\n ownCodeMarkers: resolveOwnCodeMarkers?.(storyResult.qpqFunctionRuntimeInfo, qpqConfig),\n });\n\n return actionResult(trace);\n } catch (error) {\n return actionResultError(ErrorTypeEnum.GenericError, `Trace failed for [${storyResult.correlation}]: ${(error as Error).message}`);\n }\n };\n};\n\nexport const getSystemTraceStoryActionProcessor =\n (defaultScriptPatterns: string[] = [], resolveOwnCodeMarkers?: OwnCodeMarkersResolver): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [SystemActionType.TraceStory]: getProcessTraceStory(qpqConfig, defaultScriptPatterns, resolveOwnCodeMarkers),\n });\n"]}
@@ -1,2 +1,3 @@
1
1
  export * from './getSystemTraceStoryActionProcessor';
2
+ export * from './resolveSourceMaps';
2
3
  export * from './traceStoryExecution';
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./getSystemTraceStoryActionProcessor"), exports);
18
+ __exportStar(require("./resolveSourceMaps"), exports);
18
19
  __exportStar(require("./traceStoryExecution"), exports);
19
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uEAAqD;AACrD,wDAAsC","sourcesContent":["export * from './getSystemTraceStoryActionProcessor';\nexport * from './traceStoryExecution';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uEAAqD;AACrD,sDAAoC;AACpC,wDAAsC","sourcesContent":["export * from './getSystemTraceStoryActionProcessor';\nexport * from './resolveSourceMaps';\nexport * from './traceStoryExecution';\n"]}
@@ -10,7 +10,8 @@ interface RawTraceStep {
10
10
  export declare const filterOwnCodeLocations: <TLocation extends {
11
11
  lineNumber: number;
12
12
  columnNumber?: number;
13
- }>(scriptUrl: string, locations: TLocation[]) => TLocation[];
13
+ }>(scriptUrl: string, locations: TLocation[], ownCodeMarkers?: string[]) => TLocation[];
14
+ export declare const getOwnCodeMarkersFromRoot: (root: string) => string[];
14
15
  export interface ResolvedTraceSteps {
15
16
  sources: QpqExecutionTraceSource[];
16
17
  steps: QpqExecutionTraceStep[];
@@ -38,7 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.resolveSourceMappedSteps = exports.filterOwnCodeLocations = void 0;
41
+ exports.resolveSourceMappedSteps = exports.getOwnCodeMarkersFromRoot = exports.filterOwnCodeLocations = void 0;
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const url_1 = require("url");
@@ -78,7 +78,14 @@ const loadTraceMap = (scriptPath) => {
78
78
  // worker can't load trace-mapping itself). Positions mapping into node_modules
79
79
  // (framework/deps) or not mapping at all (webpack runtime glue) are dropped. No map
80
80
  // means the bundle can't be classified — everything stays traced.
81
- const filterOwnCodeLocations = (scriptUrl, locations) => {
81
+ //
82
+ // ownCodeMarkers is a second, opt-in signal: substrings identifying the traced story's
83
+ // own source root. The node_modules check alone assumes framework/deps are bundled from
84
+ // real node_modules — true for lambda's federated remotes, but not for hosts (the dev
85
+ // server) that bundle workspace framework packages from source alongside every hosted
86
+ // service in one script, where framework code never lands in node_modules either. When
87
+ // markers are given, a location must ALSO map into one of them to count as own code.
88
+ const filterOwnCodeLocations = (scriptUrl, locations, ownCodeMarkers) => {
82
89
  const traceMap = scriptUrl ? loadTraceMap(scriptUrlToPath(scriptUrl)) : null;
83
90
  if (!traceMap) {
84
91
  return locations;
@@ -86,10 +93,24 @@ const filterOwnCodeLocations = (scriptUrl, locations) => {
86
93
  return locations.filter((location) => {
87
94
  var _a;
88
95
  const original = (0, trace_mapping_1.originalPositionFor)(traceMap, { line: location.lineNumber + 1, column: (_a = location.columnNumber) !== null && _a !== void 0 ? _a : 0 });
89
- return !!original.source && !original.source.includes('node_modules');
96
+ if (!original.source || original.source.includes('node_modules'))
97
+ return false;
98
+ return !(ownCodeMarkers === null || ownCodeMarkers === void 0 ? void 0 : ownCodeMarkers.length) || ownCodeMarkers.some((marker) => original.source.includes(marker));
90
99
  });
91
100
  };
92
101
  exports.filterOwnCodeLocations = filterOwnCodeLocations;
102
+ // Derives own-code markers from a service's absolute source root (QpqFunctionRuntimeAdvanced.basePath,
103
+ // or getApplicationConfigRoot(qpqConfig) for the relative-string runtime form). Bundlers emit
104
+ // source-map `source` entries as either real filesystem paths or webpack://<namespace>/./<path
105
+ // relative to the build context> pseudo urls — both preserve the original directory names
106
+ // verbatim, so matching on the root's own trailing segments works for either form without
107
+ // needing to know the bundler's exact context/namespace.
108
+ const getOwnCodeMarkersFromRoot = (root) => {
109
+ const normalized = root.replace(/\\/g, '/').replace(/\/+$/, '');
110
+ const tailSegments = normalized.split('/').filter(Boolean).slice(-3).join('/');
111
+ return tailSegments && tailSegments !== normalized ? [normalized, tailSegments] : [normalized];
112
+ };
113
+ exports.getOwnCodeMarkersFromRoot = getOwnCodeMarkersFromRoot;
93
114
  const resolveSourceMappedSteps = (rawSteps, scriptUrlsById) => {
94
115
  const traceMapsByScriptId = new Map();
95
116
  const sourceIndexByPath = new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"resolveSourceMaps.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/resolveSourceMaps.ts"],"names":[],"mappings":"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","sourcesContent":["// Post-processing: map the controller's raw steps (generated script positions) back to\n// original sources via the .map files sitting next to the traced scripts on disk —\n// the same files the federated store syncs to /tmp alongside the code. When a map\n// carries sourcesContent (the federated remote build embeds it), the original source\n// text is embedded in the trace so viewers need no access to the repo.\n\nimport { QpqExecutionTraceSource, QpqExecutionTraceStep, QpqExecutionTraceValue } from 'quidproquo-core';\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\nimport { originalPositionFor, sourceContentFor, TraceMap } from '@jridgewell/trace-mapping';\n\ninterface RawTraceStep {\n scriptId: string;\n line: number; // 0-based (CDP)\n column: number;\n functionName: string;\n locals: Record<string, QpqExecutionTraceValue>;\n returnValue?: QpqExecutionTraceValue;\n}\n\nconst scriptUrlToPath = (scriptUrl: string): string => (scriptUrl.startsWith('file://') ? fileURLToPath(scriptUrl) : scriptUrl);\n\n// The LAST sourceMappingURL comment wins (bundles can contain earlier ones from inlined\n// third-party code).\nconst findSourceMappingUrl = (scriptText: string): string | null => {\n const matches = scriptText.match(/\\/\\/[#@] sourceMappingURL=(\\S+)/g);\n if (!matches || matches.length === 0) return null;\n return matches[matches.length - 1].replace(/\\/\\/[#@] sourceMappingURL=/, '');\n};\n\nconst loadTraceMap = (scriptPath: string): TraceMap | null => {\n try {\n const scriptText = fs.readFileSync(scriptPath, 'utf8');\n const sourceMappingUrl = findSourceMappingUrl(scriptText);\n if (!sourceMappingUrl) return null;\n\n if (sourceMappingUrl.startsWith('data:')) {\n const base64Match = sourceMappingUrl.match(/^data:application\\/json[^,]*;base64,(.*)$/);\n const json = base64Match\n ? Buffer.from(base64Match[1], 'base64').toString('utf8')\n : decodeURIComponent(sourceMappingUrl.replace(/^data:[^,]*,/, ''));\n return new TraceMap(json);\n }\n\n const mapPath = path.resolve(path.dirname(scriptPath), sourceMappingUrl);\n return new TraceMap(fs.readFileSync(mapPath, 'utf8'));\n } catch {\n // No map is a degraded trace, not a failure — generated positions still tell the story\n return null;\n }\n};\n\n// Which candidate breakpoint positions land in the service's OWN code — answers the\n// controller worker's filterLocations request when onlyOwnCode tracing is on (the eval\n// worker can't load trace-mapping itself). Positions mapping into node_modules\n// (framework/deps) or not mapping at all (webpack runtime glue) are dropped. No map\n// means the bundle can't be classified — everything stays traced.\nexport const filterOwnCodeLocations = <TLocation extends { lineNumber: number; columnNumber?: number }>(\n scriptUrl: string,\n locations: TLocation[],\n): TLocation[] => {\n const traceMap = scriptUrl ? loadTraceMap(scriptUrlToPath(scriptUrl)) : null;\n if (!traceMap) {\n return locations;\n }\n\n return locations.filter((location) => {\n const original = originalPositionFor(traceMap, { line: location.lineNumber + 1, column: location.columnNumber ?? 0 });\n return !!original.source && !original.source.includes('node_modules');\n });\n};\n\nexport interface ResolvedTraceSteps {\n sources: QpqExecutionTraceSource[];\n steps: QpqExecutionTraceStep[];\n}\n\nexport const resolveSourceMappedSteps = (rawSteps: RawTraceStep[], scriptUrlsById: Record<string, string>): ResolvedTraceSteps => {\n const traceMapsByScriptId = new Map<string, TraceMap | null>();\n const sourceIndexByPath = new Map<string, number>();\n const sources: QpqExecutionTraceSource[] = [];\n\n const getTraceMap = (scriptId: string): TraceMap | null => {\n if (!traceMapsByScriptId.has(scriptId)) {\n const scriptUrl = scriptUrlsById[scriptId];\n traceMapsByScriptId.set(scriptId, scriptUrl ? loadTraceMap(scriptUrlToPath(scriptUrl)) : null);\n }\n return traceMapsByScriptId.get(scriptId) ?? null;\n };\n\n const getSourceIndex = (sourcePath: string, content?: string): number => {\n const existing = sourceIndexByPath.get(sourcePath);\n if (existing !== undefined) return existing;\n\n const index = sources.length;\n sources.push(content !== undefined ? { path: sourcePath, content } : { path: sourcePath });\n sourceIndexByPath.set(sourcePath, index);\n return index;\n };\n\n // Unmapped fallback: the generated script itself becomes the source, content included\n // so the viewer can still render something.\n const getGeneratedSourceIndex = (scriptId: string): number => {\n const scriptUrl = scriptUrlsById[scriptId] || scriptId;\n const existing = sourceIndexByPath.get(scriptUrl);\n if (existing !== undefined) return existing;\n\n let content: string | undefined;\n try {\n content = fs.readFileSync(scriptUrlToPath(scriptUrl), 'utf8');\n } catch {\n content = undefined;\n }\n return getSourceIndex(scriptUrl, content);\n };\n\n const steps = rawSteps.map((rawStep): QpqExecutionTraceStep => {\n const traceMap = getTraceMap(rawStep.scriptId);\n\n if (traceMap) {\n const original = originalPositionFor(traceMap, { line: rawStep.line + 1, column: rawStep.column });\n if (original.source) {\n const content = sourceContentFor(traceMap, original.source) ?? undefined;\n return {\n sourceIndex: getSourceIndex(original.source, content),\n line: original.line ?? rawStep.line + 1,\n column: original.column ?? rawStep.column,\n functionName: rawStep.functionName,\n locals: rawStep.locals,\n returnValue: rawStep.returnValue,\n };\n }\n }\n\n return {\n sourceIndex: getGeneratedSourceIndex(rawStep.scriptId),\n line: rawStep.line + 1,\n column: rawStep.column,\n functionName: rawStep.functionName,\n locals: rawStep.locals,\n returnValue: rawStep.returnValue,\n };\n });\n\n return { sources, steps };\n};\n"]}
1
+ {"version":3,"file":"resolveSourceMaps.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/resolveSourceMaps.ts"],"names":[],"mappings":"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","sourcesContent":["// Post-processing: map the controller's raw steps (generated script positions) back to\n// original sources via the .map files sitting next to the traced scripts on disk —\n// the same files the federated store syncs to /tmp alongside the code. When a map\n// carries sourcesContent (the federated remote build embeds it), the original source\n// text is embedded in the trace so viewers need no access to the repo.\n\nimport { QpqExecutionTraceSource, QpqExecutionTraceStep, QpqExecutionTraceValue } from 'quidproquo-core';\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\nimport { originalPositionFor, sourceContentFor, TraceMap } from '@jridgewell/trace-mapping';\n\ninterface RawTraceStep {\n scriptId: string;\n line: number; // 0-based (CDP)\n column: number;\n functionName: string;\n locals: Record<string, QpqExecutionTraceValue>;\n returnValue?: QpqExecutionTraceValue;\n}\n\nconst scriptUrlToPath = (scriptUrl: string): string => (scriptUrl.startsWith('file://') ? fileURLToPath(scriptUrl) : scriptUrl);\n\n// The LAST sourceMappingURL comment wins (bundles can contain earlier ones from inlined\n// third-party code).\nconst findSourceMappingUrl = (scriptText: string): string | null => {\n const matches = scriptText.match(/\\/\\/[#@] sourceMappingURL=(\\S+)/g);\n if (!matches || matches.length === 0) return null;\n return matches[matches.length - 1].replace(/\\/\\/[#@] sourceMappingURL=/, '');\n};\n\nconst loadTraceMap = (scriptPath: string): TraceMap | null => {\n try {\n const scriptText = fs.readFileSync(scriptPath, 'utf8');\n const sourceMappingUrl = findSourceMappingUrl(scriptText);\n if (!sourceMappingUrl) return null;\n\n if (sourceMappingUrl.startsWith('data:')) {\n const base64Match = sourceMappingUrl.match(/^data:application\\/json[^,]*;base64,(.*)$/);\n const json = base64Match\n ? Buffer.from(base64Match[1], 'base64').toString('utf8')\n : decodeURIComponent(sourceMappingUrl.replace(/^data:[^,]*,/, ''));\n return new TraceMap(json);\n }\n\n const mapPath = path.resolve(path.dirname(scriptPath), sourceMappingUrl);\n return new TraceMap(fs.readFileSync(mapPath, 'utf8'));\n } catch {\n // No map is a degraded trace, not a failure — generated positions still tell the story\n return null;\n }\n};\n\n// Which candidate breakpoint positions land in the service's OWN code — answers the\n// controller worker's filterLocations request when onlyOwnCode tracing is on (the eval\n// worker can't load trace-mapping itself). Positions mapping into node_modules\n// (framework/deps) or not mapping at all (webpack runtime glue) are dropped. No map\n// means the bundle can't be classified — everything stays traced.\n//\n// ownCodeMarkers is a second, opt-in signal: substrings identifying the traced story's\n// own source root. The node_modules check alone assumes framework/deps are bundled from\n// real node_modules — true for lambda's federated remotes, but not for hosts (the dev\n// server) that bundle workspace framework packages from source alongside every hosted\n// service in one script, where framework code never lands in node_modules either. When\n// markers are given, a location must ALSO map into one of them to count as own code.\nexport const filterOwnCodeLocations = <TLocation extends { lineNumber: number; columnNumber?: number }>(\n scriptUrl: string,\n locations: TLocation[],\n ownCodeMarkers?: string[],\n): TLocation[] => {\n const traceMap = scriptUrl ? loadTraceMap(scriptUrlToPath(scriptUrl)) : null;\n if (!traceMap) {\n return locations;\n }\n\n return locations.filter((location) => {\n const original = originalPositionFor(traceMap, { line: location.lineNumber + 1, column: location.columnNumber ?? 0 });\n if (!original.source || original.source.includes('node_modules')) return false;\n\n return !ownCodeMarkers?.length || ownCodeMarkers.some((marker) => original.source!.includes(marker));\n });\n};\n\n// Derives own-code markers from a service's absolute source root (QpqFunctionRuntimeAdvanced.basePath,\n// or getApplicationConfigRoot(qpqConfig) for the relative-string runtime form). Bundlers emit\n// source-map `source` entries as either real filesystem paths or webpack://<namespace>/./<path\n// relative to the build context> pseudo urls — both preserve the original directory names\n// verbatim, so matching on the root's own trailing segments works for either form without\n// needing to know the bundler's exact context/namespace.\nexport const getOwnCodeMarkersFromRoot = (root: string): string[] => {\n const normalized = root.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n const tailSegments = normalized.split('/').filter(Boolean).slice(-3).join('/');\n\n return tailSegments && tailSegments !== normalized ? [normalized, tailSegments] : [normalized];\n};\n\nexport interface ResolvedTraceSteps {\n sources: QpqExecutionTraceSource[];\n steps: QpqExecutionTraceStep[];\n}\n\nexport const resolveSourceMappedSteps = (rawSteps: RawTraceStep[], scriptUrlsById: Record<string, string>): ResolvedTraceSteps => {\n const traceMapsByScriptId = new Map<string, TraceMap | null>();\n const sourceIndexByPath = new Map<string, number>();\n const sources: QpqExecutionTraceSource[] = [];\n\n const getTraceMap = (scriptId: string): TraceMap | null => {\n if (!traceMapsByScriptId.has(scriptId)) {\n const scriptUrl = scriptUrlsById[scriptId];\n traceMapsByScriptId.set(scriptId, scriptUrl ? loadTraceMap(scriptUrlToPath(scriptUrl)) : null);\n }\n return traceMapsByScriptId.get(scriptId) ?? null;\n };\n\n const getSourceIndex = (sourcePath: string, content?: string): number => {\n const existing = sourceIndexByPath.get(sourcePath);\n if (existing !== undefined) return existing;\n\n const index = sources.length;\n sources.push(content !== undefined ? { path: sourcePath, content } : { path: sourcePath });\n sourceIndexByPath.set(sourcePath, index);\n return index;\n };\n\n // Unmapped fallback: the generated script itself becomes the source, content included\n // so the viewer can still render something.\n const getGeneratedSourceIndex = (scriptId: string): number => {\n const scriptUrl = scriptUrlsById[scriptId] || scriptId;\n const existing = sourceIndexByPath.get(scriptUrl);\n if (existing !== undefined) return existing;\n\n let content: string | undefined;\n try {\n content = fs.readFileSync(scriptUrlToPath(scriptUrl), 'utf8');\n } catch {\n content = undefined;\n }\n return getSourceIndex(scriptUrl, content);\n };\n\n const steps = rawSteps.map((rawStep): QpqExecutionTraceStep => {\n const traceMap = getTraceMap(rawStep.scriptId);\n\n if (traceMap) {\n const original = originalPositionFor(traceMap, { line: rawStep.line + 1, column: rawStep.column });\n if (original.source) {\n const content = sourceContentFor(traceMap, original.source) ?? undefined;\n return {\n sourceIndex: getSourceIndex(original.source, content),\n line: original.line ?? rawStep.line + 1,\n column: original.column ?? rawStep.column,\n functionName: rawStep.functionName,\n locals: rawStep.locals,\n returnValue: rawStep.returnValue,\n };\n }\n }\n\n return {\n sourceIndex: getGeneratedSourceIndex(rawStep.scriptId),\n line: rawStep.line + 1,\n column: rawStep.column,\n functionName: rawStep.functionName,\n locals: rawStep.locals,\n returnValue: rawStep.returnValue,\n };\n });\n\n return { sources, steps };\n};\n"]}
@@ -2,6 +2,7 @@ import { ActionProcessorList, QpqExecutionTrace, StoryResult } from 'quidproquo-
2
2
  export interface TraceStoryExecutionOptions {
3
3
  scriptPatterns?: string[];
4
4
  onlyOwnCode?: boolean;
5
+ ownCodeMarkers?: string[];
5
6
  maxSteps?: number;
6
7
  maxTraceMs?: number;
7
8
  maxValueLength?: number;
@@ -99,7 +99,7 @@ const traceStoryExecution = (storyResult_1, runtime_1, ...args_1) => __awaiter(v
99
99
  controller.postMessage({
100
100
  type: 'filteredLocations',
101
101
  requestId: message.requestId,
102
- locations: (0, resolveSourceMaps_1.filterOwnCodeLocations)(message.scriptUrl, message.locations || []),
102
+ locations: (0, resolveSourceMaps_1.filterOwnCodeLocations)(message.scriptUrl, message.locations || [], options.ownCodeMarkers),
103
103
  });
104
104
  }
105
105
  });
@@ -1 +1 @@
1
- {"version":3,"file":"traceStoryExecution.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/traceStoryExecution.ts"],"names":[],"mappings":"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","sourcesContent":["// traceStoryExecution — replay a StoryResult log against real story code and record a\n// line-by-line execution trace: every statement executed, with local variable values.\n//\n// Wraps qpqExecuteLog (deterministic replay: every impure action is answered from the\n// log's history), while a worker-thread debug controller (see controllerWorker.ts)\n// drives the V8 inspector against this thread and captures each statement + locals.\n// Because the replay is deterministic, the recorded trace carries the same information\n// an interactive debug session would. See trace-replay-plan.md.\n//\n// The CALLER must run on the process MAIN THREAD (lambda handlers and the dev server\n// do) — inspector.Session.connectToMainThread only targets the main thread.\n\nimport { ActionProcessorList, getQpqIsoDateTimeFromDate, qpqExecuteLog, QpqExecutionTrace, StoryResult } from 'quidproquo-core';\n\nimport { Worker } from 'worker_threads';\n\nimport { TraceControllerWorkerData, traceControllerWorkerMain } from './controllerWorker';\nimport { filterOwnCodeLocations, resolveSourceMappedSteps } from './resolveSourceMaps';\n\nexport interface TraceStoryExecutionOptions {\n // Regex sources matched against script urls to trace IN ADDITION to the story\n // function's own script (auto-detected via [[FunctionLocation]]). Use for bundles\n // split across chunks, e.g. ['/tmp/qpq-federated-code/'].\n scriptPatterns?: string[];\n\n // Only break on statements whose source-mapped origin is the service's own code —\n // node_modules positions get no breakpoints, so the step/time budgets are spent\n // entirely on user statements. Scripts without a source map stay fully traced.\n onlyOwnCode?: boolean;\n\n // Step budget and wall-clock budget: past either, the replay finishes untraced and\n // the trace is marked truncated. The wall clock is what bounds total runtime — locals\n // capture makes per-step cost variable, so step count alone doesn't bound time.\n maxSteps?: number;\n maxTraceMs?: number;\n\n // Serialization caps for captured locals: string length, properties per object level,\n // object walk depth for the deep json capture, and total json size per value.\n maxValueLength?: number;\n maxProperties?: number;\n maxValueDepth?: number;\n maxSerializedLength?: number;\n\n // Passed through to qpqExecuteLog — lets specific action types execute for real\n // instead of being answered from the log.\n overrides?: ActionProcessorList;\n}\n\nexport interface TraceStoryExecutionResult {\n trace: QpqExecutionTrace;\n\n // The replay's own StoryResult (result/error of re-running the story)\n replay: StoryResult<any>;\n}\n\n// Ready covers domain enable + breakpoint installation across every traced chunk —\n// generous because the service function running this has minutes of budget, and an\n// abort here wastes a replay that would have succeeded (instrumentMs in the trace stats\n// shows the real cost).\nconst READY_TIMEOUT_MS = 120_000;\nconst TRACE_TIMEOUT_MS = 60_000;\n\n// One trace at a time per process: the controller owns process-wide debugger state\n// (breakpoints, pause handling), and the story function handoff is a global.\nlet traceInFlight = false;\n\nconst waitForControllerMessage = (controller: Worker, type: string, timeoutMs: number): Promise<any> =>\n new Promise((resolve, reject) => {\n const cleanUp = () => {\n controller.off('message', onMessage);\n controller.off('error', onError);\n controller.off('exit', onExit);\n clearTimeout(timeout);\n };\n const onMessage = (message: any) => {\n if (message?.type === type) {\n cleanUp();\n resolve(message);\n } else if (message?.type === 'fatal') {\n cleanUp();\n reject(new Error(`trace controller failed: ${message.error}`));\n }\n };\n const onError = (error: Error) => {\n cleanUp();\n reject(error);\n };\n const onExit = (code: number) => {\n cleanUp();\n reject(new Error(`trace controller exited early with code [${code}]`));\n };\n const timeout = setTimeout(() => {\n cleanUp();\n reject(new Error(`timed out after ${timeoutMs}ms waiting for trace controller [${type}]`));\n }, timeoutMs);\n\n controller.on('message', onMessage);\n controller.on('error', onError);\n controller.on('exit', onExit);\n });\n\nexport const traceStoryExecution = async (\n storyResult: StoryResult<any>,\n runtime: any,\n options: TraceStoryExecutionOptions = {},\n): Promise<TraceStoryExecutionResult> => {\n if (traceInFlight) {\n throw new Error('a story trace is already in progress in this process');\n }\n traceInFlight = true;\n\n // The controller locates the story's script through this global ([[FunctionLocation]])\n (globalThis as any).__qpqTraceStoryFunction = runtime;\n\n const workerData: TraceControllerWorkerData = {\n scriptPatterns: options.scriptPatterns ?? [],\n onlyOwnCode: options.onlyOwnCode ?? false,\n maxSteps: options.maxSteps ?? 20_000,\n maxTraceMs: options.maxTraceMs ?? 120_000,\n maxValueLength: options.maxValueLength ?? 1_000,\n maxProperties: options.maxProperties ?? 32,\n maxValueDepth: options.maxValueDepth ?? 4,\n maxSerializedLength: options.maxSerializedLength ?? 30_000,\n };\n\n // Lambdas ship as single-file webpack bundles, so the controller can't be a worker\n // FILE on disk — it's serialized and run as an eval worker (see controllerWorker.ts).\n const controller = new Worker(`(${traceControllerWorkerMain.toString()})()`, {\n eval: true,\n workerData,\n });\n\n // onlyOwnCode: the worker can't load trace-mapping (eval worker, builtin requires\n // only), so it round-trips candidate breakpoint locations here for source-map\n // filtering. Attached before the ready-wait — instrumentation happens during init.\n controller.on('message', (message: any) => {\n if (message?.type === 'filterLocations') {\n controller.postMessage({\n type: 'filteredLocations',\n requestId: message.requestId,\n locations: filterOwnCodeLocations(message.scriptUrl, message.locations || []),\n });\n }\n });\n\n try {\n await waitForControllerMessage(controller, 'ready', READY_TIMEOUT_MS);\n\n const replayStartedAt = Date.now();\n const replay = await qpqExecuteLog(storyResult, runtime, options.overrides);\n const replayMs = Date.now() - replayStartedAt;\n\n const tracePromise = waitForControllerMessage(controller, 'trace', TRACE_TIMEOUT_MS);\n controller.postMessage({ type: 'done' });\n const rawTrace = await tracePromise;\n\n const { sources, steps } = resolveSourceMappedSteps(rawTrace.steps, rawTrace.scripts);\n\n const trace: QpqExecutionTrace = {\n correlation: storyResult.correlation,\n moduleName: storyResult.moduleName,\n tracedAt: getQpqIsoDateTimeFromDate(new Date()),\n truncated: rawTrace.stats.truncated,\n sources,\n steps,\n stats: {\n pauses: rawTrace.stats.pauses,\n breakpoints: rawTrace.stats.breakpoints,\n replayMs,\n localsCaptureMs: rawTrace.stats.localsCaptureMs,\n instrumentMs: rawTrace.stats.instrumentMs,\n instrumentedScriptUrls: rawTrace.stats.instrumentedScriptUrls,\n },\n };\n\n return { trace, replay };\n } finally {\n delete (globalThis as any).__qpqTraceStoryFunction;\n traceInFlight = false;\n await controller.terminate();\n }\n};\n"]}
1
+ {"version":3,"file":"traceStoryExecution.js","sourceRoot":"","sources":["../../../src/traceStoryExecution/traceStoryExecution.ts"],"names":[],"mappings":"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","sourcesContent":["// traceStoryExecution — replay a StoryResult log against real story code and record a\n// line-by-line execution trace: every statement executed, with local variable values.\n//\n// Wraps qpqExecuteLog (deterministic replay: every impure action is answered from the\n// log's history), while a worker-thread debug controller (see controllerWorker.ts)\n// drives the V8 inspector against this thread and captures each statement + locals.\n// Because the replay is deterministic, the recorded trace carries the same information\n// an interactive debug session would. See trace-replay-plan.md.\n//\n// The CALLER must run on the process MAIN THREAD (lambda handlers and the dev server\n// do) — inspector.Session.connectToMainThread only targets the main thread.\n\nimport { ActionProcessorList, getQpqIsoDateTimeFromDate, qpqExecuteLog, QpqExecutionTrace, StoryResult } from 'quidproquo-core';\n\nimport { Worker } from 'worker_threads';\n\nimport { TraceControllerWorkerData, traceControllerWorkerMain } from './controllerWorker';\nimport { filterOwnCodeLocations, resolveSourceMappedSteps } from './resolveSourceMaps';\n\nexport interface TraceStoryExecutionOptions {\n // Regex sources matched against script urls to trace IN ADDITION to the story\n // function's own script (auto-detected via [[FunctionLocation]]). Use for bundles\n // split across chunks, e.g. ['/tmp/qpq-federated-code/'].\n scriptPatterns?: string[];\n\n // Only break on statements whose source-mapped origin is the service's own code —\n // node_modules positions get no breakpoints, so the step/time budgets are spent\n // entirely on user statements. Scripts without a source map stay fully traced.\n onlyOwnCode?: boolean;\n\n // Extra own-code markers beyond the built-in \"not node_modules\" check — see\n // resolveSourceMaps.filterOwnCodeLocations. Only takes effect when onlyOwnCode is set.\n ownCodeMarkers?: string[];\n\n // Step budget and wall-clock budget: past either, the replay finishes untraced and\n // the trace is marked truncated. The wall clock is what bounds total runtime — locals\n // capture makes per-step cost variable, so step count alone doesn't bound time.\n maxSteps?: number;\n maxTraceMs?: number;\n\n // Serialization caps for captured locals: string length, properties per object level,\n // object walk depth for the deep json capture, and total json size per value.\n maxValueLength?: number;\n maxProperties?: number;\n maxValueDepth?: number;\n maxSerializedLength?: number;\n\n // Passed through to qpqExecuteLog — lets specific action types execute for real\n // instead of being answered from the log.\n overrides?: ActionProcessorList;\n}\n\nexport interface TraceStoryExecutionResult {\n trace: QpqExecutionTrace;\n\n // The replay's own StoryResult (result/error of re-running the story)\n replay: StoryResult<any>;\n}\n\n// Ready covers domain enable + breakpoint installation across every traced chunk —\n// generous because the service function running this has minutes of budget, and an\n// abort here wastes a replay that would have succeeded (instrumentMs in the trace stats\n// shows the real cost).\nconst READY_TIMEOUT_MS = 120_000;\nconst TRACE_TIMEOUT_MS = 60_000;\n\n// One trace at a time per process: the controller owns process-wide debugger state\n// (breakpoints, pause handling), and the story function handoff is a global.\nlet traceInFlight = false;\n\nconst waitForControllerMessage = (controller: Worker, type: string, timeoutMs: number): Promise<any> =>\n new Promise((resolve, reject) => {\n const cleanUp = () => {\n controller.off('message', onMessage);\n controller.off('error', onError);\n controller.off('exit', onExit);\n clearTimeout(timeout);\n };\n const onMessage = (message: any) => {\n if (message?.type === type) {\n cleanUp();\n resolve(message);\n } else if (message?.type === 'fatal') {\n cleanUp();\n reject(new Error(`trace controller failed: ${message.error}`));\n }\n };\n const onError = (error: Error) => {\n cleanUp();\n reject(error);\n };\n const onExit = (code: number) => {\n cleanUp();\n reject(new Error(`trace controller exited early with code [${code}]`));\n };\n const timeout = setTimeout(() => {\n cleanUp();\n reject(new Error(`timed out after ${timeoutMs}ms waiting for trace controller [${type}]`));\n }, timeoutMs);\n\n controller.on('message', onMessage);\n controller.on('error', onError);\n controller.on('exit', onExit);\n });\n\nexport const traceStoryExecution = async (\n storyResult: StoryResult<any>,\n runtime: any,\n options: TraceStoryExecutionOptions = {},\n): Promise<TraceStoryExecutionResult> => {\n if (traceInFlight) {\n throw new Error('a story trace is already in progress in this process');\n }\n traceInFlight = true;\n\n // The controller locates the story's script through this global ([[FunctionLocation]])\n (globalThis as any).__qpqTraceStoryFunction = runtime;\n\n const workerData: TraceControllerWorkerData = {\n scriptPatterns: options.scriptPatterns ?? [],\n onlyOwnCode: options.onlyOwnCode ?? false,\n maxSteps: options.maxSteps ?? 20_000,\n maxTraceMs: options.maxTraceMs ?? 120_000,\n maxValueLength: options.maxValueLength ?? 1_000,\n maxProperties: options.maxProperties ?? 32,\n maxValueDepth: options.maxValueDepth ?? 4,\n maxSerializedLength: options.maxSerializedLength ?? 30_000,\n };\n\n // Lambdas ship as single-file webpack bundles, so the controller can't be a worker\n // FILE on disk — it's serialized and run as an eval worker (see controllerWorker.ts).\n const controller = new Worker(`(${traceControllerWorkerMain.toString()})()`, {\n eval: true,\n workerData,\n });\n\n // onlyOwnCode: the worker can't load trace-mapping (eval worker, builtin requires\n // only), so it round-trips candidate breakpoint locations here for source-map\n // filtering. Attached before the ready-wait — instrumentation happens during init.\n controller.on('message', (message: any) => {\n if (message?.type === 'filterLocations') {\n controller.postMessage({\n type: 'filteredLocations',\n requestId: message.requestId,\n locations: filterOwnCodeLocations(message.scriptUrl, message.locations || [], options.ownCodeMarkers),\n });\n }\n });\n\n try {\n await waitForControllerMessage(controller, 'ready', READY_TIMEOUT_MS);\n\n const replayStartedAt = Date.now();\n const replay = await qpqExecuteLog(storyResult, runtime, options.overrides);\n const replayMs = Date.now() - replayStartedAt;\n\n const tracePromise = waitForControllerMessage(controller, 'trace', TRACE_TIMEOUT_MS);\n controller.postMessage({ type: 'done' });\n const rawTrace = await tracePromise;\n\n const { sources, steps } = resolveSourceMappedSteps(rawTrace.steps, rawTrace.scripts);\n\n const trace: QpqExecutionTrace = {\n correlation: storyResult.correlation,\n moduleName: storyResult.moduleName,\n tracedAt: getQpqIsoDateTimeFromDate(new Date()),\n truncated: rawTrace.stats.truncated,\n sources,\n steps,\n stats: {\n pauses: rawTrace.stats.pauses,\n breakpoints: rawTrace.stats.breakpoints,\n replayMs,\n localsCaptureMs: rawTrace.stats.localsCaptureMs,\n instrumentMs: rawTrace.stats.instrumentMs,\n instrumentedScriptUrls: rawTrace.stats.instrumentedScriptUrls,\n },\n };\n\n return { trace, replay };\n } finally {\n delete (globalThis as any).__qpqTraceStoryFunction;\n traceInFlight = false;\n await controller.terminate();\n }\n};\n"]}
@@ -19,6 +19,10 @@ const getProcessExecute = (qpqConfig) => {
19
19
  depth: (session.depth || 0) + 1,
20
20
  decodedAccessToken: session.decodedAccessToken,
21
21
  correlation: session.correlation,
22
+ // Inline functions run WITHIN the caller's function - carry its globals
23
+ // so stories like the tenant scope resolver can read route config (the
24
+ // runtime merges these under the inline function's own registration).
25
+ functionGlobals: session.functionGlobals,
22
26
  }, async () => actionProcessors, getDateNow, logger, `${moduleName}::${randomUUID()}`, QpqRuntimeType.EXECUTE_STORY, dynamicModuleLoader, inlineFunction.runtime, [], streamRegistry);
23
27
  const storyResult = await resolveStory(story, [payload.payload]);
24
28
  if (storyResult.error) {
@@ -1 +1 @@
1
- {"version":3,"file":"getInlineFunctionExecuteActionProcessor.js","sourceRoot":"","sources":["../../../../../src/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EAEb,aAAa,EACb,wBAAwB,EAIxB,YAAY,EAEZ,cAAc,GAIf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAElD,MAAM,iBAAiB,GAAG,CAAO,SAAoB,EAA8C,EAAE;IACnG,MAAM,UAAU,GAAG,YAAY,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC;IAEpE,OAAO,KAAK,EACV,OAA8C,EAC9C,OAAqB,EACrB,gBAAqC,EACrC,MAAiB,EACjB,aAAkC,EAClC,mBAAwC,EACxC,cAA8B,EAChB,EAAE;QAChB,MAAM,eAAe,GAAG,YAAY,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACtE,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QAE5F,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,+BAA+B,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC;QAC3G,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAEhE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,gDAAgD,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC;QAC5H,CAAC;QAED,MAAM,YAAY,GAAG,aAAa,CAChC,SAAS,EACT;YACE,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC;YAC/B,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,EACD,KAAK,IAAI,EAAE,CAAC,gBAAgB,EAC5B,UAAU,EACV,MAAM,EACN,GAAG,UAAU,KAAK,UAAU,EAAE,EAAE,EAChC,cAAc,CAAC,aAAa,EAC5B,mBAAmB,EACnB,cAAc,CAAC,OAAO,EACtB,EAAE,EACF,cAAc,CACf,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAEjE,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,iBAAiB,CACtB,WAAW,CAAC,KAAK,CAAC,SAAS,EAC3B,WAAW,CAAC,KAAK,CAAC,SAAS,EAC3B,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,QAAQ,WAAW,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CACrH,CAAC;QACJ,CAAC;QAED,OAAO,YAAY,CAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uCAAuC,GAAgC,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IACjJ,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,SAAS,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n createRuntime,\n DynamicModuleLoader,\n ErrorTypeEnum,\n InlineFunctionActionType,\n InlineFunctionExecuteActionPayload,\n InlineFunctionExecuteActionProcessor,\n QPQConfig,\n qpqCoreUtils,\n QpqLogger,\n QpqRuntimeType,\n StorySession,\n StorySessionUpdater,\n StreamRegistry,\n} from 'quidproquo-core';\n\nimport { randomUUID } from 'crypto';\n\nconst getDateNow = () => new Date().toISOString();\n\nconst getProcessExecute = <R, T>(qpqConfig: QPQConfig): InlineFunctionExecuteActionProcessor<R, T> => {\n const moduleName = qpqCoreUtils.getApplicationModuleName(qpqConfig);\n\n return async (\n payload: InlineFunctionExecuteActionPayload<T>,\n session: StorySession,\n actionProcessors: ActionProcessorList,\n logger: QpqLogger,\n updateSession: StorySessionUpdater,\n dynamicModuleLoader: DynamicModuleLoader,\n streamRegistry: StreamRegistry,\n ): Promise<any> => {\n const inlineFunctions = qpqCoreUtils.getAllInlineFunctions(qpqConfig);\n const inlineFunction = inlineFunctions.find((f) => f.functionName === payload.functionName);\n\n if (!inlineFunction) {\n return actionResultError(ErrorTypeEnum.NotFound, `Inline function not found: [${payload.functionName}]`);\n }\n\n const story = await dynamicModuleLoader(inlineFunction.runtime);\n\n if (!story) {\n return actionResultError(ErrorTypeEnum.NotFound, `Unable to dynamically load inline function: [${payload.functionName}]`);\n }\n\n const resolveStory = createRuntime(\n qpqConfig,\n {\n context: session.context,\n localContext: session.localContext,\n depth: (session.depth || 0) + 1,\n decodedAccessToken: session.decodedAccessToken,\n correlation: session.correlation,\n },\n async () => actionProcessors,\n getDateNow,\n logger,\n `${moduleName}::${randomUUID()}`,\n QpqRuntimeType.EXECUTE_STORY,\n dynamicModuleLoader,\n inlineFunction.runtime,\n [],\n streamRegistry,\n );\n\n const storyResult = await resolveStory(story, [payload.payload]);\n\n if (storyResult.error) {\n return actionResultError(\n storyResult.error.errorType,\n storyResult.error.errorText,\n storyResult.error.errorStack ? `${payload.functionName} -> [${storyResult.error.errorStack}]` : payload.functionName,\n );\n }\n\n return actionResult<R>(storyResult.result);\n };\n};\n\nexport const getInlineFunctionExecuteActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [InlineFunctionActionType.Execute]: getProcessExecute(qpqConfig),\n});\n"]}
1
+ {"version":3,"file":"getInlineFunctionExecuteActionProcessor.js","sourceRoot":"","sources":["../../../../../src/actionProcessor/core/inlineFunction/getInlineFunctionExecuteActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EAEb,aAAa,EACb,wBAAwB,EAIxB,YAAY,EAEZ,cAAc,GAIf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAElD,MAAM,iBAAiB,GAAG,CAAO,SAAoB,EAA8C,EAAE;IACnG,MAAM,UAAU,GAAG,YAAY,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC;IAEpE,OAAO,KAAK,EACV,OAA8C,EAC9C,OAAqB,EACrB,gBAAqC,EACrC,MAAiB,EACjB,aAAkC,EAClC,mBAAwC,EACxC,cAA8B,EAChB,EAAE;QAChB,MAAM,eAAe,GAAG,YAAY,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACtE,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QAE5F,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,+BAA+B,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC;QAC3G,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAEhE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,gDAAgD,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC;QAC5H,CAAC;QAED,MAAM,YAAY,GAAG,aAAa,CAChC,SAAS,EACT;YACE,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC;YAC/B,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,wEAAwE;YACxE,uEAAuE;YACvE,sEAAsE;YACtE,eAAe,EAAE,OAAO,CAAC,eAAe;SACzC,EACD,KAAK,IAAI,EAAE,CAAC,gBAAgB,EAC5B,UAAU,EACV,MAAM,EACN,GAAG,UAAU,KAAK,UAAU,EAAE,EAAE,EAChC,cAAc,CAAC,aAAa,EAC5B,mBAAmB,EACnB,cAAc,CAAC,OAAO,EACtB,EAAE,EACF,cAAc,CACf,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAEjE,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,iBAAiB,CACtB,WAAW,CAAC,KAAK,CAAC,SAAS,EAC3B,WAAW,CAAC,KAAK,CAAC,SAAS,EAC3B,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,QAAQ,WAAW,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CACrH,CAAC;QACJ,CAAC;QAED,OAAO,YAAY,CAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uCAAuC,GAAgC,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IACjJ,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,SAAS,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n createRuntime,\n DynamicModuleLoader,\n ErrorTypeEnum,\n InlineFunctionActionType,\n InlineFunctionExecuteActionPayload,\n InlineFunctionExecuteActionProcessor,\n QPQConfig,\n qpqCoreUtils,\n QpqLogger,\n QpqRuntimeType,\n StorySession,\n StorySessionUpdater,\n StreamRegistry,\n} from 'quidproquo-core';\n\nimport { randomUUID } from 'crypto';\n\nconst getDateNow = () => new Date().toISOString();\n\nconst getProcessExecute = <R, T>(qpqConfig: QPQConfig): InlineFunctionExecuteActionProcessor<R, T> => {\n const moduleName = qpqCoreUtils.getApplicationModuleName(qpqConfig);\n\n return async (\n payload: InlineFunctionExecuteActionPayload<T>,\n session: StorySession,\n actionProcessors: ActionProcessorList,\n logger: QpqLogger,\n updateSession: StorySessionUpdater,\n dynamicModuleLoader: DynamicModuleLoader,\n streamRegistry: StreamRegistry,\n ): Promise<any> => {\n const inlineFunctions = qpqCoreUtils.getAllInlineFunctions(qpqConfig);\n const inlineFunction = inlineFunctions.find((f) => f.functionName === payload.functionName);\n\n if (!inlineFunction) {\n return actionResultError(ErrorTypeEnum.NotFound, `Inline function not found: [${payload.functionName}]`);\n }\n\n const story = await dynamicModuleLoader(inlineFunction.runtime);\n\n if (!story) {\n return actionResultError(ErrorTypeEnum.NotFound, `Unable to dynamically load inline function: [${payload.functionName}]`);\n }\n\n const resolveStory = createRuntime(\n qpqConfig,\n {\n context: session.context,\n localContext: session.localContext,\n depth: (session.depth || 0) + 1,\n decodedAccessToken: session.decodedAccessToken,\n correlation: session.correlation,\n // Inline functions run WITHIN the caller's function - carry its globals\n // so stories like the tenant scope resolver can read route config (the\n // runtime merges these under the inline function's own registration).\n functionGlobals: session.functionGlobals,\n },\n async () => actionProcessors,\n getDateNow,\n logger,\n `${moduleName}::${randomUUID()}`,\n QpqRuntimeType.EXECUTE_STORY,\n dynamicModuleLoader,\n inlineFunction.runtime,\n [],\n streamRegistry,\n );\n\n const storyResult = await resolveStory(story, [payload.payload]);\n\n if (storyResult.error) {\n return actionResultError(\n storyResult.error.errorType,\n storyResult.error.errorText,\n storyResult.error.errorStack ? `${payload.functionName} -> [${storyResult.error.errorStack}]` : payload.functionName,\n );\n }\n\n return actionResult<R>(storyResult.result);\n };\n};\n\nexport const getInlineFunctionExecuteActionProcessor: ActionProcessorListResolver = async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [InlineFunctionActionType.Execute]: getProcessExecute(qpqConfig),\n});\n"]}
@@ -2,12 +2,12 @@ import { actionResult, actionResultError, ErrorTypeEnum, FileActionType, FileDel
2
2
  import * as fs from 'fs/promises';
3
3
  import { resolveFilePath } from './utils';
4
4
  const getProcessFileDelete = (qpqConfig, config) => {
5
- return async ({ drive, filepaths }) => {
5
+ return async ({ drive, filepaths, scope }) => {
6
6
  const deletedFiles = [];
7
7
  const errors = [];
8
8
  for (const filepath of filepaths) {
9
9
  try {
10
- const fullPath = resolveFilePath(config, qpqConfig, drive, filepath);
10
+ const fullPath = resolveFilePath(config, qpqConfig, drive, filepath, scope);
11
11
  await fs.unlink(fullPath);
12
12
  deletedFiles.push(filepath);
13
13
  }
@@ -21,6 +21,14 @@ const getProcessFileDelete = (qpqConfig, config) => {
21
21
  }
22
22
  }
23
23
  }
24
+ // Scope is shared by every filepath in the request, so a bad scope fails the
25
+ // whole call regardless of any deletes that a different code path allowed.
26
+ // Name-keyed, not instanceof: under npm link / module federation the error
27
+ // can come from another copy of quidproquo-core.
28
+ const invalidScope = errors.find(({ error }) => error?.name === 'InvalidScopeError');
29
+ if (invalidScope) {
30
+ return actionResultError(FileDeleteErrorTypeEnum.InvalidScope, invalidScope.error.message);
31
+ }
24
32
  if (errors.length > 0 && deletedFiles.length === 0) {
25
33
  // EACCES maps to the same typed error the S3 processor returns for AccessDenied
26
34
  if (errors.some(({ error }) => error.code === 'EACCES')) {
@@ -1 +1 @@
1
- {"version":3,"file":"getFileDeleteActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileDeleteActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EAEd,uBAAuB,GAExB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,oBAAoB,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAA6B,EAAE;IAC1G,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE;QACpC,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,MAAM,MAAM,GAAuC,EAAE,CAAC;QAEtD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;gBAErE,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC1B,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,0DAA0D;gBAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,gFAAgF;YAChF,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;gBACxD,OAAO,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,8BAA8B,CAAC,CAAC;YACjG,CAAC;YACD,OAAO,iBAAiB,CACtB,aAAa,CAAC,YAAY,EAC1B,2BAA2B,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,oBAAoB,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9H,CAAC;QACJ,CAAC;QAED,OAAO,YAAY,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GACvC,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n ErrorTypeEnum,\n FileActionType,\n FileDeleteActionProcessor,\n FileDeleteErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\nconst getProcessFileDelete = (qpqConfig: QPQConfig, config: FileStorageConfig): FileDeleteActionProcessor => {\n return async ({ drive, filepaths }) => {\n const deletedFiles: string[] = [];\n const errors: { filepath: string; error: any }[] = [];\n\n for (const filepath of filepaths) {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath);\n\n await fs.unlink(fullPath);\n deletedFiles.push(filepath);\n } catch (error: any) {\n // If file doesn't exist, consider it successfully deleted\n if (error.code === 'ENOENT') {\n deletedFiles.push(filepath);\n } else {\n errors.push({ filepath, error });\n }\n }\n }\n\n if (errors.length > 0 && deletedFiles.length === 0) {\n // EACCES maps to the same typed error the S3 processor returns for AccessDenied\n if (errors.some(({ error }) => error.code === 'EACCES')) {\n return actionResultError(FileDeleteErrorTypeEnum.AccessDenied, 'Access denied deleting files');\n }\n return actionResultError(\n ErrorTypeEnum.GenericError,\n `Failed to delete files: ${errors.map(({ filepath, error }) => `Failed to delete ${filepath}: ${error.message}`).join(', ')}`,\n );\n }\n\n return actionResult(deletedFiles);\n };\n};\n\nexport const getFileDeleteActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.Delete]: getProcessFileDelete(qpqConfig, config),\n });\n"]}
1
+ {"version":3,"file":"getFileDeleteActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileDeleteActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EAEd,uBAAuB,GAExB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,oBAAoB,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAA6B,EAAE;IAC1G,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3C,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,MAAM,MAAM,GAAuC,EAAE,CAAC;QAEtD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAE5E,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC1B,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,0DAA0D;gBAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC;QAED,6EAA6E;QAC7E,2EAA2E;QAC3E,2EAA2E;QAC3E,iDAAiD;QACjD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,mBAAmB,CAAC,CAAC;QACrF,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7F,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,gFAAgF;YAChF,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;gBACxD,OAAO,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,8BAA8B,CAAC,CAAC;YACjG,CAAC;YACD,OAAO,iBAAiB,CACtB,aAAa,CAAC,YAAY,EAC1B,2BAA2B,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,oBAAoB,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9H,CAAC;QACJ,CAAC;QAED,OAAO,YAAY,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GACvC,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n ErrorTypeEnum,\n FileActionType,\n FileDeleteActionProcessor,\n FileDeleteErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\nconst getProcessFileDelete = (qpqConfig: QPQConfig, config: FileStorageConfig): FileDeleteActionProcessor => {\n return async ({ drive, filepaths, scope }) => {\n const deletedFiles: string[] = [];\n const errors: { filepath: string; error: any }[] = [];\n\n for (const filepath of filepaths) {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath, scope);\n\n await fs.unlink(fullPath);\n deletedFiles.push(filepath);\n } catch (error: any) {\n // If file doesn't exist, consider it successfully deleted\n if (error.code === 'ENOENT') {\n deletedFiles.push(filepath);\n } else {\n errors.push({ filepath, error });\n }\n }\n }\n\n // Scope is shared by every filepath in the request, so a bad scope fails the\n // whole call regardless of any deletes that a different code path allowed.\n // Name-keyed, not instanceof: under npm link / module federation the error\n // can come from another copy of quidproquo-core.\n const invalidScope = errors.find(({ error }) => error?.name === 'InvalidScopeError');\n if (invalidScope) {\n return actionResultError(FileDeleteErrorTypeEnum.InvalidScope, invalidScope.error.message);\n }\n\n if (errors.length > 0 && deletedFiles.length === 0) {\n // EACCES maps to the same typed error the S3 processor returns for AccessDenied\n if (errors.some(({ error }) => error.code === 'EACCES')) {\n return actionResultError(FileDeleteErrorTypeEnum.AccessDenied, 'Access denied deleting files');\n }\n return actionResultError(\n ErrorTypeEnum.GenericError,\n `Failed to delete files: ${errors.map(({ filepath, error }) => `Failed to delete ${filepath}: ${error.message}`).join(', ')}`,\n );\n }\n\n return actionResult(deletedFiles);\n };\n};\n\nexport const getFileDeleteActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.Delete]: getProcessFileDelete(qpqConfig, config),\n });\n"]}
@@ -1,10 +1,10 @@
1
- import { actionResult, actionResultError, ErrorTypeEnum, FileActionType, FileExistsErrorTypeEnum, } from 'quidproquo-core';
1
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, FileActionType, FileExistsErrorTypeEnum, } from 'quidproquo-core';
2
2
  import * as fs from 'fs/promises';
3
3
  import { resolveFilePath } from './utils';
4
4
  const getProcessFileExists = (qpqConfig, config) => {
5
- return async ({ drive, filepath }) => {
5
+ return async ({ drive, filepath, scope }) => {
6
6
  try {
7
- const fullPath = resolveFilePath(config, qpqConfig, drive, filepath);
7
+ const fullPath = resolveFilePath(config, qpqConfig, drive, filepath, scope);
8
8
  try {
9
9
  await fs.access(fullPath);
10
10
  return actionResult(true);
@@ -21,7 +21,11 @@ const getProcessFileExists = (qpqConfig, config) => {
21
21
  }
22
22
  }
23
23
  catch (error) {
24
- return actionResultError(ErrorTypeEnum.GenericError, `Error checking file existence: ${error.message}`);
24
+ // Name-keyed, not instanceof: under npm link / module federation the
25
+ // error can come from another copy of quidproquo-core.
26
+ return actionResultErrorFromCaughtError(error, {
27
+ InvalidScopeError: (error) => actionResultError(FileExistsErrorTypeEnum.InvalidScope, error.message),
28
+ });
25
29
  }
26
30
  };
27
31
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getFileExistsActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileExistsActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EAEd,uBAAuB,GAExB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,oBAAoB,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAA6B,EAAE;IAC1G,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;QACnC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;YAErE,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC1B,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,wEAAwE;gBACxE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,0CAA0C,QAAQ,EAAE,CAAC,CAAC;gBACvH,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,iBAAiB,CAAC,aAAa,CAAC,YAAY,EAAE,kCAAkC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GACvC,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n ErrorTypeEnum,\n FileActionType,\n FileExistsActionProcessor,\n FileExistsErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\nconst getProcessFileExists = (qpqConfig: QPQConfig, config: FileStorageConfig): FileExistsActionProcessor => {\n return async ({ drive, filepath }) => {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath);\n\n try {\n await fs.access(fullPath);\n return actionResult(true);\n } catch (error: any) {\n // Missing file is a definitive false; permission problems must surface.\n if (error.code === 'ENOENT') {\n return actionResult(false);\n }\n if (error.code === 'EACCES') {\n return actionResultError(FileExistsErrorTypeEnum.AccessDenied, `Access denied checking file existence: ${filepath}`);\n }\n throw error;\n }\n } catch (error: any) {\n return actionResultError(ErrorTypeEnum.GenericError, `Error checking file existence: ${error.message}`);\n }\n };\n};\n\nexport const getFileExistsActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.Exists]: getProcessFileExists(qpqConfig, config),\n });\n"]}
1
+ {"version":3,"file":"getFileExistsActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileExistsActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,gCAAgC,EAChC,cAAc,EAEd,uBAAuB,GAExB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,oBAAoB,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAA6B,EAAE;IAC1G,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;QAC1C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;YAE5E,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC1B,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,wEAAwE;gBACxE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,OAAO,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,0CAA0C,QAAQ,EAAE,CAAC,CAAC;gBACvH,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,qEAAqE;YACrE,uDAAuD;YACvD,OAAO,gCAAgC,CAAC,KAAK,EAAE;gBAC7C,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;aACrG,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GACvC,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC;CACjE,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n FileActionType,\n FileExistsActionProcessor,\n FileExistsErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport * as fs from 'fs/promises';\n\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\nconst getProcessFileExists = (qpqConfig: QPQConfig, config: FileStorageConfig): FileExistsActionProcessor => {\n return async ({ drive, filepath, scope }) => {\n try {\n const fullPath = resolveFilePath(config, qpqConfig, drive, filepath, scope);\n\n try {\n await fs.access(fullPath);\n return actionResult(true);\n } catch (error: any) {\n // Missing file is a definitive false; permission problems must surface.\n if (error.code === 'ENOENT') {\n return actionResult(false);\n }\n if (error.code === 'EACCES') {\n return actionResultError(FileExistsErrorTypeEnum.AccessDenied, `Access denied checking file existence: ${filepath}`);\n }\n throw error;\n }\n } catch (error: unknown) {\n // Name-keyed, not instanceof: under npm link / module federation the\n // error can come from another copy of quidproquo-core.\n return actionResultErrorFromCaughtError(error, {\n InvalidScopeError: (error) => actionResultError(FileExistsErrorTypeEnum.InvalidScope, error.message),\n });\n }\n };\n};\n\nexport const getFileExistsActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.Exists]: getProcessFileExists(qpqConfig, config),\n });\n"]}
@@ -1,12 +1,18 @@
1
- import { actionResult, actionResultError, ErrorTypeEnum, FileActionType, } from 'quidproquo-core';
1
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, FileActionType, FileGenerateTemporarySecureUrlErrorTypeEnum, } from 'quidproquo-core';
2
2
  import { createSecureUrlToken, getSecureUrlBaseUrl } from './secureUrlUtils';
3
3
  import { resolveFilePath } from './utils';
4
+ // Mirror the AWS processor's SigV4 limit (7 days) so a story that would fail
5
+ // in production fails the same way on the dev server.
6
+ const maxExpirationMs = 7 * 24 * 60 * 60 * 1000;
4
7
  const getProcessFileGenerateTemporarySecureUrl = (qpqConfig, config) => {
5
- return async ({ drive, filepath, expirationMs }) => {
8
+ return async ({ drive, filepath, expirationMs, scope }) => {
9
+ if (expirationMs > maxExpirationMs) {
10
+ return actionResultError(FileGenerateTemporarySecureUrlErrorTypeEnum.ExpirationTooLong, 'Expiration exceeds the 7 day maximum for presigned URLs');
11
+ }
6
12
  try {
7
13
  // Create a token for download
8
14
  const token = createSecureUrlToken({
9
- fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath),
15
+ fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath, scope),
10
16
  operation: 'download',
11
17
  expiresAt: Date.now() + expirationMs,
12
18
  }, config.secureUrlSecret);
@@ -16,7 +22,11 @@ const getProcessFileGenerateTemporarySecureUrl = (qpqConfig, config) => {
16
22
  return actionResult(url);
17
23
  }
18
24
  catch (error) {
19
- return actionResultError(ErrorTypeEnum.GenericError, `Unable to generate temporary secure URL: ${error.message}`);
25
+ // Name-keyed, not instanceof: under npm link / module federation the
26
+ // error can come from another copy of quidproquo-core.
27
+ return actionResultErrorFromCaughtError(error, {
28
+ InvalidScopeError: (error) => actionResultError(FileGenerateTemporarySecureUrlErrorTypeEnum.InvalidScope, error.message),
29
+ });
20
30
  }
21
31
  };
22
32
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getFileGenerateTemporarySecureUrlActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,GAGf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,wCAAwC,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAAiD,EAAE;IAClJ,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE;QACjD,IAAI,CAAC;YACH,8BAA8B;YAC9B,MAAM,KAAK,GAAG,oBAAoB,CAChC;gBACE,YAAY,EAAE,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC;gBACjE,SAAS,EAAE,UAAU;gBACrB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;aACrC,EACD,MAAM,CAAC,eAAe,CACvB,CAAC;YAEF,uBAAuB;YACvB,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,GAAG,GAAG,GAAG,OAAO,0BAA0B,KAAK,EAAE,CAAC;YAExD,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,iBAAiB,CAAC,aAAa,CAAC,YAAY,EAAE,4CAA4C,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACpH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gDAAgD,GAC3D,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,0BAA0B,CAAC,EAAE,wCAAwC,CAAC,SAAS,EAAE,MAAM,CAAC;CACzG,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n ErrorTypeEnum,\n FileActionType,\n FileGenerateTemporarySecureUrlActionProcessor,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport { createSecureUrlToken, getSecureUrlBaseUrl } from './secureUrlUtils';\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\nconst getProcessFileGenerateTemporarySecureUrl = (qpqConfig: QPQConfig, config: FileStorageConfig): FileGenerateTemporarySecureUrlActionProcessor => {\n return async ({ drive, filepath, expirationMs }) => {\n try {\n // Create a token for download\n const token = createSecureUrlToken(\n {\n fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath),\n operation: 'download',\n expiresAt: Date.now() + expirationMs,\n },\n config.secureUrlSecret,\n );\n\n // Build the secure URL\n const baseUrl = getSecureUrlBaseUrl(config);\n const url = `${baseUrl}/secure-download?token=${token}`;\n\n return actionResult(url);\n } catch (error: any) {\n return actionResultError(ErrorTypeEnum.GenericError, `Unable to generate temporary secure URL: ${error.message}`);\n }\n };\n};\n\nexport const getFileGenerateTemporarySecureUrlActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.GenerateTemporarySecureUrl]: getProcessFileGenerateTemporarySecureUrl(qpqConfig, config),\n });\n"]}
1
+ {"version":3,"file":"getFileGenerateTemporarySecureUrlActionProcessor.js","sourceRoot":"","sources":["../../../../src/dynamicActionProcessor/file/getFileGenerateTemporarySecureUrlActionProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,iBAAiB,EACjB,gCAAgC,EAChC,cAAc,EAEd,2CAA2C,GAE5C,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,6EAA6E;AAC7E,sDAAsD;AACtD,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD,MAAM,wCAAwC,GAAG,CAAC,SAAoB,EAAE,MAAyB,EAAiD,EAAE;IAClJ,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE;QACxD,IAAI,YAAY,GAAG,eAAe,EAAE,CAAC;YACnC,OAAO,iBAAiB,CACtB,2CAA2C,CAAC,iBAAiB,EAC7D,yDAAyD,CAC1D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,8BAA8B;YAC9B,MAAM,KAAK,GAAG,oBAAoB,CAChC;gBACE,YAAY,EAAE,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC;gBACxE,SAAS,EAAE,UAAU;gBACrB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;aACrC,EACD,MAAM,CAAC,eAAe,CACvB,CAAC;YAEF,uBAAuB;YACvB,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,GAAG,GAAG,GAAG,OAAO,0BAA0B,KAAK,EAAE,CAAC;YAExD,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,qEAAqE;YACrE,uDAAuD;YACvD,OAAO,gCAAgC,CAAC,KAAK,EAAE;gBAC7C,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,2CAA2C,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;aACzH,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gDAAgD,GAC3D,CAAC,MAAyB,EAA+B,EAAE,CAC3D,KAAK,EAAE,SAAoB,EAAgC,EAAE,CAAC,CAAC;IAC7D,CAAC,cAAc,CAAC,0BAA0B,CAAC,EAAE,wCAAwC,CAAC,SAAS,EAAE,MAAM,CAAC;CACzG,CAAC,CAAC","sourcesContent":["import {\n ActionProcessorList,\n ActionProcessorListResolver,\n actionResult,\n actionResultError,\n actionResultErrorFromCaughtError,\n FileActionType,\n FileGenerateTemporarySecureUrlActionProcessor,\n FileGenerateTemporarySecureUrlErrorTypeEnum,\n QPQConfig,\n} from 'quidproquo-core';\n\nimport { createSecureUrlToken, getSecureUrlBaseUrl } from './secureUrlUtils';\nimport { FileStorageConfig } from './types';\nimport { resolveFilePath } from './utils';\n\n// Mirror the AWS processor's SigV4 limit (7 days) so a story that would fail\n// in production fails the same way on the dev server.\nconst maxExpirationMs = 7 * 24 * 60 * 60 * 1000;\n\nconst getProcessFileGenerateTemporarySecureUrl = (qpqConfig: QPQConfig, config: FileStorageConfig): FileGenerateTemporarySecureUrlActionProcessor => {\n return async ({ drive, filepath, expirationMs, scope }) => {\n if (expirationMs > maxExpirationMs) {\n return actionResultError(\n FileGenerateTemporarySecureUrlErrorTypeEnum.ExpirationTooLong,\n 'Expiration exceeds the 7 day maximum for presigned URLs',\n );\n }\n\n try {\n // Create a token for download\n const token = createSecureUrlToken(\n {\n fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath, scope),\n operation: 'download',\n expiresAt: Date.now() + expirationMs,\n },\n config.secureUrlSecret,\n );\n\n // Build the secure URL\n const baseUrl = getSecureUrlBaseUrl(config);\n const url = `${baseUrl}/secure-download?token=${token}`;\n\n return actionResult(url);\n } catch (error: unknown) {\n // Name-keyed, not instanceof: under npm link / module federation the\n // error can come from another copy of quidproquo-core.\n return actionResultErrorFromCaughtError(error, {\n InvalidScopeError: (error) => actionResultError(FileGenerateTemporarySecureUrlErrorTypeEnum.InvalidScope, error.message),\n });\n }\n };\n};\n\nexport const getFileGenerateTemporarySecureUrlActionProcessor =\n (config: FileStorageConfig): ActionProcessorListResolver =>\n async (qpqConfig: QPQConfig): Promise<ActionProcessorList> => ({\n [FileActionType.GenerateTemporarySecureUrl]: getProcessFileGenerateTemporarySecureUrl(qpqConfig, config),\n });\n"]}
@@ -1,13 +1,19 @@
1
- import { actionResult, actionResultError, ErrorTypeEnum, FileActionType, } from 'quidproquo-core';
1
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, FileActionType, FileGenerateTemporaryUploadSecureUrlErrorTypeEnum, } from 'quidproquo-core';
2
2
  import { createSecureUrlToken, getSecureUrlBaseUrl } from './secureUrlUtils';
3
3
  import { resolveFilePath } from './utils';
4
+ // Mirror the AWS processor's SigV4 limit (7 days) so a story that would fail
5
+ // in production fails the same way on the dev server.
6
+ const maxExpirationMs = 7 * 24 * 60 * 60 * 1000;
4
7
  const getProcessFileGenerateTemporaryUploadSecureUrl = (qpqConfig, config) => {
5
- return async ({ drive, filepath, expirationMs, contentType }) => {
8
+ return async ({ drive, filepath, expirationMs, contentType, scope }) => {
9
+ if (expirationMs > maxExpirationMs) {
10
+ return actionResultError(FileGenerateTemporaryUploadSecureUrlErrorTypeEnum.ExpirationTooLong, 'Expiration exceeds the 7 day maximum for presigned URLs');
11
+ }
6
12
  try {
7
13
  const expiresAt = Date.now() + expirationMs;
8
14
  // Create a token for upload
9
15
  const token = createSecureUrlToken({
10
- fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath),
16
+ fullFilepath: resolveFilePath(config, qpqConfig, drive, filepath, scope),
11
17
  operation: 'upload',
12
18
  expiresAt,
13
19
  contentType,
@@ -19,7 +25,11 @@ const getProcessFileGenerateTemporaryUploadSecureUrl = (qpqConfig, config) => {
19
25
  return actionResult(url);
20
26
  }
21
27
  catch (error) {
22
- return actionResultError(ErrorTypeEnum.GenericError, `Unable to generate temporary upload secure URL: ${error.message}`);
28
+ // Name-keyed, not instanceof: under npm link / module federation the
29
+ // error can come from another copy of quidproquo-core.
30
+ return actionResultErrorFromCaughtError(error, {
31
+ InvalidScopeError: (error) => actionResultError(FileGenerateTemporaryUploadSecureUrlErrorTypeEnum.InvalidScope, error.message),
32
+ });
23
33
  }
24
34
  };
25
35
  };