irgen 0.2.0

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 (244) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +312 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/dsl/aggregator.d.ts +8 -0
  9. package/dist/dsl/aggregator.d.ts.map +1 -0
  10. package/dist/dsl/aggregator.js +64 -0
  11. package/dist/dsl/aggregator.js.map +1 -0
  12. package/dist/dsl/frontend-runtime.d.ts +486 -0
  13. package/dist/dsl/frontend-runtime.d.ts.map +1 -0
  14. package/dist/dsl/frontend-runtime.js +232 -0
  15. package/dist/dsl/frontend-runtime.js.map +1 -0
  16. package/dist/dsl/runtime.d.ts +33 -0
  17. package/dist/dsl/runtime.d.ts.map +1 -0
  18. package/dist/dsl/runtime.js +120 -0
  19. package/dist/dsl/runtime.js.map +1 -0
  20. package/dist/emit/backend/adapters.d.ts +11 -0
  21. package/dist/emit/backend/adapters.d.ts.map +1 -0
  22. package/dist/emit/backend/adapters.js +374 -0
  23. package/dist/emit/backend/adapters.js.map +1 -0
  24. package/dist/emit/backend/backend-tsmorph.d.ts +5 -0
  25. package/dist/emit/backend/backend-tsmorph.d.ts.map +1 -0
  26. package/dist/emit/backend/backend-tsmorph.js +858 -0
  27. package/dist/emit/backend/backend-tsmorph.js.map +1 -0
  28. package/dist/emit/backend/fake-backend.d.ts +2 -0
  29. package/dist/emit/backend/fake-backend.d.ts.map +1 -0
  30. package/dist/emit/backend/fake-backend.js +19 -0
  31. package/dist/emit/backend/fake-backend.js.map +1 -0
  32. package/dist/emit/backend/packaging.d.ts +3 -0
  33. package/dist/emit/backend/packaging.d.ts.map +1 -0
  34. package/dist/emit/backend/packaging.js +71 -0
  35. package/dist/emit/backend/packaging.js.map +1 -0
  36. package/dist/emit/backend/server.d.ts +4 -0
  37. package/dist/emit/backend/server.d.ts.map +1 -0
  38. package/dist/emit/backend/server.js +169 -0
  39. package/dist/emit/backend/server.js.map +1 -0
  40. package/dist/emit/cli/cli-fake.d.ts +2 -0
  41. package/dist/emit/cli/cli-fake.d.ts.map +1 -0
  42. package/dist/emit/cli/cli-fake.js +33 -0
  43. package/dist/emit/cli/cli-fake.js.map +1 -0
  44. package/dist/emit/electron/electron-shell.d.ts +3 -0
  45. package/dist/emit/electron/electron-shell.d.ts.map +1 -0
  46. package/dist/emit/electron/electron-shell.js +454 -0
  47. package/dist/emit/electron/electron-shell.js.map +1 -0
  48. package/dist/emit/engine.d.ts +14 -0
  49. package/dist/emit/engine.d.ts.map +1 -0
  50. package/dist/emit/engine.js +25 -0
  51. package/dist/emit/engine.js.map +1 -0
  52. package/dist/emit/format.d.ts +2 -0
  53. package/dist/emit/format.d.ts.map +1 -0
  54. package/dist/emit/format.js +23 -0
  55. package/dist/emit/format.js.map +1 -0
  56. package/dist/emit/frontend/frontend-react.d.ts +4 -0
  57. package/dist/emit/frontend/frontend-react.d.ts.map +1 -0
  58. package/dist/emit/frontend/frontend-react.js +2021 -0
  59. package/dist/emit/frontend/frontend-react.js.map +1 -0
  60. package/dist/emit/frontend/registry.d.ts +20 -0
  61. package/dist/emit/frontend/registry.d.ts.map +1 -0
  62. package/dist/emit/frontend/registry.js +46 -0
  63. package/dist/emit/frontend/registry.js.map +1 -0
  64. package/dist/emit/frontend/runtime-emitter.d.ts +4 -0
  65. package/dist/emit/frontend/runtime-emitter.d.ts.map +1 -0
  66. package/dist/emit/frontend/runtime-emitter.js +435 -0
  67. package/dist/emit/frontend/runtime-emitter.js.map +1 -0
  68. package/dist/emit/frontend/runtime-template.d.ts +28 -0
  69. package/dist/emit/frontend/runtime-template.d.ts.map +1 -0
  70. package/dist/emit/frontend/runtime-template.js +218 -0
  71. package/dist/emit/frontend/runtime-template.js.map +1 -0
  72. package/dist/emit/frontend/ssg.d.ts +8 -0
  73. package/dist/emit/frontend/ssg.d.ts.map +1 -0
  74. package/dist/emit/frontend/ssg.js +219 -0
  75. package/dist/emit/frontend/ssg.js.map +1 -0
  76. package/dist/emit/registry.d.ts +17 -0
  77. package/dist/emit/registry.d.ts.map +1 -0
  78. package/dist/emit/registry.js +38 -0
  79. package/dist/emit/registry.js.map +1 -0
  80. package/dist/emit/static-site/css.d.ts +5 -0
  81. package/dist/emit/static-site/css.d.ts.map +1 -0
  82. package/dist/emit/static-site/css.js +872 -0
  83. package/dist/emit/static-site/css.js.map +1 -0
  84. package/dist/emit/static-site/enhancements.d.ts +11 -0
  85. package/dist/emit/static-site/enhancements.d.ts.map +1 -0
  86. package/dist/emit/static-site/enhancements.js +266 -0
  87. package/dist/emit/static-site/enhancements.js.map +1 -0
  88. package/dist/emit/static-site/static-site-html.d.ts +3 -0
  89. package/dist/emit/static-site/static-site-html.d.ts.map +1 -0
  90. package/dist/emit/static-site/static-site-html.js +1172 -0
  91. package/dist/emit/static-site/static-site-html.js.map +1 -0
  92. package/dist/emit/utils/sdk.d.ts +15 -0
  93. package/dist/emit/utils/sdk.d.ts.map +1 -0
  94. package/dist/emit/utils/sdk.js +34 -0
  95. package/dist/emit/utils/sdk.js.map +1 -0
  96. package/dist/extensions/context.d.ts +23 -0
  97. package/dist/extensions/context.d.ts.map +1 -0
  98. package/dist/extensions/context.js +43 -0
  99. package/dist/extensions/context.js.map +1 -0
  100. package/dist/index.d.ts +32 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +135 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/ir/decl/backend.raw.schema.d.ts +128 -0
  105. package/dist/ir/decl/backend.raw.schema.d.ts.map +1 -0
  106. package/dist/ir/decl/backend.raw.schema.js +24 -0
  107. package/dist/ir/decl/backend.raw.schema.js.map +1 -0
  108. package/dist/ir/decl/bundle.d.ts +15 -0
  109. package/dist/ir/decl/bundle.d.ts.map +1 -0
  110. package/dist/ir/decl/bundle.js +8 -0
  111. package/dist/ir/decl/bundle.js.map +1 -0
  112. package/dist/ir/decl/cli.raw.schema.d.ts +133 -0
  113. package/dist/ir/decl/cli.raw.schema.d.ts.map +1 -0
  114. package/dist/ir/decl/cli.raw.schema.js +20 -0
  115. package/dist/ir/decl/cli.raw.schema.js.map +1 -0
  116. package/dist/ir/decl/frontend.raw.schema.d.ts +6631 -0
  117. package/dist/ir/decl/frontend.raw.schema.d.ts.map +1 -0
  118. package/dist/ir/decl/frontend.raw.schema.js +272 -0
  119. package/dist/ir/decl/frontend.raw.schema.js.map +1 -0
  120. package/dist/ir/decl/index.d.ts +6 -0
  121. package/dist/ir/decl/index.d.ts.map +1 -0
  122. package/dist/ir/decl/index.js +6 -0
  123. package/dist/ir/decl/index.js.map +1 -0
  124. package/dist/ir/decl/normalize.schema.d.ts +9154 -0
  125. package/dist/ir/decl/normalize.schema.d.ts.map +1 -0
  126. package/dist/ir/decl/normalize.schema.js +71 -0
  127. package/dist/ir/decl/normalize.schema.js.map +1 -0
  128. package/dist/ir/domain/backend.d.ts +19 -0
  129. package/dist/ir/domain/backend.d.ts.map +1 -0
  130. package/dist/ir/domain/backend.js +3 -0
  131. package/dist/ir/domain/backend.js.map +1 -0
  132. package/dist/ir/domain/cli.d.ts +18 -0
  133. package/dist/ir/domain/cli.d.ts.map +1 -0
  134. package/dist/ir/domain/cli.js +2 -0
  135. package/dist/ir/domain/cli.js.map +1 -0
  136. package/dist/ir/domain/frontend/index.d.ts +190 -0
  137. package/dist/ir/domain/frontend/index.d.ts.map +1 -0
  138. package/dist/ir/domain/frontend/index.js +2 -0
  139. package/dist/ir/domain/frontend/index.js.map +1 -0
  140. package/dist/ir/domain/frontend.d.ts +2 -0
  141. package/dist/ir/domain/frontend.d.ts.map +1 -0
  142. package/dist/ir/domain/frontend.js +3 -0
  143. package/dist/ir/domain/frontend.js.map +1 -0
  144. package/dist/ir/frontend-contract.d.ts +187 -0
  145. package/dist/ir/frontend-contract.d.ts.map +1 -0
  146. package/dist/ir/frontend-contract.js +6 -0
  147. package/dist/ir/frontend-contract.js.map +1 -0
  148. package/dist/ir/target/backend.d.ts +11 -0
  149. package/dist/ir/target/backend.d.ts.map +1 -0
  150. package/dist/ir/target/backend.js +2 -0
  151. package/dist/ir/target/backend.js.map +1 -0
  152. package/dist/ir/target/backend.policy.d.ts +896 -0
  153. package/dist/ir/target/backend.policy.d.ts.map +1 -0
  154. package/dist/ir/target/backend.policy.js +106 -0
  155. package/dist/ir/target/backend.policy.js.map +1 -0
  156. package/dist/ir/target/cli.d.ts +3 -0
  157. package/dist/ir/target/cli.d.ts.map +1 -0
  158. package/dist/ir/target/cli.js +2 -0
  159. package/dist/ir/target/cli.js.map +1 -0
  160. package/dist/ir/target/electron.d.ts +99 -0
  161. package/dist/ir/target/electron.d.ts.map +1 -0
  162. package/dist/ir/target/electron.js +2 -0
  163. package/dist/ir/target/electron.js.map +1 -0
  164. package/dist/ir/target/electron.policy.d.ts +7015 -0
  165. package/dist/ir/target/electron.policy.d.ts.map +1 -0
  166. package/dist/ir/target/electron.policy.js +119 -0
  167. package/dist/ir/target/electron.policy.js.map +1 -0
  168. package/dist/ir/target/frontend.d.ts +12 -0
  169. package/dist/ir/target/frontend.d.ts.map +1 -0
  170. package/dist/ir/target/frontend.js +2 -0
  171. package/dist/ir/target/frontend.js.map +1 -0
  172. package/dist/ir/target/frontend.policy.d.ts +268 -0
  173. package/dist/ir/target/frontend.policy.d.ts.map +1 -0
  174. package/dist/ir/target/frontend.policy.js +33 -0
  175. package/dist/ir/target/frontend.policy.js.map +1 -0
  176. package/dist/ir/target/index.d.ts +6 -0
  177. package/dist/ir/target/index.d.ts.map +1 -0
  178. package/dist/ir/target/index.js +6 -0
  179. package/dist/ir/target/index.js.map +1 -0
  180. package/dist/ir/target/static-site.d.ts +18 -0
  181. package/dist/ir/target/static-site.d.ts.map +1 -0
  182. package/dist/ir/target/static-site.js +2 -0
  183. package/dist/ir/target/static-site.js.map +1 -0
  184. package/dist/ir/target/static-site.policy.d.ts +2911 -0
  185. package/dist/ir/target/static-site.policy.d.ts.map +1 -0
  186. package/dist/ir/target/static-site.policy.js +127 -0
  187. package/dist/ir/target/static-site.policy.js.map +1 -0
  188. package/dist/lowering/backend.d.ts +4 -0
  189. package/dist/lowering/backend.d.ts.map +1 -0
  190. package/dist/lowering/backend.js +57 -0
  191. package/dist/lowering/backend.js.map +1 -0
  192. package/dist/lowering/cli.d.ts +4 -0
  193. package/dist/lowering/cli.d.ts.map +1 -0
  194. package/dist/lowering/cli.js +22 -0
  195. package/dist/lowering/cli.js.map +1 -0
  196. package/dist/lowering/engine.d.ts +18 -0
  197. package/dist/lowering/engine.d.ts.map +1 -0
  198. package/dist/lowering/engine.js +47 -0
  199. package/dist/lowering/engine.js.map +1 -0
  200. package/dist/lowering/frontend.d.ts +9 -0
  201. package/dist/lowering/frontend.d.ts.map +1 -0
  202. package/dist/lowering/frontend.js +246 -0
  203. package/dist/lowering/frontend.js.map +1 -0
  204. package/dist/lowering/targets/to-backend.d.ts +9 -0
  205. package/dist/lowering/targets/to-backend.d.ts.map +1 -0
  206. package/dist/lowering/targets/to-backend.js +55 -0
  207. package/dist/lowering/targets/to-backend.js.map +1 -0
  208. package/dist/lowering/targets/to-cli.d.ts +4 -0
  209. package/dist/lowering/targets/to-cli.d.ts.map +1 -0
  210. package/dist/lowering/targets/to-cli.js +11 -0
  211. package/dist/lowering/targets/to-cli.js.map +1 -0
  212. package/dist/lowering/targets/to-electron.d.ts +30 -0
  213. package/dist/lowering/targets/to-electron.d.ts.map +1 -0
  214. package/dist/lowering/targets/to-electron.js +87 -0
  215. package/dist/lowering/targets/to-electron.js.map +1 -0
  216. package/dist/lowering/targets/to-frontend.d.ts +4 -0
  217. package/dist/lowering/targets/to-frontend.d.ts.map +1 -0
  218. package/dist/lowering/targets/to-frontend.js +30 -0
  219. package/dist/lowering/targets/to-frontend.js.map +1 -0
  220. package/dist/lowering/targets/to-static-site.d.ts +16 -0
  221. package/dist/lowering/targets/to-static-site.d.ts.map +1 -0
  222. package/dist/lowering/targets/to-static-site.js +30 -0
  223. package/dist/lowering/targets/to-static-site.js.map +1 -0
  224. package/dist/mappers/index.d.ts +12 -0
  225. package/dist/mappers/index.d.ts.map +1 -0
  226. package/dist/mappers/index.js +60 -0
  227. package/dist/mappers/index.js.map +1 -0
  228. package/dist/types/extension.d.ts +3 -0
  229. package/dist/types/extension.d.ts.map +1 -0
  230. package/dist/types/extension.js +2 -0
  231. package/dist/types/extension.js.map +1 -0
  232. package/dist/utils/array.d.ts +2 -0
  233. package/dist/utils/array.d.ts.map +1 -0
  234. package/dist/utils/array.js +4 -0
  235. package/dist/utils/array.js.map +1 -0
  236. package/dist/utils/index.d.ts +3 -0
  237. package/dist/utils/index.d.ts.map +1 -0
  238. package/dist/utils/index.js +3 -0
  239. package/dist/utils/index.js.map +1 -0
  240. package/dist/utils/string.d.ts +13 -0
  241. package/dist/utils/string.d.ts.map +1 -0
  242. package/dist/utils/string.js +56 -0
  243. package/dist/utils/string.js.map +1 -0
  244. package/package.json +112 -0
@@ -0,0 +1,435 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ export function emitRuntime(project, srcDir, ir) {
4
+ const libDir = path.join(srcDir, "lib");
5
+ if (!fs.existsSync(libDir))
6
+ fs.mkdirSync(libDir, { recursive: true });
7
+ emitRuntimeContract(project, libDir);
8
+ emitRuntimeImplementation(project, libDir);
9
+ emitRuntimeConfig(project, libDir, ir);
10
+ emitRuntimeHooks(project, libDir);
11
+ }
12
+ function emitRuntimeContract(project, libDir) {
13
+ const filePath = path.join(libDir, "runtime-contract.ts");
14
+ const content = `/**
15
+ * Frontend Core — Runtime Contract
16
+ */
17
+
18
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
19
+ export type ResponseType = "json" | "text" | "html" | "blob";
20
+ export type BodyType = "none" | "json" | "text" | "multipart" | "formUrlEncoded";
21
+
22
+ export type Dict<T = unknown> = Record<string, T>;
23
+ export type PathParams = Record<string, string | number>;
24
+ export type QueryParams = Record<string, string | number | boolean | null | undefined>;
25
+
26
+ export type OperationContext =
27
+ | { kind: "page"; pageId: string }
28
+ | { kind: "tableRow"; pageId: string; rowId: string }
29
+ | { kind: "tableBulk"; pageId: string; selectedIds: string[] }
30
+ | { kind: "form"; pageId: string; formId: string }
31
+ | { kind: "system"; reason: string };
32
+
33
+ export type NormalizedErrorCode =
34
+ | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "VALIDATION_ERROR"
35
+ | "CONFLICT" | "RATE_LIMITED" | "TIMEOUT" | "NETWORK_ERROR"
36
+ | "INTERNAL_ERROR" | "UNKNOWN_ERROR";
37
+
38
+ export interface FieldError { field: string; message: string; code?: string; }
39
+
40
+ export interface NormalizedError {
41
+ code: NormalizedErrorCode;
42
+ message: string;
43
+ status?: number;
44
+ details?: Dict;
45
+ fieldErrors?: FieldError[];
46
+ cause?: unknown;
47
+ raw?: unknown;
48
+ }
49
+
50
+ export type AuthState =
51
+ | { status: "anonymous" }
52
+ | { status: "authenticated"; user?: Dict; roles?: string[] }
53
+ | { status: "error"; error: NormalizedError };
54
+
55
+ export type AuthStrategyId = string;
56
+ export interface AuthStrategy {
57
+ id: AuthStrategyId;
58
+ attach(req: RuntimeRequest, ds: DataSourceRuntimeConfig): Promise<RuntimeRequest>;
59
+ loadState?: (rt: Runtime, dsId: string) => Promise<AuthState>;
60
+ onLoginSuccess?: (rt: Runtime, dsId: string, payload: unknown) => Promise<void>;
61
+ onLogout?: (rt: Runtime, dsId: string) => Promise<void>;
62
+ }
63
+
64
+ export type CsrfStrategyId = string;
65
+ export interface CsrfStrategy {
66
+ id: CsrfStrategyId;
67
+ attach(req: RuntimeRequest, rt: Runtime, dsId: string): Promise<RuntimeRequest>;
68
+ }
69
+
70
+ export interface DataSourceRuntimeConfig {
71
+ id: string;
72
+ baseUrl: string;
73
+ defaultHeaders?: Record<string, string>;
74
+ withCredentials?: boolean;
75
+ timeoutMs?: number;
76
+ authStrategyId?: AuthStrategyId;
77
+ csrfStrategyId?: CsrfStrategyId;
78
+ }
79
+
80
+ export interface EnvelopeAdapter {
81
+ id: string;
82
+ extractData: (payload: unknown) => unknown;
83
+ extractMeta?: (payload: unknown) => unknown;
84
+ extractErrorPayload?: (payload: unknown) => unknown;
85
+ }
86
+
87
+ export interface PaginationResult { total?: number; nextCursor?: string; prevCursor?: string; }
88
+ export interface PaginationAdapter { id: string; extract: (payload: unknown) => PaginationResult; }
89
+
90
+ export interface RequestBodySpec {
91
+ type: BodyType;
92
+ build?: (input: unknown, ctx: OperationContext, rt: Runtime) => Promise<unknown>;
93
+ contentType?: string;
94
+ accept?: string;
95
+ }
96
+
97
+ export interface ResponseSpec {
98
+ type: ResponseType;
99
+ envelopeAdapterId?: string;
100
+ paginationAdapterId?: string;
101
+ filenameHint?: string;
102
+ }
103
+
104
+ export interface ResultHandlingSpec {
105
+ invalidate?: Array<{ kind: string; resourceId?: string; id?: string; operationId?: string; key?: any }>;
106
+ redirectTo?: (result: OperationResultNormalized, rt: Runtime) => string | null;
107
+ openUrl?: (result: OperationResultNormalized, rt: Runtime) => string | null;
108
+ downloadAs?: (result: OperationResultNormalized, rt: Runtime) => { filename: string } | null;
109
+ toastOnSuccess?: { kind: string; message: string };
110
+ toastOnError?: { kind: string; message: string };
111
+ }
112
+
113
+ export interface OperationSpec {
114
+ id: string;
115
+ datasourceId: string;
116
+ method: HttpMethod;
117
+ path: string;
118
+ pathParams?: (input: unknown, ctx: OperationContext, rt: Runtime) => Promise<PathParams>;
119
+ query?: (input: unknown, ctx: OperationContext, rt: Runtime) => Promise<QueryParams>;
120
+ headers?: (input: unknown, ctx: OperationContext, rt: Runtime) => Promise<Record<string, string>>;
121
+ body?: RequestBodySpec;
122
+ response: ResponseSpec;
123
+ resultHandling?: ResultHandlingSpec;
124
+ requiresAuth?: boolean;
125
+ requiredRoles?: string[];
126
+ }
127
+
128
+ export interface ResourceSpec {
129
+ id: string;
130
+ datasourceId: string;
131
+ idField?: string;
132
+ listOpId?: string;
133
+ getOpId?: string;
134
+ createOpId?: string;
135
+ updateOpId?: string;
136
+ deleteOpId?: string;
137
+ }
138
+
139
+ export interface RuntimeRequest {
140
+ url: string;
141
+ method: HttpMethod;
142
+ headers: Record<string, string>;
143
+ body?: unknown;
144
+ credentials?: RequestCredentials;
145
+ signal?: AbortSignal;
146
+ responseType: ResponseType;
147
+ }
148
+
149
+ export interface OperationResultNormalized {
150
+ ok: boolean;
151
+ status?: number;
152
+ data?: unknown;
153
+ meta?: unknown;
154
+ pagination?: PaginationResult;
155
+ error?: NormalizedError;
156
+ raw?: unknown;
157
+ }
158
+
159
+ export interface Runtime {
160
+ getDataSource(dsId: string): DataSourceRuntimeConfig;
161
+ getAuthState(dsId: string): AuthState;
162
+ execute(operationId: string, input: unknown, ctx: OperationContext): Promise<OperationResultNormalized>;
163
+ invalidate?(targets: any[]): void;
164
+ }
165
+ `;
166
+ project.createSourceFile(filePath, content, { overwrite: true });
167
+ }
168
+ function emitRuntimeImplementation(project, libDir) {
169
+ const filePath = path.join(libDir, "runtime.ts");
170
+ const content = `import type * as T from "./runtime-contract";
171
+
172
+ export class BaseRuntime implements T.Runtime {
173
+ private authStates: Record<string, T.AuthState> = {};
174
+
175
+ constructor(
176
+ private datasources: T.DataSourceRuntimeConfig[],
177
+ private operations: T.OperationSpec[],
178
+ private adapters: {
179
+ envelope: T.EnvelopeAdapter[];
180
+ pagination: T.PaginationAdapter[];
181
+ },
182
+ private strategies: {
183
+ auth: Record<string, T.AuthStrategy>;
184
+ csrf: Record<string, T.CsrfStrategy>;
185
+ }
186
+ ) {}
187
+
188
+ getDataSource(dsId: string): T.DataSourceRuntimeConfig {
189
+ const ds = this.datasources.find((d) => d.id === dsId);
190
+ if (!ds) throw new Error(\`DataSource not found: \${dsId}\`);
191
+ return ds;
192
+ }
193
+
194
+ getAuthState(dsId: string): T.AuthState {
195
+ return this.authStates[dsId] || { status: "anonymous" };
196
+ }
197
+
198
+ setAuthState(dsId: string, state: T.AuthState) {
199
+ this.authStates[dsId] = state;
200
+ }
201
+
202
+ async execute(
203
+ operationId: string,
204
+ input: unknown,
205
+ ctx: T.OperationContext
206
+ ): Promise<T.OperationResultNormalized> {
207
+ const op = this.operations.find((o) => o.id === operationId);
208
+ if (!op) throw new Error(\`Operation not found: \${operationId}\`);
209
+
210
+ const ds = this.getDataSource(op.datasourceId);
211
+
212
+ try {
213
+ let url = \`\${ds.baseUrl}\${op.path}\`;
214
+
215
+ if (op.pathParams) {
216
+ const params = await op.pathParams(input, ctx, this);
217
+ for (const [key, val] of Object.entries(params)) {
218
+ url = url.replace(\`:\${key}\`, String(val));
219
+ }
220
+ }
221
+
222
+ const queryParams = op.query ? await op.query(input, ctx, this) : {};
223
+ const searchParams = new URLSearchParams();
224
+ for (const [key, val] of Object.entries(queryParams)) {
225
+ if (val !== undefined && val !== null) {
226
+ searchParams.append(key, String(val));
227
+ }
228
+ }
229
+ if (searchParams.toString()) {
230
+ url += (url.includes("?") ? "&" : "?") + searchParams.toString();
231
+ }
232
+
233
+ let headers: Record<string, string> = {
234
+ ...ds.defaultHeaders,
235
+ ...(op.headers ? await op.headers(input, ctx, this) : {}),
236
+ };
237
+
238
+ let body: any = undefined;
239
+ if (op.body && op.body.type !== "none") {
240
+ body = op.body.build ? await op.body.build(input, ctx, this) : input;
241
+
242
+ if (op.body.type === "json" && typeof body !== "string" && !(body instanceof FormData)) {
243
+ body = JSON.stringify(body);
244
+ headers["Content-Type"] = op.body.contentType || "application/json";
245
+ } else if (op.body.type === "text") {
246
+ headers["Content-Type"] = op.body.contentType || "text/plain";
247
+ }
248
+ }
249
+
250
+ const req: T.RuntimeRequest = {
251
+ url,
252
+ method: op.method,
253
+ headers,
254
+ body,
255
+ credentials: ds.withCredentials ? "include" : undefined,
256
+ responseType: op.response.type,
257
+ };
258
+
259
+ let finalReq = req;
260
+ if (ds.authStrategyId && this.strategies.auth[ds.authStrategyId]) {
261
+ finalReq = await this.strategies.auth[ds.authStrategyId].attach(finalReq, ds);
262
+ }
263
+ if (ds.csrfStrategyId && this.strategies.csrf[ds.csrfStrategyId]) {
264
+ finalReq = await this.strategies.csrf[ds.csrfStrategyId].attach(finalReq, this, ds.id);
265
+ }
266
+
267
+ const response = await fetch(finalReq.url, {
268
+ method: finalReq.method,
269
+ headers: finalReq.headers,
270
+ body: finalReq.body as any,
271
+ credentials: finalReq.credentials,
272
+ });
273
+
274
+ const result: T.OperationResultNormalized = {
275
+ ok: response.ok,
276
+ status: response.status,
277
+ };
278
+
279
+ if (!response.ok) {
280
+ result.error = await this.normalizeError(response);
281
+ return result;
282
+ }
283
+
284
+ let rawPayload: any;
285
+ if (op.response.type === "json") rawPayload = await response.json();
286
+ else if (op.response.type === "blob") rawPayload = await response.blob();
287
+ else rawPayload = await response.text();
288
+
289
+ result.raw = rawPayload;
290
+
291
+ if (op.response.envelopeAdapterId) {
292
+ const adapter = this.adapters.envelope.find(a => a.id === op.response.envelopeAdapterId);
293
+ if (adapter) {
294
+ result.data = adapter.extractData(rawPayload);
295
+ result.meta = adapter.extractMeta?.(rawPayload);
296
+ const err = adapter.extractErrorPayload?.(rawPayload);
297
+ if (err) { result.ok = false; result.error = { code: "UNKNOWN_ERROR", message: String(err), details: err }; }
298
+ } else { result.data = rawPayload; }
299
+ } else { result.data = rawPayload; }
300
+
301
+ if (op.response.paginationAdapterId) {
302
+ const adapter = this.adapters.pagination.find(a => a.id === op.response.paginationAdapterId);
303
+ if (adapter) result.pagination = adapter.extract(rawPayload);
304
+ }
305
+
306
+ return result;
307
+ } catch (err: any) {
308
+ return { ok: false, error: { code: "INTERNAL_ERROR", message: err.message, cause: err } };
309
+ }
310
+ }
311
+
312
+ private async normalizeError(response: Response): Promise<T.NormalizedError> {
313
+ const status = response.status;
314
+ let code: T.NormalizedErrorCode = "INTERNAL_ERROR";
315
+ if (status === 401) code = "UNAUTHORIZED";
316
+ else if (status === 403) code = "FORBIDDEN";
317
+ else if (status === 404) code = "NOT_FOUND";
318
+ else if (status === 422) code = "VALIDATION_ERROR";
319
+
320
+ let message = response.statusText;
321
+ let details: any = {};
322
+ try {
323
+ const payload = await response.json();
324
+ message = payload.message || message;
325
+ details = payload;
326
+ } catch (_) {}
327
+
328
+ return { code, message, status, details };
329
+ }
330
+
331
+ invalidate(targets: any[]): void {
332
+ // hook for UI-level cache invalidation
333
+ }
334
+ }
335
+ `;
336
+ project.createSourceFile(filePath, content, { overwrite: true });
337
+ }
338
+ function emitRuntimeConfig(project, libDir, ir) {
339
+ const filePath = path.join(libDir, "runtime-instance.ts");
340
+ const serializeLogic = (logic) => {
341
+ if (!logic)
342
+ return "undefined";
343
+ return `async (input: any, ctx: any, rt: any) => (rt as any).evalLogic(${JSON.stringify(logic.logic)}, undefined, { ...input, ...ctx })`;
344
+ };
345
+ const opLines = ir.operations.map(op => ` {
346
+ id: "${op.id}",
347
+ datasourceId: "${op.datasourceId}",
348
+ method: "${op.method}",
349
+ path: "${op.path}",
350
+ response: ${JSON.stringify(op.response)},
351
+ requiresAuth: ${op.requiresAuth ?? false},
352
+ ${op.pathParams ? `pathParams: ${serializeLogic(op.pathParams)},` : ""}
353
+ ${op.query ? `query: ${serializeLogic(op.query)},` : ""}
354
+ ${op.headers ? `headers: ${serializeLogic(op.headers)},` : ""}
355
+ ${op.body ? `body: { type: "${op.body.type}", build: ${serializeLogic(op.body.build)} },` : ""}
356
+ ${op.resultHandling ? `resultHandling: ${JSON.stringify(op.resultHandling)},` : ""}
357
+ }`);
358
+ const content = `import { BaseRuntime } from "./runtime";
359
+ import * as T from "./runtime-contract";
360
+
361
+ export const datasources: T.DataSourceRuntimeConfig[] = ${JSON.stringify(ir.datasources, null, 2)};
362
+
363
+ export const operations: T.OperationSpec[] = [
364
+ ${opLines.join(",\n")}
365
+ ];
366
+
367
+ export const resources: T.ResourceSpec[] = ${JSON.stringify(ir.resources, null, 2)};
368
+
369
+ export const envelopeAdapters: T.EnvelopeAdapter[] = [
370
+ { id: "ok_data_meta", extractData: (p: any) => p?.data, extractMeta: (p: any) => p?.meta, extractErrorPayload: (p: any) => p?.error },
371
+ { id: "items_cursor", extractData: (p: any) => p?.items, extractMeta: (p: any) => p?.meta, extractErrorPayload: (p: any) => p?.error }
372
+ ];
373
+
374
+ export const paginationAdapters: T.PaginationAdapter[] = [
375
+ { id: "cursor_root", extract: (p: any) => ({ nextCursor: p?.nextCursor }) }
376
+ ];
377
+
378
+ export const runtime = new BaseRuntime(
379
+ datasources,
380
+ operations,
381
+ { envelope: envelopeAdapters, pagination: paginationAdapters },
382
+ { auth: {}, csrf: {} }
383
+ );
384
+
385
+ (runtime as any).evalLogic = (logic: any, fallback: any, ctx: any) => {
386
+ return logic;
387
+ };
388
+ `;
389
+ project.createSourceFile(filePath, content, { overwrite: true });
390
+ }
391
+ function emitRuntimeHooks(project, libDir) {
392
+ const filePath = path.join(libDir, "hooks.ts");
393
+ const content = `import { useState, useCallback } from "react";
394
+ import { runtime } from "./runtime-instance";
395
+ import * as T from "./runtime-contract";
396
+
397
+ export function useOperation(operationId: string) {
398
+ const [data, setData] = useState<any>(null);
399
+ const [loading, setLoading] = useState(false);
400
+ const [error, setError] = useState<T.NormalizedError | null>(null);
401
+
402
+ const execute = useCallback(async (input: any = {}, ctx: T.OperationContext = { kind: "system", reason: "hook" }) => {
403
+ setLoading(true);
404
+ setError(null);
405
+ try {
406
+ const result = await runtime.execute(operationId, input, ctx);
407
+ if (result.ok) {
408
+ setData(result.data);
409
+ return result;
410
+ } else {
411
+ setError(result.error || { code: "UNKNOWN_ERROR", message: "Failed" } as any);
412
+ return result;
413
+ }
414
+ } finally {
415
+ setLoading(false);
416
+ }
417
+ }, [operationId]);
418
+
419
+ return { data, loading, error, execute };
420
+ }
421
+
422
+ export function useResource(resourceId: string) {
423
+ const res = runtime.getDataSource(resourceId); // this is just a placeholder for finding the resource
424
+ return {
425
+ list: useOperation(\`\${resourceId}.list\`),
426
+ get: useOperation(\`\${resourceId}.get\`),
427
+ create: useOperation(\`\${resourceId}.create\`),
428
+ update: useOperation(\`\${resourceId}.update\`),
429
+ delete: useOperation(\`\${resourceId}.delete\`),
430
+ };
431
+ }
432
+ `;
433
+ project.createSourceFile(filePath, content, { overwrite: true });
434
+ }
435
+ //# sourceMappingURL=runtime-emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-emitter.js","sourceRoot":"","sources":["../../../src/emit/frontend/runtime-emitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Runtime, OperationSpec, OperationContext, OperationResultNormalized, DataSourceRuntimeConfig, AuthState, EnvelopeAdapter, PaginationAdapter } from "../../ir/frontend-contract.js";
2
+ /**
3
+ * Headless Runtime Implementation (Phase 0 Prototype)
4
+ */
5
+ export declare class BaseRuntime implements Runtime {
6
+ private datasources;
7
+ private operations;
8
+ private adapters;
9
+ private strategies;
10
+ private authStates;
11
+ constructor(datasources: DataSourceRuntimeConfig[], operations: OperationSpec[], adapters: {
12
+ envelope: EnvelopeAdapter[];
13
+ pagination: PaginationAdapter[];
14
+ }, strategies: {
15
+ auth: any;
16
+ csrf: any;
17
+ });
18
+ getDataSource(dsId: string): DataSourceRuntimeConfig;
19
+ getAuthState(dsId: string): AuthState;
20
+ setAuthState(dsId: string, state: AuthState): void;
21
+ execute(operationId: string, input: unknown, ctx: OperationContext): Promise<OperationResultNormalized>;
22
+ private normalizeError;
23
+ private normalizePayloadError;
24
+ private normalizeException;
25
+ private handleResultSignals;
26
+ invalidate(targets: any): void;
27
+ }
28
+ //# sourceMappingURL=runtime-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-template.d.ts","sourceRoot":"","sources":["../../../src/emit/frontend/runtime-template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,yBAAyB,EACzB,uBAAuB,EACvB,SAAS,EAIT,eAAe,EACf,iBAAiB,EACpB,MAAM,+BAA+B,CAAC;AAEvC;;GAEG;AACH,qBAAa,WAAY,YAAW,OAAO;IAInC,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IATtB,OAAO,CAAC,UAAU,CAAiC;gBAGvC,WAAW,EAAE,uBAAuB,EAAE,EACtC,UAAU,EAAE,aAAa,EAAE,EAC3B,QAAQ,EAAE;QACd,QAAQ,EAAE,eAAe,EAAE,CAAC;QAC5B,UAAU,EAAE,iBAAiB,EAAE,CAAC;KACnC,EACO,UAAU,EAAE;QAChB,IAAI,EAAE,GAAG,CAAC;QACV,IAAI,EAAE,GAAG,CAAC;KACb;IAGL,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB;IAMpD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;IAIrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS;IAIrC,OAAO,CACT,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,gBAAgB,GACtB,OAAO,CAAC,yBAAyB,CAAC;YAsJvB,cAAc;IAsB5B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,mBAAmB;IAQ3B,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI;CAGjC"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Headless Runtime Implementation (Phase 0 Prototype)
3
+ */
4
+ export class BaseRuntime {
5
+ datasources;
6
+ operations;
7
+ adapters;
8
+ strategies;
9
+ authStates = {};
10
+ constructor(datasources, operations, adapters, strategies) {
11
+ this.datasources = datasources;
12
+ this.operations = operations;
13
+ this.adapters = adapters;
14
+ this.strategies = strategies;
15
+ }
16
+ getDataSource(dsId) {
17
+ const ds = this.datasources.find((d) => d.id === dsId);
18
+ if (!ds)
19
+ throw new Error(`DataSource not found: ${dsId}`);
20
+ return ds;
21
+ }
22
+ getAuthState(dsId) {
23
+ return this.authStates[dsId] || { status: "anonymous" };
24
+ }
25
+ setAuthState(dsId, state) {
26
+ this.authStates[dsId] = state;
27
+ }
28
+ async execute(operationId, input, ctx) {
29
+ const op = this.operations.find((o) => o.id === operationId);
30
+ if (!op)
31
+ throw new Error(`Operation not found: ${operationId}`);
32
+ const ds = this.getDataSource(op.datasourceId);
33
+ try {
34
+ // 1. Build initial request
35
+ let url = `${ds.baseUrl}${op.path}`;
36
+ // Resolve path params
37
+ if (op.pathParams) {
38
+ const params = await op.pathParams(input, ctx, this);
39
+ for (const [key, val] of Object.entries(params)) {
40
+ url = url.replace(`:${key}`, String(val));
41
+ }
42
+ }
43
+ // Resolve query params
44
+ const queryParams = op.query ? await op.query(input, ctx, this) : {};
45
+ const searchParams = new URLSearchParams();
46
+ for (const [key, val] of Object.entries(queryParams)) {
47
+ if (val !== undefined && val !== null) {
48
+ searchParams.append(key, String(val));
49
+ }
50
+ }
51
+ if (searchParams.toString()) {
52
+ url += (url.includes("?") ? "&" : "?") + searchParams.toString();
53
+ }
54
+ let headers = {
55
+ ...ds.defaultHeaders,
56
+ ...(op.headers ? await op.headers(input, ctx, this) : {}),
57
+ };
58
+ let body = undefined;
59
+ if (op.body && op.body.type !== "none") {
60
+ body = op.body.build ? await op.body.build(input, ctx, this) : input;
61
+ if (op.body.type === "json" && typeof body !== "string" && !(body instanceof FormData)) {
62
+ body = JSON.stringify(body);
63
+ headers["Content-Type"] = op.body.contentType || "application/json";
64
+ }
65
+ else if (op.body.type === "text") {
66
+ headers["Content-Type"] = op.body.contentType || "text/plain";
67
+ }
68
+ // Note: multipart (FormData) let fetch set the content-type with boundary
69
+ }
70
+ if (op.response.type === "json") {
71
+ headers["Accept"] = op.response.envelopeAdapterId ? "application/json" : "*/*";
72
+ }
73
+ let req = {
74
+ url,
75
+ method: op.method,
76
+ headers,
77
+ body,
78
+ credentials: ds.withCredentials ? "include" : undefined,
79
+ responseType: op.response.type,
80
+ };
81
+ // 2. Apply Auth Strategy
82
+ if (ds.authStrategyId) {
83
+ const strategy = this.strategies.auth[ds.authStrategyId];
84
+ if (strategy) {
85
+ req = await strategy.attach(req, ds);
86
+ }
87
+ }
88
+ // 3. Apply CSRF Strategy
89
+ if (ds.csrfStrategyId) {
90
+ const strategy = this.strategies.csrf[ds.csrfStrategyId];
91
+ if (strategy) {
92
+ req = await strategy.attach(req, this, ds.id);
93
+ }
94
+ }
95
+ // 4. Perform Fetch
96
+ const response = await fetch(req.url, {
97
+ method: req.method,
98
+ headers: req.headers,
99
+ body: req.body,
100
+ credentials: req.credentials,
101
+ signal: req.signal,
102
+ });
103
+ // 5. Build Result
104
+ const result = {
105
+ ok: response.ok,
106
+ status: response.status,
107
+ };
108
+ if (!response.ok) {
109
+ result.error = await this.normalizeError(response);
110
+ return result;
111
+ }
112
+ // 6. Parse and Normalize Response
113
+ let rawPayload;
114
+ if (op.response.type === "json") {
115
+ rawPayload = await response.json();
116
+ }
117
+ else if (op.response.type === "text" || op.response.type === "html") {
118
+ rawPayload = await response.text();
119
+ }
120
+ else if (op.response.type === "blob") {
121
+ rawPayload = await response.blob();
122
+ }
123
+ result.raw = rawPayload;
124
+ // Apply Envelope Adapter
125
+ if (op.response.envelopeAdapterId) {
126
+ const adapter = this.adapters.envelope.find((a) => a.id === op.response.envelopeAdapterId);
127
+ if (adapter) {
128
+ result.data = adapter.extractData(rawPayload);
129
+ result.meta = adapter.extractMeta?.(rawPayload);
130
+ const errorPayload = adapter.extractErrorPayload?.(rawPayload);
131
+ if (errorPayload) {
132
+ result.ok = false;
133
+ result.error = this.normalizePayloadError(errorPayload, response.status);
134
+ }
135
+ }
136
+ else {
137
+ result.data = rawPayload;
138
+ }
139
+ }
140
+ else {
141
+ result.data = rawPayload;
142
+ }
143
+ // Apply Pagination Adapter
144
+ if (op.response.paginationAdapterId) {
145
+ const adapter = this.adapters.pagination.find((a) => a.id === op.response.paginationAdapterId);
146
+ if (adapter) {
147
+ result.pagination = adapter.extract(rawPayload);
148
+ }
149
+ }
150
+ // 7. Handle Outcomes (Toasts, Invalidations, etc. - usually delegated to UI hooks but runtime can signal)
151
+ if (result.ok && op.resultHandling) {
152
+ this.handleResultSignals(op.resultHandling, result);
153
+ }
154
+ return result;
155
+ }
156
+ catch (err) {
157
+ return {
158
+ ok: false,
159
+ error: this.normalizeException(err),
160
+ };
161
+ }
162
+ }
163
+ async normalizeError(response) {
164
+ const status = response.status;
165
+ let code = "INTERNAL_ERROR";
166
+ let message = response.statusText;
167
+ if (status === 401)
168
+ code = "UNAUTHORIZED";
169
+ else if (status === 403)
170
+ code = "FORBIDDEN";
171
+ else if (status === 404)
172
+ code = "NOT_FOUND";
173
+ else if (status === 422)
174
+ code = "VALIDATION_ERROR";
175
+ else if (status === 409)
176
+ code = "CONFLICT";
177
+ else if (status === 429)
178
+ code = "RATE_LIMITED";
179
+ let details = {};
180
+ try {
181
+ const payload = await response.json();
182
+ message = payload.message || message;
183
+ details = payload;
184
+ }
185
+ catch (_) { }
186
+ return { code, message, status, details };
187
+ }
188
+ normalizePayloadError(payload, status) {
189
+ return {
190
+ code: "UNKNOWN_ERROR",
191
+ message: typeof payload === "string" ? payload : (payload.message || "Operation failed"),
192
+ details: payload,
193
+ status,
194
+ };
195
+ }
196
+ normalizeException(err) {
197
+ if (err.name === "AbortError")
198
+ return { code: "TIMEOUT", message: "Request timed out" };
199
+ if (err instanceof TypeError)
200
+ return { code: "NETWORK_ERROR", message: "Network error or CORS issue" };
201
+ return {
202
+ code: "INTERNAL_ERROR",
203
+ message: err.message || "An unexpected error occurred",
204
+ cause: err,
205
+ };
206
+ }
207
+ handleResultSignals(spec, result) {
208
+ // In a real implementation, this would trigger event emitters or state updates
209
+ // that the UI hooks (useOperation) listen to.
210
+ if (spec.invalidate && typeof this.invalidate === "function") {
211
+ this.invalidate(spec.invalidate);
212
+ }
213
+ }
214
+ invalidate(targets) {
215
+ // Implementation for cache invalidation (e.g. TanStack Query integration)
216
+ }
217
+ }
218
+ //# sourceMappingURL=runtime-template.js.map