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.
- package/dist/README.md +487 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +221 -254
- package/dist/common.d.ts +69 -1
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +72 -11
- package/dist/{extract.d.ts → extractor/extract.d.ts} +5 -2
- package/dist/extractor/extract.d.ts.map +1 -0
- package/dist/{extract.js → extractor/extract.js} +242 -43
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.d.ts +22 -0
- package/dist/router/crud.d.ts.map +1 -0
- package/dist/router/crud.js +65 -0
- package/dist/router/router.d.ts +77 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +358 -0
- package/dist/router/wasm.d.ts +37 -0
- package/dist/router/wasm.d.ts.map +1 -0
- package/dist/router/wasm.js +98 -0
- package/dist/ui/backend.d.ts +124 -0
- package/dist/ui/backend.d.ts.map +1 -0
- package/dist/ui/backend.js +201 -0
- package/dist/ui/client.d.ts +5 -0
- package/dist/ui/client.d.ts.map +1 -0
- package/dist/ui/client.js +7 -0
- package/package.json +70 -58
- package/LICENSE +0 -201
- package/README.md +0 -23
- package/dist/cli.wasm +0 -0
- package/dist/cloesce.d.ts +0 -108
- package/dist/cloesce.d.ts.map +0 -1
- package/dist/cloesce.js +0 -453
- package/dist/decorators.d.ts +0 -13
- package/dist/decorators.d.ts.map +0 -1
- package/dist/decorators.js +0 -13
- package/dist/dog.cloesce.js +0 -111
- package/dist/extract.d.ts.map +0 -1
- package/dist/index.d.ts +0 -24
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -24
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +0 -1
- 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["
|
|
10
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
11
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
12
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
13
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
14
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
15
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
16
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
17
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
18
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
19
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
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 "
|
|
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<
|
|
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 "
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
|
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((
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
|
493
|
-
if (typeof t === "string")
|
|
494
|
-
return
|
|
495
|
-
if ("Object" in t) {
|
|
496
|
-
return t.Object;
|
|
663
|
+
function getRootType(t) {
|
|
664
|
+
if (typeof t === "string") {
|
|
665
|
+
return t;
|
|
497
666
|
}
|
|
498
|
-
|
|
499
|
-
return
|
|
667
|
+
if ("Nullable" in t) {
|
|
668
|
+
return getRootType(t.Nullable);
|
|
500
669
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
+
}
|