cloesce 0.0.3
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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +337 -0
- package/dist/cli.wasm +0 -0
- package/dist/cloesce.d.ts +108 -0
- package/dist/cloesce.d.ts.map +1 -0
- package/dist/cloesce.js +453 -0
- package/dist/common.d.ts +96 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +96 -0
- package/dist/decorators.d.ts +13 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +13 -0
- package/dist/dog.cloesce.js +111 -0
- package/dist/extract.d.ts +14 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +517 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Use as @GET (no parentheses) */
|
|
2
|
+
export function GET(_value, _ctx) { }
|
|
3
|
+
/** Use as @POST (no parentheses) */
|
|
4
|
+
export function POST(_value, _ctx) { }
|
|
5
|
+
export function PUT(_value, _ctx) { }
|
|
6
|
+
export function PATCH(_value, _ctx) { }
|
|
7
|
+
export function DELETE(_value, _ctx) { }
|
|
8
|
+
/** Class decorator (no-op) */
|
|
9
|
+
export function D1(value, _ctx) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/** Field decorator (no-op) */
|
|
13
|
+
export function PrimaryKey(_v, _ctx) { }
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { WranglerEnv } from './cloesce.js'; // adjust import path
|
|
36
|
+
let MyEnvironment = (() => {
|
|
37
|
+
let _classDecorators = [WranglerEnv({
|
|
38
|
+
name: "my-worker",
|
|
39
|
+
compatibility_date: "2024-01-01"
|
|
40
|
+
})];
|
|
41
|
+
let _classDescriptor;
|
|
42
|
+
let _classExtraInitializers = [];
|
|
43
|
+
let _classThis;
|
|
44
|
+
var MyEnvironment = class {
|
|
45
|
+
static { _classThis = this; }
|
|
46
|
+
static {
|
|
47
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
48
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
49
|
+
MyEnvironment = _classThis = _classDescriptor.value;
|
|
50
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
51
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return MyEnvironment = _classThis;
|
|
55
|
+
})();
|
|
56
|
+
let Dog = (() => {
|
|
57
|
+
let _classDecorators = [D1];
|
|
58
|
+
let _classDescriptor;
|
|
59
|
+
let _classExtraInitializers = [];
|
|
60
|
+
let _classThis;
|
|
61
|
+
let _staticExtraInitializers = [];
|
|
62
|
+
let _instanceExtraInitializers = [];
|
|
63
|
+
let _static_woof_decorators;
|
|
64
|
+
let _id_decorators;
|
|
65
|
+
let _id_initializers = [];
|
|
66
|
+
let _id_extraInitializers = [];
|
|
67
|
+
let _get_name_decorators;
|
|
68
|
+
let _get_breed_decorators;
|
|
69
|
+
var Dog = class {
|
|
70
|
+
static { _classThis = this; }
|
|
71
|
+
static {
|
|
72
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
73
|
+
_id_decorators = [PrimaryKey];
|
|
74
|
+
_get_name_decorators = [GET];
|
|
75
|
+
_get_breed_decorators = [GET];
|
|
76
|
+
_static_woof_decorators = [POST];
|
|
77
|
+
__esDecorate(this, null, _static_woof_decorators, { kind: "method", name: "woof", static: true, private: false, access: { has: obj => "woof" in obj, get: obj => obj.woof }, metadata: _metadata }, null, _staticExtraInitializers);
|
|
78
|
+
__esDecorate(this, null, _get_name_decorators, { kind: "method", name: "get_name", static: false, private: false, access: { has: obj => "get_name" in obj, get: obj => obj.get_name }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
79
|
+
__esDecorate(this, null, _get_breed_decorators, { kind: "method", name: "get_breed", static: false, private: false, access: { has: obj => "get_breed" in obj, get: obj => obj.get_breed }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
80
|
+
__esDecorate(null, null, _id_decorators, { kind: "field", name: "id", static: false, private: false, access: { has: obj => "id" in obj, get: obj => obj.id, set: (obj, value) => { obj.id = value; } }, metadata: _metadata }, _id_initializers, _id_extraInitializers);
|
|
81
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
82
|
+
Dog = _classThis = _classDescriptor.value;
|
|
83
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
84
|
+
__runInitializers(_classThis, _staticExtraInitializers);
|
|
85
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
86
|
+
}
|
|
87
|
+
id = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _id_initializers, void 0));
|
|
88
|
+
name = __runInitializers(this, _id_extraInitializers);
|
|
89
|
+
breed;
|
|
90
|
+
preferred_treat;
|
|
91
|
+
async get_name(db, req) {
|
|
92
|
+
const who = new URL(req.url).searchParams.get("name");
|
|
93
|
+
return new Response(JSON.stringify({ hello: who }), {
|
|
94
|
+
headers: { "content-type": "application/json" },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async get_breed(db, req) {
|
|
98
|
+
const breed = new URL(req.url).searchParams.get("breed");
|
|
99
|
+
return new Response(JSON.stringify({ hello: breed }), {
|
|
100
|
+
headers: { "content-type": "application/json" },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
static async woof(db, req, phrase) {
|
|
104
|
+
return new Response(JSON.stringify({ phrase }), {
|
|
105
|
+
status: 201,
|
|
106
|
+
headers: { "content-type": "application/json" },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
return Dog = _classThis;
|
|
111
|
+
})();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Project } from "ts-morph";
|
|
2
|
+
import { CloesceAst, Either } from "./common.js";
|
|
3
|
+
export declare class CidlExtractor {
|
|
4
|
+
projectName: string;
|
|
5
|
+
version: string;
|
|
6
|
+
constructor(projectName: string, version: string);
|
|
7
|
+
extract(project: Project): Either<string, CloesceAst>;
|
|
8
|
+
private static model;
|
|
9
|
+
private static readonly primTypeMap;
|
|
10
|
+
private static cidlType;
|
|
11
|
+
private static includeTree;
|
|
12
|
+
private static method;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EASR,MAAM,UAAU,CAAC;AAElB,OAAO,EAEL,UAAU,EAGV,MAAM,EAUP,MAAM,aAAa,CAAC;AAqBrB,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,MAAM,EAAE,UAAU,CAAC;IA8CrD,OAAO,CAAC,MAAM,CAAC,KAAK;IA+KpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAQjC;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ;IAgGvB,OAAO,CAAC,MAAM,CAAC,WAAW;IA8D1B,OAAO,CAAC,MAAM,CAAC,MAAM;CAqDtB"}
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { SyntaxKind, } from "ts-morph";
|
|
2
|
+
import { HttpVerb, left, right, ExtractorError, ExtractorErrorCode, } from "./common.js";
|
|
3
|
+
import { TypeFormatFlags } from "typescript";
|
|
4
|
+
var AttributeDecoratorKind;
|
|
5
|
+
(function (AttributeDecoratorKind) {
|
|
6
|
+
AttributeDecoratorKind["PrimaryKey"] = "PrimaryKey";
|
|
7
|
+
AttributeDecoratorKind["ForeignKey"] = "ForeignKey";
|
|
8
|
+
AttributeDecoratorKind["OneToOne"] = "OneToOne";
|
|
9
|
+
AttributeDecoratorKind["OneToMany"] = "OneToMany";
|
|
10
|
+
AttributeDecoratorKind["ManyToMany"] = "ManyToMany";
|
|
11
|
+
AttributeDecoratorKind["DataSource"] = "DataSource";
|
|
12
|
+
})(AttributeDecoratorKind || (AttributeDecoratorKind = {}));
|
|
13
|
+
var ClassDecoratorKind;
|
|
14
|
+
(function (ClassDecoratorKind) {
|
|
15
|
+
ClassDecoratorKind["D1"] = "D1";
|
|
16
|
+
ClassDecoratorKind["WranglerEnv"] = "WranglerEnv";
|
|
17
|
+
ClassDecoratorKind["PlainOldObject"] = "PlainOldObject";
|
|
18
|
+
})(ClassDecoratorKind || (ClassDecoratorKind = {}));
|
|
19
|
+
var ParameterDecoratorKind;
|
|
20
|
+
(function (ParameterDecoratorKind) {
|
|
21
|
+
ParameterDecoratorKind["Inject"] = "Inject";
|
|
22
|
+
})(ParameterDecoratorKind || (ParameterDecoratorKind = {}));
|
|
23
|
+
export class CidlExtractor {
|
|
24
|
+
projectName;
|
|
25
|
+
version;
|
|
26
|
+
constructor(projectName, version) {
|
|
27
|
+
this.projectName = projectName;
|
|
28
|
+
this.version = version;
|
|
29
|
+
}
|
|
30
|
+
extract(project) {
|
|
31
|
+
const models = {};
|
|
32
|
+
const poos = {};
|
|
33
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
34
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
35
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.D1)) {
|
|
36
|
+
const result = CidlExtractor.model(classDecl, sourceFile);
|
|
37
|
+
// Error: propogate from models
|
|
38
|
+
if (!result.ok) {
|
|
39
|
+
result.value.addContext((old) => `${classDecl.getName()}.${old}`);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
models[result.value.name] = result.value;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (hasDecorator(classDecl, ClassDecoratorKind.PlainOldObject)) {
|
|
46
|
+
const result = CidlExtractor.poo(classDecl, sourceFile);
|
|
47
|
+
// Error: propogate from models
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
result.value.addContext((old) => `${classDecl.getName()}.${old}`);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
poos[result.value.name] = result.value;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
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
|
+
// Error: A wrangler environment is required
|
|
71
|
+
if (wranglerEnvs.length < 1) {
|
|
72
|
+
return err(ExtractorErrorCode.MissingWranglerEnv);
|
|
73
|
+
}
|
|
74
|
+
// Error: Only one wrangler environment can exist
|
|
75
|
+
if (wranglerEnvs.length > 1) {
|
|
76
|
+
return err(ExtractorErrorCode.TooManyWranglerEnvs, (e) => (e.context = wranglerEnvs.map((w) => w.name).toString()));
|
|
77
|
+
}
|
|
78
|
+
return right({
|
|
79
|
+
version: this.version,
|
|
80
|
+
project_name: this.projectName,
|
|
81
|
+
language: "TypeScript",
|
|
82
|
+
wrangler_env: wranglerEnvs[0],
|
|
83
|
+
models,
|
|
84
|
+
poos,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
static model(classDecl, sourceFile) {
|
|
88
|
+
const name = classDecl.getName();
|
|
89
|
+
const attributes = [];
|
|
90
|
+
const navigationProperties = [];
|
|
91
|
+
const dataSources = {};
|
|
92
|
+
const methods = {};
|
|
93
|
+
let primary_key = undefined;
|
|
94
|
+
for (const prop of classDecl.getProperties()) {
|
|
95
|
+
const decorators = prop.getDecorators();
|
|
96
|
+
const typeRes = CidlExtractor.cidlType(prop.getType());
|
|
97
|
+
// Error: invalid property type
|
|
98
|
+
if (!typeRes.ok) {
|
|
99
|
+
typeRes.value.context = prop.getName();
|
|
100
|
+
typeRes.value.snippet = prop.getText();
|
|
101
|
+
return typeRes;
|
|
102
|
+
}
|
|
103
|
+
// No decorators means this is a standard attribute
|
|
104
|
+
if (decorators.length === 0) {
|
|
105
|
+
const cidl_type = typeRes.value;
|
|
106
|
+
attributes.push({
|
|
107
|
+
foreign_key_reference: null,
|
|
108
|
+
value: {
|
|
109
|
+
name: prop.getName(),
|
|
110
|
+
cidl_type,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// TODO: Limiting to one decorator. Can't get too fancy on us.
|
|
116
|
+
const decorator = decorators[0];
|
|
117
|
+
const name = getDecoratorName(decorator);
|
|
118
|
+
// Process decorators
|
|
119
|
+
const cidl_type = typeRes.value;
|
|
120
|
+
switch (name) {
|
|
121
|
+
case AttributeDecoratorKind.PrimaryKey: {
|
|
122
|
+
primary_key = {
|
|
123
|
+
name: prop.getName(),
|
|
124
|
+
cidl_type,
|
|
125
|
+
};
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case AttributeDecoratorKind.ForeignKey: {
|
|
129
|
+
attributes.push({
|
|
130
|
+
foreign_key_reference: getDecoratorArgument(decorator, 0) ?? null,
|
|
131
|
+
value: {
|
|
132
|
+
name: prop.getName(),
|
|
133
|
+
cidl_type,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case AttributeDecoratorKind.OneToOne: {
|
|
139
|
+
const reference = getDecoratorArgument(decorator, 0);
|
|
140
|
+
// Error: One to one navigation properties requre a reference
|
|
141
|
+
if (!reference) {
|
|
142
|
+
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
143
|
+
e.snippet = prop.getText();
|
|
144
|
+
e.context = prop.getName();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
let model_name = getObjectName(cidl_type);
|
|
148
|
+
// Error: navigation properties require a model reference
|
|
149
|
+
if (!model_name) {
|
|
150
|
+
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
151
|
+
e.snippet = prop.getText();
|
|
152
|
+
e.context = prop.getName();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
navigationProperties.push({
|
|
156
|
+
var_name: prop.getName(),
|
|
157
|
+
model_name,
|
|
158
|
+
kind: { OneToOne: { reference } },
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case AttributeDecoratorKind.OneToMany: {
|
|
163
|
+
const reference = getDecoratorArgument(decorator, 0);
|
|
164
|
+
// Error: One to one navigation properties requre a reference
|
|
165
|
+
if (!reference) {
|
|
166
|
+
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
167
|
+
e.snippet = prop.getText();
|
|
168
|
+
e.context = prop.getName();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
let model_name = getObjectName(cidl_type);
|
|
172
|
+
// Error: navigation properties require a model reference
|
|
173
|
+
if (!model_name) {
|
|
174
|
+
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
175
|
+
e.snippet = prop.getText();
|
|
176
|
+
e.context = prop.getName();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
navigationProperties.push({
|
|
180
|
+
var_name: prop.getName(),
|
|
181
|
+
model_name,
|
|
182
|
+
kind: { OneToMany: { reference } },
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case AttributeDecoratorKind.ManyToMany: {
|
|
187
|
+
const unique_id = getDecoratorArgument(decorator, 0);
|
|
188
|
+
// Error: many to many attribtues require a unique id
|
|
189
|
+
if (!unique_id)
|
|
190
|
+
return err(ExtractorErrorCode.MissingManyToManyUniqueId, (e) => {
|
|
191
|
+
e.snippet = prop.getText();
|
|
192
|
+
e.context = prop.getName();
|
|
193
|
+
});
|
|
194
|
+
// Error: navigation properties require a model reference
|
|
195
|
+
let model_name = getObjectName(cidl_type);
|
|
196
|
+
if (!model_name) {
|
|
197
|
+
return err(ExtractorErrorCode.MissingNavigationPropertyReference, (e) => {
|
|
198
|
+
e.snippet = prop.getText();
|
|
199
|
+
e.context = prop.getName();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
navigationProperties.push({
|
|
203
|
+
var_name: prop.getName(),
|
|
204
|
+
model_name,
|
|
205
|
+
kind: { ManyToMany: { unique_id } },
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case AttributeDecoratorKind.DataSource: {
|
|
210
|
+
const initializer = prop.getInitializer();
|
|
211
|
+
const treeRes = CidlExtractor.includeTree(initializer, classDecl, sourceFile);
|
|
212
|
+
if (!treeRes.ok) {
|
|
213
|
+
treeRes.value.addContext((old) => `${prop.getName()} ${old}`);
|
|
214
|
+
treeRes.value.snippet = prop.getText();
|
|
215
|
+
return treeRes;
|
|
216
|
+
}
|
|
217
|
+
dataSources[prop.getName()] = {
|
|
218
|
+
name: prop.getName(),
|
|
219
|
+
tree: treeRes.value,
|
|
220
|
+
};
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (primary_key == undefined) {
|
|
226
|
+
return err(ExtractorErrorCode.MissingPrimaryKey, (e) => {
|
|
227
|
+
e.snippet = classDecl.getText();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Process methods
|
|
231
|
+
for (const m of classDecl.getMethods()) {
|
|
232
|
+
const result = CidlExtractor.method(m);
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
result.value.addContext((old) => `${m.getName()} ${old}`);
|
|
235
|
+
return left(result.value);
|
|
236
|
+
}
|
|
237
|
+
methods[result.value.name] = result.value;
|
|
238
|
+
}
|
|
239
|
+
return right({
|
|
240
|
+
name,
|
|
241
|
+
attributes,
|
|
242
|
+
primary_key,
|
|
243
|
+
navigation_properties: navigationProperties,
|
|
244
|
+
methods,
|
|
245
|
+
data_sources: dataSources,
|
|
246
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
static poo(classDecl, sourceFile) {
|
|
250
|
+
const name = classDecl.getName();
|
|
251
|
+
const attributes = [];
|
|
252
|
+
for (const prop of classDecl.getProperties()) {
|
|
253
|
+
const typeRes = CidlExtractor.cidlType(prop.getType());
|
|
254
|
+
// Error: invalid property type
|
|
255
|
+
if (!typeRes.ok) {
|
|
256
|
+
typeRes.value.context = prop.getName();
|
|
257
|
+
typeRes.value.snippet = prop.getText();
|
|
258
|
+
return typeRes;
|
|
259
|
+
}
|
|
260
|
+
const cidl_type = typeRes.value;
|
|
261
|
+
attributes.push({
|
|
262
|
+
name: prop.getName(),
|
|
263
|
+
cidl_type,
|
|
264
|
+
});
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
return right({
|
|
268
|
+
name,
|
|
269
|
+
attributes,
|
|
270
|
+
source_path: sourceFile.getFilePath().toString(),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
static primTypeMap = {
|
|
274
|
+
number: "Integer",
|
|
275
|
+
Number: "Integer",
|
|
276
|
+
string: "Text",
|
|
277
|
+
String: "Text",
|
|
278
|
+
boolean: "Integer",
|
|
279
|
+
Boolean: "Integer",
|
|
280
|
+
Date: "Text",
|
|
281
|
+
};
|
|
282
|
+
static cidlType(type, inject = false) {
|
|
283
|
+
// Void
|
|
284
|
+
if (type.isVoid()) {
|
|
285
|
+
return right("Void");
|
|
286
|
+
}
|
|
287
|
+
// Null
|
|
288
|
+
if (type.isNull()) {
|
|
289
|
+
return right({ Nullable: "Void" });
|
|
290
|
+
}
|
|
291
|
+
// Nullable via union
|
|
292
|
+
const [unwrappedType, nullable] = unwrapNullable(type);
|
|
293
|
+
const tyText = unwrappedType
|
|
294
|
+
.getText(undefined, TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)
|
|
295
|
+
.split("|")[0]
|
|
296
|
+
.trim();
|
|
297
|
+
// Primitives
|
|
298
|
+
const prim = this.primTypeMap[tyText];
|
|
299
|
+
if (prim) {
|
|
300
|
+
return right(wrapNullable(prim, nullable));
|
|
301
|
+
}
|
|
302
|
+
const generics = [
|
|
303
|
+
...unwrappedType.getAliasTypeArguments(),
|
|
304
|
+
...unwrappedType.getTypeArguments(),
|
|
305
|
+
];
|
|
306
|
+
// Error: can't handle multiple generics
|
|
307
|
+
if (generics.length > 1) {
|
|
308
|
+
return err(ExtractorErrorCode.MultipleGenericType);
|
|
309
|
+
}
|
|
310
|
+
// No generics -> inject or object
|
|
311
|
+
if (generics.length === 0) {
|
|
312
|
+
const base = inject ? { Inject: tyText } : { Object: tyText };
|
|
313
|
+
return right(wrapNullable(base, nullable));
|
|
314
|
+
}
|
|
315
|
+
// Single generic
|
|
316
|
+
const genericTy = generics[0];
|
|
317
|
+
const symbolName = unwrappedType.getSymbol()?.getName();
|
|
318
|
+
const aliasName = unwrappedType.getAliasSymbol()?.getName();
|
|
319
|
+
if (symbolName === "Promise" || aliasName === "IncludeTree") {
|
|
320
|
+
return wrapGeneric(genericTy, nullable, (inner) => inner);
|
|
321
|
+
}
|
|
322
|
+
if (unwrappedType.isArray()) {
|
|
323
|
+
return wrapGeneric(genericTy, nullable, (inner) => ({ Array: inner }));
|
|
324
|
+
}
|
|
325
|
+
if (aliasName === "HttpResult") {
|
|
326
|
+
return wrapGeneric(genericTy, nullable, (inner) => ({
|
|
327
|
+
HttpResult: inner,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
// Error: unknown type
|
|
331
|
+
return err(ExtractorErrorCode.UnknownType);
|
|
332
|
+
function wrapNullable(inner, isNullable) {
|
|
333
|
+
if (isNullable) {
|
|
334
|
+
return { Nullable: inner };
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
return inner;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function wrapGeneric(t, isNullable, wrapper) {
|
|
341
|
+
const res = CidlExtractor.cidlType(t, inject);
|
|
342
|
+
// Error: propogated from `cidlType`
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
return res;
|
|
345
|
+
}
|
|
346
|
+
return right(wrapNullable(wrapper(res.value), isNullable));
|
|
347
|
+
}
|
|
348
|
+
function unwrapNullable(ty) {
|
|
349
|
+
if (ty.isUnion()) {
|
|
350
|
+
const nonNull = ty.getUnionTypes().filter((t) => !t.isNull());
|
|
351
|
+
if (nonNull.length === 1) {
|
|
352
|
+
return [nonNull[0], true];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return [ty, false];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
static includeTree(expr, currentClass, sf) {
|
|
359
|
+
// Include trees must be of the expected form
|
|
360
|
+
if (!expr ||
|
|
361
|
+
!expr.isKind ||
|
|
362
|
+
!expr.isKind(SyntaxKind.ObjectLiteralExpression)) {
|
|
363
|
+
return err(ExtractorErrorCode.InvalidIncludeTree);
|
|
364
|
+
}
|
|
365
|
+
const result = {};
|
|
366
|
+
for (const prop of expr.getProperties()) {
|
|
367
|
+
if (!prop.isKind(SyntaxKind.PropertyAssignment))
|
|
368
|
+
continue;
|
|
369
|
+
// Error: navigation property not found
|
|
370
|
+
const navProp = findPropertyByName(currentClass, prop.getName());
|
|
371
|
+
if (!navProp) {
|
|
372
|
+
return err(ExtractorErrorCode.UnknownNavigationPropertyReference, (e) => {
|
|
373
|
+
e.snippet = expr.getText();
|
|
374
|
+
e.context = prop.getName();
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const typeRes = CidlExtractor.cidlType(navProp.getType());
|
|
378
|
+
// Error: invalid referenced nav prop type
|
|
379
|
+
if (!typeRes.ok) {
|
|
380
|
+
typeRes.value.snippet = navProp.getText();
|
|
381
|
+
typeRes.value.context = prop.getName();
|
|
382
|
+
return typeRes;
|
|
383
|
+
}
|
|
384
|
+
// Error: invalid referenced nav prop type
|
|
385
|
+
const cidl_type = typeRes.value;
|
|
386
|
+
if (typeof cidl_type === "string") {
|
|
387
|
+
return err(ExtractorErrorCode.InvalidNavigationPropertyReference, (e) => {
|
|
388
|
+
((e.snippet = navProp.getText()), (e.context = prop.getName()));
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// Recurse for nested includes
|
|
392
|
+
const initializer = prop.getInitializer?.();
|
|
393
|
+
let nestedTree = {};
|
|
394
|
+
if (initializer?.isKind?.(SyntaxKind.ObjectLiteralExpression)) {
|
|
395
|
+
const targetModel = getObjectName(cidl_type);
|
|
396
|
+
const targetClass = currentClass
|
|
397
|
+
.getSourceFile()
|
|
398
|
+
.getProject()
|
|
399
|
+
.getSourceFiles()
|
|
400
|
+
.flatMap((f) => f.getClasses())
|
|
401
|
+
.find((c) => c.getName() === targetModel);
|
|
402
|
+
if (targetClass) {
|
|
403
|
+
const treeRes = CidlExtractor.includeTree(initializer, targetClass, sf);
|
|
404
|
+
// Error: Propogated from `includeTree`
|
|
405
|
+
if (!treeRes.ok) {
|
|
406
|
+
treeRes.value.snippet = expr.getText();
|
|
407
|
+
return treeRes;
|
|
408
|
+
}
|
|
409
|
+
nestedTree = treeRes.value;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
result[navProp.getName()] = nestedTree;
|
|
413
|
+
}
|
|
414
|
+
return right(result);
|
|
415
|
+
}
|
|
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));
|
|
420
|
+
const parameters = [];
|
|
421
|
+
for (const param of method.getParameters()) {
|
|
422
|
+
// Handle injected param
|
|
423
|
+
if (param.getDecorator(ParameterDecoratorKind.Inject)) {
|
|
424
|
+
const typeRes = CidlExtractor.cidlType(param.getType(), true);
|
|
425
|
+
// Error: invalid type
|
|
426
|
+
if (!typeRes.ok) {
|
|
427
|
+
typeRes.value.snippet = method.getText();
|
|
428
|
+
typeRes.value.context = param.getName();
|
|
429
|
+
return typeRes;
|
|
430
|
+
}
|
|
431
|
+
parameters.push({
|
|
432
|
+
name: param.getName(),
|
|
433
|
+
cidl_type: typeRes.value,
|
|
434
|
+
});
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
// Handle all other params
|
|
438
|
+
const typeRes = CidlExtractor.cidlType(param.getType());
|
|
439
|
+
// Error: invalid type
|
|
440
|
+
if (!typeRes.ok) {
|
|
441
|
+
typeRes.value.snippet = method.getText();
|
|
442
|
+
typeRes.value.context = param.getName();
|
|
443
|
+
return typeRes;
|
|
444
|
+
}
|
|
445
|
+
parameters.push({
|
|
446
|
+
name: param.getName(),
|
|
447
|
+
cidl_type: typeRes.value,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
const typeRes = CidlExtractor.cidlType(method.getReturnType());
|
|
451
|
+
// Error: invalid type
|
|
452
|
+
if (!typeRes.ok) {
|
|
453
|
+
typeRes.value.snippet = method.getText();
|
|
454
|
+
return typeRes;
|
|
455
|
+
}
|
|
456
|
+
return right({
|
|
457
|
+
name: method.getName(),
|
|
458
|
+
is_static: method.isStatic(),
|
|
459
|
+
http_verb: httpVerb,
|
|
460
|
+
return_type: typeRes.value,
|
|
461
|
+
parameters,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function err(code, fn) {
|
|
466
|
+
let e = new ExtractorError(code);
|
|
467
|
+
if (fn) {
|
|
468
|
+
fn(e);
|
|
469
|
+
}
|
|
470
|
+
return left(e);
|
|
471
|
+
}
|
|
472
|
+
function getDecoratorName(decorator) {
|
|
473
|
+
const name = decorator.getName() ?? decorator.getExpression().getText();
|
|
474
|
+
return String(name).replace(/\(.*\)$/, "");
|
|
475
|
+
}
|
|
476
|
+
function getDecoratorArgument(decorator, index) {
|
|
477
|
+
const args = decorator.getArguments();
|
|
478
|
+
if (!args[index])
|
|
479
|
+
return undefined;
|
|
480
|
+
const arg = args[index];
|
|
481
|
+
// Identifier
|
|
482
|
+
if (arg.getKind?.() === SyntaxKind.Identifier) {
|
|
483
|
+
return arg.getText();
|
|
484
|
+
}
|
|
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;
|
|
491
|
+
}
|
|
492
|
+
function getObjectName(t) {
|
|
493
|
+
if (typeof t === "string")
|
|
494
|
+
return undefined;
|
|
495
|
+
if ("Object" in t) {
|
|
496
|
+
return t.Object;
|
|
497
|
+
}
|
|
498
|
+
else if ("Array" in t) {
|
|
499
|
+
return getObjectName(t.Array);
|
|
500
|
+
}
|
|
501
|
+
else if ("HttpResult" in t) {
|
|
502
|
+
if (t == null)
|
|
503
|
+
return undefined;
|
|
504
|
+
return getObjectName(t.HttpResult);
|
|
505
|
+
}
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
function findPropertyByName(cls, name) {
|
|
509
|
+
const exactMatch = cls.getProperties().find((p) => p.getName() === name);
|
|
510
|
+
return exactMatch;
|
|
511
|
+
}
|
|
512
|
+
function hasDecorator(node, name) {
|
|
513
|
+
return node.getDecorators().some((d) => {
|
|
514
|
+
const decoratorName = getDecoratorName(d);
|
|
515
|
+
return decoratorName === name || decoratorName.endsWith("." + name);
|
|
516
|
+
});
|
|
517
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { cloesce, modelsFromSql } from "./cloesce.js";
|
|
2
|
+
export { HttpResult } from "./common.js";
|
|
3
|
+
export declare const D1: ClassDecorator;
|
|
4
|
+
export declare const WranglerEnv: ClassDecorator;
|
|
5
|
+
export declare const PrimaryKey: PropertyDecorator;
|
|
6
|
+
export declare const GET: MethodDecorator;
|
|
7
|
+
export declare const POST: MethodDecorator;
|
|
8
|
+
export declare const PUT: MethodDecorator;
|
|
9
|
+
export declare const PATCH: MethodDecorator;
|
|
10
|
+
export declare const DELETE: MethodDecorator;
|
|
11
|
+
export declare const DataSource: PropertyDecorator;
|
|
12
|
+
export declare const OneToMany: (_: string) => PropertyDecorator;
|
|
13
|
+
export declare const OneToOne: (_: string) => PropertyDecorator;
|
|
14
|
+
export declare const ManyToMany: (_: string) => PropertyDecorator;
|
|
15
|
+
export declare const ForeignKey: <T>(_: T) => PropertyDecorator;
|
|
16
|
+
export declare const Inject: ParameterDecorator;
|
|
17
|
+
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
18
|
+
export type IncludeTree<T> = T extends Primitive ? never : {
|
|
19
|
+
[K in keyof T]?: T[K] extends (infer U)[] ? IncludeTree<NonNullable<U>> : IncludeTree<NonNullable<T[K]>>;
|
|
20
|
+
};
|
|
21
|
+
export declare function instantiateModelArray<T extends object>(data: any, ctor: {
|
|
22
|
+
new (): T;
|
|
23
|
+
}): T[];
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|