cloesce 0.0.5-unstable.1 → 0.0.5-unstable.10
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 +96 -106
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +12 -12
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +330 -368
- 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 +25 -26
- package/dist/extractor/err.d.ts.map +1 -1
- package/dist/extractor/err.js +95 -129
- package/dist/extractor/extract.d.ts +24 -61
- package/dist/extractor/extract.d.ts.map +1 -1
- package/dist/extractor/extract.js +1006 -836
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.d.ts +2 -3
- package/dist/router/crud.d.ts.map +1 -1
- package/dist/router/crud.js +58 -48
- 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 +93 -139
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +389 -432
- package/dist/router/validator.d.ts +7 -12
- package/dist/router/validator.d.ts.map +1 -1
- package/dist/router/validator.js +190 -159
- package/dist/router/wasm.d.ts +26 -67
- package/dist/router/wasm.d.ts.map +1 -1
- package/dist/router/wasm.js +52 -103
- package/dist/ui/backend.d.ts +103 -382
- package/dist/ui/backend.d.ts.map +1 -1
- package/dist/ui/backend.js +143 -430
- package/package.json +4 -10
- 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 -126
- package/dist/ui/common.d.ts.map +0 -1
- package/dist/ui/common.js +0 -203
package/dist/ui/backend.js
CHANGED
|
@@ -1,60 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { WasmResource, mapSql, invokeOrmWasm } from "../router/wasm.js";
|
|
1
|
+
import { MediaType } from "../ast.js";
|
|
2
|
+
import { u8ToB64 } from "../common.js";
|
|
4
3
|
/**
|
|
5
4
|
* cloesce/backend
|
|
6
5
|
*/
|
|
7
6
|
export { CloesceApp } from "../router/router.js";
|
|
8
|
-
export {
|
|
7
|
+
export { Orm } from "../router/orm.js";
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
9
|
+
* Base class for a Cloudflare KV model or navigation property.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
* - a D1 table definition (via `cloesce migrate`)
|
|
14
|
-
* - backend API endpoints (Workers)
|
|
15
|
-
* - a frontend client API
|
|
16
|
-
* - Cloudflare Wrangler configurations
|
|
11
|
+
* Consists of a `key`, `value`, and optional `metadata`.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
13
|
+
* @template V The type of the value stored in the KValue. Note that KV is schema-less,
|
|
14
|
+
* so this type is not enforced at runtime, but serves as the type the client expects.
|
|
19
15
|
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* name: string;
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
16
|
+
* @remarks
|
|
17
|
+
* - The `key` is a string that uniquely identifies the entry in the KV store.
|
|
18
|
+
* - The `value` is of generic type `V`, allowing flexibility in the type of data stored.
|
|
19
|
+
* - `V` must be serializable to JSON.
|
|
20
|
+
* - The `metadata` can hold any additional information associated with the KV entry.
|
|
28
21
|
*/
|
|
29
|
-
export
|
|
30
|
-
|
|
22
|
+
export class KValue {
|
|
23
|
+
key;
|
|
24
|
+
raw;
|
|
25
|
+
metadata;
|
|
26
|
+
get value() {
|
|
27
|
+
return this.raw;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
31
30
|
/**
|
|
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
|
-
|
|
31
|
+
* The result of a Workers endpoint.
|
|
32
|
+
*
|
|
33
|
+
* @param ok True if `status` < 400
|
|
34
|
+
* @param status The HTTP Status of a Workers request
|
|
35
|
+
* @param headers All headers that the result is to be sent with or was received with
|
|
36
|
+
* @param data JSON data yielded from a request, undefined if the request was not `ok`.
|
|
37
|
+
* @param message An error text set if the request was not `ok`.
|
|
38
|
+
*
|
|
39
|
+
* @remarks If `status` is 204 `data` will always be undefined.
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
export class HttpResult {
|
|
43
|
+
ok;
|
|
44
|
+
status;
|
|
45
|
+
headers;
|
|
46
|
+
data;
|
|
47
|
+
message;
|
|
48
|
+
mediaType;
|
|
49
|
+
constructor(ok, status, headers, data, message, mediaType) {
|
|
50
|
+
this.ok = ok;
|
|
51
|
+
this.status = status;
|
|
52
|
+
this.headers = headers;
|
|
53
|
+
this.data = data;
|
|
54
|
+
this.message = message;
|
|
55
|
+
this.mediaType = mediaType;
|
|
56
|
+
}
|
|
57
|
+
static ok(status, data, init) {
|
|
58
|
+
const headers = new Headers(init);
|
|
59
|
+
return new HttpResult(true, status, headers, data, undefined);
|
|
60
|
+
}
|
|
61
|
+
static fail(status, message, init) {
|
|
62
|
+
const headers = new Headers(init);
|
|
63
|
+
return new HttpResult(false, status, headers, undefined, message);
|
|
64
|
+
}
|
|
65
|
+
toResponse() {
|
|
66
|
+
let body;
|
|
67
|
+
switch (this.mediaType) {
|
|
68
|
+
case MediaType.Json: {
|
|
69
|
+
this.headers.set("Content-Type", "application/json");
|
|
70
|
+
body = JSON.stringify(this.data ?? {}, (_, v) => {
|
|
71
|
+
// Convert Uint8Arrays to base64 strings
|
|
72
|
+
if (v instanceof Uint8Array) {
|
|
73
|
+
return u8ToB64(v);
|
|
74
|
+
}
|
|
75
|
+
// Convert R2Object to Client R2Object representation
|
|
76
|
+
if (isR2Object(v)) {
|
|
77
|
+
return {
|
|
78
|
+
key: v.key,
|
|
79
|
+
version: v.version,
|
|
80
|
+
size: v.size,
|
|
81
|
+
etag: v.etag,
|
|
82
|
+
httpEtag: v.httpEtag,
|
|
83
|
+
uploaded: v.uploaded.toISOString(),
|
|
84
|
+
customMetadata: v.customMetadata,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (v instanceof Date) {
|
|
88
|
+
return v.toISOString();
|
|
89
|
+
}
|
|
90
|
+
return v;
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case MediaType.Octet: {
|
|
95
|
+
this.headers.set("Content-Type", "application/octet-stream");
|
|
96
|
+
// JSON structure isn't needed; assume the first
|
|
97
|
+
// value is the stream data
|
|
98
|
+
body = Object.values(this.data ?? {})[0];
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case undefined: {
|
|
102
|
+
// Errors are always text.
|
|
103
|
+
this.headers.set("Content-Type", "text/plain");
|
|
104
|
+
return new Response(this.message, {
|
|
105
|
+
status: this.status,
|
|
106
|
+
headers: this.headers,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return new Response(body, {
|
|
111
|
+
status: this.status,
|
|
112
|
+
headers: this.headers,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
setMediaType(mediaType) {
|
|
116
|
+
this.mediaType = mediaType;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export const Model = (_kinds = []) => () => { };
|
|
121
|
+
export const Service = () => { };
|
|
58
122
|
/**
|
|
59
123
|
* Declares a Wrangler environment definition.
|
|
60
124
|
*
|
|
@@ -76,7 +140,7 @@ export const PlainOldObject = () => {};
|
|
|
76
140
|
* foo(@Inject env: WranglerEnv) {...}
|
|
77
141
|
* ```
|
|
78
142
|
*/
|
|
79
|
-
export const WranglerEnv = () => {};
|
|
143
|
+
export const WranglerEnv = () => { };
|
|
80
144
|
/**
|
|
81
145
|
* Marks a property as the SQL primary key for a model.
|
|
82
146
|
*
|
|
@@ -93,127 +157,42 @@ export const WranglerEnv = () => {};
|
|
|
93
157
|
* }
|
|
94
158
|
* ```
|
|
95
159
|
*/
|
|
96
|
-
export const PrimaryKey = () => {};
|
|
160
|
+
export const PrimaryKey = () => { };
|
|
161
|
+
export const KeyParam = () => { };
|
|
162
|
+
export const KV = (_keyFormat, _namespaceBinding) => () => { };
|
|
163
|
+
export const R2 = (_keyFormat, _bucketBinding) => () => { };
|
|
97
164
|
/**
|
|
98
165
|
* Exposes a class method as an HTTP GET endpoint.
|
|
99
166
|
* The method will appear in both backend and generated client APIs.
|
|
100
167
|
*/
|
|
101
|
-
export const GET = () => {};
|
|
168
|
+
export const GET = () => { };
|
|
102
169
|
/**
|
|
103
170
|
* Exposes a class method as an HTTP POST endpoint.
|
|
104
171
|
* The method will appear in both backend and generated client APIs.
|
|
105
172
|
*/
|
|
106
|
-
export const POST = () => {};
|
|
173
|
+
export const POST = () => { };
|
|
107
174
|
/**
|
|
108
175
|
* Exposes a class method as an HTTP PUT endpoint.
|
|
109
176
|
* The method will appear in both backend and generated client APIs.
|
|
110
177
|
*/
|
|
111
|
-
export const PUT = () => {};
|
|
178
|
+
export const PUT = () => { };
|
|
112
179
|
/**
|
|
113
180
|
* Exposes a class method as an HTTP PATCH endpoint.
|
|
114
181
|
* The method will appear in both backend and generated client APIs.
|
|
115
182
|
*/
|
|
116
|
-
export const PATCH = () => {};
|
|
183
|
+
export const PATCH = () => { };
|
|
117
184
|
/**
|
|
118
185
|
* Exposes a class method as an HTTP DEL endpoint.
|
|
119
186
|
* The method will appear in both backend and generated client APIs.
|
|
120
187
|
*/
|
|
121
|
-
export const DELETE = () => {};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* ```ts
|
|
130
|
-
* @D1
|
|
131
|
-
* export class Dog {
|
|
132
|
-
* @PrimaryKey
|
|
133
|
-
* id: number;
|
|
134
|
-
*
|
|
135
|
-
* name: string;
|
|
136
|
-
* }
|
|
137
|
-
*
|
|
138
|
-
* @D1
|
|
139
|
-
* export class Person {
|
|
140
|
-
* @PrimaryKey
|
|
141
|
-
* id: number;
|
|
142
|
-
*
|
|
143
|
-
* @ForeignKey(Dog)
|
|
144
|
-
* dogId: number;
|
|
145
|
-
*
|
|
146
|
-
* @OneToOne("dogId")
|
|
147
|
-
* dog: Dog | undefined;
|
|
148
|
-
*
|
|
149
|
-
* @DataSource
|
|
150
|
-
* static readonly default: IncludeTree<Person> = {
|
|
151
|
-
* dog: {}, // join Dog table when querying Person with `default` data source
|
|
152
|
-
* };
|
|
153
|
-
* }
|
|
154
|
-
*
|
|
155
|
-
* // When queried via the ORM or client API:
|
|
156
|
-
* const orm = Orm.fromD1(env.db);
|
|
157
|
-
* const people = await orm.list(Person, Person.default);
|
|
158
|
-
*
|
|
159
|
-
* // => Person { id: 1, dogId: 2, dog: { id: 2, name: "Fido" } }[]
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
export const DataSource = () => {};
|
|
163
|
-
/**
|
|
164
|
-
* Declares a one-to-many relationship between models.
|
|
165
|
-
*
|
|
166
|
-
* The argument is the foreign key property name on the
|
|
167
|
-
* related model.
|
|
168
|
-
*
|
|
169
|
-
* Example:
|
|
170
|
-
* ```ts
|
|
171
|
-
* @OneToMany("personId")
|
|
172
|
-
* dogs: Dog[];
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
export const OneToMany = (_foreignKeyColumn) => () => {};
|
|
176
|
-
/**
|
|
177
|
-
* Declares a one-to-one relationship between models.
|
|
178
|
-
*
|
|
179
|
-
* The argument is the foreign key property name that links
|
|
180
|
-
* the two tables.
|
|
181
|
-
*
|
|
182
|
-
* Example:
|
|
183
|
-
* ```ts
|
|
184
|
-
* @OneToOne("dogId")
|
|
185
|
-
* dog: Dog | undefined;
|
|
186
|
-
* ```
|
|
187
|
-
*/
|
|
188
|
-
export const OneToOne = (_foreignKeyColumn) => () => {};
|
|
189
|
-
/**
|
|
190
|
-
* Declares a many-to-many relationship between models.
|
|
191
|
-
*
|
|
192
|
-
* The argument is a unique identifier for the generated
|
|
193
|
-
* junction table used to connect the two entities.
|
|
194
|
-
*
|
|
195
|
-
* Example:
|
|
196
|
-
* ```ts
|
|
197
|
-
* @ManyToMany("StudentsCourses")
|
|
198
|
-
* courses: Course[];
|
|
199
|
-
* ```
|
|
200
|
-
*/
|
|
201
|
-
export const ManyToMany = (_uniqueId) => () => {};
|
|
202
|
-
/**
|
|
203
|
-
* Declares a foreign key relationship between models.
|
|
204
|
-
* Directly translates to a SQLite foreign key.
|
|
205
|
-
*
|
|
206
|
-
* The argument must reference either a model class or the
|
|
207
|
-
* name of a model class as a string. The property type must
|
|
208
|
-
* match the target model’s primary key type.
|
|
209
|
-
*
|
|
210
|
-
* Example:
|
|
211
|
-
* ```ts
|
|
212
|
-
* @ForeignKey(Dog)
|
|
213
|
-
* dogId: number;
|
|
214
|
-
* ```
|
|
215
|
-
*/
|
|
216
|
-
export const ForeignKey = (_Model) => () => {};
|
|
188
|
+
export const DELETE = () => { };
|
|
189
|
+
export function OneToMany(_selector) {
|
|
190
|
+
return () => { };
|
|
191
|
+
}
|
|
192
|
+
export function OneToOne(_selector) {
|
|
193
|
+
return () => { };
|
|
194
|
+
}
|
|
195
|
+
export const ForeignKey = (_Model) => () => { };
|
|
217
196
|
/**
|
|
218
197
|
* Marks a method parameter for dependency injection.
|
|
219
198
|
*
|
|
@@ -231,285 +210,19 @@ export const ForeignKey = (_Model) => () => {};
|
|
|
231
210
|
* }
|
|
232
211
|
* ```
|
|
233
212
|
*/
|
|
234
|
-
export const Inject = () => {};
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
*
|
|
250
|
-
* The generated methods are static, exposed through both the backend
|
|
251
|
-
* and the frontend client API.
|
|
252
|
-
*
|
|
253
|
-
* Example:
|
|
254
|
-
* ```ts
|
|
255
|
-
* @CRUD(["SAVE", "GET", "LIST"])
|
|
256
|
-
* @D1
|
|
257
|
-
* export class CrudHaver {
|
|
258
|
-
* @PrimaryKey id: number;
|
|
259
|
-
* name: string;
|
|
260
|
-
* }
|
|
261
|
-
* ```
|
|
262
|
-
*/
|
|
263
|
-
export const CRUD = (_kinds) => () => {};
|
|
264
|
-
/**
|
|
265
|
-
* Exposes the ORM primitives Cloesce uses to interact with D1 databases.
|
|
266
|
-
*/
|
|
267
|
-
export class Orm {
|
|
268
|
-
db;
|
|
269
|
-
constructor(db) {
|
|
270
|
-
this.db = db;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Creates an instance of an `Orm`
|
|
274
|
-
* @param db The database to use for ORM calls.
|
|
275
|
-
*/
|
|
276
|
-
static fromD1(db) {
|
|
277
|
-
return new Orm(db);
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Maps SQL records to an instantiated Model. The records must be flat
|
|
281
|
-
* (e.g., of the form "id, name, address") or derive from a Cloesce data source view
|
|
282
|
-
* (e.g., of the form "Horse.id, Horse.name, Horse.address")
|
|
283
|
-
*
|
|
284
|
-
* Assumes the data is formatted correctly, throwing an error otherwise.
|
|
285
|
-
*
|
|
286
|
-
* @param ctor The model constructor
|
|
287
|
-
* @param records D1 Result records
|
|
288
|
-
* @param includeTree Include tree to define the relationships to join.
|
|
289
|
-
*/
|
|
290
|
-
static mapSql(ctor, records, includeTree = null) {
|
|
291
|
-
return mapSql(ctor, records, includeTree).unwrap();
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Executes an "upsert" query, adding or augmenting a model in the database.
|
|
295
|
-
*
|
|
296
|
-
* If a model's primary key is not defined in `newModel`, the query is assumed to be an insert.
|
|
297
|
-
*
|
|
298
|
-
* If a model's primary key _is_ defined, but some attributes are missing, the query is assumed to be an update.
|
|
299
|
-
*
|
|
300
|
-
* Finally, if the primary key is defined, but all attributes are included, a SQLite upsert will be performed.
|
|
301
|
-
*
|
|
302
|
-
* In any other case, an error string will be returned.
|
|
303
|
-
*
|
|
304
|
-
* ### Inserting a new Model
|
|
305
|
-
* ```ts
|
|
306
|
-
* const model = {name: "julio", lastname: "pumpkin"};
|
|
307
|
-
* const idRes = await orm.upsert(Person, model, null);
|
|
308
|
-
* ```
|
|
309
|
-
*
|
|
310
|
-
* ### Updating an existing model
|
|
311
|
-
* ```ts
|
|
312
|
-
* const model = {id: 1, name: "timothy"};
|
|
313
|
-
* const idRes = await orm.upsert(Person, model, null);
|
|
314
|
-
* // (in db)=> {id: 1, name: "timothy", lastname: "pumpkin"}
|
|
315
|
-
* ```
|
|
316
|
-
*
|
|
317
|
-
* ### Upserting a model
|
|
318
|
-
* ```ts
|
|
319
|
-
* // (assume a Person already exists)
|
|
320
|
-
* const model = {
|
|
321
|
-
* id: 1,
|
|
322
|
-
* lastname: "burger", // updates last name
|
|
323
|
-
* dog: {
|
|
324
|
-
* name: "fido" // insert dog relationship
|
|
325
|
-
* }
|
|
326
|
-
* };
|
|
327
|
-
* const idRes = await orm.upsert(Person, model, null);
|
|
328
|
-
* // (in db)=> Person: {id: 1, dogId: 1 ...} ; Dog: {id: 1, name: "fido"}
|
|
329
|
-
* ```
|
|
330
|
-
*
|
|
331
|
-
* @param ctor A model constructor.
|
|
332
|
-
* @param newModel The new or augmented model.
|
|
333
|
-
* @param includeTree An include tree describing which foreign keys to join.
|
|
334
|
-
* @returns An error string, or the primary key of the inserted model.
|
|
335
|
-
*/
|
|
336
|
-
async upsert(ctor, newModel, includeTree = null) {
|
|
337
|
-
const { wasm } = RuntimeContainer.get();
|
|
338
|
-
const args = [
|
|
339
|
-
WasmResource.fromString(ctor.name, wasm),
|
|
340
|
-
WasmResource.fromString(
|
|
341
|
-
JSON.stringify(newModel, (k, v) =>
|
|
342
|
-
v instanceof Uint8Array ? u8ToB64(v) : v,
|
|
343
|
-
),
|
|
344
|
-
wasm,
|
|
345
|
-
),
|
|
346
|
-
WasmResource.fromString(JSON.stringify(includeTree), wasm),
|
|
347
|
-
];
|
|
348
|
-
const upsertQueryRes = invokeOrmWasm(wasm.upsert_model, args, wasm);
|
|
349
|
-
if (upsertQueryRes.isLeft()) {
|
|
350
|
-
return upsertQueryRes;
|
|
351
|
-
}
|
|
352
|
-
const statements = JSON.parse(upsertQueryRes.unwrap());
|
|
353
|
-
// One of these statements is a "SELECT", which is the root model id stmt.
|
|
354
|
-
let selectIndex;
|
|
355
|
-
for (let i = statements.length - 1; i >= 0; i--) {
|
|
356
|
-
if (/^SELECT/i.test(statements[i].query)) {
|
|
357
|
-
selectIndex = i;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// Execute all statements in a batch.
|
|
362
|
-
const batchRes = await this.db.batch(
|
|
363
|
-
statements.map((s) => this.db.prepare(s.query).bind(...s.values)),
|
|
364
|
-
);
|
|
365
|
-
if (!batchRes.every((r) => r.success)) {
|
|
366
|
-
const failed = batchRes.find((r) => !r.success);
|
|
367
|
-
return Either.left(
|
|
368
|
-
failed?.error ?? "D1 batch failed, but no error was returned.",
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
const rootModelId = batchRes[selectIndex].results[0];
|
|
372
|
-
return Either.right(rootModelId.id);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Returns a select query, creating a CTE view for the model using the provided include tree.
|
|
376
|
-
*
|
|
377
|
-
* @param ctor The model constructor.
|
|
378
|
-
* @param includeTree An include tree describing which related models to join.
|
|
379
|
-
* @param from An optional custom `FROM` clause to use instead of the base table.
|
|
380
|
-
* @param tagCte An optional CTE name to tag the query with. Defaults to "Model.view".
|
|
381
|
-
*
|
|
382
|
-
* ### Example:
|
|
383
|
-
* ```ts
|
|
384
|
-
* // Using a data source
|
|
385
|
-
* const query = Orm.listQuery(Person, "default");
|
|
386
|
-
*
|
|
387
|
-
* // Using a custom from statement
|
|
388
|
-
* const query = Orm.listQuery(Person, null, "SELECT * FROM Person WHERE age > 18");
|
|
389
|
-
* ```
|
|
390
|
-
*
|
|
391
|
-
* ### Example SQL output:
|
|
392
|
-
* ```sql
|
|
393
|
-
* WITH Person_view AS (
|
|
394
|
-
* SELECT
|
|
395
|
-
* "Person"."id" AS "id",
|
|
396
|
-
* ...
|
|
397
|
-
* FROM "Person"
|
|
398
|
-
* LEFT JOIN ...
|
|
399
|
-
* )
|
|
400
|
-
* SELECT * FROM Person_view
|
|
401
|
-
* ```
|
|
402
|
-
*/
|
|
403
|
-
static listQuery(ctor, opts) {
|
|
404
|
-
const { wasm } = RuntimeContainer.get();
|
|
405
|
-
const args = [
|
|
406
|
-
WasmResource.fromString(ctor.name, wasm),
|
|
407
|
-
WasmResource.fromString(JSON.stringify(opts.includeTree ?? null), wasm),
|
|
408
|
-
WasmResource.fromString(JSON.stringify(opts.tagCte ?? null), wasm),
|
|
409
|
-
WasmResource.fromString(JSON.stringify(opts.from ?? null), wasm),
|
|
410
|
-
];
|
|
411
|
-
const res = invokeOrmWasm(wasm.list_models, args, wasm);
|
|
412
|
-
if (res.isLeft()) {
|
|
413
|
-
throw new Error(`Error invoking the Cloesce WASM Binary: ${res.value}`);
|
|
414
|
-
}
|
|
415
|
-
return res.unwrap();
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Returns a select query for a single model by primary key, creating a CTE view using the provided include tree.
|
|
419
|
-
*
|
|
420
|
-
* @param ctor The model constructor.
|
|
421
|
-
* @param includeTree An include tree describing which related models to join.
|
|
422
|
-
*
|
|
423
|
-
* ### Example:
|
|
424
|
-
* ```ts
|
|
425
|
-
* // Using a data source
|
|
426
|
-
* const query = Orm.getQuery(Person, "default");
|
|
427
|
-
* ```
|
|
428
|
-
*
|
|
429
|
-
* ### Example SQL output:
|
|
430
|
-
*
|
|
431
|
-
* ```sql
|
|
432
|
-
* WITH Person_view AS (
|
|
433
|
-
* SELECT
|
|
434
|
-
* "Person"."id" AS "id",
|
|
435
|
-
* ...
|
|
436
|
-
* FROM "Person"
|
|
437
|
-
* LEFT JOIN ...
|
|
438
|
-
* )
|
|
439
|
-
* SELECT * FROM Person_view WHERE [Person].[id] = ?
|
|
440
|
-
* ```
|
|
441
|
-
*/
|
|
442
|
-
static getQuery(ctor, includeTree) {
|
|
443
|
-
const { ast } = RuntimeContainer.get();
|
|
444
|
-
return `${this.listQuery(ctor, { includeTree })} WHERE [${ast.models[ctor.name].primary_key.name}] = ?`;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Retrieves all instances of a model from the database.
|
|
448
|
-
* @param ctor The model constructor.
|
|
449
|
-
* @param includeTree An include tree describing which related models to join.
|
|
450
|
-
* @param from An optional custom `FROM` clause to use instead of the base table.
|
|
451
|
-
* @returns Either an error string, or an array of model instances.
|
|
452
|
-
*
|
|
453
|
-
* ### Example:
|
|
454
|
-
* ```ts
|
|
455
|
-
* const orm = Orm.fromD1(env.db);
|
|
456
|
-
* const horses = await orm.list(Horse, Horse.default);
|
|
457
|
-
* ```
|
|
458
|
-
*
|
|
459
|
-
* ### Example with custom from:
|
|
460
|
-
* ```ts
|
|
461
|
-
* const orm = Orm.fromD1(env.db);
|
|
462
|
-
* const adultHorses = await orm.list(Horse, Horse.default, "SELECT * FROM Horse ORDER BY age DESC LIMIT 10");
|
|
463
|
-
* ```
|
|
464
|
-
*
|
|
465
|
-
* =>
|
|
466
|
-
*
|
|
467
|
-
* ```sql
|
|
468
|
-
* SELECT
|
|
469
|
-
* "Horse"."id" AS "id",
|
|
470
|
-
* ...
|
|
471
|
-
* FROM (SELECT * FROM Horse ORDER BY age DESC LIMIT 10)
|
|
472
|
-
* LEFT JOIN ...
|
|
473
|
-
* ```
|
|
474
|
-
*
|
|
475
|
-
*/
|
|
476
|
-
async list(ctor, opts) {
|
|
477
|
-
const sql = Orm.listQuery(ctor, opts);
|
|
478
|
-
const stmt = this.db.prepare(sql);
|
|
479
|
-
const records = await stmt.all();
|
|
480
|
-
if (!records.success) {
|
|
481
|
-
return Either.left(
|
|
482
|
-
records.error ?? "D1 query failed, but no error was returned.",
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
const mapped = Orm.mapSql(ctor, records.results, opts.includeTree ?? null);
|
|
486
|
-
return Either.right(mapped);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Retrieves a single model by primary key.
|
|
490
|
-
* @param ctor The model constructor.
|
|
491
|
-
* @param id The primary key value.
|
|
492
|
-
* @param includeTree An include tree describing which related models to join.
|
|
493
|
-
* @returns Either an error string, or the model instance (null if not found).
|
|
494
|
-
*
|
|
495
|
-
* ### Example:
|
|
496
|
-
* ```ts
|
|
497
|
-
* const orm = Orm.fromD1(env.db);
|
|
498
|
-
* const horse = await orm.get(Horse, 1, Horse.default);
|
|
499
|
-
* ```
|
|
500
|
-
*/
|
|
501
|
-
async get(ctor, id, includeTree) {
|
|
502
|
-
const sql = Orm.getQuery(ctor, includeTree);
|
|
503
|
-
const record = await this.db.prepare(sql).bind(id).run();
|
|
504
|
-
if (!record.success) {
|
|
505
|
-
return Either.left(
|
|
506
|
-
record.error ?? "D1 query failed, but no error was returned.",
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
if (record.results.length === 0) {
|
|
510
|
-
return Either.right(null);
|
|
511
|
-
}
|
|
512
|
-
const mapped = Orm.mapSql(ctor, record.results, includeTree);
|
|
513
|
-
return Either.right(mapped[0]);
|
|
514
|
-
}
|
|
213
|
+
export const Inject = () => { };
|
|
214
|
+
/** Hack to detect R2Object at runtime */
|
|
215
|
+
function isR2Object(x) {
|
|
216
|
+
if (typeof x !== "object" || x === null)
|
|
217
|
+
return false;
|
|
218
|
+
const o = x;
|
|
219
|
+
return (typeof o.key === "string" &&
|
|
220
|
+
typeof o.version === "string" &&
|
|
221
|
+
typeof o.size === "number" &&
|
|
222
|
+
typeof o.etag === "string" &&
|
|
223
|
+
typeof o.httpEtag === "string" &&
|
|
224
|
+
typeof o.uploaded === "object" &&
|
|
225
|
+
typeof o.uploaded?.getTime === "function" &&
|
|
226
|
+
typeof o.storageClass === "string" &&
|
|
227
|
+
typeof o.writeHttpMetadata === "function");
|
|
515
228
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloesce",
|
|
3
|
-
"version": "0.0.5-unstable.
|
|
3
|
+
"version": "0.0.5-unstable.10",
|
|
4
4
|
"description": "A tool to extract and compile TypeScript code into something wrangler can consume and deploy for D1 Databases and Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "vitest",
|
|
8
|
+
"test": "vitest run",
|
|
9
9
|
"format:fix": "prettier --write .",
|
|
10
10
|
"format": "prettier --check .",
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@cloudflare/workers-types": "^4.20250906.0",
|
|
23
|
+
"miniflare": "^4.20251217.0",
|
|
23
24
|
"oxlint": "^1.32.0",
|
|
24
25
|
"prettier": "^3.6.2",
|
|
25
26
|
"ts-node": "^10.9.2",
|
|
@@ -27,10 +28,6 @@
|
|
|
27
28
|
"vitest": "^3.2.4"
|
|
28
29
|
},
|
|
29
30
|
"exports": {
|
|
30
|
-
"./client": {
|
|
31
|
-
"types": "./dist/ui/client.d.ts",
|
|
32
|
-
"import": "./dist/ui/client.js"
|
|
33
|
-
},
|
|
34
31
|
"./backend": {
|
|
35
32
|
"types": "./dist/ui/backend.d.ts",
|
|
36
33
|
"import": "./dist/ui/backend.js"
|
|
@@ -43,9 +40,6 @@
|
|
|
43
40
|
"*": {
|
|
44
41
|
"backend": [
|
|
45
42
|
"dist/ui/backend.d.ts"
|
|
46
|
-
],
|
|
47
|
-
"client": [
|
|
48
|
-
"dist/ui/client.d.ts"
|
|
49
43
|
]
|
|
50
44
|
}
|
|
51
45
|
},
|
|
@@ -68,4 +62,4 @@
|
|
|
68
62
|
"orm",
|
|
69
63
|
"cli"
|
|
70
64
|
]
|
|
71
|
-
}
|
|
65
|
+
}
|
package/dist/ui/client.d.ts
DELETED
package/dist/ui/client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ui/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/ui/client.js
DELETED