cloesce 0.0.5-unstable.3 → 0.0.5-unstable.5

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 (41) hide show
  1. package/dist/ast.d.ts +96 -106
  2. package/dist/ast.d.ts.map +1 -1
  3. package/dist/ast.js +12 -12
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +330 -368
  6. package/dist/common.d.ts +23 -0
  7. package/dist/common.d.ts.map +1 -0
  8. package/dist/common.js +78 -0
  9. package/dist/extractor/err.d.ts +25 -26
  10. package/dist/extractor/err.d.ts.map +1 -1
  11. package/dist/extractor/err.js +95 -129
  12. package/dist/extractor/extract.d.ts +25 -61
  13. package/dist/extractor/extract.d.ts.map +1 -1
  14. package/dist/extractor/extract.js +999 -837
  15. package/dist/generator.wasm +0 -0
  16. package/dist/orm.wasm +0 -0
  17. package/dist/router/crud.d.ts +2 -3
  18. package/dist/router/crud.d.ts.map +1 -1
  19. package/dist/router/crud.js +58 -48
  20. package/dist/router/orm.d.ts +66 -0
  21. package/dist/router/orm.d.ts.map +1 -0
  22. package/dist/router/orm.js +447 -0
  23. package/dist/router/router.d.ts +92 -138
  24. package/dist/router/router.d.ts.map +1 -1
  25. package/dist/router/router.js +389 -432
  26. package/dist/router/validator.d.ts +7 -12
  27. package/dist/router/validator.d.ts.map +1 -1
  28. package/dist/router/validator.js +190 -159
  29. package/dist/router/wasm.d.ts +26 -67
  30. package/dist/router/wasm.d.ts.map +1 -1
  31. package/dist/router/wasm.js +52 -103
  32. package/dist/ui/backend.d.ts +103 -382
  33. package/dist/ui/backend.d.ts.map +1 -1
  34. package/dist/ui/backend.js +143 -430
  35. package/package.json +3 -9
  36. package/dist/ui/client.d.ts +0 -7
  37. package/dist/ui/client.d.ts.map +0 -1
  38. package/dist/ui/client.js +0 -2
  39. package/dist/ui/common.d.ts +0 -126
  40. package/dist/ui/common.d.ts.map +0 -1
  41. package/dist/ui/common.js +0 -203
@@ -1,882 +1,1044 @@
1
- import { Node as MorphNode, SyntaxKind, Scope } from "ts-morph";
2
- import { HttpVerb, defaultMediaType } from "../ast.js";
1
+ import { Node as MorphNode, SyntaxKind, Scope, } from "ts-morph";
2
+ import { HttpVerb, defaultMediaType, } from "../ast.js";
3
3
  import { TypeFormatFlags } from "typescript";
4
4
  import { ExtractorError, ExtractorErrorCode } from "./err.js";
5
- import { Either } from "../ui/common.js";
6
- var AttributeDecoratorKind;
7
- (function (AttributeDecoratorKind) {
8
- AttributeDecoratorKind["PrimaryKey"] = "PrimaryKey";
9
- AttributeDecoratorKind["ForeignKey"] = "ForeignKey";
10
- AttributeDecoratorKind["OneToOne"] = "OneToOne";
11
- AttributeDecoratorKind["OneToMany"] = "OneToMany";
12
- AttributeDecoratorKind["ManyToMany"] = "ManyToMany";
13
- AttributeDecoratorKind["DataSource"] = "DataSource";
14
- })(AttributeDecoratorKind || (AttributeDecoratorKind = {}));
5
+ import { Either } from "../common.js";
6
+ var PropertyDecoratorKind;
7
+ (function (PropertyDecoratorKind) {
8
+ PropertyDecoratorKind["PrimaryKey"] = "PrimaryKey";
9
+ PropertyDecoratorKind["ForeignKey"] = "ForeignKey";
10
+ PropertyDecoratorKind["OneToOne"] = "OneToOne";
11
+ PropertyDecoratorKind["OneToMany"] = "OneToMany";
12
+ PropertyDecoratorKind["ManyToMany"] = "ManyToMany";
13
+ PropertyDecoratorKind["KeyParam"] = "KeyParam";
14
+ PropertyDecoratorKind["KV"] = "KV";
15
+ PropertyDecoratorKind["R2"] = "R2";
16
+ })(PropertyDecoratorKind || (PropertyDecoratorKind = {}));
15
17
  var ClassDecoratorKind;
16
18
  (function (ClassDecoratorKind) {
17
- ClassDecoratorKind["D1"] = "D1";
18
- ClassDecoratorKind["WranglerEnv"] = "WranglerEnv";
19
- ClassDecoratorKind["PlainOldObject"] = "PlainOldObject";
20
- ClassDecoratorKind["Service"] = "Service";
21
- ClassDecoratorKind["CRUD"] = "CRUD";
19
+ ClassDecoratorKind["Model"] = "Model";
20
+ ClassDecoratorKind["WranglerEnv"] = "WranglerEnv";
21
+ ClassDecoratorKind["Service"] = "Service";
22
22
  })(ClassDecoratorKind || (ClassDecoratorKind = {}));
23
23
  var ParameterDecoratorKind;
24
24
  (function (ParameterDecoratorKind) {
25
- ParameterDecoratorKind["Inject"] = "Inject";
25
+ ParameterDecoratorKind["Inject"] = "Inject";
26
26
  })(ParameterDecoratorKind || (ParameterDecoratorKind = {}));
27
27
  export class CidlExtractor {
28
- projectName;
29
- version;
30
- constructor(projectName, version) {
31
- this.projectName = projectName;
32
- this.version = version;
33
- }
34
- extract(project) {
35
- const models = {};
36
- const poos = {};
37
- const wranglerEnvs = [];
38
- const services = {};
39
- let app_source = null;
40
- for (const sourceFile of project.getSourceFiles()) {
41
- if (
42
- sourceFile.getBaseName() === "app.cloesce.ts" ||
43
- sourceFile.getBaseName() === "seed__app.cloesce.ts" // hardcoding for tests
44
- ) {
45
- const app = CidlExtractor.app(sourceFile);
46
- if (app.isLeft()) {
47
- return app;
48
- }
49
- app_source = app.unwrap();
50
- }
51
- for (const classDecl of sourceFile.getClasses()) {
52
- const notExportedErr = err(ExtractorErrorCode.MissingExport, (e) => {
53
- e.context = classDecl.getName();
54
- e.snippet = classDecl.getText();
55
- });
56
- if (hasDecorator(classDecl, ClassDecoratorKind.D1)) {
57
- if (!classDecl.isExported()) return notExportedErr;
58
- const result = CidlExtractor.model(classDecl, sourceFile);
59
- // Error: propogate from models
60
- if (result.isLeft()) {
61
- result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
62
- return result;
63
- }
64
- const model = result.unwrap();
65
- models[model.name] = model;
66
- continue;
67
- }
68
- if (hasDecorator(classDecl, ClassDecoratorKind.Service)) {
69
- if (!classDecl.isExported()) return notExportedErr;
70
- const result = CidlExtractor.service(classDecl, sourceFile);
71
- // Error: propogate from service
72
- if (result.isLeft()) {
73
- result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
74
- return result;
75
- }
76
- const service = result.unwrap();
77
- services[service.name] = service;
78
- continue;
79
- }
80
- if (hasDecorator(classDecl, ClassDecoratorKind.PlainOldObject)) {
81
- if (!classDecl.isExported()) return notExportedErr;
82
- const result = CidlExtractor.poo(classDecl, sourceFile);
83
- // Error: propogate from models
84
- if (result.isLeft()) {
85
- result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
86
- return result;
87
- }
88
- poos[result.unwrap().name] = result.unwrap();
89
- continue;
90
- }
91
- if (hasDecorator(classDecl, ClassDecoratorKind.WranglerEnv)) {
92
- // Error: invalid attribute modifier
93
- for (const prop of classDecl.getProperties()) {
94
- const modifierRes = checkAttributeModifier(prop);
95
- if (modifierRes.isLeft()) {
96
- return modifierRes;
28
+ modelDecls;
29
+ extractedPoos;
30
+ constructor(modelDecls, extractedPoos = new Map()) {
31
+ this.modelDecls = modelDecls;
32
+ this.extractedPoos = extractedPoos;
33
+ }
34
+ static extract(projectName, project) {
35
+ const modelDecls = new Map();
36
+ const serviceDecls = new Map();
37
+ const wranglerEnvs = [];
38
+ let main_source = null;
39
+ // TODO: Concurrently across several threads?
40
+ for (const sourceFile of project.getSourceFiles()) {
41
+ // Extract main source
42
+ const mainRes = CidlExtractor.main(sourceFile);
43
+ if (mainRes.isLeft()) {
44
+ return mainRes;
45
+ }
46
+ const main = mainRes.unwrap();
47
+ if (main) {
48
+ main_source = main;
49
+ }
50
+ for (const classDecl of sourceFile.getClasses()) {
51
+ const notExportedErr = err(ExtractorErrorCode.MissingExport, (e) => {
52
+ e.context = classDecl.getName();
53
+ e.snippet = classDecl.getText();
54
+ });
55
+ for (const decorator of classDecl.getDecorators()) {
56
+ const decoratorName = decorator.getName();
57
+ switch (decoratorName) {
58
+ case ClassDecoratorKind.Model: {
59
+ if (!classDecl.isExported())
60
+ return notExportedErr;
61
+ modelDecls.set(classDecl.getName(), [classDecl, decorator]);
62
+ break;
63
+ }
64
+ case ClassDecoratorKind.Service: {
65
+ if (!classDecl.isExported())
66
+ return notExportedErr;
67
+ serviceDecls.set(classDecl.getName(), classDecl);
68
+ break;
69
+ }
70
+ case ClassDecoratorKind.WranglerEnv: {
71
+ const res = CidlExtractor.env(classDecl, sourceFile);
72
+ if (res.isLeft()) {
73
+ res.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
74
+ return res;
75
+ }
76
+ const wranglerEnv = res.unwrap();
77
+ wranglerEnvs.push(wranglerEnv);
78
+ break;
79
+ }
80
+ default: {
81
+ continue;
82
+ }
83
+ }
84
+ }
97
85
  }
98
- }
99
- const result = CidlExtractor.env(classDecl, sourceFile);
100
- if (result.isLeft()) {
101
- return result;
102
- }
103
- wranglerEnvs.push(result.unwrap());
104
86
  }
105
- }
106
- }
107
- // Error: Only one wrangler environment can exist
108
- if (wranglerEnvs.length > 1) {
109
- return err(
110
- ExtractorErrorCode.TooManyWranglerEnvs,
111
- (e) => (e.context = wranglerEnvs.map((w) => w.name).toString()),
112
- );
113
- }
114
- return Either.right({
115
- version: this.version,
116
- project_name: this.projectName,
117
- language: "TypeScript",
118
- wrangler_env: wranglerEnvs[0],
119
- models,
120
- poos,
121
- services,
122
- app_source,
123
- });
124
- }
125
- static app(sourceFile) {
126
- const symbol = sourceFile.getDefaultExportSymbol();
127
- const decl = symbol?.getDeclarations()[0];
128
- if (!decl) {
129
- return err(ExtractorErrorCode.AppMissingDefaultExport);
130
- }
131
- const getTypeText = () => {
132
- let type = undefined;
133
- if (MorphNode.isExportAssignment(decl)) {
134
- type = decl.getExpression()?.getType();
135
- }
136
- if (MorphNode.isVariableDeclaration(decl)) {
137
- type = decl.getInitializer()?.getType();
138
- }
139
- return type?.getText(
140
- undefined,
141
- TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
142
- );
143
- };
144
- const typeText = getTypeText();
145
- if (typeText === "CloesceApp") {
146
- return Either.right(sourceFile.getFilePath().toString());
147
- }
148
- return err(ExtractorErrorCode.AppMissingDefaultExport);
149
- }
150
- static model(classDecl, sourceFile) {
151
- const name = classDecl.getName();
152
- const attributes = [];
153
- const navigation_properties = [];
154
- const data_sources = {};
155
- const methods = {};
156
- const cruds = new Set();
157
- let primary_key = undefined;
158
- // Extract crud methods
159
- const crudDecorator = classDecl
160
- .getDecorators()
161
- .find((d) => getDecoratorName(d) === ClassDecoratorKind.CRUD);
162
- if (crudDecorator) {
163
- setCrudKinds(crudDecorator, cruds);
164
- }
165
- // Iterate attribtutes
166
- for (const prop of classDecl.getProperties()) {
167
- const decorators = prop.getDecorators();
168
- const typeRes = CidlExtractor.cidlType(prop.getType());
169
- // Error: invalid property type
170
- if (typeRes.isLeft()) {
171
- typeRes.value.context = prop.getName();
172
- typeRes.value.snippet = prop.getText();
173
- return typeRes;
174
- }
175
- const checkModifierRes = checkAttributeModifier(prop);
176
- // No decorators means this is a standard attribute
177
- if (decorators.length === 0) {
178
- // Error: invalid attribute modifier
179
- if (checkModifierRes.isLeft()) {
180
- return checkModifierRes;
181
- }
182
- const cidl_type = typeRes.unwrap();
183
- attributes.push({
184
- foreign_key_reference: null,
185
- value: {
186
- name: prop.getName(),
187
- cidl_type,
188
- },
87
+ const extractor = new CidlExtractor(modelDecls);
88
+ // Extract models
89
+ const models = {};
90
+ for (const [_, [classDecl, decorator]] of modelDecls) {
91
+ const res = extractor.model(classDecl, classDecl.getSourceFile(), decorator);
92
+ if (res.isLeft()) {
93
+ res.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
94
+ return res;
95
+ }
96
+ const model = res.unwrap();
97
+ models[model.name] = model;
98
+ }
99
+ // Extract services
100
+ const services = {};
101
+ for (const [_, classDecl] of serviceDecls) {
102
+ const res = extractor.service(classDecl, classDecl.getSourceFile());
103
+ if (res.isLeft()) {
104
+ res.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
105
+ return res;
106
+ }
107
+ const service = res.unwrap();
108
+ services[service.name] = service;
109
+ }
110
+ // Error: Only one wrangler environment can exist
111
+ if (wranglerEnvs.length > 1) {
112
+ return err(ExtractorErrorCode.TooManyWranglerEnvs, (e) => (e.context = wranglerEnvs.map((w) => w.name).toString()));
113
+ }
114
+ const poos = Object.fromEntries(extractor.extractedPoos);
115
+ return Either.right({
116
+ project_name: projectName,
117
+ wrangler_env: wranglerEnvs[0], // undefined if none
118
+ models,
119
+ poos,
120
+ services,
121
+ main_source,
189
122
  });
190
- continue;
191
- }
192
- // TODO: Limiting to one decorator. Can't get too fancy on us.
193
- const decorator = decorators[0];
194
- const decoratorName = getDecoratorName(decorator);
195
- // Error: invalid attribute modifier
196
- if (
197
- checkModifierRes.isLeft() &&
198
- decoratorName !== AttributeDecoratorKind.DataSource
199
- ) {
200
- return checkModifierRes;
201
- }
202
- // Process decorator
203
- const cidl_type = typeRes.unwrap();
204
- switch (decoratorName) {
205
- case AttributeDecoratorKind.PrimaryKey: {
206
- primary_key = {
207
- name: prop.getName(),
208
- cidl_type,
209
- };
210
- break;
211
- }
212
- case AttributeDecoratorKind.ForeignKey: {
213
- attributes.push({
214
- foreign_key_reference: getDecoratorArgument(decorator, 0) ?? null,
215
- value: {
216
- name: prop.getName(),
217
- cidl_type,
218
- },
219
- });
220
- break;
221
- }
222
- case AttributeDecoratorKind.OneToOne: {
223
- const reference = getDecoratorArgument(decorator, 0);
224
- // Error: One to one navigation properties requre a reference
225
- if (!reference) {
226
- return err(
227
- ExtractorErrorCode.MissingNavigationPropertyReference,
228
- (e) => {
229
- e.snippet = prop.getText();
230
- e.context = prop.getName();
231
- },
232
- );
233
- }
234
- let model_name = getObjectName(cidl_type);
235
- // Error: navigation properties require a model reference
236
- if (!model_name) {
237
- return err(
238
- ExtractorErrorCode.MissingNavigationPropertyReference,
239
- (e) => {
240
- e.snippet = prop.getText();
241
- e.context = prop.getName();
242
- },
243
- );
244
- }
245
- navigation_properties.push({
246
- var_name: prop.getName(),
247
- model_name,
248
- kind: { OneToOne: { reference } },
249
- });
250
- break;
251
- }
252
- case AttributeDecoratorKind.OneToMany: {
253
- const reference = getDecoratorArgument(decorator, 0);
254
- // Error: One to one navigation properties requre a reference
255
- if (!reference) {
256
- return err(
257
- ExtractorErrorCode.MissingNavigationPropertyReference,
258
- (e) => {
259
- e.snippet = prop.getText();
260
- e.context = prop.getName();
261
- },
262
- );
263
- }
264
- let model_name = getObjectName(cidl_type);
265
- // Error: navigation properties require a model reference
266
- if (!model_name) {
267
- return err(
268
- ExtractorErrorCode.MissingNavigationPropertyReference,
269
- (e) => {
270
- e.snippet = prop.getText();
271
- e.context = prop.getName();
272
- },
273
- );
274
- }
275
- navigation_properties.push({
276
- var_name: prop.getName(),
277
- model_name,
278
- kind: { OneToMany: { reference } },
279
- });
280
- break;
281
- }
282
- case AttributeDecoratorKind.ManyToMany: {
283
- const unique_id = getDecoratorArgument(decorator, 0);
284
- // Error: many to many attribtues require a unique id
285
- if (!unique_id)
286
- return err(ExtractorErrorCode.MissingManyToManyUniqueId, (e) => {
287
- e.snippet = prop.getText();
288
- e.context = prop.getName();
123
+ }
124
+ /**
125
+ * @returns An error if the main function is invalid, or the source code of the app function if valid.
126
+ * Undefined if no main function is defined.
127
+ */
128
+ static main(sourceFile) {
129
+ const symbol = sourceFile.getDefaultExportSymbol();
130
+ const decl = symbol?.getDeclarations()[0];
131
+ if (!decl || !MorphNode.isFunctionDeclaration(decl)) {
132
+ return Either.right(undefined);
133
+ }
134
+ // Must be named "main"
135
+ const name = decl.getName();
136
+ if (name !== "main") {
137
+ return Either.right(undefined);
138
+ }
139
+ // Must be async
140
+ if (!decl.isAsync()) {
141
+ return err(ExtractorErrorCode.InvalidMain, (e) => (e.context = "Missing async modifier"));
142
+ }
143
+ // Must have exactly 4 parameters
144
+ const params = decl.getParameters();
145
+ if (params.length !== 4) {
146
+ return err(ExtractorErrorCode.InvalidMain, (e) => {
147
+ e.context = `Expected 4 parameters, got ${params.length}`;
289
148
  });
290
- // Error: navigation properties require a model reference
291
- let model_name = getObjectName(cidl_type);
292
- if (!model_name) {
293
- return err(
294
- ExtractorErrorCode.MissingNavigationPropertyReference,
295
- (e) => {
296
- e.snippet = prop.getText();
297
- e.context = prop.getName();
298
- },
299
- );
300
- }
301
- navigation_properties.push({
302
- var_name: prop.getName(),
303
- model_name,
304
- kind: { ManyToMany: { unique_id } },
305
- });
306
- break;
307
- }
308
- case AttributeDecoratorKind.DataSource: {
309
- const isIncludeTree =
310
- prop
311
- .getType()
312
- .getText(
313
- undefined,
314
- TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
315
- ) === `IncludeTree<${name}>`;
316
- // Error: data sources must be static include trees
317
- if (!prop.isStatic() || !isIncludeTree) {
318
- return err(ExtractorErrorCode.InvalidDataSourceDefinition, (e) => {
319
- e.snippet = prop.getText();
320
- e.context = prop.getName();
149
+ }
150
+ // Expected parameter types in order
151
+ // WranglerEnv does not have a required type annotation
152
+ const expectedTypes = ["Request", null, "CloesceApp", "ExecutionContext"];
153
+ for (let i = 0; i < params.length; i++) {
154
+ const param = params[i];
155
+ const expectedType = expectedTypes[i];
156
+ if (expectedType === null) {
157
+ continue;
158
+ }
159
+ const paramType = param.getType();
160
+ const symbol = paramType.getAliasSymbol() ??
161
+ paramType.getSymbol() ??
162
+ paramType.getTargetType()?.getSymbol();
163
+ if (symbol?.getName() !== expectedType) {
164
+ return err(ExtractorErrorCode.InvalidMain, (e) => {
165
+ e.context = `Expected parameter ${i + 1} to be of type ${expectedType}, got ${paramType}`;
166
+ });
167
+ }
168
+ }
169
+ // Must return Response
170
+ const returnType = decl
171
+ .getReturnType()
172
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope);
173
+ if (returnType !== "Promise<Response>") {
174
+ return err(ExtractorErrorCode.InvalidMain, (e) => {
175
+ e.context = `Expected return type to be Promise<Response>, got ${returnType}`;
321
176
  });
322
- }
323
- const initializer = prop.getInitializer();
324
- const treeRes = CidlExtractor.includeTree(
325
- initializer,
326
- classDecl,
327
- sourceFile,
328
- );
329
- if (treeRes.isLeft()) {
330
- treeRes.value.addContext((prev) => `${prop.getName()} ${prev}`);
331
- treeRes.value.snippet = prop.getText();
332
- return treeRes;
333
- }
334
- data_sources[prop.getName()] = {
335
- name: prop.getName(),
336
- tree: treeRes.unwrap(),
337
- };
338
- break;
339
- }
340
- }
341
- }
342
- if (primary_key == undefined) {
343
- return err(ExtractorErrorCode.MissingPrimaryKey, (e) => {
344
- e.snippet = classDecl.getText();
345
- });
346
- }
347
- // Process methods
348
- for (const m of classDecl.getMethods()) {
349
- const httpVerb = m
350
- .getDecorators()
351
- .map((d) => getDecoratorName(d))
352
- .find((name) => Object.values(HttpVerb).includes(name));
353
- if (!httpVerb) {
354
- continue;
355
- }
356
- const result = CidlExtractor.modelMethod(name, m, httpVerb);
357
- if (result.isLeft()) {
358
- result.value.addContext((prev) => `${m.getName()} ${prev}`);
359
- return result;
360
- }
361
- methods[result.unwrap().name] = result.unwrap();
362
- }
363
- return Either.right({
364
- name,
365
- attributes,
366
- primary_key,
367
- navigation_properties,
368
- methods,
369
- data_sources,
370
- cruds: Array.from(cruds).sort(),
371
- source_path: sourceFile.getFilePath().toString(),
372
- });
373
- }
374
- static modelMethod(modelName, method, verb) {
375
- // Error: invalid method scope, must be public
376
- if (method.getScope() != Scope.Public) {
377
- return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
378
- e.context = method.getName();
379
- e.snippet = method.getText();
380
- });
177
+ }
178
+ return Either.right(sourceFile.getFilePath().toString());
381
179
  }
382
- let needsDataSource = !method.isStatic();
383
- const parameters = [];
384
- for (const param of method.getParameters()) {
385
- // Handle injected param
386
- if (param.getDecorator(ParameterDecoratorKind.Inject)) {
387
- const typeRes = CidlExtractor.cidlType(param.getType(), true);
388
- // Error: invalid type
389
- if (typeRes.isLeft()) {
390
- typeRes.value.snippet = method.getText();
391
- typeRes.value.context = param.getName();
392
- return typeRes;
180
+ model(classDecl, sourceFile, decorator) {
181
+ const name = classDecl.getName();
182
+ const columns = [];
183
+ const key_params = [];
184
+ const kv_objects = [];
185
+ const r2_objects = [];
186
+ const navigation_properties = [];
187
+ const data_sources = {};
188
+ const methods = {};
189
+ const cruds = new Set();
190
+ let primary_key = null;
191
+ // Extract crud methods
192
+ const arg = decorator.getArguments()[0];
193
+ if (arg && MorphNode.isArrayLiteralExpression(arg)) {
194
+ for (const a of arg.getElements()) {
195
+ cruds.add((MorphNode.isStringLiteral(a)
196
+ ? a.getLiteralValue()
197
+ : a.getText()));
198
+ }
199
+ }
200
+ // Iterate properties
201
+ for (const prop of classDecl.getProperties()) {
202
+ const typeRes = CidlExtractor.cidlType(prop.getType());
203
+ // Error: invalid property type
204
+ if (typeRes.isLeft()) {
205
+ typeRes.value.context = prop.getName();
206
+ typeRes.value.snippet = prop.getText();
207
+ return typeRes;
208
+ }
209
+ const cidl_type = typeRes.unwrap();
210
+ // Include Trees
211
+ const isIncludeTree = prop
212
+ .getType()
213
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope) === `IncludeTree<${name}>`;
214
+ if (isIncludeTree) {
215
+ // Error: data sources must be static include trees
216
+ if (!prop.isStatic()) {
217
+ return err(ExtractorErrorCode.InvalidDataSourceDefinition, (e) => {
218
+ e.snippet = prop.getText();
219
+ e.context = prop.getName();
220
+ });
221
+ }
222
+ const initializer = prop.getInitializer();
223
+ if (!initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) {
224
+ return err(ExtractorErrorCode.InvalidDataSourceDefinition, (e) => {
225
+ e.snippet = prop.getText();
226
+ e.context = prop.getName();
227
+ });
228
+ }
229
+ data_sources[prop.getName()] = {
230
+ name: prop.getName(),
231
+ tree: parseIncludeTree(initializer),
232
+ };
233
+ continue;
234
+ }
235
+ const checkModifierRes = checkPropertyModifier(prop);
236
+ // Error: invalid property modifier
237
+ if (checkModifierRes.isLeft()) {
238
+ return checkModifierRes;
239
+ }
240
+ // Infer decorator
241
+ if (prop.getDecorators().length === 0) {
242
+ this.inferModelDecorator(prop, classDecl, cidl_type);
243
+ }
244
+ const decorators = prop.getDecorators();
245
+ // Scalar column
246
+ if (decorators.length === 0) {
247
+ columns.push({
248
+ foreign_key_reference: null,
249
+ value: {
250
+ name: prop.getName(),
251
+ cidl_type,
252
+ },
253
+ });
254
+ continue;
255
+ }
256
+ const decorator = decorators[0];
257
+ const decoratorName = getDecoratorName(decorator);
258
+ // Process decorator
259
+ switch (decoratorName) {
260
+ case PropertyDecoratorKind.PrimaryKey: {
261
+ primary_key = {
262
+ name: prop.getName(),
263
+ cidl_type,
264
+ };
265
+ break;
266
+ }
267
+ case PropertyDecoratorKind.ForeignKey: {
268
+ columns.push({
269
+ foreign_key_reference: getDecoratorArgument(decorator, 0) ?? null,
270
+ value: {
271
+ name: prop.getName(),
272
+ cidl_type,
273
+ },
274
+ });
275
+ break;
276
+ }
277
+ case PropertyDecoratorKind.OneToOne: {
278
+ const selector = getSelectorPropertyName(decorator);
279
+ if (selector.isLeft()) {
280
+ return err(ExtractorErrorCode.InvalidSelectorSyntax, (e) => {
281
+ e.snippet = prop.getText();
282
+ e.context = prop.getName();
283
+ });
284
+ }
285
+ const model_name = getObjectName(cidl_type);
286
+ // Error: navigation properties require a model reference
287
+ if (!model_name) {
288
+ return err(ExtractorErrorCode.InvalidSelectorSyntax, (e) => {
289
+ e.snippet = prop.getText();
290
+ e.context = prop.getName();
291
+ });
292
+ }
293
+ navigation_properties.push({
294
+ var_name: prop.getName(),
295
+ model_reference: model_name,
296
+ kind: { OneToOne: { column_reference: selector.unwrap() } },
297
+ });
298
+ break;
299
+ }
300
+ case PropertyDecoratorKind.OneToMany: {
301
+ const selector = getSelectorPropertyName(decorator);
302
+ if (selector.isLeft()) {
303
+ return err(ExtractorErrorCode.InvalidSelectorSyntax, (e) => {
304
+ e.snippet = prop.getText();
305
+ e.context = prop.getName();
306
+ });
307
+ }
308
+ let model_name = getObjectName(cidl_type);
309
+ // Error: navigation properties require a model reference
310
+ if (!model_name) {
311
+ return err(ExtractorErrorCode.InvalidNavigationProperty, (e) => {
312
+ e.snippet = prop.getText();
313
+ e.context = prop.getName();
314
+ });
315
+ }
316
+ navigation_properties.push({
317
+ var_name: prop.getName(),
318
+ model_reference: model_name,
319
+ kind: { OneToMany: { column_reference: selector.unwrap() } },
320
+ });
321
+ break;
322
+ }
323
+ case PropertyDecoratorKind.ManyToMany: {
324
+ // Error: navigation properties require a model reference
325
+ let model_name = getObjectName(cidl_type);
326
+ if (!model_name) {
327
+ return err(ExtractorErrorCode.InvalidNavigationProperty, (e) => {
328
+ e.snippet = prop.getText();
329
+ e.context = prop.getName();
330
+ });
331
+ }
332
+ navigation_properties.push({
333
+ var_name: prop.getName(),
334
+ model_reference: model_name,
335
+ kind: "ManyToMany",
336
+ });
337
+ break;
338
+ }
339
+ case PropertyDecoratorKind.KeyParam: {
340
+ key_params.push(prop.getName());
341
+ break;
342
+ }
343
+ case PropertyDecoratorKind.KV: {
344
+ // Format and namespace binding are required
345
+ const format = getDecoratorArgument(decorator, 0);
346
+ const namespace_binding = getDecoratorArgument(decorator, 1);
347
+ if (!format || !namespace_binding) {
348
+ return err(ExtractorErrorCode.InvalidTypescriptSyntax, (e) => {
349
+ e.snippet = prop.getText();
350
+ e.context = prop.getName();
351
+ });
352
+ }
353
+ // Ensure that the prop type is KValue<T>
354
+ const ty = prop.getType();
355
+ const isArray = ty.isArray();
356
+ const elementType = isArray ? ty.getArrayElementTypeOrThrow() : ty;
357
+ const symbolName = elementType.getSymbol()?.getName();
358
+ if (symbolName !== "KValue") {
359
+ return err(ExtractorErrorCode.MissingKValue, (e) => {
360
+ e.snippet = prop.getText();
361
+ e.context = prop.getName();
362
+ });
363
+ }
364
+ kv_objects.push({
365
+ format,
366
+ namespace_binding,
367
+ value: {
368
+ name: prop.getName(),
369
+ cidl_type: isArray ? cidl_type.Array : cidl_type,
370
+ },
371
+ list_prefix: isArray,
372
+ });
373
+ break;
374
+ }
375
+ case PropertyDecoratorKind.R2: {
376
+ // Format and bucket binding are required
377
+ const format = getDecoratorArgument(decorator, 0);
378
+ const bucket_binding = getDecoratorArgument(decorator, 1);
379
+ if (!format || !bucket_binding) {
380
+ return err(ExtractorErrorCode.InvalidTypescriptSyntax, (e) => {
381
+ e.snippet = prop.getText();
382
+ e.context = prop.getName();
383
+ });
384
+ }
385
+ // Type must be R2ObjectBody
386
+ const ty = prop.getType();
387
+ const isArray = ty.isArray();
388
+ const elementType = isArray ? ty.getArrayElementTypeOrThrow() : ty;
389
+ const symbolName = elementType.getSymbol()?.getName();
390
+ if (symbolName !== "R2ObjectBody") {
391
+ return err(ExtractorErrorCode.MissingR2ObjectBody, (e) => {
392
+ e.snippet = prop.getText();
393
+ e.context = prop.getName();
394
+ });
395
+ }
396
+ r2_objects.push({
397
+ format,
398
+ bucket_binding,
399
+ var_name: prop.getName(),
400
+ list_prefix: isArray,
401
+ });
402
+ break;
403
+ }
404
+ }
393
405
  }
394
- parameters.push({
395
- name: param.getName(),
396
- cidl_type: typeRes.unwrap(),
406
+ // Process methods
407
+ for (const m of classDecl.getMethods()) {
408
+ const httpVerb = m
409
+ .getDecorators()
410
+ .map(getDecoratorName)
411
+ .find((name) => Object.values(HttpVerb).includes(name));
412
+ if (!httpVerb) {
413
+ continue;
414
+ }
415
+ const result = this.method(m, httpVerb);
416
+ if (result.isLeft()) {
417
+ result.value.addContext((prev) => `${m.getName()} ${prev}`);
418
+ return result;
419
+ }
420
+ methods[result.unwrap().name] = result.unwrap();
421
+ }
422
+ return Either.right({
423
+ name,
424
+ columns,
425
+ primary_key,
426
+ navigation_properties,
427
+ key_params,
428
+ kv_objects,
429
+ r2_objects,
430
+ methods,
431
+ data_sources,
432
+ cruds: Array.from(cruds).sort(),
433
+ source_path: sourceFile.getFilePath().toString(),
397
434
  });
398
- continue;
399
- }
400
- // Handle all other params
401
- const typeRes = CidlExtractor.cidlType(param.getType());
402
- // Error: invalid type
403
- if (typeRes.isLeft()) {
404
- typeRes.value.snippet = method.getText();
405
- typeRes.value.context = param.getName();
406
- return typeRes;
407
- }
408
- if (typeof typeRes.value !== "string" && "DataSource" in typeRes.value) {
409
- needsDataSource = false;
410
- }
411
- parameters.push({
412
- name: param.getName(),
413
- cidl_type: typeRes.unwrap(),
414
- });
415
- }
416
- const typeRes = CidlExtractor.cidlType(method.getReturnType());
417
- // Error: invalid type
418
- if (typeRes.isLeft()) {
419
- typeRes.value.snippet = method.getText();
420
- return typeRes;
421
- }
422
- // Sugaring: add data source
423
- if (needsDataSource) {
424
- parameters.push({
425
- name: "__dataSource",
426
- cidl_type: { DataSource: modelName },
427
- });
428
435
  }
429
- return Either.right({
430
- name: method.getName(),
431
- is_static: method.isStatic(),
432
- http_verb: verb,
433
- return_media: defaultMediaType(),
434
- return_type: typeRes.unwrap(),
435
- parameters_media: defaultMediaType(),
436
- parameters,
437
- });
438
- }
439
- static service(classDecl, sourceFile) {
440
- const attributes = [];
441
- const methods = {};
442
- // Attributes
443
- for (const prop of classDecl.getProperties()) {
444
- const typeRes = CidlExtractor.cidlType(prop.getType(), true);
445
- // Error: invalid property type
446
- if (typeRes.isLeft()) {
447
- typeRes.value.context = prop.getName();
448
- typeRes.value.snippet = prop.getText();
449
- return typeRes;
450
- }
451
- if (typeof typeRes.value === "string" || !("Inject" in typeRes.value)) {
452
- return err(ExtractorErrorCode.InvalidServiceAttribute, (e) => {
453
- e.context = prop.getName();
454
- e.snippet = prop.getText();
436
+ service(classDecl, sourceFile) {
437
+ const attributes = [];
438
+ const methods = {};
439
+ // Properties
440
+ for (const prop of classDecl.getProperties()) {
441
+ const typeRes = CidlExtractor.cidlType(prop.getType(), true);
442
+ // Error: invalid property type
443
+ if (typeRes.isLeft()) {
444
+ typeRes.value.context = prop.getName();
445
+ typeRes.value.snippet = prop.getText();
446
+ return typeRes;
447
+ }
448
+ if (typeof typeRes.value === "string" || !("Inject" in typeRes.value)) {
449
+ return err(ExtractorErrorCode.InvalidServiceProperty, (e) => {
450
+ e.context = prop.getName();
451
+ e.snippet = prop.getText();
452
+ });
453
+ }
454
+ // Error: invalid property modifier
455
+ const checkModifierRes = checkPropertyModifier(prop);
456
+ if (checkModifierRes.isLeft()) {
457
+ return checkModifierRes;
458
+ }
459
+ attributes.push({
460
+ var_name: prop.getName(),
461
+ inject_reference: typeRes.value.Inject,
462
+ });
463
+ }
464
+ // Methods
465
+ for (const m of classDecl.getMethods()) {
466
+ const httpVerb = m
467
+ .getDecorators()
468
+ .map(getDecoratorName)
469
+ .find((name) => Object.values(HttpVerb).includes(name));
470
+ if (!httpVerb) {
471
+ continue;
472
+ }
473
+ const res = this.method(m, httpVerb);
474
+ if (res.isLeft()) {
475
+ return res;
476
+ }
477
+ const serviceMethod = res.unwrap();
478
+ methods[serviceMethod.name] = serviceMethod;
479
+ }
480
+ return Either.right({
481
+ name: classDecl.getName(),
482
+ attributes,
483
+ methods,
484
+ source_path: sourceFile.getFilePath().toString(),
455
485
  });
456
- }
457
- const checkModifierRes = checkAttributeModifier(prop);
458
- if (checkModifierRes.isLeft()) {
459
- return checkModifierRes;
460
- }
461
- attributes.push({
462
- var_name: prop.getName(),
463
- injected: typeRes.value.Inject,
464
- });
465
- }
466
- // Methods
467
- for (const m of classDecl.getMethods()) {
468
- const httpVerb = m
469
- .getDecorators()
470
- .map((d) => getDecoratorName(d))
471
- .find((name) => Object.values(HttpVerb).includes(name));
472
- if (!httpVerb) {
473
- continue;
474
- }
475
- const res = CidlExtractor.serviceMethod(m, httpVerb);
476
- if (res.isLeft()) {
477
- return res;
478
- }
479
- const serviceMethod = res.unwrap();
480
- methods[serviceMethod.name] = serviceMethod;
481
486
  }
482
- return Either.right({
483
- name: classDecl.getName(),
484
- attributes,
485
- methods,
486
- source_path: sourceFile.getFilePath().toString(),
487
- });
488
- }
489
- static serviceMethod(method, verb) {
490
- // Error: invalid method scope, must be public
491
- if (method.getScope() != Scope.Public) {
492
- return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
493
- e.context = method.getName();
494
- e.snippet = method.getText();
495
- });
496
- }
497
- const parameters = [];
498
- for (const param of method.getParameters()) {
499
- // Handle injected param
500
- if (param.getDecorator(ParameterDecoratorKind.Inject)) {
501
- const typeRes = CidlExtractor.cidlType(param.getType(), true);
487
+ method(method, verb) {
488
+ // Error: invalid method scope, must be public
489
+ if (method.getScope() != Scope.Public) {
490
+ return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
491
+ e.context = method.getName();
492
+ e.snippet = method.getText();
493
+ });
494
+ }
495
+ const parameters = [];
496
+ for (const param of method.getParameters()) {
497
+ // Handle injected param
498
+ if (param.getDecorator(ParameterDecoratorKind.Inject)) {
499
+ const typeRes = CidlExtractor.cidlType(param.getType(), true);
500
+ // Error: invalid type
501
+ if (typeRes.isLeft()) {
502
+ typeRes.value.snippet = method.getText();
503
+ typeRes.value.context = param.getName();
504
+ return typeRes;
505
+ }
506
+ parameters.push({
507
+ name: param.getName(),
508
+ cidl_type: typeRes.unwrap(),
509
+ });
510
+ continue;
511
+ }
512
+ // Handle all other params
513
+ const typeRes = CidlExtractor.cidlType(param.getType());
514
+ // Error: invalid type
515
+ if (typeRes.isLeft()) {
516
+ typeRes.value.snippet = method.getText();
517
+ typeRes.value.context = param.getName();
518
+ return typeRes;
519
+ }
520
+ // Extract any POOs used as parameter types
521
+ const objectName = getObjectName(typeRes.unwrap());
522
+ if (objectName &&
523
+ !this.extractedPoos.has(objectName) &&
524
+ !this.modelDecls.has(objectName)) {
525
+ const res = this.poo(method.getSourceFile().getClassOrThrow(objectName), method.getSourceFile());
526
+ if (res.isLeft()) {
527
+ res.value.addContext((prev) => `${param.getName()}.${prev}`);
528
+ return res;
529
+ }
530
+ }
531
+ parameters.push({
532
+ name: param.getName(),
533
+ cidl_type: typeRes.unwrap(),
534
+ });
535
+ }
536
+ const typeRes = CidlExtractor.cidlType(method.getReturnType());
502
537
  // Error: invalid type
503
538
  if (typeRes.isLeft()) {
504
- typeRes.value.snippet = method.getText();
505
- typeRes.value.context = param.getName();
506
- return typeRes;
539
+ typeRes.value.snippet = method.getText();
540
+ return typeRes;
541
+ }
542
+ // Extract any POOs used as return types
543
+ const objectName = getObjectName(typeRes.unwrap());
544
+ if (objectName &&
545
+ !this.extractedPoos.has(objectName) &&
546
+ !this.modelDecls.has(objectName)) {
547
+ const res = this.poo(method.getSourceFile().getClassOrThrow(objectName), method.getSourceFile());
548
+ if (res.isLeft()) {
549
+ res.value.addContext((prev) => `returns ${prev}`);
550
+ return res;
551
+ }
507
552
  }
508
- parameters.push({
509
- name: param.getName(),
510
- cidl_type: typeRes.unwrap(),
553
+ return Either.right({
554
+ name: method.getName(),
555
+ http_verb: verb,
556
+ is_static: method.isStatic(),
557
+ return_media: defaultMediaType(),
558
+ return_type: typeRes.unwrap(),
559
+ parameters_media: defaultMediaType(),
560
+ parameters,
511
561
  });
512
- continue;
513
- }
514
- // Handle all other params
515
- const typeRes = CidlExtractor.cidlType(param.getType());
516
- // Error: invalid type
517
- if (typeRes.isLeft()) {
518
- typeRes.value.snippet = method.getText();
519
- typeRes.value.context = param.getName();
520
- return typeRes;
521
- }
522
- parameters.push({
523
- name: param.getName(),
524
- cidl_type: typeRes.unwrap(),
525
- });
526
- }
527
- const typeRes = CidlExtractor.cidlType(method.getReturnType());
528
- // Error: invalid type
529
- if (typeRes.isLeft()) {
530
- typeRes.value.snippet = method.getText();
531
- return typeRes;
532
- }
533
- return Either.right({
534
- name: method.getName(),
535
- http_verb: verb,
536
- is_static: method.isStatic(),
537
- return_media: defaultMediaType(),
538
- return_type: typeRes.unwrap(),
539
- parameters_media: defaultMediaType(),
540
- parameters,
541
- });
542
- }
543
- static poo(classDecl, sourceFile) {
544
- const name = classDecl.getName();
545
- const attributes = [];
546
- for (const prop of classDecl.getProperties()) {
547
- const typeRes = CidlExtractor.cidlType(prop.getType());
548
- // Error: invalid property type
549
- if (typeRes.isLeft()) {
550
- typeRes.value.context = prop.getName();
551
- typeRes.value.snippet = prop.getText();
552
- return typeRes;
553
- }
554
- // Error: invalid attribute modifier
555
- const modifierRes = checkAttributeModifier(prop);
556
- if (modifierRes.isLeft()) {
557
- return modifierRes;
558
- }
559
- const cidl_type = typeRes.unwrap();
560
- attributes.push({
561
- name: prop.getName(),
562
- cidl_type,
563
- });
564
- continue;
565
- }
566
- return Either.right({
567
- name,
568
- attributes,
569
- source_path: sourceFile.getFilePath().toString(),
570
- });
571
- }
572
- static env(classDecl, sourceFile) {
573
- const vars = {};
574
- let binding;
575
- for (const prop of classDecl.getProperties()) {
576
- if (
577
- prop
578
- .getType()
579
- .getText(
580
- undefined,
581
- TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
582
- ) === "D1Database"
583
- ) {
584
- binding = prop.getName();
585
- continue;
586
- }
587
- const ty = CidlExtractor.cidlType(prop.getType());
588
- if (ty.isLeft()) {
589
- ty.value.context = prop.getName();
590
- ty.value.snippet = prop.getText();
591
- return ty;
592
- }
593
- vars[prop.getName()] = ty.unwrap();
594
- }
595
- if (!binding) {
596
- return err(ExtractorErrorCode.MissingDatabaseBinding);
597
- }
598
- return Either.right({
599
- name: classDecl.getName(),
600
- source_path: sourceFile.getFilePath().toString(),
601
- db_binding: binding,
602
- vars,
603
- });
604
- }
605
- static primTypeMap = {
606
- number: "Real",
607
- Number: "Real",
608
- Integer: "Integer",
609
- string: "Text",
610
- String: "Text",
611
- boolean: "Boolean",
612
- Boolean: "Boolean",
613
- Date: "DateIso",
614
- Uint8Array: "Blob",
615
- Stream: "Stream",
616
- };
617
- static cidlType(type, inject = false) {
618
- // Void
619
- if (type.isVoid()) {
620
- return Either.right("Void");
621
- }
622
- // Null
623
- if (type.isNull()) {
624
- return Either.right({ Nullable: "Void" });
625
- }
626
- // Nullable via union
627
- const [unwrappedType, nullable] = unwrapNullable(type);
628
- const tyText = unwrappedType
629
- .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
630
- .split("|")[0]
631
- .trim();
632
- // Primitives
633
- const prim = this.primTypeMap[tyText];
634
- if (prim) {
635
- return Either.right(wrapNullable(prim, nullable));
636
- }
637
- const generics = [
638
- ...unwrappedType.getAliasTypeArguments(),
639
- ...unwrappedType.getTypeArguments(),
640
- ];
641
- // Error: can't handle multiple generics
642
- if (generics.length > 1) {
643
- return err(ExtractorErrorCode.MultipleGenericType);
644
- }
645
- // No generics -> inject or object
646
- if (generics.length === 0) {
647
- const base = inject ? { Inject: tyText } : { Object: tyText };
648
- return Either.right(wrapNullable(base, nullable));
649
- }
650
- // Single generic
651
- const genericTy = generics[0];
652
- const symbolName = unwrappedType.getSymbol()?.getName();
653
- const aliasName = unwrappedType.getAliasSymbol()?.getName();
654
- if (aliasName === "DataSourceOf") {
655
- return Either.right(
656
- wrapNullable(
657
- {
658
- DataSource: genericTy.getText(
659
- undefined,
660
- TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
661
- ),
662
- },
663
- nullable,
664
- ),
665
- );
666
562
  }
667
- if (aliasName === "DeepPartial") {
668
- const [_, genericTyNullable] = unwrapNullable(genericTy);
669
- const genericTyGenerics = [
670
- ...genericTy.getAliasTypeArguments(),
671
- ...genericTy.getTypeArguments(),
672
- ];
673
- // Expect partials to be of the exact form DeepPartial<Model>
674
- if (
675
- genericTyNullable ||
676
- genericTy.isUnion() ||
677
- genericTyGenerics.length > 0
678
- ) {
679
- return err(ExtractorErrorCode.InvalidPartialType);
680
- }
681
- return Either.right(
682
- wrapNullable(
683
- {
684
- Partial: genericTy
685
- .getText(
686
- undefined,
687
- TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
688
- )
689
- .split("|")[0]
690
- .trim(),
691
- },
692
- nullable,
693
- ),
694
- );
695
- }
696
- if (symbolName === "Promise" || aliasName === "IncludeTree") {
697
- // Unwrap promises
698
- return wrapGeneric(genericTy, nullable, (inner) => inner);
699
- }
700
- if (unwrappedType.isArray()) {
701
- return wrapGeneric(genericTy, nullable, (inner) => ({ Array: inner }));
702
- }
703
- if (symbolName === "HttpResult") {
704
- return wrapGeneric(genericTy, nullable, (inner) => ({
705
- HttpResult: inner,
706
- }));
707
- }
708
- // Error: unknown type
709
- return err(ExtractorErrorCode.UnknownType);
710
- function wrapNullable(inner, isNullable) {
711
- if (isNullable) {
712
- return { Nullable: inner };
713
- } else {
714
- return inner;
715
- }
716
- }
717
- function wrapGeneric(t, isNullable, wrapper) {
718
- const res = CidlExtractor.cidlType(t, inject);
719
- // Error: propogated from `cidlType`
720
- return res.map((inner) => wrapNullable(wrapper(inner), isNullable));
563
+ poo(classDecl, sourceFile) {
564
+ const name = classDecl.getName();
565
+ const attributes = [];
566
+ // Error: POOs must be exported
567
+ if (!classDecl.isExported()) {
568
+ return err(ExtractorErrorCode.MissingExport, (e) => {
569
+ e.context = name;
570
+ e.snippet = classDecl.getText();
571
+ });
572
+ }
573
+ for (const prop of classDecl.getProperties()) {
574
+ // Error: invalid property modifier
575
+ const modifierRes = checkPropertyModifier(prop);
576
+ if (modifierRes.isLeft()) {
577
+ return modifierRes;
578
+ }
579
+ const typeRes = CidlExtractor.cidlType(prop.getType());
580
+ // Error: invalid property type
581
+ if (typeRes.isLeft()) {
582
+ typeRes.value.context = prop.getName();
583
+ typeRes.value.snippet = prop.getText();
584
+ return typeRes;
585
+ }
586
+ const cidl_type = typeRes.unwrap();
587
+ // Check that the type is an already extracted POO, or a model decl.
588
+ // If not, find the source and extract it as a POO.
589
+ const objectName = getObjectName(cidl_type);
590
+ if (objectName &&
591
+ !this.extractedPoos.has(objectName) &&
592
+ !this.modelDecls.has(objectName)) {
593
+ const res = this.poo(classDecl.getSourceFile().getClassOrThrow(objectName), classDecl.getSourceFile());
594
+ if (res.isLeft()) {
595
+ res.value.addContext((prev) => `${prop.getName()}.${prev}`);
596
+ return res;
597
+ }
598
+ }
599
+ attributes.push({
600
+ name: prop.getName(),
601
+ cidl_type,
602
+ });
603
+ continue;
604
+ }
605
+ // Mark as extracted
606
+ const poo = {
607
+ name,
608
+ attributes,
609
+ source_path: sourceFile.getFilePath().toString(),
610
+ };
611
+ this.extractedPoos.set(name, poo);
612
+ return Either.right(null);
721
613
  }
722
- function unwrapNullable(ty) {
723
- if (!ty.isUnion()) return [ty, false];
724
- const unions = ty.getUnionTypes();
725
- const nonNulls = unions.filter((t) => !t.isNull() && !t.isUndefined());
726
- const hasNullable = nonNulls.length < unions.length;
727
- // Booleans seperate into [null, true, false] from the `getUnionTypes` call
728
- if (
729
- nonNulls.length === 2 &&
730
- nonNulls.every((t) => t.isBooleanLiteral())
731
- ) {
732
- return [nonNulls[0].getApparentType(), hasNullable];
733
- }
734
- return [nonNulls[0] ?? ty, hasNullable];
614
+ // public for tests
615
+ static env(classDecl, sourceFile) {
616
+ const vars = {};
617
+ let d1_binding = undefined;
618
+ const kv_bindings = [];
619
+ const r2_bindings = [];
620
+ for (const prop of classDecl.getProperties()) {
621
+ // Error: invalid property modifier
622
+ const checkModifierRes = checkPropertyModifier(prop);
623
+ if (checkModifierRes.isLeft()) {
624
+ return checkModifierRes;
625
+ }
626
+ // TODO: Support multiple D1 bindings
627
+ if (prop
628
+ .getType()
629
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope) === "D1Database") {
630
+ d1_binding = prop.getName();
631
+ continue;
632
+ }
633
+ if (prop.getType().getSymbol()?.getName() === "KVNamespace") {
634
+ kv_bindings.push(prop.getName());
635
+ continue;
636
+ }
637
+ if (prop.getType().getSymbol()?.getName() === "R2Bucket") {
638
+ r2_bindings.push(prop.getName());
639
+ continue;
640
+ }
641
+ const ty = CidlExtractor.cidlType(prop.getType());
642
+ if (ty.isLeft()) {
643
+ ty.value.context = prop.getName();
644
+ ty.value.snippet = prop.getText();
645
+ return ty;
646
+ }
647
+ vars[prop.getName()] = ty.unwrap();
648
+ }
649
+ return Either.right({
650
+ name: classDecl.getName(),
651
+ source_path: sourceFile.getFilePath().toString(),
652
+ d1_binding,
653
+ kv_bindings,
654
+ r2_bindings,
655
+ vars,
656
+ });
735
657
  }
736
- }
737
- static includeTree(expr, currentClass, sf) {
738
- // Include trees must be of the expected form
739
- if (
740
- !expr ||
741
- !expr.isKind ||
742
- !expr.isKind(SyntaxKind.ObjectLiteralExpression)
743
- ) {
744
- return err(ExtractorErrorCode.InvalidIncludeTree);
658
+ static primTypeMap = {
659
+ number: "Real",
660
+ Number: "Real",
661
+ Integer: "Integer",
662
+ string: "Text",
663
+ String: "Text",
664
+ boolean: "Boolean",
665
+ Boolean: "Boolean",
666
+ Date: "DateIso",
667
+ Uint8Array: "Blob",
668
+ };
669
+ // public for tests
670
+ static cidlType(type, inject = false) {
671
+ // Void
672
+ if (type.isVoid()) {
673
+ return Either.right("Void");
674
+ }
675
+ // Unknown
676
+ if (type.isUnknown()) {
677
+ return Either.right("JsonValue");
678
+ }
679
+ // Null
680
+ if (type.isNull()) {
681
+ return Either.right({ Nullable: "Void" });
682
+ }
683
+ // Nullable via union
684
+ const [unwrappedType, nullable] = unwrapNullable(type);
685
+ const tyText = unwrappedType
686
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
687
+ .split("|")[0]
688
+ .trim();
689
+ // Primitives
690
+ const prim = this.primTypeMap[tyText];
691
+ if (prim) {
692
+ return Either.right(wrapNullable(prim, nullable));
693
+ }
694
+ const generics = [
695
+ ...unwrappedType.getAliasTypeArguments(),
696
+ ...unwrappedType.getTypeArguments(),
697
+ ];
698
+ // Error: can't handle multiple generics
699
+ if (generics.length > 1) {
700
+ return err(ExtractorErrorCode.MultipleGenericType);
701
+ }
702
+ // No generics -> inject or object
703
+ if (generics.length === 0) {
704
+ const base = inject ? { Inject: tyText } : { Object: tyText };
705
+ return Either.right(wrapNullable(base, nullable));
706
+ }
707
+ // Single generic
708
+ const genericTy = generics[0];
709
+ const symbolName = unwrappedType.getSymbol()?.getName();
710
+ const aliasName = unwrappedType.getAliasSymbol()?.getName();
711
+ if (aliasName === "DataSourceOf") {
712
+ return Either.right(wrapNullable({
713
+ DataSource: genericTy.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope),
714
+ }, nullable));
715
+ }
716
+ if (aliasName === "DeepPartial") {
717
+ const [_, genericTyNullable] = unwrapNullable(genericTy);
718
+ const genericTyGenerics = [
719
+ ...genericTy.getAliasTypeArguments(),
720
+ ...genericTy.getTypeArguments(),
721
+ ];
722
+ // Expect partials to be of the exact form DeepPartial<Model>
723
+ if (genericTyNullable ||
724
+ genericTy.isUnion() ||
725
+ genericTyGenerics.length > 0) {
726
+ return err(ExtractorErrorCode.InvalidPartialType);
727
+ }
728
+ return Either.right(wrapNullable({
729
+ Partial: genericTy
730
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
731
+ .split("|")[0]
732
+ .trim(),
733
+ }, nullable));
734
+ }
735
+ if (symbolName === ReadableStream.name) {
736
+ return Either.right(wrapNullable("Stream", nullable));
737
+ }
738
+ if (symbolName === Promise.name ||
739
+ aliasName === "IncludeTree" ||
740
+ symbolName === "KValue") {
741
+ return wrapGeneric(genericTy, nullable, (inner) => inner);
742
+ }
743
+ if (unwrappedType.isArray()) {
744
+ return wrapGeneric(genericTy, nullable, (inner) => ({ Array: inner }));
745
+ }
746
+ if (symbolName === "HttpResult") {
747
+ return wrapGeneric(genericTy, nullable, (inner) => ({
748
+ HttpResult: inner,
749
+ }));
750
+ }
751
+ // Error: unknown type
752
+ return err(ExtractorErrorCode.UnknownType);
753
+ function wrapNullable(inner, isNullable) {
754
+ if (isNullable) {
755
+ return { Nullable: inner };
756
+ }
757
+ else {
758
+ return inner;
759
+ }
760
+ }
761
+ function wrapGeneric(t, isNullable, wrapper) {
762
+ const res = CidlExtractor.cidlType(t, inject);
763
+ return res.map((inner) => wrapNullable(wrapper(inner), isNullable));
764
+ }
765
+ function unwrapNullable(ty) {
766
+ if (!ty.isUnion())
767
+ return [ty, false];
768
+ const unions = ty.getUnionTypes();
769
+ const nonNulls = unions.filter((t) => !t.isNull());
770
+ const hasNullable = nonNulls.length < unions.length;
771
+ // Booleans seperate into [null, true, false] from the `getUnionTypes` call
772
+ if (nonNulls.length === 2 &&
773
+ nonNulls.every((t) => t.isBooleanLiteral())) {
774
+ return [nonNulls[0].getApparentType(), hasNullable];
775
+ }
776
+ const stripUndefined = nonNulls.filter((t) => !t.isUndefined());
777
+ return [stripUndefined[0] ?? ty, hasNullable];
778
+ }
745
779
  }
746
- const result = {};
747
- for (const prop of expr.getProperties()) {
748
- if (!prop.isKind(SyntaxKind.PropertyAssignment)) continue;
749
- // Error: navigation property not found
750
- const navProp = findPropertyByName(currentClass, prop.getName());
751
- if (!navProp) {
752
- return err(
753
- ExtractorErrorCode.UnknownNavigationPropertyReference,
754
- (e) => {
755
- e.snippet = expr.getText();
756
- e.context = prop.getName();
757
- },
758
- );
759
- }
760
- const typeRes = CidlExtractor.cidlType(navProp.getType());
761
- // Error: invalid referenced nav prop type
762
- if (typeRes.isLeft()) {
763
- typeRes.value.snippet = navProp.getText();
764
- typeRes.value.context = prop.getName();
765
- return typeRes;
766
- }
767
- // Error: invalid referenced nav prop type
768
- const cidl_type = typeRes.unwrap();
769
- if (typeof cidl_type === "string") {
770
- return err(
771
- ExtractorErrorCode.InvalidNavigationPropertyReference,
772
- (e) => {
773
- e.snippet = navProp.getText();
774
- e.context = prop.getName();
775
- },
776
- );
777
- }
778
- // Recurse for nested includes
779
- const initializer = prop.getInitializer?.();
780
- let nestedTree = {};
781
- if (initializer?.isKind?.(SyntaxKind.ObjectLiteralExpression)) {
782
- const targetModel = getObjectName(cidl_type);
783
- const targetClass = currentClass
784
- .getSourceFile()
785
- .getProject()
786
- .getSourceFiles()
787
- .flatMap((f) => f.getClasses())
788
- .find((c) => c.getName() === targetModel);
789
- if (targetClass) {
790
- const treeRes = CidlExtractor.includeTree(
791
- initializer,
792
- targetClass,
793
- sf,
794
- );
795
- // Error: Propogated from `includeTree`
796
- if (treeRes.isLeft()) {
797
- treeRes.value.snippet = expr.getText();
798
- return treeRes;
799
- }
800
- nestedTree = treeRes.unwrap();
801
- }
802
- }
803
- result[navProp.getName()] = nestedTree;
780
+ /**
781
+ * Mutates the property declaration to add inferred decorators based on naming conventions.
782
+ */
783
+ inferModelDecorator(prop, classDecl, cidlType) {
784
+ const className = classDecl.getName();
785
+ const objectName = getObjectName(cidlType);
786
+ const normalizedPropName = normalizeName(prop.getName());
787
+ // Primary Key
788
+ if (normalizedPropName === "id" ||
789
+ normalizedPropName === `${className.toLowerCase()}id`) {
790
+ // Add a primary key decorator
791
+ prop.addDecorator({
792
+ name: PropertyDecoratorKind.PrimaryKey,
793
+ arguments: [],
794
+ });
795
+ return;
796
+ }
797
+ // Foreign Key
798
+ if (normalizedPropName.endsWith("id")) {
799
+ const referencedNavName = prop
800
+ .getName()
801
+ .slice(0, prop.getName().length - (normalizedPropName.endsWith("_id") ? 3 : 2));
802
+ const oneToOneProperties = classDecl
803
+ .getProperties()
804
+ .filter((p) => p.getName() === referencedNavName);
805
+ if (oneToOneProperties.length > 1) {
806
+ console.warn(`
807
+ Cannot infer ForeignKey relationship due to ambiguity, model ${className}, property ${prop.getName()}
808
+ could match ${oneToOneProperties.map((p) => p.getName()).join(", ")}
809
+ `);
810
+ return;
811
+ }
812
+ // If a one to one property exists with the expected name, use that
813
+ if (oneToOneProperties[0] !== undefined) {
814
+ const oneToOneProperty = oneToOneProperties[0];
815
+ const navModelTypeRes = CidlExtractor.cidlType(oneToOneProperty?.getType());
816
+ if (navModelTypeRes.isLeft()) {
817
+ navModelTypeRes.value.context = prop.getName();
818
+ navModelTypeRes.value.snippet = prop.getText();
819
+ return navModelTypeRes;
820
+ }
821
+ const navModelType = navModelTypeRes.unwrap();
822
+ const objectName = getObjectName(navModelType);
823
+ if (objectName) {
824
+ // Add a foreign key decorator
825
+ prop.addDecorator({
826
+ name: PropertyDecoratorKind.ForeignKey,
827
+ arguments: [objectName],
828
+ });
829
+ return;
830
+ }
831
+ }
832
+ if (objectName !== undefined) {
833
+ const oneToManyClassDecl = this.modelDecls.get(objectName)?.[0];
834
+ const containsOneToManyProp = oneToManyClassDecl
835
+ ?.getProperties()
836
+ .filter((p) => {
837
+ const tyRes = CidlExtractor.cidlType(p.getType());
838
+ if (tyRes.isLeft()) {
839
+ return false;
840
+ }
841
+ const ty = tyRes.unwrap();
842
+ const navObjectName = getObjectName(ty);
843
+ if (navObjectName !== className) {
844
+ return false;
845
+ }
846
+ if (typeof ty === "string" || !("Array" in ty)) {
847
+ return false;
848
+ }
849
+ return true;
850
+ });
851
+ if (containsOneToManyProp) {
852
+ if (containsOneToManyProp.length > 1) {
853
+ console.warn(`
854
+ Cannot infer ForeignKey relationship due to ambiguity, model ${className}, property ${prop.getName()}
855
+ could match ${containsOneToManyProp.map((p) => p.getName()).join(", ")}
856
+ `);
857
+ return;
858
+ }
859
+ // Add a foreign key decorator
860
+ prop.addDecorator({
861
+ name: PropertyDecoratorKind.ForeignKey,
862
+ arguments: [objectName],
863
+ });
864
+ return;
865
+ }
866
+ }
867
+ }
868
+ // One to Many + Many to Many
869
+ if (objectName !== undefined &&
870
+ typeof cidlType !== "string" &&
871
+ "Array" in cidlType) {
872
+ const referencedModelDecl = this.modelDecls.get(objectName)?.[0];
873
+ const normalizedModelIdName = `${normalizeName(className)}id`;
874
+ const foreignKeyProps = [];
875
+ const manyToManyProps = [];
876
+ for (const prop of referencedModelDecl?.getProperties() ?? []) {
877
+ const tyRes = CidlExtractor.cidlType(prop.getType());
878
+ if (tyRes.isLeft()) {
879
+ continue;
880
+ }
881
+ const ty = tyRes.unwrap();
882
+ const navObjectName = getObjectName(ty);
883
+ const normalizedPropName = normalizeName(prop.getName());
884
+ if (typeof ty !== "string" &&
885
+ "Array" in ty &&
886
+ navObjectName === className) {
887
+ // Many to Many
888
+ manyToManyProps.push(prop);
889
+ }
890
+ else if (normalizedPropName === normalizedModelIdName) {
891
+ // One to Many
892
+ foreignKeyProps.push(prop);
893
+ }
894
+ }
895
+ if (foreignKeyProps.length > 1) {
896
+ console.warn(`
897
+ Cannot infer OneToMany relationship due to ambiguity, model ${className}, property ${prop.getName()}
898
+ could match ${foreignKeyProps.map((p) => p.getName()).join(", ")}
899
+ `);
900
+ return;
901
+ }
902
+ if (manyToManyProps.length > 1) {
903
+ console.warn(`
904
+ Cannot infer ManyToMany relationship due to ambiguity, model ${className}, property ${prop.getName()}
905
+ could match ${manyToManyProps.map((p) => p.getName()).join(", ")}
906
+ `);
907
+ return;
908
+ }
909
+ const hasForeignKeyProp = foreignKeyProps.at(0);
910
+ const hasManyToManyProp = manyToManyProps.at(0);
911
+ if (hasForeignKeyProp && hasManyToManyProp) {
912
+ console.warn(`
913
+ Cannot infer relationship due to ambiguity, model ${className}, property ${prop.getName()}
914
+ could be OneToMany or ManyToMany
915
+ `);
916
+ return;
917
+ }
918
+ if (hasForeignKeyProp) {
919
+ // Add a one to many decorator
920
+ prop.addDecorator({
921
+ name: PropertyDecoratorKind.OneToMany,
922
+ arguments: [`(m: any) => m.${hasForeignKeyProp.getName()}`],
923
+ });
924
+ return;
925
+ }
926
+ if (hasManyToManyProp) {
927
+ // Add a many to many decorator
928
+ prop.addDecorator({
929
+ name: PropertyDecoratorKind.ManyToMany,
930
+ arguments: [],
931
+ });
932
+ return;
933
+ }
934
+ }
935
+ // One to One
936
+ if (objectName !== undefined) {
937
+ const normalizedPropIdName = `${normalizedPropName}id`;
938
+ const foreignKeyProps = classDecl.getProperties().filter((p) => {
939
+ const norm = normalizeName(p.getName());
940
+ return norm === normalizedPropIdName;
941
+ });
942
+ if (foreignKeyProps.length > 1) {
943
+ console.warn(`
944
+ Cannot infer OneToOne relationship due to ambiguity, model ${className}, property ${prop.getName()}
945
+ could match ${foreignKeyProps.map((p) => p.getName()).join(", ")}
946
+ `);
947
+ }
948
+ if (foreignKeyProps.at(0) !== undefined) {
949
+ const foreignKey = foreignKeyProps[0];
950
+ // Add a one to one decorator
951
+ prop.addDecorator({
952
+ name: PropertyDecoratorKind.OneToOne,
953
+ arguments: [`(_m: any) => m.${foreignKey.getName()}`],
954
+ });
955
+ return;
956
+ }
957
+ }
804
958
  }
805
- return Either.right(result);
806
- }
807
959
  }
808
960
  function err(code, fn) {
809
- let e = new ExtractorError(code);
810
- if (fn) {
811
- fn(e);
812
- }
813
- return Either.left(e);
961
+ let e = new ExtractorError(code);
962
+ if (fn) {
963
+ fn(e);
964
+ }
965
+ return Either.left(e);
814
966
  }
815
967
  function getDecoratorName(decorator) {
816
- const name = decorator.getName() ?? decorator.getExpression().getText();
817
- return String(name).replace(/\(.*\)$/, "");
968
+ const name = decorator.getName() ?? decorator.getExpression().getText();
969
+ return String(name).replace(/\(.*\)$/, "");
818
970
  }
819
971
  function getDecoratorArgument(decorator, index) {
820
- const args = decorator.getArguments();
821
- if (!args[index]) return undefined;
822
- const arg = args[index];
823
- if (arg.getKind?.() === SyntaxKind.Identifier) {
824
- return arg.getText();
825
- }
826
- return arg.getLiteralValue();
972
+ const args = decorator.getArguments();
973
+ if (!args[index])
974
+ return undefined;
975
+ const arg = args[index];
976
+ if (arg.getKind?.() === SyntaxKind.Identifier) {
977
+ return arg.getText();
978
+ }
979
+ return arg.getLiteralValue();
827
980
  }
828
981
  function getRootType(t) {
829
- if (typeof t === "string") {
982
+ if (typeof t === "string") {
983
+ return t;
984
+ }
985
+ if ("Nullable" in t) {
986
+ return getRootType(t.Nullable);
987
+ }
988
+ if ("Array" in t) {
989
+ return getRootType(t.Array);
990
+ }
991
+ if ("HttpResult" in t) {
992
+ return getRootType(t.HttpResult);
993
+ }
830
994
  return t;
831
- }
832
- if ("Nullable" in t) {
833
- return getRootType(t.Nullable);
834
- }
835
- if ("Array" in t) {
836
- return getRootType(t.Array);
837
- }
838
- if ("HttpResult" in t) {
839
- return getRootType(t.HttpResult);
840
- }
841
- return t;
842
995
  }
843
996
  function getObjectName(t) {
844
- const root = getRootType(t);
845
- if (typeof root !== "string" && "Object" in root) {
846
- return root["Object"];
847
- }
848
- return undefined;
849
- }
850
- function setCrudKinds(d, cruds) {
851
- const arg = d.getArguments()[0];
852
- if (!arg) {
853
- return;
854
- }
855
- if (MorphNode.isArrayLiteralExpression(arg)) {
856
- for (const a of arg.getElements()) {
857
- cruds.add(
858
- MorphNode.isStringLiteral(a) ? a.getLiteralValue() : a.getText(),
859
- );
997
+ const root = getRootType(t);
998
+ if (typeof root !== "string" && "Object" in root) {
999
+ return root["Object"];
860
1000
  }
861
- }
1001
+ return undefined;
862
1002
  }
863
- function findPropertyByName(cls, name) {
864
- const exactMatch = cls.getProperties().find((p) => p.getName() === name);
865
- return exactMatch;
1003
+ function parseIncludeTree(objLiteral) {
1004
+ const result = {};
1005
+ objLiteral.getProperties().forEach((prop) => {
1006
+ if (prop.isKind(SyntaxKind.PropertyAssignment)) {
1007
+ const name = prop.getName();
1008
+ const init = prop.getInitializer();
1009
+ // Check if it's a nested object literal
1010
+ if (init?.isKind(SyntaxKind.ObjectLiteralExpression)) {
1011
+ result[name] = parseIncludeTree(init); // Recurse
1012
+ }
1013
+ else {
1014
+ result[name] = {}; // Empty object by default
1015
+ }
1016
+ }
1017
+ });
1018
+ return result;
866
1019
  }
867
- function hasDecorator(node, name) {
868
- return node.getDecorators().some((d) => {
869
- const decoratorName = getDecoratorName(d);
870
- return decoratorName === name || decoratorName.endsWith("." + name);
871
- });
1020
+ function checkPropertyModifier(prop) {
1021
+ // Error: properties must be just 'public'
1022
+ if (prop.getScope() != Scope.Public || prop.isReadonly() || prop.isStatic()) {
1023
+ return err(ExtractorErrorCode.InvalidPropertyModifier, (e) => {
1024
+ e.context = prop.getName();
1025
+ e.snippet = prop.getText();
1026
+ });
1027
+ }
1028
+ return Either.right(null);
872
1029
  }
873
- function checkAttributeModifier(prop) {
874
- // Error: attributes must be just 'public'
875
- if (prop.getScope() != Scope.Public || prop.isReadonly() || prop.isStatic()) {
876
- return err(ExtractorErrorCode.InvalidAttributeModifier, (e) => {
877
- e.context = prop.getName();
878
- e.snippet = prop.getText();
879
- });
880
- }
881
- return Either.right(null);
1030
+ function normalizeName(name) {
1031
+ return name.toLowerCase().replace(/_/g, "");
1032
+ }
1033
+ export function getSelectorPropertyName(decorator) {
1034
+ const call = decorator.getCallExpression();
1035
+ const selector = call?.getArguments()[0];
1036
+ if (!selector?.isKind(SyntaxKind.ArrowFunction)) {
1037
+ return err(ExtractorErrorCode.InvalidSelectorSyntax);
1038
+ }
1039
+ const body = selector.getBody();
1040
+ if (!body.isKind(SyntaxKind.PropertyAccessExpression)) {
1041
+ return err(ExtractorErrorCode.InvalidSelectorSyntax);
1042
+ }
1043
+ return Either.right(body.getName());
882
1044
  }