cloesce 0.0.3 → 0.0.4-unstable.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 (44) hide show
  1. package/dist/README.md +487 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +221 -254
  4. package/dist/common.d.ts +69 -1
  5. package/dist/common.d.ts.map +1 -1
  6. package/dist/common.js +72 -11
  7. package/dist/{extract.d.ts → extractor/extract.d.ts} +5 -2
  8. package/dist/extractor/extract.d.ts.map +1 -0
  9. package/dist/{extract.js → extractor/extract.js} +242 -43
  10. package/dist/generator.wasm +0 -0
  11. package/dist/orm.wasm +0 -0
  12. package/dist/router/crud.d.ts +22 -0
  13. package/dist/router/crud.d.ts.map +1 -0
  14. package/dist/router/crud.js +65 -0
  15. package/dist/router/router.d.ts +77 -0
  16. package/dist/router/router.d.ts.map +1 -0
  17. package/dist/router/router.js +358 -0
  18. package/dist/router/wasm.d.ts +37 -0
  19. package/dist/router/wasm.d.ts.map +1 -0
  20. package/dist/router/wasm.js +98 -0
  21. package/dist/ui/backend.d.ts +124 -0
  22. package/dist/ui/backend.d.ts.map +1 -0
  23. package/dist/ui/backend.js +201 -0
  24. package/dist/ui/client.d.ts +5 -0
  25. package/dist/ui/client.d.ts.map +1 -0
  26. package/dist/ui/client.js +7 -0
  27. package/package.json +70 -58
  28. package/LICENSE +0 -201
  29. package/README.md +0 -23
  30. package/dist/cli.wasm +0 -0
  31. package/dist/cloesce.d.ts +0 -108
  32. package/dist/cloesce.d.ts.map +0 -1
  33. package/dist/cloesce.js +0 -453
  34. package/dist/decorators.d.ts +0 -13
  35. package/dist/decorators.d.ts.map +0 -1
  36. package/dist/decorators.js +0 -13
  37. package/dist/dog.cloesce.js +0 -111
  38. package/dist/extract.d.ts.map +0 -1
  39. package/dist/index.d.ts +0 -24
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/index.js +0 -24
  42. package/dist/types.d.ts +0 -4
  43. package/dist/types.d.ts.map +0 -1
  44. package/dist/types.js +0 -1
package/dist/common.js CHANGED
@@ -6,31 +6,61 @@ export function right(value) {
6
6
  }
7
7
  export var ExtractorErrorCode;
8
8
  (function (ExtractorErrorCode) {
9
- ExtractorErrorCode[ExtractorErrorCode["UnknownType"] = 0] = "UnknownType";
10
- ExtractorErrorCode[ExtractorErrorCode["MultipleGenericType"] = 1] = "MultipleGenericType";
11
- ExtractorErrorCode[ExtractorErrorCode["InvalidIncludeTree"] = 2] = "InvalidIncludeTree";
12
- ExtractorErrorCode[ExtractorErrorCode["UnknownNavigationPropertyReference"] = 3] = "UnknownNavigationPropertyReference";
13
- ExtractorErrorCode[ExtractorErrorCode["InvalidNavigationPropertyReference"] = 4] = "InvalidNavigationPropertyReference";
14
- ExtractorErrorCode[ExtractorErrorCode["MissingNavigationPropertyReference"] = 5] = "MissingNavigationPropertyReference";
15
- ExtractorErrorCode[ExtractorErrorCode["MissingManyToManyUniqueId"] = 6] = "MissingManyToManyUniqueId";
16
- ExtractorErrorCode[ExtractorErrorCode["MissingPrimaryKey"] = 7] = "MissingPrimaryKey";
17
- ExtractorErrorCode[ExtractorErrorCode["MissingWranglerEnv"] = 8] = "MissingWranglerEnv";
18
- ExtractorErrorCode[ExtractorErrorCode["TooManyWranglerEnvs"] = 9] = "TooManyWranglerEnvs";
19
- ExtractorErrorCode[ExtractorErrorCode["MissingFile"] = 10] = "MissingFile";
9
+ ExtractorErrorCode[ExtractorErrorCode["MissingExport"] = 0] = "MissingExport";
10
+ ExtractorErrorCode[ExtractorErrorCode["AppMissingDefaultExport"] = 1] = "AppMissingDefaultExport";
11
+ ExtractorErrorCode[ExtractorErrorCode["UnknownType"] = 2] = "UnknownType";
12
+ ExtractorErrorCode[ExtractorErrorCode["MultipleGenericType"] = 3] = "MultipleGenericType";
13
+ ExtractorErrorCode[ExtractorErrorCode["DataSourceMissingStatic"] = 4] = "DataSourceMissingStatic";
14
+ ExtractorErrorCode[ExtractorErrorCode["InvalidPartialType"] = 5] = "InvalidPartialType";
15
+ ExtractorErrorCode[ExtractorErrorCode["InvalidIncludeTree"] = 6] = "InvalidIncludeTree";
16
+ ExtractorErrorCode[ExtractorErrorCode["InvalidAttributeModifier"] = 7] = "InvalidAttributeModifier";
17
+ ExtractorErrorCode[ExtractorErrorCode["InvalidApiMethodModifier"] = 8] = "InvalidApiMethodModifier";
18
+ ExtractorErrorCode[ExtractorErrorCode["UnknownNavigationPropertyReference"] = 9] = "UnknownNavigationPropertyReference";
19
+ ExtractorErrorCode[ExtractorErrorCode["InvalidNavigationPropertyReference"] = 10] = "InvalidNavigationPropertyReference";
20
+ ExtractorErrorCode[ExtractorErrorCode["MissingNavigationPropertyReference"] = 11] = "MissingNavigationPropertyReference";
21
+ ExtractorErrorCode[ExtractorErrorCode["MissingManyToManyUniqueId"] = 12] = "MissingManyToManyUniqueId";
22
+ ExtractorErrorCode[ExtractorErrorCode["MissingPrimaryKey"] = 13] = "MissingPrimaryKey";
23
+ ExtractorErrorCode[ExtractorErrorCode["MissingWranglerEnv"] = 14] = "MissingWranglerEnv";
24
+ ExtractorErrorCode[ExtractorErrorCode["TooManyWranglerEnvs"] = 15] = "TooManyWranglerEnvs";
25
+ ExtractorErrorCode[ExtractorErrorCode["MissingFile"] = 16] = "MissingFile";
20
26
  })(ExtractorErrorCode || (ExtractorErrorCode = {}));
21
27
  const errorInfoMap = {
28
+ [ExtractorErrorCode.MissingExport]: {
29
+ description: "All Cloesce types must be exported.",
30
+ suggestion: "Add `export` to the class definition.",
31
+ },
32
+ [ExtractorErrorCode.AppMissingDefaultExport]: {
33
+ description: "app.cloesce.ts does not export a CloesceApp by default",
34
+ suggestion: "Export an instantiated CloesceApp in app.cloesce.ts",
35
+ },
22
36
  [ExtractorErrorCode.UnknownType]: {
23
37
  description: "Encountered an unknown or unsupported type",
24
38
  suggestion: "Refer to the documentation on valid Cloesce TS types",
25
39
  },
40
+ [ExtractorErrorCode.InvalidPartialType]: {
41
+ description: "Partial types must only contain a model or plain old object",
42
+ suggestion: "Refer to the documentation on valid Cloesce TS types",
43
+ },
26
44
  [ExtractorErrorCode.MultipleGenericType]: {
27
45
  description: "Cloesce does not yet support types with multiple generics",
28
46
  suggestion: "Simplify your type to use only a single generic parameter, ie Foo<T>",
29
47
  },
48
+ [ExtractorErrorCode.DataSourceMissingStatic]: {
49
+ description: "Data Sources must be declared as static",
50
+ suggestion: "Declare your data source as `static readonly`",
51
+ },
30
52
  [ExtractorErrorCode.InvalidIncludeTree]: {
31
53
  description: "Invalid Include Tree",
32
54
  suggestion: "Include trees must only contain references to a model's navigation properties.",
33
55
  },
56
+ [ExtractorErrorCode.InvalidAttributeModifier]: {
57
+ description: "Attributes can only be public on a Model, Plain Old Object or Wrangler Environment",
58
+ suggestion: "Change the attribute modifier to just `public`",
59
+ },
60
+ [ExtractorErrorCode.InvalidApiMethodModifier]: {
61
+ description: "Model methods must be public if they are decorated as GET, POST, PUT, PATCH",
62
+ suggestion: "Change the method modifier to just `public`",
63
+ },
34
64
  [ExtractorErrorCode.UnknownNavigationPropertyReference]: {
35
65
  description: "Unknown Navigation Property Reference",
36
66
  suggestion: "Verify that the navigation property reference model exists, or create a model.",
@@ -78,6 +108,36 @@ export class ExtractorError {
78
108
  this.context = fn(this.context ?? "");
79
109
  }
80
110
  }
111
+ /**
112
+ * A container for middleware. If an instance is exported from `app.cloesce.ts`, it will be used in the
113
+ * appropriate location, with global middleware happening before any routing occurs.
114
+ */
115
+ export class CloesceApp {
116
+ global = [];
117
+ model = new Map();
118
+ method = new Map();
119
+ useGlobal(m) {
120
+ this.global.push(m);
121
+ }
122
+ useModel(ctor, m) {
123
+ if (this.model.has(ctor.name)) {
124
+ this.model.get(ctor.name).push(m);
125
+ }
126
+ else {
127
+ this.model.set(ctor.name, [m]);
128
+ }
129
+ }
130
+ useMethod(ctor, method, m) {
131
+ if (!this.method.has(ctor.name)) {
132
+ this.method.set(ctor.name, new Map());
133
+ }
134
+ const methods = this.method.get(ctor.name);
135
+ if (!methods.has(method)) {
136
+ methods.set(method, []);
137
+ }
138
+ methods.get(method).push(m);
139
+ }
140
+ }
81
141
  export function isNullableType(ty) {
82
142
  return typeof ty === "object" && ty !== null && "Nullable" in ty;
83
143
  }
@@ -94,3 +154,4 @@ export function getNavigationPropertyCidlType(nav) {
94
154
  ? { Object: nav.model_name }
95
155
  : { Array: { Object: nav.model_name } };
96
156
  }
157
+ export const NO_DATA_SOURCE = "none";
@@ -1,14 +1,17 @@
1
1
  import { Project } from "ts-morph";
2
- import { CloesceAst, Either } from "./common.js";
2
+ import { CloesceAst, Either, ExtractorError } from "../common.js";
3
3
  export declare class CidlExtractor {
4
4
  projectName: string;
5
5
  version: string;
6
6
  constructor(projectName: string, version: string);
7
- extract(project: Project): Either<string, CloesceAst>;
7
+ extract(project: Project): Either<ExtractorError, CloesceAst>;
8
+ private static app;
8
9
  private static model;
10
+ private static poo;
9
11
  private static readonly primTypeMap;
10
12
  private static cidlType;
11
13
  private static includeTree;
12
14
  private static method;
15
+ private static crudMethod;
13
16
  }
14
17
  //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/extractor/extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,OAAO,EAUR,MAAM,UAAU,CAAC;AAElB,OAAO,EAEL,UAAU,EAGV,MAAM,EAUN,cAAc,EAKf,MAAM,cAAc,CAAC;AAuBtB,qBAAa,aAAa;IAEf,WAAW,EAAE,MAAM;IACnB,OAAO,EAAE,MAAM;gBADf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM;IAGxB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC;IA8F7D,OAAO,CAAC,MAAM,CAAC,GAAG;IA0BlB,OAAO,CAAC,MAAM,CAAC,KAAK;IAgQpB,OAAO,CAAC,MAAM,CAAC,GAAG;IAsClB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAOjC;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ;IAkJvB,OAAO,CAAC,MAAM,CAAC,WAAW;IAqF1B,OAAO,CAAC,MAAM,CAAC,MAAM;IAiFrB,OAAO,CAAC,MAAM,CAAC,UAAU;CAsD1B"}
@@ -1,5 +1,5 @@
1
- import { SyntaxKind, } from "ts-morph";
2
- import { HttpVerb, left, right, ExtractorError, ExtractorErrorCode, } from "./common.js";
1
+ import { Node as MorphNode, SyntaxKind, Scope, } from "ts-morph";
2
+ import { HttpVerb, left, right, ExtractorError, ExtractorErrorCode, CloesceApp, } from "../common.js";
3
3
  import { TypeFormatFlags } from "typescript";
4
4
  var AttributeDecoratorKind;
5
5
  (function (AttributeDecoratorKind) {
@@ -15,6 +15,7 @@ var ClassDecoratorKind;
15
15
  ClassDecoratorKind["D1"] = "D1";
16
16
  ClassDecoratorKind["WranglerEnv"] = "WranglerEnv";
17
17
  ClassDecoratorKind["PlainOldObject"] = "PlainOldObject";
18
+ ClassDecoratorKind["CRUD"] = "CRUD";
18
19
  })(ClassDecoratorKind || (ClassDecoratorKind = {}));
19
20
  var ParameterDecoratorKind;
20
21
  (function (ParameterDecoratorKind) {
@@ -30,43 +31,64 @@ export class CidlExtractor {
30
31
  extract(project) {
31
32
  const models = {};
32
33
  const poos = {};
34
+ const wranglerEnvs = [];
35
+ let app_source = null;
33
36
  for (const sourceFile of project.getSourceFiles()) {
37
+ if (sourceFile.getBaseName() === "app.cloesce.ts" ||
38
+ sourceFile.getBaseName() === "seed__app.cloesce.ts" // hardcoding for tests
39
+ ) {
40
+ const app = CidlExtractor.app(sourceFile);
41
+ if (!app.ok) {
42
+ return app;
43
+ }
44
+ app_source = app.value;
45
+ }
34
46
  for (const classDecl of sourceFile.getClasses()) {
47
+ const notExportedErr = err(ExtractorErrorCode.MissingExport, (e) => {
48
+ e.context = classDecl.getName();
49
+ e.snippet = classDecl.getText();
50
+ });
35
51
  if (hasDecorator(classDecl, ClassDecoratorKind.D1)) {
52
+ if (!classDecl.isExported())
53
+ return notExportedErr;
36
54
  const result = CidlExtractor.model(classDecl, sourceFile);
37
55
  // Error: propogate from models
38
56
  if (!result.ok) {
39
- result.value.addContext((old) => `${classDecl.getName()}.${old}`);
57
+ result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
40
58
  return result;
41
59
  }
42
60
  models[result.value.name] = result.value;
43
61
  continue;
44
62
  }
45
63
  if (hasDecorator(classDecl, ClassDecoratorKind.PlainOldObject)) {
64
+ if (!classDecl.isExported())
65
+ return notExportedErr;
46
66
  const result = CidlExtractor.poo(classDecl, sourceFile);
47
67
  // Error: propogate from models
48
68
  if (!result.ok) {
49
- result.value.addContext((old) => `${classDecl.getName()}.${old}`);
69
+ result.value.addContext((prev) => `${classDecl.getName()}.${prev}`);
50
70
  return result;
51
71
  }
52
72
  poos[result.value.name] = result.value;
53
73
  continue;
54
74
  }
75
+ if (hasDecorator(classDecl, ClassDecoratorKind.WranglerEnv)) {
76
+ if (!classDecl.isExported())
77
+ return notExportedErr;
78
+ // Error: invalid attribute modifier
79
+ for (const prop of classDecl.getProperties()) {
80
+ const modifierRes = checkAttributeModifier(prop);
81
+ if (modifierRes) {
82
+ return modifierRes;
83
+ }
84
+ }
85
+ wranglerEnvs.push({
86
+ name: classDecl.getName(),
87
+ source_path: sourceFile.getFilePath().toString(),
88
+ });
89
+ }
55
90
  }
56
91
  }
57
- const wranglerEnvs = project
58
- .getSourceFiles()
59
- .flatMap((sourceFile) => {
60
- return sourceFile
61
- .getClasses()
62
- .filter((classDecl) => hasDecorator(classDecl, ClassDecoratorKind.WranglerEnv))
63
- .map((classDecl) => {
64
- return {
65
- name: classDecl.getName(),
66
- source_path: sourceFile.getFilePath().toString(),
67
- };
68
- });
69
- });
70
92
  // Error: A wrangler environment is required
71
93
  if (wranglerEnvs.length < 1) {
72
94
  return err(ExtractorErrorCode.MissingWranglerEnv);
@@ -82,15 +104,46 @@ export class CidlExtractor {
82
104
  wrangler_env: wranglerEnvs[0],
83
105
  models,
84
106
  poos,
107
+ app_source,
85
108
  });
86
109
  }
110
+ static app(sourceFile) {
111
+ const symbol = sourceFile.getDefaultExportSymbol();
112
+ const decl = symbol?.getDeclarations()[0];
113
+ if (!decl) {
114
+ return err(ExtractorErrorCode.AppMissingDefaultExport);
115
+ }
116
+ const getTypeText = () => {
117
+ if (MorphNode.isExportAssignment(decl)) {
118
+ return decl.getExpression()?.getType().getText();
119
+ }
120
+ if (MorphNode.isVariableDeclaration(decl)) {
121
+ return decl.getInitializer()?.getType().getText();
122
+ }
123
+ return undefined;
124
+ };
125
+ const typeText = getTypeText();
126
+ if (typeText === CloesceApp.name) {
127
+ return right(sourceFile.getFilePath().toString());
128
+ }
129
+ return err(ExtractorErrorCode.AppMissingDefaultExport);
130
+ }
87
131
  static model(classDecl, sourceFile) {
88
132
  const name = classDecl.getName();
89
133
  const attributes = [];
90
134
  const navigationProperties = [];
91
135
  const dataSources = {};
92
136
  const methods = {};
137
+ let cruds = [];
93
138
  let primary_key = undefined;
139
+ // Extract crud methods
140
+ const crudDecorator = classDecl
141
+ .getDecorators()
142
+ .find((d) => getDecoratorName(d) === ClassDecoratorKind.CRUD);
143
+ if (crudDecorator) {
144
+ cruds = getCrudKinds(crudDecorator);
145
+ }
146
+ // Iterate attribtutes
94
147
  for (const prop of classDecl.getProperties()) {
95
148
  const decorators = prop.getDecorators();
96
149
  const typeRes = CidlExtractor.cidlType(prop.getType());
@@ -100,8 +153,13 @@ export class CidlExtractor {
100
153
  typeRes.value.snippet = prop.getText();
101
154
  return typeRes;
102
155
  }
156
+ const checkModifierRes = checkAttributeModifier(prop);
103
157
  // No decorators means this is a standard attribute
104
158
  if (decorators.length === 0) {
159
+ // Error: invalid attribute modifier
160
+ if (checkModifierRes !== undefined) {
161
+ return checkModifierRes;
162
+ }
105
163
  const cidl_type = typeRes.value;
106
164
  attributes.push({
107
165
  foreign_key_reference: null,
@@ -115,6 +173,11 @@ export class CidlExtractor {
115
173
  // TODO: Limiting to one decorator. Can't get too fancy on us.
116
174
  const decorator = decorators[0];
117
175
  const name = getDecoratorName(decorator);
176
+ // Error: invalid attribute modifier
177
+ if (checkModifierRes !== undefined &&
178
+ name !== AttributeDecoratorKind.DataSource) {
179
+ return checkModifierRes;
180
+ }
118
181
  // Process decorators
119
182
  const cidl_type = typeRes.value;
120
183
  switch (name) {
@@ -207,10 +270,17 @@ export class CidlExtractor {
207
270
  break;
208
271
  }
209
272
  case AttributeDecoratorKind.DataSource: {
273
+ // Error: data sources must be static
274
+ if (!prop.isStatic()) {
275
+ return err(ExtractorErrorCode.DataSourceMissingStatic, (e) => {
276
+ e.snippet = prop.getText();
277
+ e.context = prop.getName();
278
+ });
279
+ }
210
280
  const initializer = prop.getInitializer();
211
281
  const treeRes = CidlExtractor.includeTree(initializer, classDecl, sourceFile);
212
282
  if (!treeRes.ok) {
213
- treeRes.value.addContext((old) => `${prop.getName()} ${old}`);
283
+ treeRes.value.addContext((prev) => `${prop.getName()} ${prev}`);
214
284
  treeRes.value.snippet = prop.getText();
215
285
  return treeRes;
216
286
  }
@@ -229,13 +299,26 @@ export class CidlExtractor {
229
299
  }
230
300
  // Process methods
231
301
  for (const m of classDecl.getMethods()) {
232
- const result = CidlExtractor.method(m);
302
+ const httpVerb = m
303
+ .getDecorators()
304
+ .map((d) => getDecoratorName(d))
305
+ .find((name) => Object.values(HttpVerb).includes(name));
306
+ if (!httpVerb) {
307
+ continue;
308
+ }
309
+ const result = CidlExtractor.method(name, m, httpVerb);
233
310
  if (!result.ok) {
234
- result.value.addContext((old) => `${m.getName()} ${old}`);
311
+ result.value.addContext((prev) => `${m.getName()} ${prev}`);
235
312
  return left(result.value);
236
313
  }
237
314
  methods[result.value.name] = result.value;
238
315
  }
316
+ // Add CRUD methods
317
+ for (const crud of cruds) {
318
+ // TODO: This overwrites any exisiting impl-- is that what we want?
319
+ const crudMethod = CidlExtractor.crudMethod(crud, primary_key, name);
320
+ methods[crudMethod.name] = crudMethod;
321
+ }
239
322
  return right({
240
323
  name,
241
324
  attributes,
@@ -257,6 +340,11 @@ export class CidlExtractor {
257
340
  typeRes.value.snippet = prop.getText();
258
341
  return typeRes;
259
342
  }
343
+ // Error: invalid attribute modifier
344
+ const modifierRes = checkAttributeModifier(prop);
345
+ if (modifierRes) {
346
+ return modifierRes;
347
+ }
260
348
  const cidl_type = typeRes.value;
261
349
  attributes.push({
262
350
  name: prop.getName(),
@@ -277,7 +365,6 @@ export class CidlExtractor {
277
365
  String: "Text",
278
366
  boolean: "Integer",
279
367
  Boolean: "Integer",
280
- Date: "Text",
281
368
  };
282
369
  static cidlType(type, inject = false) {
283
370
  // Void
@@ -316,6 +403,30 @@ export class CidlExtractor {
316
403
  const genericTy = generics[0];
317
404
  const symbolName = unwrappedType.getSymbol()?.getName();
318
405
  const aliasName = unwrappedType.getAliasSymbol()?.getName();
406
+ if (aliasName === "DataSourceOf") {
407
+ return right(wrapNullable({
408
+ DataSource: genericTy.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope),
409
+ }, nullable));
410
+ }
411
+ if (aliasName === "DeepPartial") {
412
+ const [_, genericTyNullable] = unwrapNullable(genericTy);
413
+ const genericTyGenerics = [
414
+ ...genericTy.getAliasTypeArguments(),
415
+ ...genericTy.getTypeArguments(),
416
+ ];
417
+ // Expect partials to be of the exact form DeepPartial<Model>
418
+ if (genericTyNullable ||
419
+ genericTy.isUnion() ||
420
+ genericTyGenerics.length > 0) {
421
+ return err(ExtractorErrorCode.InvalidPartialType);
422
+ }
423
+ return right(wrapNullable({
424
+ Partial: genericTy
425
+ .getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
426
+ .split("|")[0]
427
+ .trim(),
428
+ }, nullable));
429
+ }
319
430
  if (symbolName === "Promise" || aliasName === "IncludeTree") {
320
431
  return wrapGeneric(genericTy, nullable, (inner) => inner);
321
432
  }
@@ -413,10 +524,15 @@ export class CidlExtractor {
413
524
  }
414
525
  return right(result);
415
526
  }
416
- static method(method) {
417
- const decorators = method.getDecorators();
418
- const decoratorNames = decorators.map((d) => getDecoratorName(d));
419
- const httpVerb = decoratorNames.find((name) => Object.values(HttpVerb).includes(name));
527
+ static method(modelName, method, httpVerb) {
528
+ // Error: invalid method scope, must be public
529
+ if (method.getScope() != Scope.Public) {
530
+ return err(ExtractorErrorCode.InvalidApiMethodModifier, (e) => {
531
+ e.context = method.getName();
532
+ e.snippet = method.getText();
533
+ });
534
+ }
535
+ let needsDataSource = !method.isStatic();
420
536
  const parameters = [];
421
537
  for (const param of method.getParameters()) {
422
538
  // Handle injected param
@@ -442,6 +558,10 @@ export class CidlExtractor {
442
558
  typeRes.value.context = param.getName();
443
559
  return typeRes;
444
560
  }
561
+ const rootType = getRootType(typeRes.value);
562
+ if (typeof rootType !== "string" && "DataSource" in rootType) {
563
+ needsDataSource = false;
564
+ }
445
565
  parameters.push({
446
566
  name: param.getName(),
447
567
  cidl_type: typeRes.value,
@@ -453,6 +573,13 @@ export class CidlExtractor {
453
573
  typeRes.value.snippet = method.getText();
454
574
  return typeRes;
455
575
  }
576
+ // Sugaring: add data source
577
+ if (needsDataSource) {
578
+ parameters.push({
579
+ name: "__dataSource",
580
+ cidl_type: { DataSource: modelName },
581
+ });
582
+ }
456
583
  return right({
457
584
  name: method.getName(),
458
585
  is_static: method.isStatic(),
@@ -461,6 +588,56 @@ export class CidlExtractor {
461
588
  parameters,
462
589
  });
463
590
  }
591
+ static crudMethod(crud, primaryKey, modelName) {
592
+ // TODO: Should this impementation be in some JSON project file s.t. other
593
+ // langs can use it?
594
+ return {
595
+ POST: {
596
+ name: "post",
597
+ is_static: true,
598
+ http_verb: HttpVerb.POST,
599
+ return_type: { HttpResult: { Object: modelName } },
600
+ parameters: [
601
+ {
602
+ name: "obj",
603
+ cidl_type: { Partial: modelName },
604
+ },
605
+ {
606
+ name: "dataSource",
607
+ cidl_type: { DataSource: modelName },
608
+ },
609
+ ],
610
+ },
611
+ GET: {
612
+ name: "get",
613
+ is_static: true,
614
+ http_verb: HttpVerb.GET,
615
+ return_type: { HttpResult: { Object: modelName } },
616
+ parameters: [
617
+ {
618
+ name: "id",
619
+ cidl_type: primaryKey.cidl_type,
620
+ },
621
+ {
622
+ name: "dataSource",
623
+ cidl_type: { DataSource: modelName },
624
+ },
625
+ ],
626
+ },
627
+ LIST: {
628
+ name: "list",
629
+ is_static: true,
630
+ http_verb: HttpVerb.GET,
631
+ return_type: { HttpResult: { Array: { Object: modelName } } },
632
+ parameters: [
633
+ {
634
+ name: "dataSource",
635
+ cidl_type: { DataSource: modelName },
636
+ },
637
+ ],
638
+ },
639
+ }[crud];
640
+ }
464
641
  }
465
642
  function err(code, fn) {
466
643
  let e = new ExtractorError(code);
@@ -478,33 +655,46 @@ function getDecoratorArgument(decorator, index) {
478
655
  if (!args[index])
479
656
  return undefined;
480
657
  const arg = args[index];
481
- // Identifier
482
658
  if (arg.getKind?.() === SyntaxKind.Identifier) {
483
659
  return arg.getText();
484
660
  }
485
- // String literal
486
- const text = arg.getText?.();
487
- if (!text)
488
- return undefined;
489
- const match = text.match(/^['"](.*)['"]$/);
490
- return match ? match[1] : text;
661
+ return arg.getLiteralValue();
491
662
  }
492
- function getObjectName(t) {
493
- if (typeof t === "string")
494
- return undefined;
495
- if ("Object" in t) {
496
- return t.Object;
663
+ function getRootType(t) {
664
+ if (typeof t === "string") {
665
+ return t;
497
666
  }
498
- else if ("Array" in t) {
499
- return getObjectName(t.Array);
667
+ if ("Nullable" in t) {
668
+ return getRootType(t.Nullable);
500
669
  }
501
- else if ("HttpResult" in t) {
502
- if (t == null)
503
- return undefined;
504
- return getObjectName(t.HttpResult);
670
+ if ("Array" in t) {
671
+ return getRootType(t.Array);
672
+ }
673
+ if ("HttpResult" in t) {
674
+ return getRootType(t.HttpResult);
675
+ }
676
+ return t;
677
+ }
678
+ function getObjectName(t) {
679
+ const root = getRootType(t);
680
+ if (typeof root !== "string" && "Object" in root) {
681
+ return root["Object"];
505
682
  }
506
683
  return undefined;
507
684
  }
685
+ function getCrudKinds(d) {
686
+ const arg = d.getArguments()[0];
687
+ if (!arg)
688
+ return [];
689
+ if (MorphNode.isArrayLiteralExpression(arg)) {
690
+ return arg
691
+ .getElements()
692
+ .map((e) => (MorphNode.isStringLiteral(e)
693
+ ? e.getLiteralValue()
694
+ : e.getText()));
695
+ }
696
+ return [];
697
+ }
508
698
  function findPropertyByName(cls, name) {
509
699
  const exactMatch = cls.getProperties().find((p) => p.getName() === name);
510
700
  return exactMatch;
@@ -515,3 +705,12 @@ function hasDecorator(node, name) {
515
705
  return decoratorName === name || decoratorName.endsWith("." + name);
516
706
  });
517
707
  }
708
+ function checkAttributeModifier(prop) {
709
+ // Error: attributes must be just 'public'
710
+ if (prop.getScope() != Scope.Public || prop.isReadonly() || prop.isStatic()) {
711
+ return err(ExtractorErrorCode.InvalidAttributeModifier, (e) => {
712
+ e.context = prop.getName();
713
+ e.snippet = prop.getText();
714
+ });
715
+ }
716
+ }
Binary file
package/dist/orm.wasm ADDED
Binary file
@@ -0,0 +1,22 @@
1
+ import { D1Database } from "@cloudflare/workers-types/experimental";
2
+ import { HttpResult } from "../common.js";
3
+ /**
4
+ * A wrapper for Model Instances, containing definitions for built-in CRUD methods.
5
+ */
6
+ export declare class CrudContext {
7
+ private d1;
8
+ private instance;
9
+ private ctor;
10
+ private constructor();
11
+ static fromInstance(d1: D1Database, instance: any, ctor: new () => object): CrudContext;
12
+ static fromCtor(d1: D1Database, ctor: new () => object): CrudContext;
13
+ /**
14
+ * Invokes a method on the instance, intercepting built-in CRUD methods and injecting
15
+ * a default definition.
16
+ */
17
+ interceptCrud(methodName: string): Function;
18
+ upsert(obj: object, dataSource: string): Promise<HttpResult<unknown>>;
19
+ get(id: any, dataSource: string): Promise<HttpResult<unknown>>;
20
+ list(dataSource: string): Promise<HttpResult<unknown>>;
21
+ }
22
+ //# sourceMappingURL=crud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/router/crud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAkB,MAAM,cAAc,CAAC;AAG1D;;GAEG;AACH,qBAAa,WAAW;IAEpB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,IAAI;IAHd,OAAO;IAMP,MAAM,CAAC,YAAY,CACjB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,EACb,IAAI,EAAE,UAAU,MAAM,GACrB,WAAW;IAId,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,MAAM,GAAG,WAAW;IAIpE;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAWrC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAkBrE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAS9D,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;CAQ7D"}
@@ -0,0 +1,65 @@
1
+ import { NO_DATA_SOURCE } from "../common.js";
2
+ import { Orm } from "../ui/backend.js";
3
+ /**
4
+ * A wrapper for Model Instances, containing definitions for built-in CRUD methods.
5
+ */
6
+ export class CrudContext {
7
+ d1;
8
+ instance;
9
+ ctor;
10
+ constructor(d1, instance, ctor) {
11
+ this.d1 = d1;
12
+ this.instance = instance;
13
+ this.ctor = ctor;
14
+ }
15
+ static fromInstance(d1, instance, ctor) {
16
+ return new this(d1, instance, ctor);
17
+ }
18
+ static fromCtor(d1, ctor) {
19
+ return new this(d1, ctor, ctor);
20
+ }
21
+ /**
22
+ * Invokes a method on the instance, intercepting built-in CRUD methods and injecting
23
+ * a default definition.
24
+ */
25
+ interceptCrud(methodName) {
26
+ const map = {
27
+ post: this.upsert.bind(this),
28
+ get: this.get.bind(this),
29
+ list: this.list.bind(this),
30
+ };
31
+ const fn = this.instance && this.instance[methodName];
32
+ return fn ? fn.bind(this.instance) : map[methodName];
33
+ }
34
+ async upsert(obj, dataSource) {
35
+ const normalizedDs = dataSource === NO_DATA_SOURCE ? null : dataSource;
36
+ const includeTree = normalizedDs ? this.ctor[normalizedDs] : null;
37
+ // Upsert
38
+ const orm = Orm.fromD1(this.d1);
39
+ const upsert = await orm.upsert(this.ctor, obj, includeTree);
40
+ if (!upsert.ok) {
41
+ return { ok: false, status: 500, data: upsert.value }; // TODO: better status code?
42
+ }
43
+ // Get
44
+ const get = await orm.get(this.ctor, upsert.value, normalizedDs);
45
+ return get.ok
46
+ ? { ok: true, status: 200, data: get.value }
47
+ : { ok: false, status: 500, data: get.value };
48
+ }
49
+ async get(id, dataSource) {
50
+ const normalizedDs = dataSource === NO_DATA_SOURCE ? null : dataSource;
51
+ const orm = Orm.fromD1(this.d1);
52
+ const res = await orm.get(this.ctor, id, normalizedDs);
53
+ return res.ok
54
+ ? { ok: true, status: 200, data: res.value }
55
+ : { ok: false, status: 500, data: res.value };
56
+ }
57
+ async list(dataSource) {
58
+ const normalizedDs = dataSource === NO_DATA_SOURCE ? null : dataSource;
59
+ const orm = Orm.fromD1(this.d1);
60
+ const res = await orm.list(this.ctor, normalizedDs);
61
+ return res.ok
62
+ ? { ok: true, status: 200, data: res.value }
63
+ : { ok: false, status: 500, data: res.value };
64
+ }
65
+ }