cloesce 0.0.5-unstable.4 → 0.0.5-unstable.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +28 -18
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +3 -3
- package/dist/cli.js +4 -5
- package/dist/common.d.ts +23 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +78 -0
- package/dist/extractor/err.d.ts +12 -13
- package/dist/extractor/err.d.ts.map +1 -1
- package/dist/extractor/err.js +41 -46
- package/dist/extractor/extract.d.ts +21 -14
- package/dist/extractor/extract.d.ts.map +1 -1
- package/dist/extractor/extract.js +572 -348
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.d.ts +1 -2
- package/dist/router/crud.d.ts.map +1 -1
- package/dist/router/crud.js +36 -27
- package/dist/router/orm.d.ts +66 -0
- package/dist/router/orm.d.ts.map +1 -0
- package/dist/router/orm.js +447 -0
- package/dist/router/router.d.ts +21 -30
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +116 -116
- package/dist/router/validator.d.ts +1 -1
- package/dist/router/validator.d.ts.map +1 -1
- package/dist/router/validator.js +48 -4
- package/dist/router/wasm.d.ts +9 -16
- package/dist/router/wasm.d.ts.map +1 -1
- package/dist/router/wasm.js +10 -49
- package/dist/ui/backend.d.ts +95 -341
- package/dist/ui/backend.d.ts.map +1 -1
- package/dist/ui/backend.js +135 -409
- package/package.json +3 -9
- package/dist/ui/client.d.ts +0 -7
- package/dist/ui/client.d.ts.map +0 -1
- package/dist/ui/client.js +0 -2
- package/dist/ui/common.d.ts +0 -103
- package/dist/ui/common.d.ts.map +0 -1
- package/dist/ui/common.js +0 -191
package/dist/router/router.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OrmWasmExports } from "./wasm.js";
|
|
2
|
-
import { HttpResult } from "../ui/backend.js";
|
|
3
|
-
import { Either, KeysOfType } from "../ui/common.js";
|
|
4
2
|
import { CloesceAst, Model, ApiMethod, Service } from "../ast.js";
|
|
3
|
+
import { Either } from "../common.js";
|
|
4
|
+
import { KeysOfType, HttpResult } from "../ui/backend.js";
|
|
5
5
|
/**
|
|
6
6
|
* Dependency injection container, mapping an object type name to an instance of that object.
|
|
7
7
|
*
|
|
@@ -15,7 +15,7 @@ export type DependencyContainer = Map<string, any>;
|
|
|
15
15
|
*/
|
|
16
16
|
export type ConstructorRegistry = Record<string, new () => any>;
|
|
17
17
|
/**
|
|
18
|
-
* Singleton instance containing the
|
|
18
|
+
* Singleton instance containing the CIDL, constructor registry, and wasm binary.
|
|
19
19
|
* These values are guaranteed to never change throughout a workers lifetime.
|
|
20
20
|
*/
|
|
21
21
|
export declare class RuntimeContainer {
|
|
@@ -45,44 +45,34 @@ export declare enum RouterError {
|
|
|
45
45
|
UnknownPrefix = 0,
|
|
46
46
|
UnknownRoute = 1,
|
|
47
47
|
UnmatchedHttpVerb = 2,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
InstantiatedMethodMissingPrimaryKey = 3,
|
|
49
|
+
InstantiatedMethodMissingKeyParam = 4,
|
|
50
|
+
RequestMissingBody = 5,
|
|
51
|
+
RequestBodyMissingParameters = 6,
|
|
52
|
+
RequestBodyInvalidParameter = 7,
|
|
53
|
+
InstantiatedMethodMissingDataSource = 8,
|
|
54
|
+
MissingDependency = 9,
|
|
55
|
+
InvalidDatabaseQuery = 10,
|
|
56
|
+
ModelNotFound = 11,
|
|
57
|
+
UncaughtException = 12
|
|
57
58
|
}
|
|
58
59
|
export declare class CloesceApp {
|
|
59
60
|
routePrefix: string;
|
|
61
|
+
static init(ast: CloesceAst, ctorReg: ConstructorRegistry): Promise<CloesceApp>;
|
|
60
62
|
private globalMiddleware;
|
|
61
63
|
/**
|
|
62
|
-
* Registers global middleware
|
|
64
|
+
* Registers global middleware that runs before all requests.
|
|
63
65
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* @param m - The middleware function to register.
|
|
67
|
-
*/
|
|
68
|
-
onRequest(m: MiddlewareFn): void;
|
|
69
|
-
private resultMiddleware;
|
|
70
|
-
/**
|
|
71
|
-
* Registers middleware which runs after the response is generated, but before
|
|
72
|
-
* it is returned to the client.
|
|
73
|
-
*
|
|
74
|
-
* Optionally, return a new HttpResult to short-circuit the response.
|
|
75
|
-
*
|
|
76
|
-
* Errors thrown in response middleware are caught and returned as a 500 response.
|
|
66
|
+
* Runs before namespace middleware, request validation, and method middleware.
|
|
77
67
|
*
|
|
78
68
|
* TODO: Middleware may violate the API contract and return unexpected types
|
|
79
69
|
*
|
|
80
70
|
* @param m - The middleware function to register.
|
|
81
71
|
*/
|
|
82
|
-
|
|
72
|
+
onRun(m: MiddlewareFn): void;
|
|
83
73
|
private namespaceMiddleware;
|
|
84
74
|
/**
|
|
85
|
-
* Registers middleware for a specific namespace (
|
|
75
|
+
* Registers middleware for a specific namespace (model or service)
|
|
86
76
|
*
|
|
87
77
|
* Runs before request validation and method middleware.
|
|
88
78
|
*
|
|
@@ -111,13 +101,14 @@ export declare class CloesceApp {
|
|
|
111
101
|
/**
|
|
112
102
|
* Runs the Cloesce app. Intended to be called from the generated workers code.
|
|
113
103
|
*/
|
|
114
|
-
run(request: Request, env: any
|
|
104
|
+
run(request: Request, env: any): Promise<Response>;
|
|
115
105
|
}
|
|
116
106
|
export type MatchedRoute = {
|
|
117
107
|
kind: "model" | "service";
|
|
118
108
|
namespace: string;
|
|
119
109
|
method: ApiMethod;
|
|
120
|
-
|
|
110
|
+
primaryKey: string | null;
|
|
111
|
+
keyParams: Record<string, string>;
|
|
121
112
|
model?: Model;
|
|
122
113
|
service?: Service;
|
|
123
114
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"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"}
|
package/dist/router/router.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadOrmWasm } from "./wasm.js";
|
|
2
2
|
import { proxyCrud } from "./crud.js";
|
|
3
|
-
import { HttpResult, Orm } from "../ui/backend.js";
|
|
4
|
-
import { Either } from "../ui/common.js";
|
|
5
3
|
import { NO_DATA_SOURCE, MediaType, } from "../ast.js";
|
|
6
4
|
import { RuntimeValidator } from "./validator.js";
|
|
5
|
+
import { Either, InternalError } from "../common.js";
|
|
6
|
+
import { Orm, HttpResult } from "../ui/backend.js";
|
|
7
7
|
/**
|
|
8
|
-
* Singleton instance containing the
|
|
8
|
+
* Singleton instance containing the CIDL, constructor registry, and wasm binary.
|
|
9
9
|
* These values are guaranteed to never change throughout a workers lifetime.
|
|
10
10
|
*/
|
|
11
11
|
export class RuntimeContainer {
|
|
@@ -42,48 +42,39 @@ export var RouterError;
|
|
|
42
42
|
RouterError[RouterError["UnknownPrefix"] = 0] = "UnknownPrefix";
|
|
43
43
|
RouterError[RouterError["UnknownRoute"] = 1] = "UnknownRoute";
|
|
44
44
|
RouterError[RouterError["UnmatchedHttpVerb"] = 2] = "UnmatchedHttpVerb";
|
|
45
|
-
RouterError[RouterError["
|
|
46
|
-
RouterError[RouterError["
|
|
47
|
-
RouterError[RouterError["
|
|
48
|
-
RouterError[RouterError["
|
|
49
|
-
RouterError[RouterError["
|
|
50
|
-
RouterError[RouterError["
|
|
51
|
-
RouterError[RouterError["
|
|
52
|
-
RouterError[RouterError["
|
|
53
|
-
RouterError[RouterError["
|
|
45
|
+
RouterError[RouterError["InstantiatedMethodMissingPrimaryKey"] = 3] = "InstantiatedMethodMissingPrimaryKey";
|
|
46
|
+
RouterError[RouterError["InstantiatedMethodMissingKeyParam"] = 4] = "InstantiatedMethodMissingKeyParam";
|
|
47
|
+
RouterError[RouterError["RequestMissingBody"] = 5] = "RequestMissingBody";
|
|
48
|
+
RouterError[RouterError["RequestBodyMissingParameters"] = 6] = "RequestBodyMissingParameters";
|
|
49
|
+
RouterError[RouterError["RequestBodyInvalidParameter"] = 7] = "RequestBodyInvalidParameter";
|
|
50
|
+
RouterError[RouterError["InstantiatedMethodMissingDataSource"] = 8] = "InstantiatedMethodMissingDataSource";
|
|
51
|
+
RouterError[RouterError["MissingDependency"] = 9] = "MissingDependency";
|
|
52
|
+
RouterError[RouterError["InvalidDatabaseQuery"] = 10] = "InvalidDatabaseQuery";
|
|
53
|
+
RouterError[RouterError["ModelNotFound"] = 11] = "ModelNotFound";
|
|
54
|
+
RouterError[RouterError["UncaughtException"] = 12] = "UncaughtException";
|
|
54
55
|
})(RouterError || (RouterError = {}));
|
|
55
56
|
export class CloesceApp {
|
|
56
57
|
routePrefix = "api";
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
* TODO: Middleware may violate the API contract and return unexpected types
|
|
62
|
-
*
|
|
63
|
-
* @param m - The middleware function to register.
|
|
64
|
-
*/
|
|
65
|
-
onRequest(m) {
|
|
66
|
-
this.globalMiddleware.push(m);
|
|
58
|
+
static async init(ast, ctorReg) {
|
|
59
|
+
await RuntimeContainer.init(ast, ctorReg);
|
|
60
|
+
return new CloesceApp();
|
|
67
61
|
}
|
|
68
|
-
|
|
62
|
+
globalMiddleware = [];
|
|
69
63
|
/**
|
|
70
|
-
* Registers middleware
|
|
71
|
-
* it is returned to the client.
|
|
64
|
+
* Registers global middleware that runs before all requests.
|
|
72
65
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* Errors thrown in response middleware are caught and returned as a 500 response.
|
|
66
|
+
* Runs before namespace middleware, request validation, and method middleware.
|
|
76
67
|
*
|
|
77
68
|
* TODO: Middleware may violate the API contract and return unexpected types
|
|
78
69
|
*
|
|
79
70
|
* @param m - The middleware function to register.
|
|
80
71
|
*/
|
|
81
|
-
|
|
82
|
-
this.
|
|
72
|
+
onRun(m) {
|
|
73
|
+
this.globalMiddleware.push(m);
|
|
83
74
|
}
|
|
84
75
|
namespaceMiddleware = new Map();
|
|
85
76
|
/**
|
|
86
|
-
* Registers middleware for a specific namespace (
|
|
77
|
+
* Registers middleware for a specific namespace (model or service)
|
|
87
78
|
*
|
|
88
79
|
* Runs before request validation and method middleware.
|
|
89
80
|
*
|
|
@@ -94,12 +85,12 @@ export class CloesceApp {
|
|
|
94
85
|
* @param m - The middleware function to register.
|
|
95
86
|
*/
|
|
96
87
|
onNamespace(ctor, m) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.namespaceMiddleware.set(ctor.name, [m]);
|
|
88
|
+
const existing = this.namespaceMiddleware.get(ctor.name);
|
|
89
|
+
if (existing) {
|
|
90
|
+
existing.push(m);
|
|
91
|
+
return;
|
|
102
92
|
}
|
|
93
|
+
this.namespaceMiddleware.set(ctor.name, [m]);
|
|
103
94
|
}
|
|
104
95
|
methodMiddleware = new Map();
|
|
105
96
|
/**
|
|
@@ -115,14 +106,17 @@ export class CloesceApp {
|
|
|
115
106
|
* @param m - The middleware function to register.
|
|
116
107
|
*/
|
|
117
108
|
onMethod(ctor, method, m) {
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
let classMap = this.methodMiddleware.get(ctor.name);
|
|
110
|
+
if (!classMap) {
|
|
111
|
+
classMap = new Map();
|
|
112
|
+
this.methodMiddleware.set(ctor.name, classMap);
|
|
120
113
|
}
|
|
121
|
-
|
|
122
|
-
if (!
|
|
123
|
-
|
|
114
|
+
let methodArray = classMap.get(method);
|
|
115
|
+
if (!methodArray) {
|
|
116
|
+
methodArray = [];
|
|
117
|
+
classMap.set(method, methodArray);
|
|
124
118
|
}
|
|
125
|
-
|
|
119
|
+
methodArray.push(m);
|
|
126
120
|
}
|
|
127
121
|
async router(request, env, ast, ctorReg, di) {
|
|
128
122
|
// Global middleware
|
|
@@ -138,7 +132,7 @@ export class CloesceApp {
|
|
|
138
132
|
return routeRes.value;
|
|
139
133
|
}
|
|
140
134
|
const route = routeRes.unwrap();
|
|
141
|
-
//
|
|
135
|
+
// Namespace middleware
|
|
142
136
|
for (const m of this.namespaceMiddleware.get(route.namespace) ?? []) {
|
|
143
137
|
const res = await m(di);
|
|
144
138
|
if (res) {
|
|
@@ -161,23 +155,7 @@ export class CloesceApp {
|
|
|
161
155
|
}
|
|
162
156
|
}
|
|
163
157
|
// Hydration
|
|
164
|
-
const hydrated = await (
|
|
165
|
-
if (route.kind == "model") {
|
|
166
|
-
// TODO: Support multiple D1 bindings
|
|
167
|
-
// It's been verified by the compiler that wrangler_env exists if a D1 model is present
|
|
168
|
-
const d1 = env[ast.wrangler_env.db_binding];
|
|
169
|
-
// Proxy CRUD
|
|
170
|
-
if (route.method.is_static) {
|
|
171
|
-
return Either.right(proxyCrud(ctorReg[route.namespace], ctorReg[route.namespace], d1));
|
|
172
|
-
}
|
|
173
|
-
return await hydrateModelD1(ctorReg, d1, route.model, route.id, dataSource);
|
|
174
|
-
}
|
|
175
|
-
// Services
|
|
176
|
-
if (route.method.is_static) {
|
|
177
|
-
return Either.right(ctorReg[route.namespace]);
|
|
178
|
-
}
|
|
179
|
-
return Either.right(di.get(route.namespace));
|
|
180
|
-
})();
|
|
158
|
+
const hydrated = await hydrate(di, ctorReg, route, dataSource, env);
|
|
181
159
|
if (hydrated.isLeft()) {
|
|
182
160
|
return hydrated.value;
|
|
183
161
|
}
|
|
@@ -187,8 +165,8 @@ export class CloesceApp {
|
|
|
187
165
|
/**
|
|
188
166
|
* Runs the Cloesce app. Intended to be called from the generated workers code.
|
|
189
167
|
*/
|
|
190
|
-
async run(request, env
|
|
191
|
-
|
|
168
|
+
async run(request, env) {
|
|
169
|
+
const { ast, constructorRegistry: ctorReg } = RuntimeContainer.get();
|
|
192
170
|
const di = new Map();
|
|
193
171
|
if (ast.wrangler_env) {
|
|
194
172
|
di.set(ast.wrangler_env.name, env);
|
|
@@ -196,11 +174,12 @@ export class CloesceApp {
|
|
|
196
174
|
di.set("Request", request);
|
|
197
175
|
// Note: Services are in topological order
|
|
198
176
|
for (const name in ast.services) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
177
|
+
const serviceMeta = ast.services[name];
|
|
178
|
+
const service = {};
|
|
179
|
+
for (const attr of serviceMeta.attributes) {
|
|
180
|
+
const injected = di.get(attr.inject_reference);
|
|
202
181
|
if (!injected) {
|
|
203
|
-
return exit(500, RouterError.MissingDependency, `An injected parameter was missing from the instance registry: ${JSON.stringify(attr.
|
|
182
|
+
return exit(500, RouterError.MissingDependency, `An injected parameter was missing from the instance registry: ${JSON.stringify(attr.inject_reference)}`)
|
|
204
183
|
.unwrapLeft()
|
|
205
184
|
.toResponse();
|
|
206
185
|
}
|
|
@@ -210,13 +189,10 @@ export class CloesceApp {
|
|
|
210
189
|
di.set(name, Object.assign(new ctorReg[name](), service));
|
|
211
190
|
}
|
|
212
191
|
try {
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (res) {
|
|
218
|
-
return res.toResponse();
|
|
219
|
-
}
|
|
192
|
+
const httpResult = await this.router(request, env, ast, ctorReg, di);
|
|
193
|
+
// Log any 500 errors
|
|
194
|
+
if (httpResult.status === 500) {
|
|
195
|
+
console.error("A caught error occurred in the Cloesce Router: ", httpResult.message);
|
|
220
196
|
}
|
|
221
197
|
return httpResult.toResponse();
|
|
222
198
|
}
|
|
@@ -262,10 +238,12 @@ function matchRoute(request, ast, routePrefix) {
|
|
|
262
238
|
if (parts.length < 2) {
|
|
263
239
|
return notFound(RouterError.UnknownPrefix);
|
|
264
240
|
}
|
|
265
|
-
//
|
|
241
|
+
// Route format: /{namespace}/...{id}/{method}
|
|
266
242
|
const namespace = parts[0];
|
|
267
243
|
const methodName = parts[parts.length - 1];
|
|
268
|
-
const id = parts.length
|
|
244
|
+
const id = parts.length > 2
|
|
245
|
+
? parts.slice(1, parts.length - 1).map(decodeURIComponent)
|
|
246
|
+
: [];
|
|
269
247
|
const model = ast.models[namespace];
|
|
270
248
|
if (model) {
|
|
271
249
|
const method = model.methods[methodName];
|
|
@@ -274,19 +252,25 @@ function matchRoute(request, ast, routePrefix) {
|
|
|
274
252
|
if (request.method !== method.http_verb) {
|
|
275
253
|
return notFound(RouterError.UnmatchedHttpVerb);
|
|
276
254
|
}
|
|
255
|
+
const hasPrimaryKey = model.primary_key !== null;
|
|
256
|
+
const offset = hasPrimaryKey ? 1 : 0;
|
|
257
|
+
const primaryKey = hasPrimaryKey ? (id.at(0) ?? null) : null;
|
|
258
|
+
const keyParams = Object.fromEntries(id
|
|
259
|
+
.slice(offset)
|
|
260
|
+
.map((v, i) => [model.key_params[i], decodeURIComponent(v)]));
|
|
277
261
|
return Either.right({
|
|
278
262
|
kind: "model",
|
|
279
263
|
namespace,
|
|
280
264
|
method,
|
|
281
265
|
model,
|
|
282
|
-
|
|
266
|
+
primaryKey,
|
|
267
|
+
keyParams,
|
|
283
268
|
});
|
|
284
269
|
}
|
|
285
270
|
const service = ast.services[namespace];
|
|
286
271
|
if (service) {
|
|
287
272
|
const method = service.methods[methodName];
|
|
288
|
-
|
|
289
|
-
if (!method || id)
|
|
273
|
+
if (!method || id.length > 0)
|
|
290
274
|
return notFound(RouterError.UnknownRoute);
|
|
291
275
|
if (request.method !== method.http_verb) {
|
|
292
276
|
return notFound(RouterError.UnmatchedHttpVerb);
|
|
@@ -296,7 +280,8 @@ function matchRoute(request, ast, routePrefix) {
|
|
|
296
280
|
namespace,
|
|
297
281
|
method,
|
|
298
282
|
service,
|
|
299
|
-
|
|
283
|
+
primaryKey: null,
|
|
284
|
+
keyParams: {},
|
|
300
285
|
});
|
|
301
286
|
}
|
|
302
287
|
return notFound(RouterError.UnknownRoute);
|
|
@@ -309,9 +294,20 @@ function matchRoute(request, ast, routePrefix) {
|
|
|
309
294
|
async function validateRequest(request, ast, ctorReg, route) {
|
|
310
295
|
// Error state: any missing parameter, body, or malformed input will exit with 400.
|
|
311
296
|
const invalidRequest = (c) => exit(400, c, "Invalid Request Body");
|
|
312
|
-
//
|
|
313
|
-
if (route.kind === "model" && !route.method.is_static
|
|
314
|
-
|
|
297
|
+
// Validate instantiated model ids
|
|
298
|
+
if (route.kind === "model" && !route.method.is_static) {
|
|
299
|
+
const model = route.model;
|
|
300
|
+
if (model.primary_key !== null && route.primaryKey === null) {
|
|
301
|
+
return invalidRequest(RouterError.InstantiatedMethodMissingPrimaryKey);
|
|
302
|
+
}
|
|
303
|
+
if (model.key_params.length !== Object.keys(route.keyParams).length) {
|
|
304
|
+
return invalidRequest(RouterError.InstantiatedMethodMissingKeyParam);
|
|
305
|
+
}
|
|
306
|
+
for (const keyParam of model.key_params) {
|
|
307
|
+
if (!(keyParam in route.keyParams)) {
|
|
308
|
+
return invalidRequest(RouterError.InstantiatedMethodMissingKeyParam);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
315
311
|
}
|
|
316
312
|
// Filter out injected parameters
|
|
317
313
|
const requiredParams = route.method.parameters.filter((p) => !(typeof p.cidl_type === "object" && "Inject" in p.cidl_type));
|
|
@@ -334,7 +330,7 @@ async function validateRequest(request, ast, ctorReg, route) {
|
|
|
334
330
|
break;
|
|
335
331
|
}
|
|
336
332
|
default: {
|
|
337
|
-
throw new
|
|
333
|
+
throw new InternalError("not implemented");
|
|
338
334
|
}
|
|
339
335
|
}
|
|
340
336
|
}
|
|
@@ -345,8 +341,7 @@ async function validateRequest(request, ast, ctorReg, route) {
|
|
|
345
341
|
if (!requiredParams.every((p) => p.name in params)) {
|
|
346
342
|
return invalidRequest(RouterError.RequestBodyMissingParameters);
|
|
347
343
|
}
|
|
348
|
-
// Validate all parameters type
|
|
349
|
-
// Octet streams can be passed as is
|
|
344
|
+
// Validate all parameters type. Octet streams need no validation.
|
|
350
345
|
if (route.method.parameters_media !== MediaType.Octet) {
|
|
351
346
|
for (const p of requiredParams) {
|
|
352
347
|
const res = RuntimeValidator.validate(params[p.name], p.cidl_type, ast, ctorReg);
|
|
@@ -361,50 +356,55 @@ async function validateRequest(request, ast, ctorReg, route) {
|
|
|
361
356
|
.filter((p) => typeof p.cidl_type === "object" &&
|
|
362
357
|
"DataSource" in p.cidl_type &&
|
|
363
358
|
p.cidl_type.DataSource === route.namespace)
|
|
364
|
-
.map((p) => params[p.name])
|
|
365
|
-
|
|
359
|
+
.map((p) => params[p.name])
|
|
360
|
+
.at(0);
|
|
361
|
+
if (route.kind === "model" &&
|
|
362
|
+
!route.method.is_static &&
|
|
363
|
+
dataSource === undefined) {
|
|
366
364
|
return invalidRequest(RouterError.InstantiatedMethodMissingDataSource);
|
|
367
365
|
}
|
|
368
366
|
return Either.right({ params, dataSource: dataSource ?? null });
|
|
369
367
|
}
|
|
370
368
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* @returns 404 if no record was found for the provided ID
|
|
374
|
-
* @returns 500 if the D1 database is not synced with Cloesce and yields an error
|
|
375
|
-
* @returns The instantiated model on success
|
|
369
|
+
* Hydrates a model or service instance for method dispatch.
|
|
370
|
+
* @returns 500 or the hydrated instance
|
|
376
371
|
*/
|
|
377
|
-
async function
|
|
372
|
+
async function hydrate(di, ctorReg, route, dataSource, env) {
|
|
373
|
+
if (route.kind === "service") {
|
|
374
|
+
if (route.method.is_static) {
|
|
375
|
+
return Either.right(ctorReg[route.namespace]);
|
|
376
|
+
}
|
|
377
|
+
return Either.right(di.get(route.namespace));
|
|
378
|
+
}
|
|
379
|
+
const model = route.model;
|
|
380
|
+
const modelCtor = ctorReg[model.name];
|
|
381
|
+
// Static methods operate on the class itself, no hydration needed
|
|
382
|
+
if (route.method.is_static) {
|
|
383
|
+
return Either.right(proxyCrud(modelCtor, modelCtor, env));
|
|
384
|
+
}
|
|
385
|
+
const includeTree = dataSource === NO_DATA_SOURCE
|
|
386
|
+
? null
|
|
387
|
+
: ctorReg[model.name][dataSource];
|
|
388
|
+
const orm = Orm.fromEnv(env);
|
|
378
389
|
// Error state: If some outside force tweaked the database schema, the query may fail.
|
|
379
390
|
// Otherwise, this indicates a bug in the compiler or runtime.
|
|
380
391
|
const malformedQuery = (e) => exit(500, RouterError.InvalidDatabaseQuery, `Error in hydration query, is the database out of sync with the backend?: ${e instanceof Error ? e.message : String(e)}`);
|
|
381
|
-
// Query DB
|
|
382
|
-
let records;
|
|
383
392
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return exit(404, RouterError.ModelNotFound, "Record not found");
|
|
394
|
-
}
|
|
395
|
-
if (records.error) {
|
|
396
|
-
return malformedQuery(records.error);
|
|
393
|
+
const result = await orm.get(modelCtor, {
|
|
394
|
+
id: route.primaryKey,
|
|
395
|
+
includeTree,
|
|
396
|
+
keyParams: route.keyParams,
|
|
397
|
+
});
|
|
398
|
+
// Result will only be null if the instance does not exist
|
|
399
|
+
// for a D1 query.
|
|
400
|
+
if (result === null) {
|
|
401
|
+
return exit(404, RouterError.ModelNotFound, `Model instance of type ${model.name} with primary key ${route.primaryKey} not found`);
|
|
397
402
|
}
|
|
403
|
+
return Either.right(result);
|
|
398
404
|
}
|
|
399
405
|
catch (e) {
|
|
400
|
-
return malformedQuery(e);
|
|
401
|
-
}
|
|
402
|
-
// Hydrate
|
|
403
|
-
const models = mapSql(constructorRegistry[model.name], records.results, model.data_sources[dataSource]?.tree ?? {});
|
|
404
|
-
if (models.isLeft()) {
|
|
405
|
-
return malformedQuery(models.value);
|
|
406
|
+
return malformedQuery(JSON.stringify(e));
|
|
406
407
|
}
|
|
407
|
-
return Either.right(models.unwrap()[0]);
|
|
408
408
|
}
|
|
409
409
|
/**
|
|
410
410
|
* Calls a method on a model given a list of parameters.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CidlType, CloesceAst } from "../ast";
|
|
2
|
-
import { Either } from "../ui/common";
|
|
3
2
|
import { ConstructorRegistry } from "./router";
|
|
3
|
+
import { Either } from "../common";
|
|
4
4
|
/**
|
|
5
5
|
* Runtime type validation, asserting that the structure of a value follows the
|
|
6
6
|
* correlated CidlType.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/router/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,UAAU,EAIX,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/router/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,UAAU,EAIX,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAW,MAAM,WAAW,CAAC;AAK5C;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,OAAO;gBADP,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,mBAAmB;IAGtC,MAAM,CAAC,QAAQ,CACb,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,mBAAmB,GAC3B,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;IAIpB,OAAO,CAAC,OAAO;CA0OhB"}
|
package/dist/router/validator.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NO_DATA_SOURCE, getNavigationPropertyCidlType, isNullableType, } from "../ast";
|
|
2
|
-
import { Either, b64ToU8 } from "../
|
|
2
|
+
import { Either, b64ToU8 } from "../common";
|
|
3
|
+
import { KValue } from "../ui/backend";
|
|
4
|
+
// TODO: Create a "cleaned" object only with the values checked, discarding any extra.
|
|
3
5
|
/**
|
|
4
6
|
* Runtime type validation, asserting that the structure of a value follows the
|
|
5
7
|
* correlated CidlType.
|
|
@@ -27,6 +29,10 @@ export class RuntimeValidator {
|
|
|
27
29
|
return new RuntimeValidator(ast, ctorReg).recurse(value, cidlType, false);
|
|
28
30
|
}
|
|
29
31
|
recurse(value, cidlType, isPartial) {
|
|
32
|
+
// JsonValue accepts anything
|
|
33
|
+
if (cidlType === "JsonValue") {
|
|
34
|
+
return Either.right(value);
|
|
35
|
+
}
|
|
30
36
|
isPartial ||= typeof cidlType !== "string" && "Partial" in cidlType;
|
|
31
37
|
if (value === undefined) {
|
|
32
38
|
// We will let arrays be undefined and interpret that as an empty array.
|
|
@@ -92,7 +98,7 @@ export class RuntimeValidator {
|
|
|
92
98
|
return Either.left(null);
|
|
93
99
|
const valueObj = value;
|
|
94
100
|
// Validate + instantiate PK
|
|
95
|
-
{
|
|
101
|
+
if (model.primary_key !== null) {
|
|
96
102
|
const pk = model.primary_key;
|
|
97
103
|
const res = this.recurse(valueObj[pk.name], pk.cidl_type, isPartial);
|
|
98
104
|
if (res.isLeft()) {
|
|
@@ -101,8 +107,8 @@ export class RuntimeValidator {
|
|
|
101
107
|
value[pk.name] = res.unwrap();
|
|
102
108
|
}
|
|
103
109
|
// Validate + instantiate attributes
|
|
104
|
-
for (let i = 0; i < model.
|
|
105
|
-
const attr = model.
|
|
110
|
+
for (let i = 0; i < model.columns.length; i++) {
|
|
111
|
+
const attr = model.columns[i];
|
|
106
112
|
const res = this.recurse(valueObj[attr.value.name], attr.value.cidl_type, isPartial);
|
|
107
113
|
if (res.isLeft()) {
|
|
108
114
|
return res;
|
|
@@ -118,6 +124,44 @@ export class RuntimeValidator {
|
|
|
118
124
|
}
|
|
119
125
|
value[nav.var_name] = res.unwrap();
|
|
120
126
|
}
|
|
127
|
+
// Validate KV Objects
|
|
128
|
+
for (let i = 0; i < model.kv_objects.length; i++) {
|
|
129
|
+
const kvObjMeta = model.kv_objects[i];
|
|
130
|
+
const kvObj = valueObj[kvObjMeta.value.name];
|
|
131
|
+
if (kvObj === undefined) {
|
|
132
|
+
// only allowed if partial
|
|
133
|
+
if (isPartial) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return Either.left(null);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (typeof kvObj !== "object") {
|
|
141
|
+
return Either.left(null);
|
|
142
|
+
}
|
|
143
|
+
// value["raw"] should exist and be of type kvObj.value.cidl_type
|
|
144
|
+
const rawRes = this.recurse(kvObj.raw, kvObjMeta.value.cidl_type, isPartial);
|
|
145
|
+
if (rawRes.isLeft()) {
|
|
146
|
+
return rawRes;
|
|
147
|
+
}
|
|
148
|
+
// if value["metadata"] exists, it should be a JsonValue
|
|
149
|
+
const metadataRes = this.recurse(kvObj.metadata, "JsonValue", isPartial);
|
|
150
|
+
if (metadataRes.isLeft()) {
|
|
151
|
+
return metadataRes;
|
|
152
|
+
}
|
|
153
|
+
// key must exist and be a string
|
|
154
|
+
// TODO: validate it follows the format?
|
|
155
|
+
const keyRes = this.recurse(kvObj.key, "Text", isPartial);
|
|
156
|
+
if (keyRes.isLeft()) {
|
|
157
|
+
return keyRes;
|
|
158
|
+
}
|
|
159
|
+
value[kvObjMeta.value.name] = Object.assign(new KValue(), {
|
|
160
|
+
key: keyRes.unwrap(),
|
|
161
|
+
raw: rawRes.unwrap(),
|
|
162
|
+
metadata: metadataRes.unwrap(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
121
165
|
// Don't instantiate partials
|
|
122
166
|
if (isPartial) {
|
|
123
167
|
return Either.right(value);
|
package/dist/router/wasm.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Either } from "../ui/common.js";
|
|
1
|
+
import { CloesceAst } from "../ast.js";
|
|
2
|
+
import { Either } from "../common.js";
|
|
4
3
|
/**
|
|
5
|
-
* WASM ABI
|
|
4
|
+
* Cloesce WASM ABI
|
|
6
5
|
*/
|
|
7
6
|
export interface OrmWasmExports {
|
|
8
7
|
memory: WebAssembly.Memory;
|
|
@@ -11,21 +10,20 @@ export interface OrmWasmExports {
|
|
|
11
10
|
set_meta_ptr(ptr: number, len: number): number;
|
|
12
11
|
alloc(len: number): number;
|
|
13
12
|
dealloc(ptr: number, len: number): void;
|
|
14
|
-
map_sql(model_name_ptr: number, model_name_len: number, sql_rows_ptr: number, sql_rows_len: number, include_tree_ptr: number, include_tree_len: number): boolean;
|
|
15
13
|
upsert_model(model_name_ptr: number, model_name_len: number, new_model_ptr: number, new_model_len: number, include_tree_ptr: number, include_tree_len: number): boolean;
|
|
16
|
-
|
|
14
|
+
select_model(model_name_ptr: number, model_name_len: number, from_ptr: number, from_len: number, include_tree_ptr: number, include_tree_len: number): boolean;
|
|
15
|
+
map(model_name_ptr: number, model_name_len: number, d1_result_ptr: number, d1_result_len: number, include_tree_ptr: number, include_tree_len: number): boolean;
|
|
17
16
|
}
|
|
18
|
-
/**
|
|
19
|
-
* RAII for wasm memory
|
|
20
|
-
*/
|
|
21
17
|
export declare class WasmResource {
|
|
22
18
|
private wasm;
|
|
23
19
|
ptr: number;
|
|
24
20
|
len: number;
|
|
25
|
-
constructor(
|
|
21
|
+
private constructor();
|
|
26
22
|
free(): void;
|
|
27
23
|
/**
|
|
28
|
-
* Copies a value from TS memory to WASM memory.
|
|
24
|
+
* Copies a value from TS memory to WASM memory.
|
|
25
|
+
*
|
|
26
|
+
* A subsequent call to `free` is necessary.
|
|
29
27
|
*/
|
|
30
28
|
static fromString(str: string, wasm: OrmWasmExports): WasmResource;
|
|
31
29
|
}
|
|
@@ -37,9 +35,4 @@ export declare function loadOrmWasm(ast: CloesceAst, wasm?: WebAssembly.Instance
|
|
|
37
35
|
* Returns an Either where Left is an error message and Right the raw string result.
|
|
38
36
|
*/
|
|
39
37
|
export declare function invokeOrmWasm(fn: (...args: number[]) => boolean, args: WasmResource[], wasm: OrmWasmExports): Either<string, string>;
|
|
40
|
-
/**
|
|
41
|
-
* Calls the object relational mapping function to turn a row of SQL records into
|
|
42
|
-
* an instantiated object.
|
|
43
|
-
*/
|
|
44
|
-
export declare function mapSql<T extends object>(ctor: new () => T, records: Record<string, any>[], includeTree: IncludeTree<T> | CidlIncludeTree | null): Either<string, T[]>;
|
|
45
38
|
//# sourceMappingURL=wasm.d.ts.map
|