cloesce 0.0.5-unstable.4 → 0.0.5-unstable.6

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.
@@ -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 cidl, constructor registry, and wasm binary.
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
- InstantiatedMethodMissingId = 3,
49
- RequestMissingBody = 4,
50
- RequestBodyMissingParameters = 5,
51
- RequestBodyInvalidParameter = 6,
52
- InstantiatedMethodMissingDataSource = 7,
53
- MissingDependency = 8,
54
- InvalidDatabaseQuery = 9,
55
- ModelNotFound = 10,
56
- UncaughtException = 11
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 which runs before any route matching.
64
+ * Registers global middleware that runs before all requests.
63
65
  *
64
- * TODO: Middleware may violate the API contract and return unexpected types
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
- onResult(m: ResultMiddlewareFn): void;
72
+ onRun(m: MiddlewareFn): void;
83
73
  private namespaceMiddleware;
84
74
  /**
85
- * Registers middleware for a specific namespace (being, a model or service)
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, ast: CloesceAst, ctorReg: ConstructorRegistry): Promise<Response>;
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
- id: string | null;
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":"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"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAe,MAAM,WAAW,CAAC;AAGxD,OAAO,EACL,UAAU,EACV,KAAK,EACL,SAAS,EAET,OAAO,EAER,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,MAAM,EAAiB,MAAM,cAAc,CAAC;AACrD,OAAO,EAAO,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC;AAEhE;;;GAGG;AACH,qBAAa,gBAAgB;aAGT,GAAG,EAAE,UAAU;aACf,mBAAmB,EAAE,mBAAmB;aACxC,IAAI,EAAE,cAAc;IAJtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO;WAMM,IAAI,CACf,GAAG,EAAE,UAAU,EACf,mBAAmB,EAAE,mBAAmB,EACxC,IAAI,CAAC,EAAE,WAAW,CAAC,QAAQ;IAO7B,MAAM,CAAC,GAAG,IAAI,gBAAgB;IAI9B;;OAEG;IACH,MAAM,CAAC,OAAO;CAGf;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD,MAAM,MAAM,YAAY,GAAG,CACzB,EAAE,EAAE,mBAAmB,KACpB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;AAEhC,MAAM,MAAM,kBAAkB,GAAG,CAC/B,EAAE,EAAE,mBAAmB,EACvB,MAAM,EAAE,UAAU,KACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;AAEhC;;GAEG;AACH,oBAAY,WAAW;IACrB,aAAa,IAAA;IACb,YAAY,IAAA;IACZ,iBAAiB,IAAA;IACjB,mCAAmC,IAAA;IACnC,iCAAiC,IAAA;IACjC,kBAAkB,IAAA;IAClB,4BAA4B,IAAA;IAC5B,2BAA2B,IAAA;IAC3B,mCAAmC,IAAA;IACnC,iBAAiB,IAAA;IACjB,oBAAoB,KAAA;IACpB,aAAa,KAAA;IACb,iBAAiB,KAAA;CAClB;AAED,qBAAa,UAAU;IACd,WAAW,EAAE,MAAM,CAAS;WAEf,IAAI,CACtB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC;IAKtB,OAAO,CAAC,gBAAgB,CAAsB;IAE9C;;;;;;;;OAQG;IACI,KAAK,CAAC,CAAC,EAAE,YAAY;IAI5B,OAAO,CAAC,mBAAmB,CAA0C;IAErE;;;;;;;;;;OAUG;IACI,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE,YAAY;IAUxD,OAAO,CAAC,gBAAgB,CACZ;IAEZ;;;;;;;;;;;OAWG;IACI,QAAQ,CAAC,CAAC,EACf,IAAI,EAAE,UAAU,CAAC,EACjB,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAC5C,CAAC,EAAE,YAAY;YAiBH,MAAM;IA0DpB;;OAEG;IACU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;CAsEhE;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,iBAAS,UAAU,CACjB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,EACf,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CA0ElC;AAED;;;;GAIG;AACH,iBAAe,eAAe,CAC5B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,YAAY,GAClB,OAAO,CACR,MAAM,CAAC,UAAU,EAAE;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAC3E,CAmGA;AAoED;;;GAGG;AACH,iBAAe,cAAc,CAC3B,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,mBAAmB,EACvB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CA2C9B;AAaD;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;CAK5B,CAAC"}
@@ -1,11 +1,11 @@
1
- import { mapSql, loadOrmWasm } from "./wasm.js";
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 cidl, constructor registry, and wasm binary.
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["InstantiatedMethodMissingId"] = 3] = "InstantiatedMethodMissingId";
46
- RouterError[RouterError["RequestMissingBody"] = 4] = "RequestMissingBody";
47
- RouterError[RouterError["RequestBodyMissingParameters"] = 5] = "RequestBodyMissingParameters";
48
- RouterError[RouterError["RequestBodyInvalidParameter"] = 6] = "RequestBodyInvalidParameter";
49
- RouterError[RouterError["InstantiatedMethodMissingDataSource"] = 7] = "InstantiatedMethodMissingDataSource";
50
- RouterError[RouterError["MissingDependency"] = 8] = "MissingDependency";
51
- RouterError[RouterError["InvalidDatabaseQuery"] = 9] = "InvalidDatabaseQuery";
52
- RouterError[RouterError["ModelNotFound"] = 10] = "ModelNotFound";
53
- RouterError[RouterError["UncaughtException"] = 11] = "UncaughtException";
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
- globalMiddleware = [];
58
- /**
59
- * Registers global middleware which runs before any route matching.
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
- resultMiddleware = [];
62
+ globalMiddleware = [];
69
63
  /**
70
- * Registers middleware which runs after the response is generated, but before
71
- * it is returned to the client.
64
+ * Registers global middleware that runs before all requests.
72
65
  *
73
- * Optionally, return a new HttpResult to short-circuit the response.
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
- onResult(m) {
82
- this.resultMiddleware.push(m);
72
+ onRun(m) {
73
+ this.globalMiddleware.push(m);
83
74
  }
84
75
  namespaceMiddleware = new Map();
85
76
  /**
86
- * Registers middleware for a specific namespace (being, a model or service)
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
- if (this.namespaceMiddleware.has(ctor.name)) {
98
- this.namespaceMiddleware.get(ctor.name).push(m);
99
- }
100
- else {
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
- if (!this.methodMiddleware.has(ctor.name)) {
119
- this.methodMiddleware.set(ctor.name, new Map());
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
- const methods = this.methodMiddleware.get(ctor.name);
122
- if (!methods.has(method)) {
123
- methods.set(method, []);
114
+ let methodArray = classMap.get(method);
115
+ if (!methodArray) {
116
+ methodArray = [];
117
+ classMap.set(method, methodArray);
124
118
  }
125
- methods.get(method).push(m);
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
- // Model middleware
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 (async () => {
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, ast, ctorReg) {
191
- await RuntimeContainer.init(ast, ctorReg);
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 service = ast.services[name];
200
- for (const attr of service.attributes) {
201
- const injected = di.get(attr.injected);
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.injected)}`)
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
- let httpResult = await this.router(request, env, ast, ctorReg, di);
214
- // Response middleware
215
- for (const m of this.resultMiddleware) {
216
- const res = await m(di, httpResult);
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
- // Extract pattern
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 === 3 ? parts[1] : null;
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
- id,
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
- // Services do not have IDs.
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
- id: null,
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
- // Models must have an ID on instantiated methods.
313
- if (route.kind === "model" && !route.method.is_static && route.id == null) {
314
- return invalidRequest(RouterError.InstantiatedMethodMissingId);
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 Error("not implemented");
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])[0];
365
- if (route.kind === "model" && !route.method.is_static && !dataSource) {
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
- * Queries D1 for a particular model's ID, then transforms the SQL column output into
372
- * an instance of a model using the provided include tree and metadata as a guide.
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 hydrateModelD1(constructorRegistry, d1, model, id, dataSource) {
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
- let includeTree = dataSource === NO_DATA_SOURCE
385
- ? null
386
- : constructorRegistry[model.name][dataSource];
387
- records = await d1
388
- .prepare(Orm.getQuery(constructorRegistry[model.name], includeTree))
389
- .bind(id)
390
- .run();
391
- // Error state: If no record is found for the id, return a 404
392
- if (!records?.results) {
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,MAAM,EAAW,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;;;;;;;;;;;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;CA+KhB"}
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"}
@@ -1,5 +1,7 @@
1
1
  import { NO_DATA_SOURCE, getNavigationPropertyCidlType, isNullableType, } from "../ast";
2
- import { Either, b64ToU8 } from "../ui/common";
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.attributes.length; i++) {
105
- const attr = model.attributes[i];
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);
@@ -1,8 +1,7 @@
1
- import { CidlIncludeTree, CloesceAst } from "../ast.js";
2
- import { IncludeTree } from "../ui/backend.js";
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
- list_models(model_name_ptr: number, model_name_len: number, include_tree_ptr: number, include_tree_len: number, tag_cte_ptr: number, tag_cte_len: number, custom_from_ptr: number, custom_from_len: number): boolean;
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(wasm: OrmWasmExports, ptr: number, len: number);
21
+ private constructor();
26
22
  free(): void;
27
23
  /**
28
- * Copies a value from TS memory to WASM memory. A subsequent `free` is necessary.
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