cloesce 0.0.3-fix.1 → 0.0.3-fix.2
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/{cli.js → extractor/cli.js} +237 -242
- package/dist/{cli.wasm → extractor/cli.wasm} +0 -0
- package/dist/{extract.js → extractor/extract.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{cloesce.js → runtime/runtime.js} +122 -175
- package/dist/runtime/runtime.wasm +0 -0
- package/package.json +61 -60
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cloesce.d.ts +0 -108
- package/dist/cloesce.d.ts.map +0 -1
- package/dist/common.d.ts +0 -96
- package/dist/common.d.ts.map +0 -1
- package/dist/decorators.d.ts +0 -13
- package/dist/decorators.d.ts.map +0 -1
- package/dist/decorators.js +0 -13
- package/dist/dog.cloesce.js +0 -111
- package/dist/extract.d.ts +0 -14
- package/dist/extract.d.ts.map +0 -1
- package/dist/index.d.ts +0 -24
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
|
@@ -1,50 +1,74 @@
|
|
|
1
|
-
import { left, right, isNullableType, getNavigationPropertyCidlType, } from "
|
|
1
|
+
import { left, right, isNullableType, getNavigationPropertyCidlType, } from "../common.js";
|
|
2
|
+
// Requires the rust runtime binary to have been built
|
|
3
|
+
import mod from "../../dist/runtime.wasm";
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
* These values are guaranteed to never change throughout a workers lifetime.
|
|
5
|
+
* RAII for wasm memory
|
|
5
6
|
*/
|
|
6
|
-
class
|
|
7
|
+
class WasmResource {
|
|
8
|
+
wasm;
|
|
9
|
+
ptr;
|
|
10
|
+
len;
|
|
11
|
+
constructor(wasm, ptr, len) {
|
|
12
|
+
this.wasm = wasm;
|
|
13
|
+
this.ptr = ptr;
|
|
14
|
+
this.len = len;
|
|
15
|
+
}
|
|
16
|
+
free() {
|
|
17
|
+
this.wasm.dealloc(this.ptr, this.len);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Copies a value from TS memory to WASM memory. A subsequent `free` is necessary.
|
|
21
|
+
*/
|
|
22
|
+
static fromString(str, wasm) {
|
|
23
|
+
const encoder = new TextEncoder();
|
|
24
|
+
const bytes = encoder.encode(str);
|
|
25
|
+
const ptr = wasm.alloc(bytes.length);
|
|
26
|
+
const mem = new Uint8Array(wasm.memory.buffer, ptr, bytes.length);
|
|
27
|
+
mem.set(bytes);
|
|
28
|
+
return new this(wasm, ptr, bytes.length);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Singleton instances of the cidl, constructor registry, and wasm binary.
|
|
33
|
+
* These values are guaranteed to never change throughout a program lifetime.
|
|
34
|
+
*/
|
|
35
|
+
class RuntimeContainer {
|
|
7
36
|
ast;
|
|
8
37
|
constructorRegistry;
|
|
38
|
+
wasm;
|
|
9
39
|
static instance;
|
|
10
|
-
constructor(ast, constructorRegistry) {
|
|
40
|
+
constructor(ast, constructorRegistry, wasm) {
|
|
11
41
|
this.ast = ast;
|
|
12
42
|
this.constructorRegistry = constructorRegistry;
|
|
43
|
+
this.wasm = wasm;
|
|
13
44
|
}
|
|
14
|
-
static init(ast, constructorRegistry) {
|
|
15
|
-
if (
|
|
16
|
-
|
|
45
|
+
static async init(ast, constructorRegistry, wasm) {
|
|
46
|
+
if (this.instance) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Load WASM
|
|
50
|
+
const wasmInstance = (wasm ??
|
|
51
|
+
(await WebAssembly.instantiate(mod)));
|
|
52
|
+
const modelMeta = WasmResource.fromString(JSON.stringify(ast.models), wasmInstance.exports);
|
|
53
|
+
if (wasmInstance.exports.set_meta_ptr(modelMeta.ptr, modelMeta.len) != 0) {
|
|
54
|
+
modelMeta.free();
|
|
55
|
+
throw Error("The WASM Module failed to load due to an invalid CIDL");
|
|
17
56
|
}
|
|
57
|
+
// Intentionally leak `modelMeta`, it should exist for the programs lifetime.
|
|
58
|
+
this.instance = new RuntimeContainer(ast, constructorRegistry, wasmInstance.exports);
|
|
18
59
|
}
|
|
19
60
|
static get() {
|
|
20
61
|
return this.instance;
|
|
21
62
|
}
|
|
22
63
|
}
|
|
23
64
|
/**
|
|
24
|
-
*
|
|
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,
|
|
65
|
+
* Runtime entry point. Given a request, undergoes: routing, validating,
|
|
37
66
|
* hydrating, and method dispatch.
|
|
38
|
-
*
|
|
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
|
|
67
|
+
*
|
|
44
68
|
* @returns A Response with an `HttpResult` JSON body.
|
|
45
69
|
*/
|
|
46
70
|
export async function cloesce(request, ast, constructorRegistry, instanceRegistry, envMeta, api_route) {
|
|
47
|
-
|
|
71
|
+
await RuntimeContainer.init(ast, constructorRegistry);
|
|
48
72
|
const d1 = instanceRegistry.get(envMeta.envName)[envMeta.dbName];
|
|
49
73
|
// Match the route to a model method
|
|
50
74
|
const route = matchRoute(request, ast, api_route);
|
|
@@ -53,25 +77,25 @@ export async function cloesce(request, ast, constructorRegistry, instanceRegistr
|
|
|
53
77
|
}
|
|
54
78
|
const { method, model, id } = route.value;
|
|
55
79
|
// Validate request body to the model method
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
58
|
-
return toResponse(
|
|
80
|
+
const validation = await validateRequest(request, ast, model, method, id);
|
|
81
|
+
if (!validation.ok) {
|
|
82
|
+
return toResponse(validation.value);
|
|
59
83
|
}
|
|
60
|
-
const
|
|
84
|
+
const { params, dataSource } = validation.value;
|
|
61
85
|
// Instantatiate the model
|
|
62
86
|
let instance;
|
|
63
87
|
if (method.is_static) {
|
|
64
88
|
instance = constructorRegistry[model.name];
|
|
65
89
|
}
|
|
66
90
|
else {
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
69
|
-
return toResponse(
|
|
91
|
+
const hydratedModel = await hydrateModel(constructorRegistry, d1, model, id, dataSource);
|
|
92
|
+
if (!hydratedModel.ok) {
|
|
93
|
+
return toResponse(hydratedModel.value);
|
|
70
94
|
}
|
|
71
|
-
instance =
|
|
95
|
+
instance = hydratedModel.value;
|
|
72
96
|
}
|
|
73
97
|
// Dispatch a method on the model and return the result
|
|
74
|
-
return toResponse(await methodDispatch(instance, instanceRegistry, envMeta, method,
|
|
98
|
+
return toResponse(await methodDispatch(instance, instanceRegistry, envMeta, method, params));
|
|
75
99
|
}
|
|
76
100
|
/**
|
|
77
101
|
* Matches a request to a method on a model.
|
|
@@ -80,6 +104,8 @@ export async function cloesce(request, ast, constructorRegistry, instanceRegistr
|
|
|
80
104
|
*/
|
|
81
105
|
function matchRoute(request, ast, api_route) {
|
|
82
106
|
const url = new URL(request.url);
|
|
107
|
+
// Error state: We expect an exact request format, and expect that the model
|
|
108
|
+
// and are apart of the CIDL
|
|
83
109
|
const notFound = (e) => left(errorState(404, `Path not found: ${e} ${url.pathname}`));
|
|
84
110
|
const routeParts = url.pathname
|
|
85
111
|
.slice(api_route.length)
|
|
@@ -129,13 +155,13 @@ async function validateRequest(request, ast, model, method, id) {
|
|
|
129
155
|
const url = new URL(request.url);
|
|
130
156
|
let dataSource = url.searchParams.get("dataSource");
|
|
131
157
|
// Extract url or body parameters
|
|
132
|
-
let
|
|
158
|
+
let params;
|
|
133
159
|
if (method.http_verb === "GET") {
|
|
134
|
-
|
|
160
|
+
params = Object.fromEntries(url.searchParams.entries());
|
|
135
161
|
}
|
|
136
162
|
else {
|
|
137
163
|
try {
|
|
138
|
-
|
|
164
|
+
params = await request.json();
|
|
139
165
|
}
|
|
140
166
|
catch {
|
|
141
167
|
return invalidRequest("Could not retrieve JSON body.");
|
|
@@ -146,17 +172,17 @@ async function validateRequest(request, ast, model, method, id) {
|
|
|
146
172
|
return invalidRequest(`Unknown data source ${dataSource}`);
|
|
147
173
|
}
|
|
148
174
|
// Ensure all required params exist
|
|
149
|
-
if (!requiredParams.every((p) => p.name in
|
|
175
|
+
if (!requiredParams.every((p) => p.name in params)) {
|
|
150
176
|
return invalidRequest(`Missing parameters.`);
|
|
151
177
|
}
|
|
152
178
|
// Validate all parameters type
|
|
153
179
|
for (const p of requiredParams) {
|
|
154
|
-
const value =
|
|
180
|
+
const value = params[p.name];
|
|
155
181
|
if (!validateCidlType(ast, value, p.cidl_type)) {
|
|
156
182
|
return invalidRequest("Invalid parameters.");
|
|
157
183
|
}
|
|
158
184
|
}
|
|
159
|
-
return right(
|
|
185
|
+
return right({ params, dataSource });
|
|
160
186
|
}
|
|
161
187
|
/**
|
|
162
188
|
* Queries D1 for a particular model's ID, then transforms the SQL column output into
|
|
@@ -165,7 +191,7 @@ async function validateRequest(request, ast, model, method, id) {
|
|
|
165
191
|
* @returns 500 if the D1 database is not synced with Cloesce and yields an error
|
|
166
192
|
* @returns The instantiated model on success
|
|
167
193
|
*/
|
|
168
|
-
async function hydrateModel(
|
|
194
|
+
async function hydrateModel(constructorRegistry, d1, model, id, dataSource) {
|
|
169
195
|
// Error state: If the D1 database has been tweaked outside of Cloesce
|
|
170
196
|
// resulting in a malformed query, exit with a 500.
|
|
171
197
|
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)}`));
|
|
@@ -192,8 +218,7 @@ async function hydrateModel(ast, constructorRegistry, d1, model, id, dataSource)
|
|
|
192
218
|
// Get include tree
|
|
193
219
|
const includeTree = dataSource !== null ? model.data_sources[dataSource].tree : {};
|
|
194
220
|
// Hydrate
|
|
195
|
-
const models =
|
|
196
|
-
console.log(JSON.stringify(models));
|
|
221
|
+
const models = modelsFromSql(constructorRegistry[model.name], records.results, includeTree);
|
|
197
222
|
return right(models[0]);
|
|
198
223
|
}
|
|
199
224
|
/**
|
|
@@ -294,142 +319,65 @@ function validateCidlType(ast, value, cidlType) {
|
|
|
294
319
|
return false;
|
|
295
320
|
}
|
|
296
321
|
/**
|
|
297
|
-
*
|
|
322
|
+
* Creates model instances given a properly formatted SQL record, being either:
|
|
298
323
|
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
324
|
+
* 1. Flat, relationship-less (ex: id, name, location, ...)
|
|
325
|
+
* 2. `DataSource` formatted (ex: Horse.id, Horse.name, Horse.rider, ...)
|
|
301
326
|
*
|
|
302
|
-
* @
|
|
327
|
+
* @param ctor The type of the model
|
|
328
|
+
* @param records SQL records
|
|
329
|
+
* @param includeTree The include tree to use when parsing the records
|
|
330
|
+
* @returns An instantiated array of `T`, containing one or more objects.
|
|
303
331
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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;
|
|
332
|
+
export function modelsFromSql(ctor, records, includeTree) {
|
|
333
|
+
const { ast, constructorRegistry, wasm } = RuntimeContainer.get();
|
|
334
|
+
const modelName = WasmResource.fromString(ctor.name, wasm);
|
|
335
|
+
const rows = WasmResource.fromString(JSON.stringify(records), wasm);
|
|
336
|
+
const includeTreeJson = WasmResource.fromString(JSON.stringify(includeTree), wasm);
|
|
337
|
+
// Invoke the ORM
|
|
338
|
+
const jsonResults = (() => {
|
|
339
|
+
let resPtr;
|
|
340
|
+
let resLen;
|
|
341
|
+
try {
|
|
342
|
+
resPtr = wasm.object_relational_mapping(modelName.ptr, modelName.len, rows.ptr, rows.len, includeTreeJson.ptr, includeTreeJson.len);
|
|
343
|
+
resLen = wasm.get_return_len();
|
|
344
|
+
// Parse the results as JSON
|
|
345
|
+
return JSON.parse(new TextDecoder().decode(new Uint8Array(wasm.memory.buffer, resPtr, resLen)));
|
|
356
346
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
347
|
+
finally {
|
|
348
|
+
modelName.free();
|
|
349
|
+
rows.free();
|
|
350
|
+
includeTreeJson.free();
|
|
351
|
+
// Could resPtr some how be set but not resLen? Kind of a flaw
|
|
352
|
+
// in how WASM works.
|
|
353
|
+
if (resPtr && resLen)
|
|
354
|
+
wasm.dealloc(resPtr, resLen);
|
|
360
355
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
356
|
+
})();
|
|
357
|
+
return jsonResults.map((obj) => instantiateDfs(obj, ast.models[ctor.name], includeTree));
|
|
358
|
+
// The result that comes back is just raw JSON, run a DFS on each navigation property
|
|
359
|
+
// in the include tree provided, instantiating each object via constructor registry.
|
|
360
|
+
function instantiateDfs(m, meta, includeTree) {
|
|
361
|
+
m = Object.assign(new constructorRegistry[meta.name](), m);
|
|
362
|
+
if (!includeTree) {
|
|
363
|
+
return m;
|
|
368
364
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (isOneToMany) {
|
|
373
|
-
const navArray = instance[navProp.var_name];
|
|
374
|
-
const alreadyExists = navArray.some((item) => item[nestedPkName] === nestedPkValue);
|
|
375
|
-
if (alreadyExists) {
|
|
365
|
+
for (const navProp of meta.navigation_properties) {
|
|
366
|
+
const nestedIncludeTree = includeTree[navProp.var_name];
|
|
367
|
+
if (!nestedIncludeTree)
|
|
376
368
|
continue;
|
|
369
|
+
const nestedMeta = ast.models[navProp.model_name];
|
|
370
|
+
const value = m[navProp.var_name];
|
|
371
|
+
// One to Many, Many to Many
|
|
372
|
+
if (Array.isArray(value)) {
|
|
373
|
+
m[navProp.var_name] = value.map((child) => instantiateDfs(child, nestedMeta, nestedIncludeTree));
|
|
377
374
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (instance[navProp.var_name] != null) {
|
|
382
|
-
continue;
|
|
375
|
+
// One to one
|
|
376
|
+
else if (value) {
|
|
377
|
+
m[navProp.var_name] = instantiateDfs(value, nestedMeta, nestedIncludeTree);
|
|
383
378
|
}
|
|
384
379
|
}
|
|
385
|
-
|
|
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
|
-
}
|
|
380
|
+
return m;
|
|
433
381
|
}
|
|
434
382
|
}
|
|
435
383
|
function errorState(status, message) {
|
|
@@ -442,12 +390,11 @@ function toResponse(r) {
|
|
|
442
390
|
});
|
|
443
391
|
}
|
|
444
392
|
/**
|
|
445
|
-
*
|
|
393
|
+
* For testing purposes
|
|
446
394
|
*/
|
|
447
395
|
export const _cloesceInternal = {
|
|
448
396
|
matchRoute,
|
|
449
397
|
validateRequest,
|
|
450
|
-
hydrateModel,
|
|
451
398
|
methodDispatch,
|
|
452
|
-
|
|
399
|
+
RuntimeContainer,
|
|
453
400
|
};
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cloesce",
|
|
3
|
-
"version": "0.0.3-fix.
|
|
4
|
-
"description": "A tool to extract and compile TypeScript code into something wrangler can consume and deploy for D1 Databases and Cloudflare Workers",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "cloesce",
|
|
3
|
+
"version": "0.0.3-fix.2",
|
|
4
|
+
"description": "A tool to extract and compile TypeScript code into something wrangler can consume and deploy for D1 Databases and Cloudflare Workers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "vitest",
|
|
11
|
+
"format:fix": "prettier --write .",
|
|
12
|
+
"format": "prettier --check .",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"build": "tsc -p tsconfig.json"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"cmd-ts": "^0.14.1",
|
|
18
|
+
"prettier": "^3.6.2",
|
|
19
|
+
"ts-morph": "^22.0.0",
|
|
20
|
+
"ts-node": "^10.9.2",
|
|
21
|
+
"typescript": "^5.6.0",
|
|
22
|
+
"wrangler": "^4.34.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@cloudflare/workers-types": "^4.20250906.0",
|
|
26
|
+
"prettier": "^3.6.2",
|
|
27
|
+
"vitest": "^3.2.4",
|
|
28
|
+
"vite-plugin-wasm": "^3.5.0"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./cli": "./dist/cli.js"
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"cloesce": "./dist/extractor/cli.js"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/**/*",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"sideEffects": false,
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.17"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/bens-schreiber/cloesce.git"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/bens-schreiber/cloesce",
|
|
54
|
+
"keywords": [
|
|
55
|
+
"cloudflare",
|
|
56
|
+
"workers",
|
|
57
|
+
"d1",
|
|
58
|
+
"orm",
|
|
59
|
+
"cli"
|
|
60
|
+
]
|
|
61
|
+
}
|
package/dist/cli.d.ts
DELETED
package/dist/cli.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cloesce.d.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { D1Database } from "@cloudflare/workers-types/experimental/index.js";
|
|
2
|
-
import { HttpResult, Either, ModelMethod, CloesceAst, Model } from "./common.js";
|
|
3
|
-
import { IncludeTree } from "./index.js";
|
|
4
|
-
/**
|
|
5
|
-
* A map of model names to their respective constructor.
|
|
6
|
-
*
|
|
7
|
-
* The value accepted into the `cloesce` function is generated by the Cloesce compiler, and
|
|
8
|
-
* is guaranteed to contain all model definitions.
|
|
9
|
-
*/
|
|
10
|
-
type ModelConstructorRegistry = Record<string, new () => UserDefinedModel>;
|
|
11
|
-
/**
|
|
12
|
-
* A dependency injection container, mapping an object type name to an instance of that object.
|
|
13
|
-
*
|
|
14
|
-
* The value accepted into the `cloesce` function is generated by the Cloesce compiler, and is
|
|
15
|
-
* guaranteed to contain all injected model method parameters.
|
|
16
|
-
*/
|
|
17
|
-
type InstanceRegistry = Map<string, any>;
|
|
18
|
-
/**
|
|
19
|
-
* Users will create Cloesce models, which have metadata for them in the ast.
|
|
20
|
-
* For TypeScript's purposes, these models can be anything. We can assume any
|
|
21
|
-
* `UserDefinedModel` has been verified by the compiler.
|
|
22
|
-
*/
|
|
23
|
-
type UserDefinedModel = any;
|
|
24
|
-
type InstantiatedUserDefinedModel = object;
|
|
25
|
-
/**
|
|
26
|
-
* Given a request, this represents a map of each body / url param name to
|
|
27
|
-
* its actual value. Unknown, as the a request can be anything.
|
|
28
|
-
*/
|
|
29
|
-
type RequestParamMap = Record<string, unknown>;
|
|
30
|
-
/**
|
|
31
|
-
* Meta information on the wrangler env and db bindings
|
|
32
|
-
*/
|
|
33
|
-
interface MetaWranglerEnv {
|
|
34
|
-
envName: string;
|
|
35
|
-
dbName: string;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Creates model instances given a properly formatted SQL record
|
|
39
|
-
* (either a foreign-key-less model or derived from a Cloesce generated view)
|
|
40
|
-
* @param ctor The type of the model
|
|
41
|
-
* @param records SQL records
|
|
42
|
-
* @param includeTree The include tree to use when parsing the records
|
|
43
|
-
* @returns
|
|
44
|
-
*/
|
|
45
|
-
export declare function modelsFromSql<T>(ctor: new () => T, records: Record<string, any>[], includeTree: IncludeTree<T> | null): T[];
|
|
46
|
-
/**
|
|
47
|
-
* Cloesce entry point. Given a request, undergoes routing, validating,
|
|
48
|
-
* hydrating, and method dispatch.
|
|
49
|
-
* @param ast The CIDL AST
|
|
50
|
-
* @param constructorRegistry A mapping of user defined class names to their respective constructor
|
|
51
|
-
* @param instanceRegistry A mapping of a dependency class name to its instantiated object.
|
|
52
|
-
* @param request An incoming request to the workers server
|
|
53
|
-
* @param api_route The url's path to the api, e.g. api/v1/fooapi/
|
|
54
|
-
* @param envMeta Meta information on the wrangler env and D1 databases
|
|
55
|
-
* @returns A Response with an `HttpResult` JSON body.
|
|
56
|
-
*/
|
|
57
|
-
export declare function cloesce(request: Request, ast: CloesceAst, constructorRegistry: ModelConstructorRegistry, instanceRegistry: InstanceRegistry, envMeta: MetaWranglerEnv, api_route: string): Promise<Response>;
|
|
58
|
-
/**
|
|
59
|
-
* Matches a request to a method on a model.
|
|
60
|
-
* @param api_route The route from the domain to the actual API, ie https://foo.com/route/to/api => route/to/api/
|
|
61
|
-
* @returns 404 or a `MatchedRoute`
|
|
62
|
-
*/
|
|
63
|
-
declare function matchRoute(request: Request, ast: CloesceAst, api_route: string): Either<HttpResult, MatchedRoute>;
|
|
64
|
-
/**
|
|
65
|
-
* Validates the request's body/search params against a ModelMethod
|
|
66
|
-
* @returns 400 or a `RequestParamMap` consisting of each parameters name mapped to its value, and
|
|
67
|
-
* a data source
|
|
68
|
-
*/
|
|
69
|
-
declare function validateRequest(request: Request, ast: CloesceAst, model: Model, method: ModelMethod, id: string | null): Promise<Either<HttpResult, [RequestParamMap, string | null]>>;
|
|
70
|
-
/**
|
|
71
|
-
* Queries D1 for a particular model's ID, then transforms the SQL column output into
|
|
72
|
-
* an instance of a model using the provided include tree and metadata as a guide.
|
|
73
|
-
* @returns 404 if no record was found for the provided ID
|
|
74
|
-
* @returns 500 if the D1 database is not synced with Cloesce and yields an error
|
|
75
|
-
* @returns The instantiated model on success
|
|
76
|
-
*/
|
|
77
|
-
declare function hydrateModel(ast: CloesceAst, constructorRegistry: ModelConstructorRegistry, d1: D1Database, model: Model, id: string, dataSource: string | null): Promise<Either<HttpResult, object>>;
|
|
78
|
-
/**
|
|
79
|
-
* Calls a method on a model given a list of parameters.
|
|
80
|
-
* @returns 500 on an uncaught client error, 200 with a result body on success
|
|
81
|
-
*/
|
|
82
|
-
declare function methodDispatch(instance: InstantiatedUserDefinedModel, instanceRegistry: InstanceRegistry, envMeta: MetaWranglerEnv, method: ModelMethod, params: Record<string, unknown>): Promise<HttpResult<unknown>>;
|
|
83
|
-
/**
|
|
84
|
-
* Actual implementation of sql to model mapping.
|
|
85
|
-
*
|
|
86
|
-
* TODO: If we don't want to write this in every language, would it be possible to create a
|
|
87
|
-
* single WASM binary for this method?
|
|
88
|
-
*
|
|
89
|
-
* @throws generic errors if the metadata is missing some value
|
|
90
|
-
*/
|
|
91
|
-
declare function _modelsFromSql(modelName: string, ast: CloesceAst, constructorRegistry: ModelConstructorRegistry, records: Record<string, any>[], includeTree: Record<string, UserDefinedModel> | null): InstantiatedUserDefinedModel[];
|
|
92
|
-
interface MatchedRoute {
|
|
93
|
-
model: Model;
|
|
94
|
-
method: ModelMethod;
|
|
95
|
-
id: string | null;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Each individual state of the `cloesce` function for testing purposes.
|
|
99
|
-
*/
|
|
100
|
-
export declare const _cloesceInternal: {
|
|
101
|
-
matchRoute: typeof matchRoute;
|
|
102
|
-
validateRequest: typeof validateRequest;
|
|
103
|
-
hydrateModel: typeof hydrateModel;
|
|
104
|
-
methodDispatch: typeof methodDispatch;
|
|
105
|
-
_modelsFromSql: typeof _modelsFromSql;
|
|
106
|
-
};
|
|
107
|
-
export {};
|
|
108
|
-
//# sourceMappingURL=cloesce.d.ts.map
|
package/dist/cloesce.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cloesce.d.ts","sourceRoot":"","sources":["../src/cloesce.ts"],"names":[],"mappings":"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"}
|