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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloesce.d.ts","sourceRoot":"","sources":["../src/cloesce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,453 @@
1
+ import { left, right, isNullableType, getNavigationPropertyCidlType, } from "./common.js";
2
+ /**
3
+ * Singleton instances of the MetaCidl and Constructor Registry.
4
+ * These values are guaranteed to never change throughout a workers lifetime.
5
+ */
6
+ class MetaContainer {
7
+ ast;
8
+ constructorRegistry;
9
+ static instance;
10
+ constructor(ast, constructorRegistry) {
11
+ this.ast = ast;
12
+ this.constructorRegistry = constructorRegistry;
13
+ }
14
+ static init(ast, constructorRegistry) {
15
+ if (!this.instance) {
16
+ this.instance = new MetaContainer(ast, constructorRegistry);
17
+ }
18
+ }
19
+ static get() {
20
+ return this.instance;
21
+ }
22
+ }
23
+ /**
24
+ * Creates model instances given a properly formatted SQL record
25
+ * (either a foreign-key-less model or derived from a Cloesce generated view)
26
+ * @param ctor The type of the model
27
+ * @param records SQL records
28
+ * @param includeTree The include tree to use when parsing the records
29
+ * @returns
30
+ */
31
+ export function modelsFromSql(ctor, records, includeTree) {
32
+ const { ast, constructorRegistry } = MetaContainer.get();
33
+ return _modelsFromSql(ctor.name, ast, constructorRegistry, records, includeTree);
34
+ }
35
+ /**
36
+ * Cloesce entry point. Given a request, undergoes routing, validating,
37
+ * hydrating, and method dispatch.
38
+ * @param ast The CIDL AST
39
+ * @param constructorRegistry A mapping of user defined class names to their respective constructor
40
+ * @param instanceRegistry A mapping of a dependency class name to its instantiated object.
41
+ * @param request An incoming request to the workers server
42
+ * @param api_route The url's path to the api, e.g. api/v1/fooapi/
43
+ * @param envMeta Meta information on the wrangler env and D1 databases
44
+ * @returns A Response with an `HttpResult` JSON body.
45
+ */
46
+ export async function cloesce(request, ast, constructorRegistry, instanceRegistry, envMeta, api_route) {
47
+ MetaContainer.init(ast, constructorRegistry);
48
+ const d1 = instanceRegistry.get(envMeta.envName)[envMeta.dbName];
49
+ // Match the route to a model method
50
+ const route = matchRoute(request, ast, api_route);
51
+ if (!route.ok) {
52
+ return toResponse(route.value);
53
+ }
54
+ const { method, model, id } = route.value;
55
+ // Validate request body to the model method
56
+ const isValidRequest = await validateRequest(request, ast, model, method, id);
57
+ if (!isValidRequest.ok) {
58
+ return toResponse(isValidRequest.value);
59
+ }
60
+ const [requestParamMap, dataSource] = isValidRequest.value;
61
+ // Instantatiate the model
62
+ let instance;
63
+ if (method.is_static) {
64
+ instance = constructorRegistry[model.name];
65
+ }
66
+ else {
67
+ const successfulModel = await hydrateModel(ast, constructorRegistry, d1, model, id, dataSource);
68
+ if (!successfulModel.ok) {
69
+ return toResponse(successfulModel.value);
70
+ }
71
+ instance = successfulModel.value;
72
+ }
73
+ // Dispatch a method on the model and return the result
74
+ return toResponse(await methodDispatch(instance, instanceRegistry, envMeta, method, requestParamMap));
75
+ }
76
+ /**
77
+ * Matches a request to a method on a model.
78
+ * @param api_route The route from the domain to the actual API, ie https://foo.com/route/to/api => route/to/api/
79
+ * @returns 404 or a `MatchedRoute`
80
+ */
81
+ function matchRoute(request, ast, api_route) {
82
+ const url = new URL(request.url);
83
+ const notFound = (e) => left(errorState(404, `Path not found: ${e} ${url.pathname}`));
84
+ const routeParts = url.pathname
85
+ .slice(api_route.length)
86
+ .split("/")
87
+ .filter(Boolean);
88
+ if (routeParts.length < 2) {
89
+ return notFound("Expected /model/method or /model/:id/method");
90
+ }
91
+ // Attempt to extract from routeParts
92
+ const modelName = routeParts[0];
93
+ const methodName = routeParts[routeParts.length - 1];
94
+ const id = routeParts.length === 3 ? routeParts[1] : null;
95
+ const model = ast.models[modelName];
96
+ if (!model) {
97
+ return notFound(`Unknown model ${modelName}`);
98
+ }
99
+ const method = model.methods[methodName];
100
+ if (!method) {
101
+ return notFound(`Unknown method ${modelName}.${methodName}`);
102
+ }
103
+ if (request.method !== method.http_verb) {
104
+ return notFound("Unmatched HTTP method");
105
+ }
106
+ return right({
107
+ model,
108
+ method,
109
+ id,
110
+ });
111
+ }
112
+ /**
113
+ * Validates the request's body/search params against a ModelMethod
114
+ * @returns 400 or a `RequestParamMap` consisting of each parameters name mapped to its value, and
115
+ * a data source
116
+ */
117
+ async function validateRequest(request, ast, model, method, id) {
118
+ // Error state: any missing parameter, body, or malformed input will exit with 400.
119
+ const invalidRequest = (e) => left(errorState(400, `Invalid Request Body: ${e}`));
120
+ if (!method.is_static && id == null) {
121
+ return invalidRequest("Id's are required for instantiated methods.");
122
+ }
123
+ // Filter out any injected parameters that will not be passed
124
+ // by the query.
125
+ const requiredParams = method.parameters.filter((p) => !(typeof p.cidl_type === "object" &&
126
+ p.cidl_type !== null &&
127
+ "Inject" in p.cidl_type));
128
+ // Extract data source
129
+ const url = new URL(request.url);
130
+ let dataSource = url.searchParams.get("dataSource");
131
+ // Extract url or body parameters
132
+ let requestBodyMap;
133
+ if (method.http_verb === "GET") {
134
+ requestBodyMap = Object.fromEntries(url.searchParams.entries());
135
+ }
136
+ else {
137
+ try {
138
+ requestBodyMap = await request.json();
139
+ }
140
+ catch {
141
+ return invalidRequest("Could not retrieve JSON body.");
142
+ }
143
+ }
144
+ // Validate data source if exists
145
+ if (dataSource && !(dataSource in model.data_sources)) {
146
+ return invalidRequest(`Unknown data source ${dataSource}`);
147
+ }
148
+ // Ensure all required params exist
149
+ if (!requiredParams.every((p) => p.name in requestBodyMap)) {
150
+ return invalidRequest(`Missing parameters.`);
151
+ }
152
+ // Validate all parameters type
153
+ for (const p of requiredParams) {
154
+ const value = requestBodyMap[p.name];
155
+ if (!validateCidlType(ast, value, p.cidl_type)) {
156
+ return invalidRequest("Invalid parameters.");
157
+ }
158
+ }
159
+ return right([requestBodyMap, dataSource]);
160
+ }
161
+ /**
162
+ * Queries D1 for a particular model's ID, then transforms the SQL column output into
163
+ * an instance of a model using the provided include tree and metadata as a guide.
164
+ * @returns 404 if no record was found for the provided ID
165
+ * @returns 500 if the D1 database is not synced with Cloesce and yields an error
166
+ * @returns The instantiated model on success
167
+ */
168
+ async function hydrateModel(ast, constructorRegistry, d1, model, id, dataSource) {
169
+ // Error state: If the D1 database has been tweaked outside of Cloesce
170
+ // resulting in a malformed query, exit with a 500.
171
+ const malformedQuery = (e) => left(errorState(500, `Error in hydration query, is the database out of sync with the backend?: ${e instanceof Error ? e.message : String(e)}`));
172
+ // Error state: If no record is found for the id, return a 404
173
+ const missingRecord = left(errorState(404, "Record not found"));
174
+ const pk = model.primary_key.name;
175
+ const query = dataSource !== null
176
+ ? `SELECT * FROM "${model.name}.${dataSource}" WHERE "${model.name}.${pk}" = ?`
177
+ : `SELECT * FROM "${model.name}" WHERE "${pk}" = ?`;
178
+ // Query DB
179
+ let records;
180
+ try {
181
+ records = await d1.prepare(query).bind(id).run();
182
+ if (!records) {
183
+ return missingRecord;
184
+ }
185
+ if (records.error) {
186
+ return malformedQuery(records.error);
187
+ }
188
+ }
189
+ catch (e) {
190
+ return malformedQuery(e);
191
+ }
192
+ // Get include tree
193
+ const includeTree = dataSource !== null ? model.data_sources[dataSource].tree : {};
194
+ // Hydrate
195
+ const models = _modelsFromSql(model.name, ast, constructorRegistry, records.results, includeTree);
196
+ console.log(JSON.stringify(models));
197
+ return right(models[0]);
198
+ }
199
+ /**
200
+ * Calls a method on a model given a list of parameters.
201
+ * @returns 500 on an uncaught client error, 200 with a result body on success
202
+ */
203
+ async function methodDispatch(instance, instanceRegistry, envMeta, method, params) {
204
+ // Error state: Client code ran into an uncaught exception.
205
+ const uncaughtException = (e) => errorState(500, `Uncaught exception in method dispatch: ${e instanceof Error ? e.message : String(e)}`);
206
+ // For now, the only injected dependency is the wrangler env,
207
+ // so we will assume that is what this is
208
+ const paramArray = method.parameters.map((p) => params[p.name] == undefined
209
+ ? instanceRegistry.get(envMeta.envName)
210
+ : params[p.name]);
211
+ // Ensure the result is always some HttpResult
212
+ const resultWrapper = (res) => {
213
+ const rt = method.return_type;
214
+ if (rt === null) {
215
+ return { ok: true, status: 200 };
216
+ }
217
+ if (typeof rt === "object" && rt !== null && "HttpResult" in rt) {
218
+ return res;
219
+ }
220
+ return { ok: true, status: 200, data: res };
221
+ };
222
+ try {
223
+ return resultWrapper(await instance[method.name](...paramArray));
224
+ }
225
+ catch (e) {
226
+ return uncaughtException(e);
227
+ }
228
+ }
229
+ function validateCidlType(ast, value, cidlType) {
230
+ if (value === undefined)
231
+ return false;
232
+ // TODO: consequences of null checking like this? 'null' is passed in
233
+ // as a string for GET requests...
234
+ const nullable = isNullableType(cidlType);
235
+ if (value == null || value === "null")
236
+ return nullable;
237
+ if (nullable) {
238
+ cidlType = cidlType.Nullable; // Unwrap the nullable type
239
+ }
240
+ // Handle primitive string types with switch
241
+ if (typeof cidlType === "string") {
242
+ switch (cidlType) {
243
+ case "Integer":
244
+ return Number.isInteger(Number(value));
245
+ case "Real":
246
+ return !Number.isNaN(Number(value));
247
+ case "Text":
248
+ return typeof value === "string";
249
+ case "Blob":
250
+ return value instanceof Blob || value instanceof ArrayBuffer;
251
+ default:
252
+ return false;
253
+ }
254
+ }
255
+ // Handle Models
256
+ if ("Object" in cidlType && ast.models[cidlType.Object]) {
257
+ const model = ast.models[cidlType.Object];
258
+ if (!model || typeof value !== "object")
259
+ return false;
260
+ const valueObj = value;
261
+ // Validate attributes
262
+ if (!model.attributes.every((attr) => validateCidlType(ast, valueObj[attr.value.name], attr.value.cidl_type))) {
263
+ return false;
264
+ }
265
+ // Validate navigation properties
266
+ return model.navigation_properties.every((nav) => {
267
+ const navValue = valueObj[nav.var_name];
268
+ return (navValue == null ||
269
+ validateCidlType(ast, navValue, getNavigationPropertyCidlType(nav)));
270
+ });
271
+ }
272
+ // Handle Plain Old Objects
273
+ if ("Object" in cidlType && ast.poos[cidlType.Object]) {
274
+ const poo = ast.poos[cidlType.Object];
275
+ if (!poo || typeof value !== "object")
276
+ return false;
277
+ const valueObj = value;
278
+ // Validate attributes
279
+ if (!poo.attributes.every((attr) => validateCidlType(ast, valueObj[attr.name], attr.cidl_type))) {
280
+ return false;
281
+ }
282
+ }
283
+ if ("Array" in cidlType) {
284
+ const arr = cidlType.Array;
285
+ return (Array.isArray(value) && value.every((v) => validateCidlType(ast, v, arr)));
286
+ }
287
+ if ("HttpResult" in cidlType) {
288
+ if (value === null)
289
+ return cidlType.HttpResult === null;
290
+ if (cidlType.HttpResult === null)
291
+ return false;
292
+ return validateCidlType(ast, value, cidlType.HttpResult);
293
+ }
294
+ return false;
295
+ }
296
+ /**
297
+ * Actual implementation of sql to model mapping.
298
+ *
299
+ * TODO: If we don't want to write this in every language, would it be possible to create a
300
+ * single WASM binary for this method?
301
+ *
302
+ * @throws generic errors if the metadata is missing some value
303
+ */
304
+ // Main function that creates instances from SQL records
305
+ function _modelsFromSql(modelName, ast, constructorRegistry, records, includeTree) {
306
+ const model = ast.models[modelName];
307
+ if (!model)
308
+ return [];
309
+ const Constructor = constructorRegistry[modelName];
310
+ if (!Constructor)
311
+ return [];
312
+ const pkName = model.primary_key.name;
313
+ const resultMap = new Map();
314
+ for (const record of records) {
315
+ const pkValue = record[`${modelName}.${pkName}`] ?? record[pkName];
316
+ if (pkValue == null)
317
+ continue;
318
+ let instance = resultMap.get(pkValue);
319
+ if (!instance) {
320
+ instance = new Constructor();
321
+ instance[pkName] = pkValue;
322
+ // Set scalar attributes
323
+ for (const attr of model.attributes) {
324
+ const attrName = attr.value.name;
325
+ const prefixedKey = `${modelName}.${attrName}`;
326
+ const nonPrefixedKey = attrName;
327
+ if (prefixedKey in record) {
328
+ instance[attrName] = record[prefixedKey];
329
+ }
330
+ else if (nonPrefixedKey in record) {
331
+ instance[attrName] = record[nonPrefixedKey];
332
+ }
333
+ }
334
+ // Initialize ALL navigation properties at root level
335
+ // If not in include tree, initialize OneToMany and ManyToMany as empty arrays
336
+ for (const navProp of model.navigation_properties) {
337
+ if ("OneToMany" in navProp.kind || "ManyToMany" in navProp.kind) {
338
+ // Always initialize OneToMany and ManyToMany as empty arrays
339
+ instance[navProp.var_name] = [];
340
+ }
341
+ // OneToOne properties left as undefined unless populated
342
+ }
343
+ resultMap.set(pkValue, instance);
344
+ }
345
+ // Process navigation properties that are in the include tree
346
+ if (includeTree) {
347
+ processNavigationProperties(instance, model, modelName, includeTree, record, ast, constructorRegistry);
348
+ }
349
+ }
350
+ return Array.from(resultMap.values());
351
+ }
352
+ function processNavigationProperties(instance, model, prefix, includeTree, record, ast, constructorRegistry) {
353
+ for (const navProp of model.navigation_properties) {
354
+ if (!(navProp.var_name in includeTree)) {
355
+ continue;
356
+ }
357
+ const nestedModel = ast.models[navProp.model_name];
358
+ if (!nestedModel) {
359
+ continue;
360
+ }
361
+ // Extract nested model's primary key - check both prefixed and non-prefixed
362
+ const nestedPkName = nestedModel.primary_key.name;
363
+ const prefixedNestedPkKey = `${prefix}.${navProp.var_name}.${nestedPkName}`;
364
+ const nonPrefixedNestedPkKey = `${navProp.var_name}.${nestedPkName}`;
365
+ const nestedPkValue = record[prefixedNestedPkKey] ?? record[nonPrefixedNestedPkKey];
366
+ if (nestedPkValue == null) {
367
+ continue; // No nested object in this row
368
+ }
369
+ // Determine if this is OneToMany/ManyToMany or OneToOne
370
+ const isOneToMany = "OneToMany" in navProp.kind || "ManyToMany" in navProp.kind;
371
+ // Check if we already added this nested object (for OneToMany)
372
+ if (isOneToMany) {
373
+ const navArray = instance[navProp.var_name];
374
+ const alreadyExists = navArray.some((item) => item[nestedPkName] === nestedPkValue);
375
+ if (alreadyExists) {
376
+ continue;
377
+ }
378
+ }
379
+ else {
380
+ // For OneToOne, check if already set
381
+ if (instance[navProp.var_name] != null) {
382
+ continue;
383
+ }
384
+ }
385
+ const NestedConstructor = constructorRegistry[navProp.model_name];
386
+ if (!NestedConstructor) {
387
+ continue;
388
+ }
389
+ const nestedInstance = new NestedConstructor();
390
+ nestedInstance[nestedPkName] = nestedPkValue;
391
+ // Assign nested scalar attributes - check both prefixed and non-prefixed
392
+ for (const nestedAttr of nestedModel.attributes) {
393
+ const nestedAttrName = nestedAttr.value.name;
394
+ const prefixedKey = `${prefix}.${navProp.var_name}.${nestedAttrName}`;
395
+ const nonPrefixedKey = `${navProp.var_name}.${nestedAttrName}`;
396
+ // Check prefixed key first, then non-prefixed
397
+ if (prefixedKey in record) {
398
+ nestedInstance[nestedAttrName] = record[prefixedKey];
399
+ }
400
+ else if (nonPrefixedKey in record) {
401
+ nestedInstance[nestedAttrName] = record[nonPrefixedKey];
402
+ }
403
+ }
404
+ // Initialize ALL navigation properties on the nested instance
405
+ // If not in include tree, initialize OneToMany and ManyToMany as empty arrays
406
+ const nestedIncludeTree = includeTree[navProp.var_name];
407
+ for (const nestedNavProp of nestedModel.navigation_properties) {
408
+ const isInIncludeTree = nestedIncludeTree &&
409
+ typeof nestedIncludeTree === "object" &&
410
+ nestedNavProp.var_name in nestedIncludeTree;
411
+ if ("OneToMany" in nestedNavProp.kind ||
412
+ "ManyToMany" in nestedNavProp.kind) {
413
+ // Always initialize OneToMany and ManyToMany as arrays (empty if not in include tree)
414
+ nestedInstance[nestedNavProp.var_name] = [];
415
+ }
416
+ else if (!isInIncludeTree) {
417
+ // OneToOne not in include tree - leave as undefined or null
418
+ // Will be set during recursive processing if in include tree
419
+ }
420
+ }
421
+ // Recursively process nested navigation properties that are in the include tree
422
+ if (nestedIncludeTree && typeof nestedIncludeTree === "object") {
423
+ processNavigationProperties(nestedInstance, nestedModel, `${prefix}.${navProp.var_name}`, nestedIncludeTree, record, ast, constructorRegistry);
424
+ }
425
+ // Assign the nested instance based on relationship type
426
+ if (isOneToMany) {
427
+ instance[navProp.var_name].push(nestedInstance);
428
+ }
429
+ else {
430
+ // OneToOne - assign directly
431
+ instance[navProp.var_name] = nestedInstance;
432
+ }
433
+ }
434
+ }
435
+ function errorState(status, message) {
436
+ return { ok: false, status, message };
437
+ }
438
+ function toResponse(r) {
439
+ return new Response(JSON.stringify(r), {
440
+ status: r.status,
441
+ headers: { "Content-Type": "application/json" },
442
+ });
443
+ }
444
+ /**
445
+ * Each individual state of the `cloesce` function for testing purposes.
446
+ */
447
+ export const _cloesceInternal = {
448
+ matchRoute,
449
+ validateRequest,
450
+ hydrateModel,
451
+ methodDispatch,
452
+ _modelsFromSql,
453
+ };
@@ -0,0 +1,96 @@
1
+ export type Either<L, R> = {
2
+ ok: false;
3
+ value: L;
4
+ } | {
5
+ ok: true;
6
+ value: R;
7
+ };
8
+ export declare function left<L>(value: L): Either<L, never>;
9
+ export declare function right<R>(value: R): Either<never, R>;
10
+ export type HttpResult<T = unknown> = {
11
+ ok: boolean;
12
+ status: number;
13
+ data?: T;
14
+ message?: string;
15
+ };
16
+ export type CidlType = "Void" | "Integer" | "Real" | "Text" | "Blob" | {
17
+ Inject: string;
18
+ } | {
19
+ Model: string;
20
+ } | {
21
+ Nullable: CidlType;
22
+ } | {
23
+ Array: CidlType;
24
+ } | {
25
+ HttpResult: CidlType;
26
+ };
27
+ export declare function isNullableType(ty: CidlType): boolean;
28
+ export declare enum HttpVerb {
29
+ GET = "GET",
30
+ POST = "POST",
31
+ PUT = "PUT",
32
+ PATCH = "PATCH",
33
+ DELETE = "DELETE"
34
+ }
35
+ export interface NamedTypedValue {
36
+ name: string;
37
+ cidl_type: CidlType;
38
+ }
39
+ export interface ModelAttribute {
40
+ value: NamedTypedValue;
41
+ foreign_key_reference: string | null;
42
+ }
43
+ export interface ModelMethod {
44
+ name: string;
45
+ is_static: boolean;
46
+ http_verb: HttpVerb;
47
+ return_type: CidlType | null;
48
+ parameters: NamedTypedValue[];
49
+ }
50
+ export type NavigationPropertyKind = {
51
+ OneToOne: {
52
+ reference: string;
53
+ };
54
+ } | {
55
+ OneToMany: {
56
+ reference: string;
57
+ };
58
+ } | {
59
+ ManyToMany: {
60
+ unique_id: string;
61
+ };
62
+ };
63
+ export interface NavigationProperty {
64
+ var_name: string;
65
+ model_name: string;
66
+ kind: NavigationPropertyKind;
67
+ }
68
+ export declare function getNavigationPropertyCidlType(nav: NavigationProperty): CidlType;
69
+ export interface Model {
70
+ name: string;
71
+ primary_key: NamedTypedValue;
72
+ attributes: ModelAttribute[];
73
+ navigation_properties: NavigationProperty[];
74
+ methods: Record<string, ModelMethod>;
75
+ data_sources: Record<string, DataSource>;
76
+ source_path: string;
77
+ }
78
+ export interface CidlIncludeTree {
79
+ [key: string]: CidlIncludeTree;
80
+ }
81
+ export interface DataSource {
82
+ name: string;
83
+ tree: CidlIncludeTree;
84
+ }
85
+ export interface WranglerEnv {
86
+ name: string;
87
+ source_path: string;
88
+ }
89
+ export interface CloesceAst {
90
+ version: string;
91
+ project_name: string;
92
+ language: "TypeScript";
93
+ wrangler_env: WranglerEnv;
94
+ models: Record<string, Model>;
95
+ }
96
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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"}
package/dist/common.js ADDED
@@ -0,0 +1,96 @@
1
+ export function left(value) {
2
+ return { ok: false, value };
3
+ }
4
+ export function right(value) {
5
+ return { ok: true, value };
6
+ }
7
+ export var ExtractorErrorCode;
8
+ (function (ExtractorErrorCode) {
9
+ ExtractorErrorCode[ExtractorErrorCode["UnknownType"] = 0] = "UnknownType";
10
+ ExtractorErrorCode[ExtractorErrorCode["MultipleGenericType"] = 1] = "MultipleGenericType";
11
+ ExtractorErrorCode[ExtractorErrorCode["InvalidIncludeTree"] = 2] = "InvalidIncludeTree";
12
+ ExtractorErrorCode[ExtractorErrorCode["UnknownNavigationPropertyReference"] = 3] = "UnknownNavigationPropertyReference";
13
+ ExtractorErrorCode[ExtractorErrorCode["InvalidNavigationPropertyReference"] = 4] = "InvalidNavigationPropertyReference";
14
+ ExtractorErrorCode[ExtractorErrorCode["MissingNavigationPropertyReference"] = 5] = "MissingNavigationPropertyReference";
15
+ ExtractorErrorCode[ExtractorErrorCode["MissingManyToManyUniqueId"] = 6] = "MissingManyToManyUniqueId";
16
+ ExtractorErrorCode[ExtractorErrorCode["MissingPrimaryKey"] = 7] = "MissingPrimaryKey";
17
+ ExtractorErrorCode[ExtractorErrorCode["MissingWranglerEnv"] = 8] = "MissingWranglerEnv";
18
+ ExtractorErrorCode[ExtractorErrorCode["TooManyWranglerEnvs"] = 9] = "TooManyWranglerEnvs";
19
+ ExtractorErrorCode[ExtractorErrorCode["MissingFile"] = 10] = "MissingFile";
20
+ })(ExtractorErrorCode || (ExtractorErrorCode = {}));
21
+ const errorInfoMap = {
22
+ [ExtractorErrorCode.UnknownType]: {
23
+ description: "Encountered an unknown or unsupported type",
24
+ suggestion: "Refer to the documentation on valid Cloesce TS types",
25
+ },
26
+ [ExtractorErrorCode.MultipleGenericType]: {
27
+ description: "Cloesce does not yet support types with multiple generics",
28
+ suggestion: "Simplify your type to use only a single generic parameter, ie Foo<T>",
29
+ },
30
+ [ExtractorErrorCode.InvalidIncludeTree]: {
31
+ description: "Invalid Include Tree",
32
+ suggestion: "Include trees must only contain references to a model's navigation properties.",
33
+ },
34
+ [ExtractorErrorCode.UnknownNavigationPropertyReference]: {
35
+ description: "Unknown Navigation Property Reference",
36
+ suggestion: "Verify that the navigation property reference model exists, or create a model.",
37
+ },
38
+ [ExtractorErrorCode.InvalidNavigationPropertyReference]: {
39
+ description: "Invalid Navigation Property Reference",
40
+ suggestion: "Ensure the navigation property points to a valid model field",
41
+ },
42
+ [ExtractorErrorCode.MissingNavigationPropertyReference]: {
43
+ description: "Missing Navigation Property Reference",
44
+ suggestion: "Navigation properties require a foreign key model attribute reference",
45
+ },
46
+ [ExtractorErrorCode.MissingManyToManyUniqueId]: {
47
+ description: "Missing unique id on Many to Many navigation property",
48
+ suggestion: "Define a unique identifier field for the Many-to-Many relationship",
49
+ },
50
+ [ExtractorErrorCode.MissingPrimaryKey]: {
51
+ description: "Missing primary key on a model",
52
+ suggestion: "Add a primary key field to your model (e.g., `id: number`)",
53
+ },
54
+ [ExtractorErrorCode.MissingWranglerEnv]: {
55
+ description: "Missing a wrangler environment definition in the project",
56
+ suggestion: "Add a @WranglerEnv class in your project.",
57
+ },
58
+ [ExtractorErrorCode.TooManyWranglerEnvs]: {
59
+ description: "Too many wrangler environments defined in the project",
60
+ suggestion: "Consolidate or remove unused @WranglerEnv's",
61
+ },
62
+ [ExtractorErrorCode.MissingFile]: {
63
+ description: "A specified input file could not be found",
64
+ suggestion: "Verify the input file path is correct",
65
+ },
66
+ };
67
+ export function getErrorInfo(code) {
68
+ return errorInfoMap[code];
69
+ }
70
+ export class ExtractorError {
71
+ code;
72
+ context;
73
+ snippet;
74
+ constructor(code) {
75
+ this.code = code;
76
+ }
77
+ addContext(fn) {
78
+ this.context = fn(this.context ?? "");
79
+ }
80
+ }
81
+ export function isNullableType(ty) {
82
+ return typeof ty === "object" && ty !== null && "Nullable" in ty;
83
+ }
84
+ export var HttpVerb;
85
+ (function (HttpVerb) {
86
+ HttpVerb["GET"] = "GET";
87
+ HttpVerb["POST"] = "POST";
88
+ HttpVerb["PUT"] = "PUT";
89
+ HttpVerb["PATCH"] = "PATCH";
90
+ HttpVerb["DELETE"] = "DELETE";
91
+ })(HttpVerb || (HttpVerb = {}));
92
+ export function getNavigationPropertyCidlType(nav) {
93
+ return "OneToOne" in nav.kind
94
+ ? { Object: nav.model_name }
95
+ : { Array: { Object: nav.model_name } };
96
+ }
@@ -0,0 +1,13 @@
1
+ import type { Handler } from "./types.js";
2
+ /** Use as @GET (no parentheses) */
3
+ export declare function GET(_value: Handler, _ctx: ClassMethodDecoratorContext): void;
4
+ /** Use as @POST (no parentheses) */
5
+ export declare function POST(_value: Handler, _ctx: ClassMethodDecoratorContext): void;
6
+ export declare function PUT(_value: Handler, _ctx: ClassMethodDecoratorContext): void;
7
+ export declare function PATCH(_value: Handler, _ctx: ClassMethodDecoratorContext): void;
8
+ export declare function DELETE(_value: Handler, _ctx: ClassMethodDecoratorContext): void;
9
+ /** Class decorator (no-op) */
10
+ export declare function D1<T extends new (...a: any[]) => object>(value: T, _ctx: ClassDecoratorContext<T>): T;
11
+ /** Field decorator (no-op) */
12
+ export declare function PrimaryKey(_v: undefined, _ctx: ClassFieldDecoratorContext): void;
13
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,mCAAmC;AACnC,wBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,QAAI;AAE1E,oCAAoC;AACpC,wBAAgB,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,QAAI;AAE3E,wBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,QAAI;AAE1E,wBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,QAAI;AAE5E,wBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,QAAI;AAE7E,8BAA8B;AAC9B,wBAAgB,EAAE,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,MAAM,EACtD,KAAK,EAAE,CAAC,EACR,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAG/B;AAED,8BAA8B;AAC9B,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,0BAA0B,QAAI"}