laymos 0.0.1

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +740 -0
  3. package/dist/cli/cli.d.ts +3 -0
  4. package/dist/cli/cli.d.ts.map +1 -0
  5. package/dist/cli/cli.js +6 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +1 -0
  9. package/dist/cli/run.d.ts +11 -0
  10. package/dist/cli/run.d.ts.map +1 -0
  11. package/dist/cli/run.js +242 -0
  12. package/dist/config/define-config.d.ts +3 -0
  13. package/dist/config/define-config.d.ts.map +1 -0
  14. package/dist/config/define-config.js +228 -0
  15. package/dist/config/index.d.ts +5 -0
  16. package/dist/config/index.d.ts.map +1 -0
  17. package/dist/config/index.js +4 -0
  18. package/dist/config/layer-graph.d.ts +7 -0
  19. package/dist/config/layer-graph.d.ts.map +1 -0
  20. package/dist/config/layer-graph.js +53 -0
  21. package/dist/config/layer.d.ts +5 -0
  22. package/dist/config/layer.d.ts.map +1 -0
  23. package/dist/config/layer.js +21 -0
  24. package/dist/config/module.d.ts +9 -0
  25. package/dist/config/module.d.ts.map +1 -0
  26. package/dist/config/module.js +29 -0
  27. package/dist/config/path.d.ts +4 -0
  28. package/dist/config/path.d.ts.map +1 -0
  29. package/dist/config/path.js +32 -0
  30. package/dist/config/types.d.ts +36 -0
  31. package/dist/config/types.d.ts.map +1 -0
  32. package/dist/config/types.js +1 -0
  33. package/dist/engine/1-extract/extract.d.ts +12 -0
  34. package/dist/engine/1-extract/extract.d.ts.map +1 -0
  35. package/dist/engine/1-extract/extract.js +160 -0
  36. package/dist/engine/1-extract/index.d.ts +3 -0
  37. package/dist/engine/1-extract/index.d.ts.map +1 -0
  38. package/dist/engine/1-extract/index.js +1 -0
  39. package/dist/engine/2-resolve/index.d.ts +3 -0
  40. package/dist/engine/2-resolve/index.d.ts.map +1 -0
  41. package/dist/engine/2-resolve/index.js +1 -0
  42. package/dist/engine/2-resolve/resolve.d.ts +24 -0
  43. package/dist/engine/2-resolve/resolve.d.ts.map +1 -0
  44. package/dist/engine/2-resolve/resolve.js +74 -0
  45. package/dist/engine/3-evaluate/evaluate.d.ts +10 -0
  46. package/dist/engine/3-evaluate/evaluate.d.ts.map +1 -0
  47. package/dist/engine/3-evaluate/evaluate.js +80 -0
  48. package/dist/engine/3-evaluate/index.d.ts +3 -0
  49. package/dist/engine/3-evaluate/index.d.ts.map +1 -0
  50. package/dist/engine/3-evaluate/index.js +1 -0
  51. package/dist/engine/4-emit/emit.d.ts +7 -0
  52. package/dist/engine/4-emit/emit.d.ts.map +1 -0
  53. package/dist/engine/4-emit/emit.js +77 -0
  54. package/dist/engine/4-emit/index.d.ts +2 -0
  55. package/dist/engine/4-emit/index.d.ts.map +1 -0
  56. package/dist/engine/4-emit/index.js +1 -0
  57. package/dist/engine/errors.d.ts +19 -0
  58. package/dist/engine/errors.d.ts.map +1 -0
  59. package/dist/engine/errors.js +5 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +1 -0
  63. package/dist/node.d.ts +24 -0
  64. package/dist/node.d.ts.map +1 -0
  65. package/dist/node.js +139 -0
  66. package/dist/report/architecture.d.ts +104 -0
  67. package/dist/report/architecture.d.ts.map +1 -0
  68. package/dist/report/architecture.js +1 -0
  69. package/dist/report/index.d.ts +3 -0
  70. package/dist/report/index.d.ts.map +1 -0
  71. package/dist/report/index.js +2 -0
  72. package/dist/report/stories.d.ts +22 -0
  73. package/dist/report/stories.d.ts.map +1 -0
  74. package/dist/report/stories.js +1 -0
  75. package/dist/story/artifact/artifact.d.ts +57 -0
  76. package/dist/story/artifact/artifact.d.ts.map +1 -0
  77. package/dist/story/artifact/artifact.js +234 -0
  78. package/dist/story/artifact/index.d.ts +2 -0
  79. package/dist/story/artifact/index.d.ts.map +1 -0
  80. package/dist/story/artifact/index.js +1 -0
  81. package/dist/story/artifact/storage.d.ts +10 -0
  82. package/dist/story/artifact/storage.d.ts.map +1 -0
  83. package/dist/story/artifact/storage.js +162 -0
  84. package/dist/story/artifact/types.d.ts +74 -0
  85. package/dist/story/artifact/types.d.ts.map +1 -0
  86. package/dist/story/artifact/types.js +1 -0
  87. package/dist/story/core/blocks.d.ts +20 -0
  88. package/dist/story/core/blocks.d.ts.map +1 -0
  89. package/dist/story/core/blocks.js +104 -0
  90. package/dist/story/core/declare.d.ts +63 -0
  91. package/dist/story/core/declare.d.ts.map +1 -0
  92. package/dist/story/core/declare.js +62 -0
  93. package/dist/story/core/index.d.ts +4 -0
  94. package/dist/story/core/index.d.ts.map +1 -0
  95. package/dist/story/core/index.js +1 -0
  96. package/dist/story/core/recorder.d.ts +38 -0
  97. package/dist/story/core/recorder.d.ts.map +1 -0
  98. package/dist/story/core/recorder.js +28 -0
  99. package/dist/story/core/runtime-context.d.ts +14 -0
  100. package/dist/story/core/runtime-context.d.ts.map +1 -0
  101. package/dist/story/core/runtime-context.js +19 -0
  102. package/dist/story/core/types.d.ts +21 -0
  103. package/dist/story/core/types.d.ts.map +1 -0
  104. package/dist/story/core/types.js +1 -0
  105. package/dist/story/effect/effect.d.ts +52 -0
  106. package/dist/story/effect/effect.d.ts.map +1 -0
  107. package/dist/story/effect/effect.js +99 -0
  108. package/dist/story/effect/index.d.ts +3 -0
  109. package/dist/story/effect/index.d.ts.map +1 -0
  110. package/dist/story/effect/index.js +1 -0
  111. package/dist/story/runner/index.d.ts +3 -0
  112. package/dist/story/runner/index.d.ts.map +1 -0
  113. package/dist/story/runner/index.js +1 -0
  114. package/dist/story/runner/runner.d.ts +44 -0
  115. package/dist/story/runner/runner.d.ts.map +1 -0
  116. package/dist/story/runner/runner.js +476 -0
  117. package/dist/story/story-runtime/index.d.ts +2 -0
  118. package/dist/story/story-runtime/index.d.ts.map +1 -0
  119. package/dist/story/story-runtime/index.js +1 -0
  120. package/dist/story/story-runtime/story-runtime.d.ts +52 -0
  121. package/dist/story/story-runtime/story-runtime.d.ts.map +1 -0
  122. package/dist/story/story-runtime/story-runtime.js +194 -0
  123. package/dist/story/vanilla/index.d.ts +29 -0
  124. package/dist/story/vanilla/index.d.ts.map +1 -0
  125. package/dist/story/vanilla/index.js +78 -0
  126. package/package.json +68 -0
@@ -0,0 +1,5 @@
1
+ import { Data } from 'effect';
2
+ export class ConfigLoadError extends Data.TaggedError('ConfigLoadError') {
3
+ }
4
+ export class ExtractError extends Data.TaggedError('ExtractError') {
5
+ }
@@ -0,0 +1,2 @@
1
+ export * from './config/index.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './config/index.js';
package/dist/node.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { Effect } from 'effect';
2
+ import type { LaymosError } from './engine/errors.js';
3
+ import type { LaymosReport } from './report/index.js';
4
+ import type { StoryArtifact, StoryCatalog, StoryGroupPath } from './report/stories.js';
5
+ import { StoryDiscoveryError, StoryRunnerError } from './story/runner/index.js';
6
+ import type { StoryFailure, StoriesRunResult, StoryRunOptions } from './story/runner/index.js';
7
+ export { ConfigLoadError, ExtractError } from './engine/errors.js';
8
+ export { StoryDiscoveryError, StoryRunnerError } from './story/runner/index.js';
9
+ export type { StoryDiscoveryIssue, StoriesRunResult, StoryFailure, StoryRunOptions, } from './story/runner/index.js';
10
+ /** load config → extract → resolve → evaluate → emit; violations are data, config errors fail. */
11
+ export declare function analyzeProject(baseDir: string): Effect.Effect<LaymosReport, LaymosError>;
12
+ export interface StoryRunResult {
13
+ readonly status: 'passed' | 'failed';
14
+ readonly artifact: StoryArtifact;
15
+ readonly failures: readonly StoryFailure[];
16
+ }
17
+ export type AllStoriesRunResult = StoriesRunResult;
18
+ export declare function discoverStories(baseDir: string): Effect.Effect<StoryCatalog, StoryDiscoveryError>;
19
+ export declare function runStory(baseDir: string, storyId: string, options?: StoryRunOptions): Effect.Effect<StoryRunResult, StoryRunnerError>;
20
+ export declare function runAllStories(baseDir: string, options?: StoryRunOptions): Effect.Effect<AllStoriesRunResult, StoryRunnerError>;
21
+ export declare function runStoryGroup(baseDir: string, groupPath: StoryGroupPath, options?: StoryRunOptions): Effect.Effect<StoriesRunResult, StoryDiscoveryError | StoryRunnerError>;
22
+ /** Runs the given Story files (all of them when empty) and returns fresh evidence. */
23
+ export declare function runStories(baseDir: string, storyIds: readonly string[], options?: StoryRunOptions): Effect.Effect<StoriesRunResult, StoryRunnerError>;
24
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAUhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,mBAAmB,EACnB,gBAAgB,EAEjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChF,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,yBAAyB,CAAC;AAEjC,kGAAkG;AAClG,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAa1C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEnD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAElD;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAqBjD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAEtD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,cAAc,EACzB,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,GAAG,gBAAgB,CAAC,CA0BzE;AAED,sFAAsF;AACtF,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAEnD"}
package/dist/node.js ADDED
@@ -0,0 +1,139 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Effect } from 'effect';
4
+ import { createJiti } from 'jiti';
5
+ import { defineConfig } from './config/define-config.js';
6
+ import { extractFileGraph } from './engine/1-extract/index.js';
7
+ import { resolveProject } from './engine/2-resolve/index.js';
8
+ import { evaluateRules } from './engine/3-evaluate/index.js';
9
+ import { emitReport } from './engine/4-emit/index.js';
10
+ import { ConfigLoadError } from './engine/errors.js';
11
+ import { discoverStories as discoverStoryCatalog, StoryRunnerError, executeStories, } from './story/runner/index.js';
12
+ export { ConfigLoadError, ExtractError } from './engine/errors.js';
13
+ export { StoryDiscoveryError, StoryRunnerError } from './story/runner/index.js';
14
+ /** load config → extract → resolve → evaluate → emit; violations are data, config errors fail. */
15
+ export function analyzeProject(baseDir) {
16
+ return Effect.gen(function* () {
17
+ const config = yield* loadConfig(baseDir);
18
+ const warnings = findMissingPathWarnings(baseDir, config);
19
+ const fileGraph = yield* extractFileGraph(baseDir, config.sourceRoots, config.ignore ?? []);
20
+ const resolved = yield* resolveProject(config, fileGraph);
21
+ const evaluation = yield* evaluateRules(resolved);
22
+ return yield* emitReport(resolved, evaluation, warnings);
23
+ });
24
+ }
25
+ export function discoverStories(baseDir) {
26
+ return discoverStoryCatalog(baseDir);
27
+ }
28
+ export function runStory(baseDir, storyId, options) {
29
+ return executeStories(baseDir, [storyId], options).pipe(Effect.flatMap((result) => {
30
+ const artifact = result.report.stories[storyId];
31
+ if (artifact === undefined) {
32
+ const cause = new Error(`Story "${storyId}" did not run`);
33
+ return Effect.fail(new StoryRunnerError({
34
+ operation: 'execute',
35
+ message: cause.message,
36
+ cause,
37
+ }));
38
+ }
39
+ return Effect.succeed({
40
+ status: result.status,
41
+ artifact,
42
+ failures: result.failures,
43
+ });
44
+ }));
45
+ }
46
+ export function runAllStories(baseDir, options) {
47
+ return executeStories(baseDir, [], options);
48
+ }
49
+ export function runStoryGroup(baseDir, groupPath, options) {
50
+ return discoverStories(baseDir).pipe(Effect.flatMap((catalog) => {
51
+ const groupExists = catalog.groups.some(({ path }) => samePath(path, groupPath));
52
+ if (!groupExists) {
53
+ const cause = new Error(`Story Group "${groupPath.join(' / ')}" was not found`);
54
+ return Effect.fail(new StoryRunnerError({
55
+ operation: 'execute',
56
+ message: cause.message,
57
+ cause,
58
+ }));
59
+ }
60
+ const storyIds = catalog.stories
61
+ .filter(({ groupPath: storyGroupPath }) => startsWithPath(storyGroupPath, groupPath))
62
+ .map(({ storyId }) => storyId);
63
+ return executeStories(baseDir, storyIds, options);
64
+ }));
65
+ }
66
+ /** Runs the given Story files (all of them when empty) and returns fresh evidence. */
67
+ export function runStories(baseDir, storyIds, options) {
68
+ return executeStories(baseDir, storyIds, options);
69
+ }
70
+ function samePath(left, right) {
71
+ return (left.length === right.length &&
72
+ left.every((segment, index) => segment === right[index]));
73
+ }
74
+ function startsWithPath(path, prefix) {
75
+ return prefix.every((segment, index) => path[index] === segment);
76
+ }
77
+ function loadConfig(baseDir) {
78
+ const path = resolve(baseDir, 'laymos.config.ts');
79
+ return Effect.tryPromise({
80
+ try: async () => {
81
+ if (!existsSync(path)) {
82
+ throw new Error(`Config file not found: ${path}`);
83
+ }
84
+ const jiti = createJiti(import.meta.url, {
85
+ interopDefault: true,
86
+ moduleCache: false,
87
+ });
88
+ const imported = (await jiti.import(path));
89
+ const config = imported.default;
90
+ if (!isLaymosConfig(config)) {
91
+ throw new Error(`Config at "${path}" must default-export a value created with defineConfig()`);
92
+ }
93
+ return defineConfig(config);
94
+ },
95
+ catch: (cause) => new ConfigLoadError({ path, cause }),
96
+ });
97
+ }
98
+ function isLaymosConfig(value) {
99
+ if (typeof value !== 'object' || value === null || !('graphs' in value)) {
100
+ return false;
101
+ }
102
+ const config = value;
103
+ return (Array.isArray(config.graphs) &&
104
+ config.graphs.every((graph) => graph?.kind === 'layer-graph') &&
105
+ Array.isArray(config.sourceRoots) &&
106
+ (config.modules === undefined || Array.isArray(config.modules)) &&
107
+ (config.moduleRules === undefined || Array.isArray(config.moduleRules)) &&
108
+ (config.ignore === undefined || Array.isArray(config.ignore)));
109
+ }
110
+ function findMissingPathWarnings(baseDir, config) {
111
+ const warnings = [];
112
+ for (const path of config.sourceRoots) {
113
+ if (!existsSync(resolve(baseDir, path))) {
114
+ warnings.push({ kind: 'missing-source-root', path });
115
+ }
116
+ }
117
+ const layers = new Set(config.graphs.flatMap((graph) => [...graph.layers]));
118
+ for (const layer of layers) {
119
+ for (const path of layer.paths) {
120
+ if (!existsSync(resolve(baseDir, path))) {
121
+ warnings.push({
122
+ kind: 'missing-layer-path',
123
+ layer: layer.name,
124
+ path,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ for (const module of config.modules ?? []) {
130
+ if (!existsSync(resolve(baseDir, module.path))) {
131
+ warnings.push({
132
+ kind: 'missing-module-path',
133
+ module: module.path,
134
+ path: module.path,
135
+ });
136
+ }
137
+ }
138
+ return warnings;
139
+ }
@@ -0,0 +1,104 @@
1
+ export interface ReportLayer {
2
+ readonly paths: readonly string[];
3
+ readonly description?: string;
4
+ }
5
+ export interface ReportLayerEdge {
6
+ readonly from: string;
7
+ readonly to: string;
8
+ }
9
+ export interface ReportGraph {
10
+ readonly name: string;
11
+ readonly layers: readonly string[];
12
+ readonly edges: readonly ReportLayerEdge[];
13
+ readonly description?: string;
14
+ }
15
+ export interface ReportModule {
16
+ readonly description?: string;
17
+ }
18
+ export interface ReportModuleRules {
19
+ readonly module: string;
20
+ readonly canImport?: readonly string[];
21
+ readonly canImportedBy?: readonly string[];
22
+ }
23
+ export interface ReportArchitecture {
24
+ readonly sourceRoots: readonly string[];
25
+ readonly layers: Readonly<Record<string, ReportLayer>>;
26
+ readonly graphs: readonly ReportGraph[];
27
+ readonly modules: Readonly<Record<string, ReportModule>>;
28
+ readonly moduleRules: readonly ReportModuleRules[];
29
+ readonly ignoredPaths: readonly string[];
30
+ }
31
+ export type ReportFile = {
32
+ readonly kind: 'ignored';
33
+ readonly imports: readonly string[];
34
+ } | {
35
+ readonly kind: 'uncovered';
36
+ readonly imports: readonly string[];
37
+ } | {
38
+ readonly kind: 'covered';
39
+ readonly layer: string;
40
+ readonly module?: string;
41
+ readonly imports: readonly string[];
42
+ };
43
+ export interface LayerViolation {
44
+ readonly kind: 'layer';
45
+ readonly from: {
46
+ readonly layer: string;
47
+ readonly file: string;
48
+ };
49
+ readonly to: {
50
+ readonly layer: string;
51
+ readonly file: string;
52
+ };
53
+ }
54
+ export interface ModuleViolation {
55
+ readonly kind: 'module';
56
+ readonly rule: 'canImport' | 'canImportedBy';
57
+ readonly from: {
58
+ readonly module: string;
59
+ readonly layer: string;
60
+ readonly file: string;
61
+ };
62
+ readonly to: {
63
+ readonly module: string;
64
+ readonly layer: string;
65
+ readonly file: string;
66
+ };
67
+ }
68
+ export type Violation = LayerViolation | ModuleViolation;
69
+ export interface LayerCoverage {
70
+ readonly totalFiles: number;
71
+ readonly coveredFiles: number;
72
+ readonly uncovered: readonly string[];
73
+ }
74
+ export interface ModuleCoverage {
75
+ readonly layer: string;
76
+ readonly totalFiles: number;
77
+ readonly coveredFiles: number;
78
+ readonly uncovered: readonly string[];
79
+ }
80
+ export interface Coverage {
81
+ readonly layers: LayerCoverage;
82
+ readonly modules: readonly ModuleCoverage[];
83
+ }
84
+ export type AnalysisWarning = {
85
+ readonly kind: 'missing-source-root';
86
+ readonly path: string;
87
+ } | {
88
+ readonly kind: 'missing-layer-path';
89
+ readonly layer: string;
90
+ readonly path: string;
91
+ } | {
92
+ readonly kind: 'missing-module-path';
93
+ readonly module: string;
94
+ readonly path: string;
95
+ };
96
+ export interface LaymosReport {
97
+ readonly schemaVersion: 2;
98
+ readonly architecture: ReportArchitecture;
99
+ readonly files: Readonly<Record<string, ReportFile>>;
100
+ readonly violations: readonly Violation[];
101
+ readonly coverage: Coverage;
102
+ readonly warnings: readonly AnalysisWarning[];
103
+ }
104
+ //# sourceMappingURL=architecture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture.d.ts","sourceRoot":"","sources":["../../src/report/architecture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from './architecture.js';
2
+ export * from './stories.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './architecture.js';
2
+ export * from './stories.js';
@@ -0,0 +1,22 @@
1
+ import type { StoryArtifact, StoryId } from '../story/artifact/types.js';
2
+ export type { BlockId, ExecutionItem, ExecutionPath, ScenarioOutcome, StoryArm, StoryArtifact, StoryBlock, StoryBlockVisitOutcome, StoryDecisionValue, StoryId, StoryScenario, StoryScenarioFailure, StoryScenarioFailurePhase, StorySelectedArm, StorySourceLocation, } from '../story/artifact/types.js';
3
+ export interface LaymosStoriesReport {
4
+ readonly stories: Readonly<Record<StoryId, StoryArtifact>>;
5
+ }
6
+ export type StoryGroupPath = readonly string[];
7
+ export interface StoryCatalogGroup {
8
+ readonly path: StoryGroupPath;
9
+ readonly name: string;
10
+ readonly description: string;
11
+ }
12
+ export interface StoryCatalogStory {
13
+ readonly storyId: StoryId;
14
+ readonly name: string;
15
+ readonly description: string;
16
+ readonly groupPath: StoryGroupPath;
17
+ }
18
+ export interface StoryCatalog {
19
+ readonly groups: readonly StoryCatalogGroup[];
20
+ readonly stories: readonly StoryCatalogStory[];
21
+ }
22
+ //# sourceMappingURL=stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stories.d.ts","sourceRoot":"","sources":["../../src/report/stories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAEzE,YAAY,EACV,OAAO,EACP,aAAa,EACb,aAAa,EACb,eAAe,EACf,QAAQ,EACR,aAAa,EACb,UAAU,EACV,sBAAsB,EACtB,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,oBAAoB,EACpB,yBAAyB,EACzB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,MAAM,EAAE,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import type { ExecutionPath, StoryBlock, StoryBlockVisitOutcome } from './types.js';
2
+ import type { ArmDeclaration, BlockDeclaration, SelectedArm, StoryRecorder } from '../core/recorder.js';
3
+ import type { Attributes } from '../core/types.js';
4
+ interface MutableVisit {
5
+ readonly blockId: string;
6
+ outcome: StoryBlockVisitOutcome;
7
+ readonly startOffsetMillis: number;
8
+ durationMillis: number;
9
+ readonly selectedArm?: SelectedArm;
10
+ readonly attributes?: Readonly<Record<string, unknown>>;
11
+ readonly children: MutableExecutionItem[];
12
+ }
13
+ type MutableExecutionItem = MutableVisit | {
14
+ readonly parallel: MutableExecutionItem[][];
15
+ };
16
+ interface Container {
17
+ readonly items: MutableExecutionItem[];
18
+ readonly active: Set<VisitToken>;
19
+ parallel: {
20
+ readonly parallel: MutableExecutionItem[][];
21
+ } | undefined;
22
+ }
23
+ interface VisitToken {
24
+ readonly visit: MutableVisit;
25
+ readonly container: Container;
26
+ readonly children: Container;
27
+ }
28
+ export declare class StoryBlockRegistry {
29
+ private readonly baseDir;
30
+ private readonly blocks;
31
+ constructor(baseDir: string);
32
+ declare(block: BlockDeclaration): string;
33
+ declareArm(block: BlockDeclaration, arm: ArmDeclaration): void;
34
+ toRecord(): Readonly<Record<string, StoryBlock>>;
35
+ }
36
+ export declare class ScenarioRecorder implements StoryRecorder {
37
+ private readonly blocks;
38
+ private readonly root;
39
+ private readonly anchor;
40
+ private closed;
41
+ private active;
42
+ constructor(blocks: StoryBlockRegistry);
43
+ declareArm(block: BlockDeclaration, arm: ArmDeclaration): void;
44
+ start(block: BlockDeclaration, selectedArm: SelectedArm | undefined, attributes: Attributes | undefined, parent: unknown): VisitToken;
45
+ finish(token: unknown, outcome: StoryBlockVisitOutcome): void;
46
+ close(): void;
47
+ /** Activates recording for the shared Story execution phase. */
48
+ activate(): void;
49
+ /** Deactivates recording outside the shared Story execution phase. */
50
+ deactivate(): void;
51
+ execution(): ExecutionPath;
52
+ private interrupt;
53
+ private offset;
54
+ }
55
+ export type { ExecutionItem, ExecutionPath, StoryArtifact } from './types.js';
56
+ export declare function roundMillis(value: number): number;
57
+ //# sourceMappingURL=artifact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../../src/story/artifact/artifact.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,aAAa,EAEb,UAAU,EACV,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,aAAa,EACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,UAAU,YAAY;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAC3C;AAED,KAAK,oBAAoB,GACrB,YAAY,GACZ;IAAE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,CAAA;CAAE,CAAC;AAEpD,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,EAAE,oBAAoB,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACjC,QAAQ,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,CAAA;KAAE,GAAG,SAAS,CAAC;CACvE;AAED,UAAU,UAAU;IAClB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AASD,qBAAa,kBAAkB;IAGjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAE7D,YAA6B,OAAO,EAAE,MAAM,EAAI;IAEhD,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAyBvC;IAED,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAc7D;IAED,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAgB/C;CACF;AAED,qBAAa,gBAAiB,YAAW,aAAa;IAMxC,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IAEvB,YAA6B,MAAM,EAAE,kBAAkB,EAAI;IAE3D,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAG7D;IAED,KAAK,CACH,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,MAAM,EAAE,OAAO,GACd,UAAU,CAuBZ;IAED,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAU5D;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,gEAAgE;IAChE,QAAQ,IAAI,IAAI,CAEf;IAED,sEAAsE;IACtE,UAAU,IAAI,IAAI,CAEjB;IAED,SAAS,IAAI,aAAa,CAEzB;IAED,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,MAAM;CAGf;AAED,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,234 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { relative, sep } from 'node:path';
3
+ const closedVisitToken = Symbol('closed-story-visit');
4
+ export class StoryBlockRegistry {
5
+ baseDir;
6
+ blocks = new Map();
7
+ constructor(baseDir) {
8
+ this.baseDir = baseDir;
9
+ }
10
+ declare(block) {
11
+ const location = normalizeLocation(this.baseDir, block);
12
+ const id = `${location.file}:${location.line}:${location.column}`;
13
+ const existing = this.blocks.get(id);
14
+ if (existing !== undefined) {
15
+ if (existing.block.name !== block.name ||
16
+ existing.block.kind !== block.kind ||
17
+ existing.block.description !== block.description) {
18
+ throw new Error(`Conflicting Story Block declarations at ${id}`);
19
+ }
20
+ return id;
21
+ }
22
+ const common = {
23
+ name: block.name,
24
+ description: block.description,
25
+ location,
26
+ };
27
+ const storyBlock = block.kind === 'decision'
28
+ ? { ...common, kind: 'decision', arms: [] }
29
+ : { ...common, kind: 'block' };
30
+ this.blocks.set(id, { block: storyBlock, arms: new Map() });
31
+ return id;
32
+ }
33
+ declareArm(block, arm) {
34
+ const id = this.declare(block);
35
+ const registered = this.blocks.get(id);
36
+ if (registered?.block.kind !== 'decision') {
37
+ throw new Error(`Block "${block.name}" cannot declare Decision Arms`);
38
+ }
39
+ const key = armKey(arm);
40
+ const existing = registered.arms.get(key);
41
+ if (existing !== undefined && !sameArm(existing, arm)) {
42
+ throw new Error(`Conflicting metadata for Arm "${arm.name}" on Decision "${block.name}"`);
43
+ }
44
+ registered.arms.set(key, arm);
45
+ }
46
+ toRecord() {
47
+ return Object.fromEntries([...this.blocks.entries()]
48
+ .sort(([left], [right]) => left.localeCompare(right))
49
+ .map(([id, registered]) => [
50
+ id,
51
+ registered.block.kind === 'decision'
52
+ ? {
53
+ ...registered.block,
54
+ arms: [...registered.arms.values()].sort((left, right) => armKey(left).localeCompare(armKey(right))),
55
+ }
56
+ : registered.block,
57
+ ]));
58
+ }
59
+ }
60
+ export class ScenarioRecorder {
61
+ blocks;
62
+ root = makeContainer([]);
63
+ anchor = performance.now();
64
+ closed = false;
65
+ active = false;
66
+ constructor(blocks) {
67
+ this.blocks = blocks;
68
+ }
69
+ declareArm(block, arm) {
70
+ if (this.closed || !this.active)
71
+ return;
72
+ this.blocks.declareArm(block, arm);
73
+ }
74
+ start(block, selectedArm, attributes, parent) {
75
+ if (this.closed || !this.active)
76
+ return closedVisitToken;
77
+ const parentToken = isVisitToken(parent) ? parent : undefined;
78
+ const container = parentToken?.children ?? this.root;
79
+ const visit = {
80
+ blockId: this.blocks.declare(block),
81
+ outcome: 'succeeded',
82
+ startOffsetMillis: this.offset(),
83
+ durationMillis: 0,
84
+ ...(selectedArm === undefined ? {} : { selectedArm }),
85
+ ...(attributes === undefined
86
+ ? {}
87
+ : { attributes: serializeAttributes(block, attributes) }),
88
+ children: [],
89
+ };
90
+ const token = {
91
+ visit,
92
+ container,
93
+ children: makeContainer(visit.children),
94
+ };
95
+ appendVisit(container, token);
96
+ return token;
97
+ }
98
+ finish(token, outcome) {
99
+ if (this.closed || token === closedVisitToken)
100
+ return;
101
+ if (!isVisitToken(token))
102
+ throw new Error('Invalid Story Block Visit token');
103
+ token.visit.outcome = outcome;
104
+ token.visit.durationMillis = roundMillis(this.offset() - token.visit.startOffsetMillis);
105
+ token.container.active.delete(token);
106
+ if (token.container.active.size === 0)
107
+ token.container.parallel = undefined;
108
+ }
109
+ close() {
110
+ for (const token of this.root.active)
111
+ this.interrupt(token);
112
+ this.closed = true;
113
+ }
114
+ /** Activates recording for the shared Story execution phase. */
115
+ activate() {
116
+ if (!this.closed)
117
+ this.active = true;
118
+ }
119
+ /** Deactivates recording outside the shared Story execution phase. */
120
+ deactivate() {
121
+ this.active = false;
122
+ }
123
+ execution() {
124
+ return normalizePath(this.root.items);
125
+ }
126
+ interrupt(token) {
127
+ token.visit.outcome = 'interrupted';
128
+ token.visit.durationMillis = roundMillis(this.offset() - token.visit.startOffsetMillis);
129
+ for (const child of token.children.active)
130
+ this.interrupt(child);
131
+ }
132
+ offset() {
133
+ return roundMillis(performance.now() - this.anchor);
134
+ }
135
+ }
136
+ export function roundMillis(value) {
137
+ return Math.round(value * 1000) / 1000;
138
+ }
139
+ function makeContainer(items) {
140
+ return { items, active: new Set(), parallel: undefined };
141
+ }
142
+ function appendVisit(container, token) {
143
+ if (container.active.size === 0) {
144
+ container.items.push(token.visit);
145
+ }
146
+ else if (container.parallel === undefined) {
147
+ const prior = [...container.active][0];
148
+ if (prior === undefined)
149
+ throw new Error('Invalid active Story Visit state');
150
+ const priorIndex = container.items.lastIndexOf(prior.visit);
151
+ if (priorIndex < 0) {
152
+ throw new Error('Ambiguous concurrent Story Block structure');
153
+ }
154
+ const parallel = { parallel: [[prior.visit], [token.visit]] };
155
+ container.items.splice(priorIndex, 1, parallel);
156
+ container.parallel = parallel;
157
+ }
158
+ else {
159
+ container.parallel.parallel.push([token.visit]);
160
+ }
161
+ container.active.add(token);
162
+ }
163
+ function normalizePath(items) {
164
+ const normalized = [];
165
+ for (const item of items) {
166
+ if ('parallel' in item) {
167
+ const branches = item.parallel
168
+ .map(normalizePath)
169
+ .filter((branch) => branch.length > 0)
170
+ .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
171
+ if (branches.length === 1)
172
+ normalized.push(...branches[0]);
173
+ else if (branches.length > 1)
174
+ normalized.push({ parallel: branches });
175
+ continue;
176
+ }
177
+ normalized.push({
178
+ blockId: item.blockId,
179
+ outcome: item.outcome,
180
+ startOffsetMillis: item.startOffsetMillis,
181
+ durationMillis: item.durationMillis,
182
+ ...(item.selectedArm === undefined
183
+ ? {}
184
+ : { selectedArm: item.selectedArm }),
185
+ ...(item.attributes === undefined ? {} : { attributes: item.attributes }),
186
+ children: normalizePath(item.children),
187
+ });
188
+ }
189
+ return normalized;
190
+ }
191
+ function normalizeLocation(baseDir, block) {
192
+ const file = block.location.file.startsWith('/')
193
+ ? relative(realpathSync(baseDir), realpathSync(block.location.file))
194
+ : block.location.file;
195
+ return {
196
+ file: file.split(sep).join('/'),
197
+ line: block.location.line,
198
+ column: block.location.column,
199
+ };
200
+ }
201
+ function armKey(arm) {
202
+ return arm.kind === 'otherwise'
203
+ ? 'otherwise'
204
+ : `literal:${typeof arm.value}:${JSON.stringify(arm.value)}`;
205
+ }
206
+ function sameArm(left, right) {
207
+ return (left.kind === right.kind &&
208
+ left.name === right.name &&
209
+ left.description === right.description &&
210
+ (left.kind === 'otherwise' ||
211
+ (right.kind === 'literal' && Object.is(left.value, right.value))));
212
+ }
213
+ function serializeAttributes(block, attributes) {
214
+ try {
215
+ const json = JSON.stringify(attributes);
216
+ if (json === undefined)
217
+ throw new TypeError('value serialized to undefined');
218
+ const value = JSON.parse(json);
219
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
220
+ throw new TypeError('value did not serialize to an object');
221
+ }
222
+ return value;
223
+ }
224
+ catch (cause) {
225
+ throw new Error(`Attributes for Story Block "${block.name}" at ${block.location.file}:${block.location.line}:${block.location.column} are not JSON-serializable`, { cause });
226
+ }
227
+ }
228
+ function isVisitToken(value) {
229
+ return (typeof value === 'object' &&
230
+ value !== null &&
231
+ 'visit' in value &&
232
+ 'container' in value &&
233
+ 'children' in value);
234
+ }
@@ -0,0 +1,2 @@
1
+ export { roundMillis, ScenarioRecorder, StoryBlockRegistry, } from './artifact.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/story/artifact/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
@@ -0,0 +1 @@
1
+ export { roundMillis, ScenarioRecorder, StoryBlockRegistry, } from './artifact.js';
@@ -0,0 +1,10 @@
1
+ import type { StoryArtifact, StoryIndex } from './types.js';
2
+ export declare function writeRawArtifact(baseDir: string, storyId: string, artifact: StoryArtifact): Promise<void>;
3
+ export declare function clearRawArtifacts(baseDir: string): Promise<void>;
4
+ export declare function commitRawArtifacts(baseDir: string, complete: boolean): Promise<{
5
+ readonly index: StoryIndex;
6
+ readonly committedStoryIds: ReadonlySet<string>;
7
+ }>;
8
+ export declare function getStoryIndex(baseDir: string): Promise<StoryIndex>;
9
+ export declare function getStoryArtifact(baseDir: string, storyId: string): Promise<StoryArtifact>;
10
+ //# sourceMappingURL=storage.d.ts.map