@loomstack/core 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.
@@ -0,0 +1,472 @@
1
+ interface FeatureRoute {
2
+ id: string;
3
+ path: string;
4
+ view: string;
5
+ }
6
+ interface FeatureManifest {
7
+ id: string;
8
+ name: string;
9
+ description?: string;
10
+ entities: string[];
11
+ routes: FeatureRoute[];
12
+ actions: string[];
13
+ queries: string[];
14
+ permissions?: Record<string, string>;
15
+ }
16
+ interface LoomStackProjectConfig {
17
+ appName: string;
18
+ packageManager: "pnpm";
19
+ frontend: "react";
20
+ backend: "koa";
21
+ database: "postgres";
22
+ featuresDir: string;
23
+ generatedDir: string;
24
+ }
25
+ interface FrameworkError {
26
+ code: string;
27
+ severity: "error" | "warning";
28
+ message: string;
29
+ file?: string;
30
+ location?: {
31
+ line?: number;
32
+ column?: number;
33
+ };
34
+ repair: string;
35
+ relatedFiles?: string[];
36
+ docs?: string;
37
+ }
38
+ interface CommandResult<T = Record<string, unknown>> {
39
+ ok: boolean;
40
+ data?: T;
41
+ errors?: FrameworkError[];
42
+ }
43
+ interface NamedFile {
44
+ name: string;
45
+ file: string;
46
+ }
47
+ interface ViewFile extends NamedFile {
48
+ route?: string;
49
+ }
50
+ interface ScannedFeature {
51
+ id: string;
52
+ directory: string;
53
+ manifestPath: string;
54
+ manifest: FeatureManifest;
55
+ schemas: NamedFile[];
56
+ actions: NamedFile[];
57
+ queries: NamedFile[];
58
+ views: ViewFile[];
59
+ tests: string[];
60
+ }
61
+ interface FeatureGraphNode {
62
+ id: string;
63
+ manifest: string;
64
+ entities: string[];
65
+ actions: NamedFile[];
66
+ queries: NamedFile[];
67
+ views: ViewFile[];
68
+ tests: string[];
69
+ routes: string[];
70
+ }
71
+ interface FeatureGraph {
72
+ features: FeatureGraphNode[];
73
+ dependencies: Array<{
74
+ from: string;
75
+ to: string;
76
+ }>;
77
+ }
78
+ interface LoomStackProject {
79
+ root: string;
80
+ configPath: string;
81
+ config: LoomStackProjectConfig;
82
+ features: ScannedFeature[];
83
+ graph: FeatureGraph;
84
+ }
85
+ interface ScanResult {
86
+ project?: LoomStackProject;
87
+ errors: FrameworkError[];
88
+ }
89
+ interface VerifyResult {
90
+ ok: boolean;
91
+ errors: FrameworkError[];
92
+ warnings: FrameworkError[];
93
+ }
94
+ interface GeneratedFileRecord {
95
+ path: string;
96
+ sha256: string;
97
+ }
98
+ interface GeneratedFilesManifest {
99
+ generatedBy: "loomstack";
100
+ doNotEdit: true;
101
+ files: GeneratedFileRecord[];
102
+ }
103
+
104
+ interface ErrorDefinition {
105
+ title: string;
106
+ defaultMessage: string;
107
+ repair: string;
108
+ docs: string;
109
+ }
110
+ declare const ERROR_CATALOG: {
111
+ readonly loomstack1001: {
112
+ readonly title: "Feature ID does not match folder name";
113
+ readonly defaultMessage: "The feature manifest ID must match its folder name.";
114
+ readonly repair: "Rename the folder or update feature.yaml so the feature ID matches the folder name.";
115
+ readonly docs: "implementation-plan/docs/10-verifier-and-error-system.md";
116
+ };
117
+ readonly loomstack1002: {
118
+ readonly title: "Missing or invalid feature manifest field";
119
+ readonly defaultMessage: "A required feature manifest field is missing or invalid.";
120
+ readonly repair: "Add the required field to feature.yaml using the canonical manifest shape.";
121
+ readonly docs: "implementation-plan/docs/03-feature-contract.md";
122
+ };
123
+ readonly loomstack1003: {
124
+ readonly title: "Manifest action is not exported";
125
+ readonly defaultMessage: "An action declared in the manifest has no implementation.";
126
+ readonly repair: "Create the action file or update the manifest action name.";
127
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
128
+ };
129
+ readonly loomstack1004: {
130
+ readonly title: "Exported action is missing from manifest";
131
+ readonly defaultMessage: "An exported action is not declared in feature.yaml.";
132
+ readonly repair: "Add the action name to feature.yaml or remove the action implementation.";
133
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
134
+ };
135
+ readonly loomstack1005: {
136
+ readonly title: "Duplicate route path";
137
+ readonly defaultMessage: "Route paths must be globally unique.";
138
+ readonly repair: "Change one route path so every route path is globally unique.";
139
+ readonly docs: "implementation-plan/docs/03-feature-contract.md";
140
+ };
141
+ readonly loomstack1006: {
142
+ readonly title: "Invalid feature manifest YAML";
143
+ readonly defaultMessage: "feature.yaml could not be parsed.";
144
+ readonly repair: "Fix the YAML syntax and run loomstack verify again.";
145
+ readonly docs: "implementation-plan/docs/03-feature-contract.md";
146
+ };
147
+ readonly loomstack1007: {
148
+ readonly title: "Manifest query is not exported";
149
+ readonly defaultMessage: "A query declared in the manifest has no implementation.";
150
+ readonly repair: "Create the query file or update the manifest query name.";
151
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
152
+ };
153
+ readonly loomstack1008: {
154
+ readonly title: "Exported query is missing from manifest";
155
+ readonly defaultMessage: "An exported query is not declared in feature.yaml.";
156
+ readonly repair: "Add the query name to feature.yaml or remove the query implementation.";
157
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
158
+ };
159
+ readonly loomstack1009: {
160
+ readonly title: "Manifest view is not exported";
161
+ readonly defaultMessage: "A route references a view that does not exist.";
162
+ readonly repair: "Create the declared *.view.tsx file or update the route view in feature.yaml.";
163
+ readonly docs: "implementation-plan/docs/06-react-frontend-adapter.md";
164
+ };
165
+ readonly loomstack1010: {
166
+ readonly title: "Manifest entity is not exported";
167
+ readonly defaultMessage: "An entity declared in the manifest has no schema export.";
168
+ readonly repair: "Export the entity from model.schema.ts or update the manifest entity name.";
169
+ readonly docs: "implementation-plan/docs/04-schema-and-domain-layer.md";
170
+ };
171
+ readonly loomstack1011: {
172
+ readonly title: "Invalid feature name";
173
+ readonly defaultMessage: "Feature IDs must be kebab-case.";
174
+ readonly repair: "Use a lowercase kebab-case feature name such as project-notes.";
175
+ readonly docs: "implementation-plan/docs/03-feature-contract.md";
176
+ };
177
+ readonly loomstack2001: {
178
+ readonly title: "Forbidden database import in UI file";
179
+ readonly defaultMessage: "Database imports are forbidden in React UI files.";
180
+ readonly repair: "Move database access into queries/*.query.ts or actions/*.action.ts.";
181
+ readonly docs: "implementation-plan/docs/10-verifier-and-error-system.md";
182
+ };
183
+ readonly loomstack2002: {
184
+ readonly title: "Forbidden raw fetch in UI file";
185
+ readonly defaultMessage: "Raw fetch is forbidden in React UI files.";
186
+ readonly repair: "Use the generated loomstack action/query client.";
187
+ readonly docs: "implementation-plan/docs/10-verifier-and-error-system.md";
188
+ };
189
+ readonly loomstack2003: {
190
+ readonly title: "Koa import in feature logic";
191
+ readonly defaultMessage: "Feature logic must be transport-independent.";
192
+ readonly repair: "Remove the Koa dependency and use LoomStackRequestContext.";
193
+ readonly docs: "implementation-plan/docs/10-verifier-and-error-system.md";
194
+ };
195
+ readonly loomstack3001: {
196
+ readonly title: "Action input validation failed";
197
+ readonly defaultMessage: "Action input did not match its schema.";
198
+ readonly repair: "Send input matching the action input schema.";
199
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
200
+ };
201
+ readonly loomstack3002: {
202
+ readonly title: "Runtime output validation failed";
203
+ readonly defaultMessage: "Action or query output did not match its schema.";
204
+ readonly repair: "Return a value matching the declared output schema.";
205
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
206
+ };
207
+ readonly loomstack3003: {
208
+ readonly title: "Authentication required";
209
+ readonly defaultMessage: "This operation requires an authenticated user.";
210
+ readonly repair: "Authenticate the request before calling this operation.";
211
+ readonly docs: "implementation-plan/docs/05-action-query-runtime.md";
212
+ };
213
+ readonly loomstack4040: {
214
+ readonly title: "Unknown action";
215
+ readonly defaultMessage: "The requested action is not registered.";
216
+ readonly repair: "Use an action declared in feature.yaml and run loomstack generate.";
217
+ readonly docs: "implementation-plan/docs/07-koa-backend-adapter.md";
218
+ };
219
+ readonly loomstack4041: {
220
+ readonly title: "Unknown query";
221
+ readonly defaultMessage: "The requested query is not registered.";
222
+ readonly repair: "Use a query declared in feature.yaml and run loomstack generate.";
223
+ readonly docs: "implementation-plan/docs/07-koa-backend-adapter.md";
224
+ };
225
+ readonly loomstack4050: {
226
+ readonly title: "Invalid RPC method";
227
+ readonly defaultMessage: "loomstack RPC endpoints accept only HTTP POST.";
228
+ readonly repair: "Send the action or query request with HTTP POST.";
229
+ readonly docs: "implementation-plan/docs/07-koa-backend-adapter.md";
230
+ };
231
+ readonly loomstack4001: {
232
+ readonly title: "Generated file was manually modified";
233
+ readonly defaultMessage: "A generated file does not match its recorded hash.";
234
+ readonly repair: "Run loomstack generate or move custom logic out of the generated file.";
235
+ readonly docs: "implementation-plan/docs/09-code-generation.md";
236
+ };
237
+ readonly loomstack4002: {
238
+ readonly title: "Generated file is stale";
239
+ readonly defaultMessage: "Generated output does not match current feature contracts.";
240
+ readonly repair: "Run loomstack generate.";
241
+ readonly docs: "implementation-plan/docs/09-code-generation.md";
242
+ };
243
+ readonly loomstack5001: {
244
+ readonly title: "Missing loomstack config";
245
+ readonly defaultMessage: "No loomstack.config.ts was found.";
246
+ readonly repair: "Create loomstack.config.ts or run the command inside a loomstack project.";
247
+ readonly docs: "implementation-plan/docs/02-repository-structure.md";
248
+ };
249
+ readonly loomstack5002: {
250
+ readonly title: "Target already exists";
251
+ readonly defaultMessage: "The requested target already exists.";
252
+ readonly repair: "Choose another name or remove the existing target first.";
253
+ readonly docs: "implementation-plan/docs/08-cli-specification.md";
254
+ };
255
+ readonly loomstack5003: {
256
+ readonly title: "Invalid project configuration";
257
+ readonly defaultMessage: "loomstack.config.ts does not contain the required golden-path values.";
258
+ readonly repair: "Use React, Koa, PostgreSQL, pnpm, and explicit featuresDir/generatedDir values.";
259
+ readonly docs: "implementation-plan/docs/02-repository-structure.md";
260
+ };
261
+ readonly loomstack6001: {
262
+ readonly title: "Unsupported PostgreSQL field";
263
+ readonly defaultMessage: "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
264
+ readonly repair: "Use a supported scalar field or provide explicit persistence logic.";
265
+ readonly docs: "implementation-plan/docs/04-schema-and-domain-layer.md";
266
+ };
267
+ };
268
+ type ErrorCode = keyof typeof ERROR_CATALOG;
269
+ declare function frameworkError(code: ErrorCode, options?: {
270
+ message?: string;
271
+ severity?: "error" | "warning";
272
+ file?: string;
273
+ relatedFiles?: string[];
274
+ location?: {
275
+ line?: number;
276
+ column?: number;
277
+ };
278
+ }): FrameworkError;
279
+ declare function serializeFrameworkError(error: FrameworkError): FrameworkError;
280
+ declare function explainError(code: string): {
281
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
282
+ title: "Feature ID does not match folder name";
283
+ defaultMessage: "The feature manifest ID must match its folder name.";
284
+ repair: "Rename the folder or update feature.yaml so the feature ID matches the folder name.";
285
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md";
286
+ code: string;
287
+ } | {
288
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
289
+ title: "Missing or invalid feature manifest field";
290
+ defaultMessage: "A required feature manifest field is missing or invalid.";
291
+ repair: "Add the required field to feature.yaml using the canonical manifest shape.";
292
+ docs: "implementation-plan/docs/03-feature-contract.md";
293
+ code: string;
294
+ } | {
295
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
296
+ title: "Manifest action is not exported";
297
+ defaultMessage: "An action declared in the manifest has no implementation.";
298
+ repair: "Create the action file or update the manifest action name.";
299
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
300
+ code: string;
301
+ } | {
302
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
303
+ title: "Exported action is missing from manifest";
304
+ defaultMessage: "An exported action is not declared in feature.yaml.";
305
+ repair: "Add the action name to feature.yaml or remove the action implementation.";
306
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
307
+ code: string;
308
+ } | {
309
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
310
+ title: "Duplicate route path";
311
+ defaultMessage: "Route paths must be globally unique.";
312
+ repair: "Change one route path so every route path is globally unique.";
313
+ docs: "implementation-plan/docs/03-feature-contract.md";
314
+ code: string;
315
+ } | {
316
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
317
+ title: "Invalid feature manifest YAML";
318
+ defaultMessage: "feature.yaml could not be parsed.";
319
+ repair: "Fix the YAML syntax and run loomstack verify again.";
320
+ docs: "implementation-plan/docs/03-feature-contract.md";
321
+ code: string;
322
+ } | {
323
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
324
+ title: "Manifest query is not exported";
325
+ defaultMessage: "A query declared in the manifest has no implementation.";
326
+ repair: "Create the query file or update the manifest query name.";
327
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
328
+ code: string;
329
+ } | {
330
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
331
+ title: "Exported query is missing from manifest";
332
+ defaultMessage: "An exported query is not declared in feature.yaml.";
333
+ repair: "Add the query name to feature.yaml or remove the query implementation.";
334
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
335
+ code: string;
336
+ } | {
337
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
338
+ title: "Manifest view is not exported";
339
+ defaultMessage: "A route references a view that does not exist.";
340
+ repair: "Create the declared *.view.tsx file or update the route view in feature.yaml.";
341
+ docs: "implementation-plan/docs/06-react-frontend-adapter.md";
342
+ code: string;
343
+ } | {
344
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
345
+ title: "Manifest entity is not exported";
346
+ defaultMessage: "An entity declared in the manifest has no schema export.";
347
+ repair: "Export the entity from model.schema.ts or update the manifest entity name.";
348
+ docs: "implementation-plan/docs/04-schema-and-domain-layer.md";
349
+ code: string;
350
+ } | {
351
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
352
+ title: "Invalid feature name";
353
+ defaultMessage: "Feature IDs must be kebab-case.";
354
+ repair: "Use a lowercase kebab-case feature name such as project-notes.";
355
+ docs: "implementation-plan/docs/03-feature-contract.md";
356
+ code: string;
357
+ } | {
358
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
359
+ title: "Forbidden database import in UI file";
360
+ defaultMessage: "Database imports are forbidden in React UI files.";
361
+ repair: "Move database access into queries/*.query.ts or actions/*.action.ts.";
362
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md";
363
+ code: string;
364
+ } | {
365
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
366
+ title: "Forbidden raw fetch in UI file";
367
+ defaultMessage: "Raw fetch is forbidden in React UI files.";
368
+ repair: "Use the generated loomstack action/query client.";
369
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md";
370
+ code: string;
371
+ } | {
372
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
373
+ title: "Koa import in feature logic";
374
+ defaultMessage: "Feature logic must be transport-independent.";
375
+ repair: "Remove the Koa dependency and use LoomStackRequestContext.";
376
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md";
377
+ code: string;
378
+ } | {
379
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
380
+ title: "Action input validation failed";
381
+ defaultMessage: "Action input did not match its schema.";
382
+ repair: "Send input matching the action input schema.";
383
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
384
+ code: string;
385
+ } | {
386
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
387
+ title: "Runtime output validation failed";
388
+ defaultMessage: "Action or query output did not match its schema.";
389
+ repair: "Return a value matching the declared output schema.";
390
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
391
+ code: string;
392
+ } | {
393
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
394
+ title: "Authentication required";
395
+ defaultMessage: "This operation requires an authenticated user.";
396
+ repair: "Authenticate the request before calling this operation.";
397
+ docs: "implementation-plan/docs/05-action-query-runtime.md";
398
+ code: string;
399
+ } | {
400
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
401
+ title: "Unknown action";
402
+ defaultMessage: "The requested action is not registered.";
403
+ repair: "Use an action declared in feature.yaml and run loomstack generate.";
404
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md";
405
+ code: string;
406
+ } | {
407
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
408
+ title: "Unknown query";
409
+ defaultMessage: "The requested query is not registered.";
410
+ repair: "Use a query declared in feature.yaml and run loomstack generate.";
411
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md";
412
+ code: string;
413
+ } | {
414
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
415
+ title: "Invalid RPC method";
416
+ defaultMessage: "loomstack RPC endpoints accept only HTTP POST.";
417
+ repair: "Send the action or query request with HTTP POST.";
418
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md";
419
+ code: string;
420
+ } | {
421
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
422
+ title: "Generated file was manually modified";
423
+ defaultMessage: "A generated file does not match its recorded hash.";
424
+ repair: "Run loomstack generate or move custom logic out of the generated file.";
425
+ docs: "implementation-plan/docs/09-code-generation.md";
426
+ code: string;
427
+ } | {
428
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
429
+ title: "Generated file is stale";
430
+ defaultMessage: "Generated output does not match current feature contracts.";
431
+ repair: "Run loomstack generate.";
432
+ docs: "implementation-plan/docs/09-code-generation.md";
433
+ code: string;
434
+ } | {
435
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
436
+ title: "Missing loomstack config";
437
+ defaultMessage: "No loomstack.config.ts was found.";
438
+ repair: "Create loomstack.config.ts or run the command inside a loomstack project.";
439
+ docs: "implementation-plan/docs/02-repository-structure.md";
440
+ code: string;
441
+ } | {
442
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
443
+ title: "Target already exists";
444
+ defaultMessage: "The requested target already exists.";
445
+ repair: "Choose another name or remove the existing target first.";
446
+ docs: "implementation-plan/docs/08-cli-specification.md";
447
+ code: string;
448
+ } | {
449
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
450
+ title: "Invalid project configuration";
451
+ defaultMessage: "loomstack.config.ts does not contain the required golden-path values.";
452
+ repair: "Use React, Koa, PostgreSQL, pnpm, and explicit featuresDir/generatedDir values.";
453
+ docs: "implementation-plan/docs/02-repository-structure.md";
454
+ code: string;
455
+ } | {
456
+ why: "The feature manifest ID must match its folder name." | "A required feature manifest field is missing or invalid." | "An action declared in the manifest has no implementation." | "An exported action is not declared in feature.yaml." | "Route paths must be globally unique." | "feature.yaml could not be parsed." | "A query declared in the manifest has no implementation." | "An exported query is not declared in feature.yaml." | "A route references a view that does not exist." | "An entity declared in the manifest has no schema export." | "Feature IDs must be kebab-case." | "Database imports are forbidden in React UI files." | "Raw fetch is forbidden in React UI files." | "Feature logic must be transport-independent." | "Action input did not match its schema." | "Action or query output did not match its schema." | "This operation requires an authenticated user." | "The requested action is not registered." | "The requested query is not registered." | "loomstack RPC endpoints accept only HTTP POST." | "A generated file does not match its recorded hash." | "Generated output does not match current feature contracts." | "No loomstack.config.ts was found." | "The requested target already exists." | "loomstack.config.ts does not contain the required golden-path values." | "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
457
+ title: "Unsupported PostgreSQL field";
458
+ defaultMessage: "An entity field cannot be represented by the v0.1 PostgreSQL adapter.";
459
+ repair: "Use a supported scalar field or provide explicit persistence logic.";
460
+ docs: "implementation-plan/docs/04-schema-and-domain-layer.md";
461
+ code: string;
462
+ } | undefined;
463
+
464
+ declare function toProjectPath(path: string): string;
465
+ declare function discoverProjectRoot(start: string): string | undefined;
466
+ declare function loadProjectConfig(root: string): {
467
+ config?: LoomStackProjectConfig;
468
+ errors: ReturnType<typeof frameworkError>[];
469
+ };
470
+ declare function scanProject(rootInput: string): ScanResult;
471
+
472
+ export { type CommandResult, ERROR_CATALOG, type ErrorCode, type ErrorDefinition, type FeatureGraph, type FeatureGraphNode, type FeatureManifest, type FeatureRoute, type FrameworkError, type GeneratedFileRecord, type GeneratedFilesManifest, type LoomStackProject, type LoomStackProjectConfig, type NamedFile, type ScanResult, type ScannedFeature, type VerifyResult, type ViewFile, discoverProjectRoot, explainError, frameworkError, loadProjectConfig, scanProject, serializeFrameworkError, toProjectPath };
package/dist/index.js ADDED
@@ -0,0 +1,415 @@
1
+ // src/errors.ts
2
+ var ERROR_CATALOG = {
3
+ loomstack1001: {
4
+ title: "Feature ID does not match folder name",
5
+ defaultMessage: "The feature manifest ID must match its folder name.",
6
+ repair: "Rename the folder or update feature.yaml so the feature ID matches the folder name.",
7
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md"
8
+ },
9
+ loomstack1002: {
10
+ title: "Missing or invalid feature manifest field",
11
+ defaultMessage: "A required feature manifest field is missing or invalid.",
12
+ repair: "Add the required field to feature.yaml using the canonical manifest shape.",
13
+ docs: "implementation-plan/docs/03-feature-contract.md"
14
+ },
15
+ loomstack1003: {
16
+ title: "Manifest action is not exported",
17
+ defaultMessage: "An action declared in the manifest has no implementation.",
18
+ repair: "Create the action file or update the manifest action name.",
19
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
20
+ },
21
+ loomstack1004: {
22
+ title: "Exported action is missing from manifest",
23
+ defaultMessage: "An exported action is not declared in feature.yaml.",
24
+ repair: "Add the action name to feature.yaml or remove the action implementation.",
25
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
26
+ },
27
+ loomstack1005: {
28
+ title: "Duplicate route path",
29
+ defaultMessage: "Route paths must be globally unique.",
30
+ repair: "Change one route path so every route path is globally unique.",
31
+ docs: "implementation-plan/docs/03-feature-contract.md"
32
+ },
33
+ loomstack1006: {
34
+ title: "Invalid feature manifest YAML",
35
+ defaultMessage: "feature.yaml could not be parsed.",
36
+ repair: "Fix the YAML syntax and run loomstack verify again.",
37
+ docs: "implementation-plan/docs/03-feature-contract.md"
38
+ },
39
+ loomstack1007: {
40
+ title: "Manifest query is not exported",
41
+ defaultMessage: "A query declared in the manifest has no implementation.",
42
+ repair: "Create the query file or update the manifest query name.",
43
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
44
+ },
45
+ loomstack1008: {
46
+ title: "Exported query is missing from manifest",
47
+ defaultMessage: "An exported query is not declared in feature.yaml.",
48
+ repair: "Add the query name to feature.yaml or remove the query implementation.",
49
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
50
+ },
51
+ loomstack1009: {
52
+ title: "Manifest view is not exported",
53
+ defaultMessage: "A route references a view that does not exist.",
54
+ repair: "Create the declared *.view.tsx file or update the route view in feature.yaml.",
55
+ docs: "implementation-plan/docs/06-react-frontend-adapter.md"
56
+ },
57
+ loomstack1010: {
58
+ title: "Manifest entity is not exported",
59
+ defaultMessage: "An entity declared in the manifest has no schema export.",
60
+ repair: "Export the entity from model.schema.ts or update the manifest entity name.",
61
+ docs: "implementation-plan/docs/04-schema-and-domain-layer.md"
62
+ },
63
+ loomstack1011: {
64
+ title: "Invalid feature name",
65
+ defaultMessage: "Feature IDs must be kebab-case.",
66
+ repair: "Use a lowercase kebab-case feature name such as project-notes.",
67
+ docs: "implementation-plan/docs/03-feature-contract.md"
68
+ },
69
+ loomstack2001: {
70
+ title: "Forbidden database import in UI file",
71
+ defaultMessage: "Database imports are forbidden in React UI files.",
72
+ repair: "Move database access into queries/*.query.ts or actions/*.action.ts.",
73
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md"
74
+ },
75
+ loomstack2002: {
76
+ title: "Forbidden raw fetch in UI file",
77
+ defaultMessage: "Raw fetch is forbidden in React UI files.",
78
+ repair: "Use the generated loomstack action/query client.",
79
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md"
80
+ },
81
+ loomstack2003: {
82
+ title: "Koa import in feature logic",
83
+ defaultMessage: "Feature logic must be transport-independent.",
84
+ repair: "Remove the Koa dependency and use LoomStackRequestContext.",
85
+ docs: "implementation-plan/docs/10-verifier-and-error-system.md"
86
+ },
87
+ loomstack3001: {
88
+ title: "Action input validation failed",
89
+ defaultMessage: "Action input did not match its schema.",
90
+ repair: "Send input matching the action input schema.",
91
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
92
+ },
93
+ loomstack3002: {
94
+ title: "Runtime output validation failed",
95
+ defaultMessage: "Action or query output did not match its schema.",
96
+ repair: "Return a value matching the declared output schema.",
97
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
98
+ },
99
+ loomstack3003: {
100
+ title: "Authentication required",
101
+ defaultMessage: "This operation requires an authenticated user.",
102
+ repair: "Authenticate the request before calling this operation.",
103
+ docs: "implementation-plan/docs/05-action-query-runtime.md"
104
+ },
105
+ loomstack4040: {
106
+ title: "Unknown action",
107
+ defaultMessage: "The requested action is not registered.",
108
+ repair: "Use an action declared in feature.yaml and run loomstack generate.",
109
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md"
110
+ },
111
+ loomstack4041: {
112
+ title: "Unknown query",
113
+ defaultMessage: "The requested query is not registered.",
114
+ repair: "Use a query declared in feature.yaml and run loomstack generate.",
115
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md"
116
+ },
117
+ loomstack4050: {
118
+ title: "Invalid RPC method",
119
+ defaultMessage: "loomstack RPC endpoints accept only HTTP POST.",
120
+ repair: "Send the action or query request with HTTP POST.",
121
+ docs: "implementation-plan/docs/07-koa-backend-adapter.md"
122
+ },
123
+ loomstack4001: {
124
+ title: "Generated file was manually modified",
125
+ defaultMessage: "A generated file does not match its recorded hash.",
126
+ repair: "Run loomstack generate or move custom logic out of the generated file.",
127
+ docs: "implementation-plan/docs/09-code-generation.md"
128
+ },
129
+ loomstack4002: {
130
+ title: "Generated file is stale",
131
+ defaultMessage: "Generated output does not match current feature contracts.",
132
+ repair: "Run loomstack generate.",
133
+ docs: "implementation-plan/docs/09-code-generation.md"
134
+ },
135
+ loomstack5001: {
136
+ title: "Missing loomstack config",
137
+ defaultMessage: "No loomstack.config.ts was found.",
138
+ repair: "Create loomstack.config.ts or run the command inside a loomstack project.",
139
+ docs: "implementation-plan/docs/02-repository-structure.md"
140
+ },
141
+ loomstack5002: {
142
+ title: "Target already exists",
143
+ defaultMessage: "The requested target already exists.",
144
+ repair: "Choose another name or remove the existing target first.",
145
+ docs: "implementation-plan/docs/08-cli-specification.md"
146
+ },
147
+ loomstack5003: {
148
+ title: "Invalid project configuration",
149
+ defaultMessage: "loomstack.config.ts does not contain the required golden-path values.",
150
+ repair: "Use React, Koa, PostgreSQL, pnpm, and explicit featuresDir/generatedDir values.",
151
+ docs: "implementation-plan/docs/02-repository-structure.md"
152
+ },
153
+ loomstack6001: {
154
+ title: "Unsupported PostgreSQL field",
155
+ defaultMessage: "An entity field cannot be represented by the v0.1 PostgreSQL adapter.",
156
+ repair: "Use a supported scalar field or provide explicit persistence logic.",
157
+ docs: "implementation-plan/docs/04-schema-and-domain-layer.md"
158
+ }
159
+ };
160
+ function frameworkError(code, options = {}) {
161
+ const definition = ERROR_CATALOG[code];
162
+ return {
163
+ code,
164
+ severity: options.severity ?? "error",
165
+ message: options.message ?? definition.defaultMessage,
166
+ repair: definition.repair,
167
+ docs: definition.docs,
168
+ ...options.file ? { file: options.file } : {},
169
+ ...options.relatedFiles ? { relatedFiles: options.relatedFiles } : {},
170
+ ...options.location ? { location: options.location } : {}
171
+ };
172
+ }
173
+ function serializeFrameworkError(error) {
174
+ return JSON.parse(JSON.stringify(error));
175
+ }
176
+ function explainError(code) {
177
+ const definition = ERROR_CATALOG[code];
178
+ if (!definition) return void 0;
179
+ return { code, ...definition, why: definition.defaultMessage };
180
+ }
181
+
182
+ // src/scanner.ts
183
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
184
+ import { dirname, join, relative, resolve, sep } from "path";
185
+ import { parse } from "yaml";
186
+ var FEATURE_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
187
+ var DEFAULT_CONFIG = {
188
+ packageManager: "pnpm",
189
+ frontend: "react",
190
+ backend: "koa",
191
+ database: "postgres",
192
+ featuresDir: "features",
193
+ generatedDir: ".loomstack"
194
+ };
195
+ function toProjectPath(path) {
196
+ return path.split(sep).join("/");
197
+ }
198
+ function discoverProjectRoot(start) {
199
+ let current = resolve(start);
200
+ while (true) {
201
+ if (existsSync(join(current, "loomstack.config.ts"))) return current;
202
+ const parent = dirname(current);
203
+ if (parent === current) return void 0;
204
+ current = parent;
205
+ }
206
+ }
207
+ function quotedValue(source, key) {
208
+ const match = source.match(new RegExp(`${key}\\s*:\\s*["']([^"']+)["']`));
209
+ return match?.[1];
210
+ }
211
+ function loadProjectConfig(root) {
212
+ const configPath = join(root, "loomstack.config.ts");
213
+ if (!existsSync(configPath)) {
214
+ return { errors: [frameworkError("loomstack5001", { file: "loomstack.config.ts" })] };
215
+ }
216
+ const source = readFileSync(configPath, "utf8");
217
+ const appName = quotedValue(source, "appName");
218
+ const values = {
219
+ packageManager: quotedValue(source, "packageManager"),
220
+ frontend: quotedValue(source, "frontend"),
221
+ backend: quotedValue(source, "backend"),
222
+ database: quotedValue(source, "database"),
223
+ featuresDir: quotedValue(source, "featuresDir"),
224
+ generatedDir: quotedValue(source, "generatedDir")
225
+ };
226
+ if (!appName || values.packageManager !== DEFAULT_CONFIG.packageManager || values.frontend !== DEFAULT_CONFIG.frontend || values.backend !== DEFAULT_CONFIG.backend || values.database !== DEFAULT_CONFIG.database || values.featuresDir !== DEFAULT_CONFIG.featuresDir || values.generatedDir !== DEFAULT_CONFIG.generatedDir) {
227
+ return { errors: [frameworkError("loomstack5003", { file: "loomstack.config.ts" })] };
228
+ }
229
+ return {
230
+ config: {
231
+ appName,
232
+ packageManager: "pnpm",
233
+ frontend: "react",
234
+ backend: "koa",
235
+ database: "postgres",
236
+ featuresDir: values.featuresDir,
237
+ generatedDir: values.generatedDir
238
+ },
239
+ errors: []
240
+ };
241
+ }
242
+ function filesIn(directory, suffix) {
243
+ if (!existsSync(directory)) return [];
244
+ return readdirSync(directory).filter((name) => name.endsWith(suffix) && statSync(join(directory, name)).isFile()).sort().map((name) => join(directory, name));
245
+ }
246
+ function discoverNamedFiles(root, directory, suffix, kind) {
247
+ const expression = new RegExp(`export\\s+const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${kind}\\b`);
248
+ return filesIn(directory, suffix).flatMap((file) => {
249
+ const match = readFileSync(file, "utf8").match(expression);
250
+ return match?.[1] ? [{ name: match[1], file: toProjectPath(relative(root, file)) }] : [];
251
+ });
252
+ }
253
+ function discoverViews(root, directory) {
254
+ return filesIn(directory, ".view.tsx").flatMap((file) => {
255
+ const source = readFileSync(file, "utf8");
256
+ const loomStackName = source.match(/\bname\s*:\s*["']([A-Za-z_$][\w$]*)["']/)?.[1];
257
+ const namedExport = source.match(/export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*view\s*\(/)?.[1];
258
+ const name = loomStackName ?? namedExport;
259
+ return name ? [{ name, file: toProjectPath(relative(root, file)) }] : [];
260
+ });
261
+ }
262
+ function discoverSchemas(root, featureDirectory) {
263
+ const file = join(featureDirectory, "model.schema.ts");
264
+ if (!existsSync(file)) return [];
265
+ const source = readFileSync(file, "utf8");
266
+ return [...source.matchAll(/export\s+const\s+([A-Za-z_$][\w$]*)Schema\s*=\s*entity\s*\(\s*["']([^"']+)["']/g)].map((match) => ({ name: match[2] ?? match[1] ?? "", file: toProjectPath(relative(root, file)) })).filter((item) => item.name);
267
+ }
268
+ function validateManifest(value, manifestPath, folderName) {
269
+ const errors = [];
270
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
271
+ return { errors: [frameworkError("loomstack1002", { file: manifestPath })] };
272
+ }
273
+ const input = value;
274
+ const requiredStrings = ["id", "name"];
275
+ for (const key of requiredStrings) {
276
+ if (typeof input[key] !== "string" || input[key].length === 0) {
277
+ errors.push(frameworkError("loomstack1002", { message: `Missing or invalid required field: ${key}.`, file: manifestPath }));
278
+ }
279
+ }
280
+ for (const key of ["entities", "routes", "actions", "queries"]) {
281
+ if (!Array.isArray(input[key])) {
282
+ errors.push(frameworkError("loomstack1002", { message: `Missing or invalid required array: ${key}.`, file: manifestPath }));
283
+ }
284
+ }
285
+ if (errors.length > 0) return { errors };
286
+ const id = input.id;
287
+ if (!FEATURE_ID.test(id)) errors.push(frameworkError("loomstack1011", { file: manifestPath }));
288
+ if (id !== folderName) {
289
+ errors.push(frameworkError("loomstack1001", { message: `Feature ID "${id}" does not match folder "${folderName}".`, file: manifestPath }));
290
+ }
291
+ if (input.description !== void 0 && typeof input.description !== "string") {
292
+ errors.push(frameworkError("loomstack1002", { message: "description must be a string when provided.", file: manifestPath }));
293
+ }
294
+ if (input.permissions !== void 0 && (!input.permissions || typeof input.permissions !== "object" || Array.isArray(input.permissions) || Object.values(input.permissions).some((value2) => typeof value2 !== "string"))) {
295
+ errors.push(frameworkError("loomstack1002", { message: "permissions must map string names to string requirements.", file: manifestPath }));
296
+ }
297
+ for (const key of ["entities", "actions", "queries"]) {
298
+ const values = input[key];
299
+ if (values.some((value2) => typeof value2 !== "string" || value2.length === 0) || new Set(values).size !== values.length) {
300
+ errors.push(frameworkError("loomstack1002", { message: `${key} must contain unique, non-empty strings.`, file: manifestPath }));
301
+ }
302
+ }
303
+ if (input.entities.some((name) => !/^[A-Z][A-Za-z0-9]*$/.test(name))) {
304
+ errors.push(frameworkError("loomstack1002", { message: "Entity names must be PascalCase identifiers.", file: manifestPath }));
305
+ }
306
+ for (const key of ["actions", "queries"]) {
307
+ if (input[key].some((name) => !/^[A-Za-z_$][\w$]*$/.test(name))) {
308
+ errors.push(frameworkError("loomstack1002", { message: `${key} names must be JavaScript identifiers.`, file: manifestPath }));
309
+ }
310
+ }
311
+ const routes = input.routes;
312
+ const routeIds = /* @__PURE__ */ new Set();
313
+ for (const route of routes) {
314
+ if (!route || typeof route !== "object") {
315
+ errors.push(frameworkError("loomstack1002", { message: "Every route must be an object with id, path, and view.", file: manifestPath }));
316
+ continue;
317
+ }
318
+ const candidate = route;
319
+ if (["id", "path", "view"].some((key) => typeof candidate[key] !== "string" || candidate[key].length === 0)) {
320
+ errors.push(frameworkError("loomstack1002", { message: "Every route requires string id, path, and view fields.", file: manifestPath }));
321
+ } else if (!candidate.path.startsWith("/") || !/^[A-Za-z_$][\w$]*$/.test(candidate.view)) {
322
+ errors.push(frameworkError("loomstack1002", { message: "Route paths must start with / and route views must be JavaScript identifiers.", file: manifestPath }));
323
+ }
324
+ if (typeof candidate.id === "string") {
325
+ if (routeIds.has(candidate.id)) errors.push(frameworkError("loomstack1002", { message: `Duplicate route ID: ${candidate.id}.`, file: manifestPath }));
326
+ routeIds.add(candidate.id);
327
+ }
328
+ }
329
+ if (errors.length > 0) return { errors };
330
+ return { manifest: input, errors };
331
+ }
332
+ function scanProject(rootInput) {
333
+ const root = resolve(rootInput);
334
+ const loaded = loadProjectConfig(root);
335
+ if (!loaded.config) return { errors: loaded.errors };
336
+ const config = loaded.config;
337
+ const featuresDirectory = join(root, config.featuresDir);
338
+ const errors = [...loaded.errors];
339
+ const features = [];
340
+ if (existsSync(featuresDirectory)) {
341
+ const featureFolders = readdirSync(featuresDirectory).filter((name) => statSync(join(featuresDirectory, name)).isDirectory()).sort();
342
+ for (const folderName of featureFolders) {
343
+ const directory = join(featuresDirectory, folderName);
344
+ const absoluteManifest = join(directory, "feature.yaml");
345
+ if (!existsSync(absoluteManifest)) continue;
346
+ const manifestPath = toProjectPath(relative(root, absoluteManifest));
347
+ let raw;
348
+ try {
349
+ raw = parse(readFileSync(absoluteManifest, "utf8"));
350
+ } catch (error) {
351
+ errors.push(frameworkError("loomstack1006", { message: `Invalid YAML: ${error instanceof Error ? error.message : String(error)}`, file: manifestPath }));
352
+ continue;
353
+ }
354
+ const validated = validateManifest(raw, manifestPath, folderName);
355
+ errors.push(...validated.errors);
356
+ if (!validated.manifest) continue;
357
+ const feature = {
358
+ id: folderName,
359
+ directory: toProjectPath(relative(root, directory)),
360
+ manifestPath,
361
+ manifest: validated.manifest,
362
+ schemas: discoverSchemas(root, directory),
363
+ actions: discoverNamedFiles(root, join(directory, "actions"), ".action.ts", "action"),
364
+ queries: discoverNamedFiles(root, join(directory, "queries"), ".query.ts", "query"),
365
+ views: discoverViews(root, join(directory, "ui")),
366
+ tests: filesIn(join(directory, "tests"), ".test.ts").map((file) => toProjectPath(relative(root, file)))
367
+ };
368
+ feature.views = feature.views.map((view) => {
369
+ const route = feature.manifest.routes.find((candidate) => candidate.view === view.name)?.path;
370
+ return route ? { ...view, route } : view;
371
+ });
372
+ features.push(feature);
373
+ }
374
+ }
375
+ const paths = /* @__PURE__ */ new Map();
376
+ for (const feature of features) {
377
+ for (const route of feature.manifest.routes) {
378
+ const owner = paths.get(route.path);
379
+ if (owner) {
380
+ errors.push(frameworkError("loomstack1005", {
381
+ message: `Route path "${route.path}" is declared by both ${owner} and ${feature.id}.`,
382
+ file: feature.manifestPath,
383
+ relatedFiles: [features.find((item) => item.id === owner)?.manifestPath ?? owner]
384
+ }));
385
+ } else paths.set(route.path, feature.id);
386
+ }
387
+ }
388
+ const graph = {
389
+ features: features.map((feature) => ({
390
+ id: feature.id,
391
+ manifest: feature.manifestPath,
392
+ entities: [...feature.manifest.entities].sort(),
393
+ actions: [...feature.actions].sort((a, b) => a.name.localeCompare(b.name)),
394
+ queries: [...feature.queries].sort((a, b) => a.name.localeCompare(b.name)),
395
+ views: [...feature.views].sort((a, b) => a.name.localeCompare(b.name)),
396
+ tests: [...feature.tests].sort(),
397
+ routes: feature.manifest.routes.map((route) => route.path).sort()
398
+ })),
399
+ dependencies: []
400
+ };
401
+ return {
402
+ project: { root, configPath: "loomstack.config.ts", config, features, graph },
403
+ errors
404
+ };
405
+ }
406
+ export {
407
+ ERROR_CATALOG,
408
+ discoverProjectRoot,
409
+ explainError,
410
+ frameworkError,
411
+ loadProjectConfig,
412
+ scanProject,
413
+ serializeFrameworkError,
414
+ toProjectPath
415
+ };
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@loomstack/core",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "yaml": "^2.8.1"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm --dts --clean",
20
+ "test": "vitest run"
21
+ }
22
+ }