cloesce 0.0.4-unstable.3 → 0.0.4-unstable.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,287 @@
1
- import { left, right } from "../common.js";
1
+ import { left, right, } from "../common.js";
2
2
  import { RuntimeContainer } from "../router/router.js";
3
- import { WasmResource, fromSql, invokeOrmWasm } from "../router/wasm.js";
3
+ import { WasmResource, mapSql as mapSql, invokeOrmWasm, } from "../router/wasm.js";
4
4
  export { cloesce } from "../router/router.js";
5
5
  export { CloesceApp } from "../common.js";
6
- // Compiler hints
6
+ /**
7
+ * Marks a class as a D1-backed SQL model.
8
+ *
9
+ * Classes annotated with `@D1` are compiled into:
10
+ * - a D1 table definition (via `cloesce migrate`)
11
+ * - backend API endpoints (Workers)
12
+ * - a frontend client API
13
+ * - Cloudflare Wrangler configurations
14
+ *
15
+ * Each `@D1` class must define exactly one `@PrimaryKey`.
16
+ *
17
+ * Example:
18
+ *```ts
19
+ * @D1
20
+ * export class Horse {
21
+ * @PrimaryKey id: number;
22
+ * name: string;
23
+ * }
24
+ * ```
25
+ */
7
26
  export const D1 = () => { };
27
+ /**
28
+ * Marks a class as a plain serializable object.
29
+ *
30
+ * `@PlainOldObject` types represent data that can be safely
31
+ * returned from a model method or API endpoint without being
32
+ * treated as a database model.
33
+ *
34
+ * These are often used for DTOs or view models.
35
+ *
36
+ * Example:
37
+ * ```ts
38
+ * @PlainOldObject
39
+ * export class CatStuff {
40
+ * catFacts: string[];
41
+ * catNames: string[];
42
+ * }
43
+ * ```
44
+ */
8
45
  export const PlainOldObject = () => { };
46
+ /**
47
+ * Declares a Wrangler environment definition.
48
+ *
49
+ * A `@WranglerEnv` class describes environment bindings
50
+ * available to your Cloudflare Worker at runtime.
51
+ *
52
+ * The environment instance is automatically injected into
53
+ * decorated methods using `@Inject`.
54
+ *
55
+ * Example:
56
+ * ```ts
57
+ * @WranglerEnv
58
+ * export class Env {
59
+ * db: D1Database;
60
+ * motd: string;
61
+ * }
62
+ *
63
+ * // in a method...
64
+ * foo(@Inject env: WranglerEnv) {...}
65
+ * ```
66
+ */
9
67
  export const WranglerEnv = () => { };
68
+ /**
69
+ * Marks a property as the SQL primary key for a model.
70
+ *
71
+ * Every `@D1` class must define exactly one primary key.
72
+ *
73
+ * Cannot be null.
74
+ *
75
+ * Example:
76
+ * ```ts
77
+ * @D1
78
+ * export class User {
79
+ * @PrimaryKey id: number;
80
+ * name: string;
81
+ * }
82
+ * ```
83
+ */
10
84
  export const PrimaryKey = () => { };
85
+ /**
86
+ * Exposes a class method as an HTTP GET endpoint.
87
+ * The method will appear in both backend and generated client APIs.
88
+ */
11
89
  export const GET = () => { };
90
+ /**
91
+ * Exposes a class method as an HTTP POST endpoint.
92
+ * The method will appear in both backend and generated client APIs.
93
+ */
12
94
  export const POST = () => { };
95
+ /**
96
+ * Exposes a class method as an HTTP PUT endpoint.
97
+ * The method will appear in both backend and generated client APIs.
98
+ */
13
99
  export const PUT = () => { };
100
+ /**
101
+ * Exposes a class method as an HTTP PATCH endpoint.
102
+ * The method will appear in both backend and generated client APIs.
103
+ */
14
104
  export const PATCH = () => { };
105
+ /**
106
+ * Exposes a class method as an HTTP DEL endpoint.
107
+ * The method will appear in both backend and generated client APIs.
108
+ */
15
109
  export const DELETE = () => { };
110
+ /**
111
+ * Declares a static property as a data source.
112
+ *
113
+ * Data sources describe SQL view definitions (joins) for
114
+ * model relationships. They define which related models
115
+ * are automatically included when querying. Data sources
116
+ * can only reference navigation properties, not scalar
117
+ * attributes.
118
+ *
119
+ * Example:
120
+ * ```ts
121
+ * @D1
122
+ * export class Dog {
123
+ * @PrimaryKey
124
+ * id: number;
125
+ *
126
+ * name: string;
127
+ * }
128
+ *
129
+ * @D1
130
+ * export class Person {
131
+ * @PrimaryKey
132
+ * id: number;
133
+ *
134
+ * @ForeignKey(Dog)
135
+ * dogId: number;
136
+ *
137
+ * @OneToOne("dogId")
138
+ * dog: Dog | undefined;
139
+ *
140
+ * // 👇 Defines a data source that joins the related Dog record
141
+ * @DataSource
142
+ * static readonly default: IncludeTree<Person> = {
143
+ * dog: {},
144
+ * };
145
+ * }
146
+ *
147
+ * // The above will generate an SQL view similar to:
148
+ * // CREATE VIEW "Person.default" AS
149
+ * // SELECT
150
+ * // "Person"."id" AS "id",
151
+ * // "Person"."dogId" AS "dogId",
152
+ * // "Dog"."id" AS "dog.id",
153
+ * // "Dog"."name" AS "dog.name"
154
+ * // FROM "Person"
155
+ * // LEFT JOIN "Dog" ON "Person"."dogId" = "Dog"."id";
156
+ *
157
+ * // When queried via the ORM or client API:
158
+ * const orm = Orm.fromD1(env.db);
159
+ * const people = (await orm.list(Person, "default")).value;
160
+ * // Each Person instance will now include a populated .dog property.
161
+ * ```
162
+ */
16
163
  export const DataSource = () => { };
164
+ /**
165
+ * Declares a one-to-many relationship between models.
166
+ *
167
+ * The argument is the foreign key property name on the
168
+ * related model.
169
+ *
170
+ * Example:
171
+ * ```ts
172
+ * @OneToMany("personId")
173
+ * dogs: Dog[];
174
+ * ```
175
+ */
17
176
  export const OneToMany = (_) => () => { };
177
+ /**
178
+ * Declares a one-to-one relationship between models.
179
+ *
180
+ * The argument is the foreign key property name that links
181
+ * the two tables.
182
+ *
183
+ * Example:
184
+ * ```ts
185
+ * @OneToOne("dogId")
186
+ * dog: Dog | undefined;
187
+ * ```
188
+ */
18
189
  export const OneToOne = (_) => () => { };
190
+ /**
191
+ * Declares a many-to-many relationship between models.
192
+ *
193
+ * The argument is a unique identifier for the generated
194
+ * junction table used to connect the two entities.
195
+ *
196
+ * Example:
197
+ * ```ts
198
+ * @ManyToMany("StudentsCourses")
199
+ * courses: Course[];
200
+ * ```
201
+ */
19
202
  export const ManyToMany = (_) => () => { };
203
+ /**
204
+ * Declares a foreign key relationship between models.
205
+ * Directly translates to a SQLite foreign key.
206
+ *
207
+ * The argument must reference either a model class or the
208
+ * name of a model class as a string. The property type must
209
+ * match the target model’s primary key type.
210
+ *
211
+ * Example:
212
+ * ```ts
213
+ * @ForeignKey(Dog)
214
+ * dogId: number;
215
+ * ```
216
+ */
20
217
  export const ForeignKey = (_) => () => { };
218
+ /**
219
+ * Marks a method parameter for dependency injection.
220
+ *
221
+ * Injected parameters can receive environment bindings,
222
+ * middleware-provided objects, or other registered values.
223
+ *
224
+ * Example:
225
+ * ```ts
226
+ * @POST
227
+ * async neigh(@Inject env: WranglerEnv) {
228
+ * return `i am ${this.name}`;
229
+ * }
230
+ * ```
231
+ */
21
232
  export const Inject = () => { };
233
+ /**
234
+ * Enables automatic CRUD method generation for a model.
235
+ *
236
+ * The argument is a list of CRUD operation kinds
237
+ * (e.g. `"SAVE"`, `"GET"`, `"LIST"`) to generate for the model.
238
+ *
239
+ * Cloesce will emit corresponding backend methods and frontend
240
+ * client bindings automatically, removing the need to manually
241
+ * define common API operations.
242
+ *
243
+ * Supported kinds:
244
+ * - **"SAVE"** — Performs an *upsert* (insert or update) for a model instance.
245
+ * - **"GET"** — Retrieves a single record by its primary key, optionally using a `DataSource`.
246
+ * - **"LIST"** — Retrieves all records for the model, using the specified `DataSource`.
247
+ * - **(future)** `"DELETE"` — Will remove a record by primary key once implemented.
248
+ *
249
+ * The generated methods are static, exposed through both the backend
250
+ * (Worker endpoints) and the frontend client API.
251
+ *
252
+ * Example:
253
+ * ```ts
254
+ * @CRUD(["SAVE", "GET", "LIST"])
255
+ * @D1
256
+ * export class CrudHaver {
257
+ * @PrimaryKey id: number;
258
+ * name: string;
259
+ * }
260
+ *
261
+ * // Generated methods (conceptually):
262
+ * // static async save(item: CrudHaver): Promise<HttpResult<CrudHaver>>
263
+ * // static async get(id: number, dataSource?: string): Promise<HttpResult<CrudHaver>>
264
+ * // static async list(dataSource?: string): Promise<HttpResult<CrudHaver[]>>
265
+ * ```
266
+ */
22
267
  export const CRUD = (_kinds) => () => { };
23
268
  /**
24
- * ORM functions which use metadata to translate arguments to valid SQL queries.
269
+ * Provides helper methods for performing ORM operations against a D1 database.
270
+ *
271
+ * The `Orm` class uses the Cloesce metadata system to generate, execute,
272
+ * and map SQL queries for model classes decorated with `@D1`.
273
+ *
274
+ * Typical operations include:
275
+ * - `fromD1(db)` — create an ORM instance bound to a `D1Database`
276
+ * - `upsert()` — insert or update a model
277
+ * - `list()` — fetch all instances of a model
278
+ * - `get()` — fetch one instance by primary key
279
+ *
280
+ * Example:
281
+ * ```ts
282
+ * const orm = Orm.fromD1(env.db);
283
+ * const horses = (await orm.list(Horse, "default")).value;
284
+ * ```
25
285
  */
26
286
  export class Orm {
27
287
  db;
@@ -42,10 +302,9 @@ export class Orm {
42
302
  * @param ctor The model constructor
43
303
  * @param records D1 Result records
44
304
  * @param includeTree Include tree to define the relationships to join.
45
- * @returns
46
305
  */
47
- static fromSql(ctor, records, includeTree) {
48
- return fromSql(ctor, records, includeTree);
306
+ static mapSql(ctor, records, includeTree = null) {
307
+ return mapSql(ctor, records, includeTree);
49
308
  }
50
309
  /**
51
310
  * Returns a SQL query to insert a model into the database. Uses an IncludeTree as a guide for
@@ -60,7 +319,7 @@ export class Orm {
60
319
  * @param includeTree An include tree describing which foreign keys to join.
61
320
  * @returns Either an error string, or the insert query string.
62
321
  */
63
- static upsertQuery(ctor, newModel, includeTree) {
322
+ static upsertQuery(ctor, newModel, includeTree = null) {
64
323
  const { wasm } = RuntimeContainer.get();
65
324
  const args = [
66
325
  WasmResource.fromString(ctor.name, wasm),
@@ -110,7 +369,7 @@ export class Orm {
110
369
  * @param includeTree An include tree describing which foreign keys to join.
111
370
  * @returns An error string, or the primary key of the inserted model.
112
371
  */
113
- async upsert(ctor, newModel, includeTree) {
372
+ async upsert(ctor, newModel, includeTree = null) {
114
373
  let upsertQueryRes = Orm.upsertQuery(ctor, newModel, includeTree);
115
374
  if (!upsertQueryRes.ok) {
116
375
  return upsertQueryRes;
@@ -141,17 +400,17 @@ export class Orm {
141
400
  /**
142
401
  * Returns a query of the form `SELECT * FROM [Model.DataSource]`
143
402
  */
144
- static listQuery(ctor, includeTree) {
403
+ static listQuery(ctor, includeTree = null) {
145
404
  if (includeTree) {
146
405
  return `SELECT * FROM [${ctor.name}.${includeTree.toString()}]`;
147
406
  }
148
407
  return `SELECT * FROM [${ctor.name}]`;
149
408
  }
150
409
  /**
151
- * Returns a query of the form `SELECT * FROM [Model.DataSource] WHERE [Model.PrimaryKey] = ?`.
410
+ * Returns a query of the form `SELECT * FROM [Model.DataSource] WHERE [PrimaryKey] = ?`.
152
411
  * Requires the id parameter to be bound (use db.prepare().bind)
153
412
  */
154
- static getQuery(ctor, includeTree) {
413
+ static getQuery(ctor, includeTree = null) {
155
414
  const { ast } = RuntimeContainer.get();
156
415
  if (includeTree) {
157
416
  return `${this.listQuery(ctor, includeTree)} WHERE [${ast.models[ctor.name].primary_key.name}] = ?`;
@@ -162,7 +421,7 @@ export class Orm {
162
421
  * Executes a query of the form `SELECT * FROM [Model.DataSource]`, returning all results
163
422
  * as instantiated models.
164
423
  */
165
- async list(ctor, includeTreeKey) {
424
+ async list(ctor, includeTreeKey = null) {
166
425
  const q = Orm.listQuery(ctor, includeTreeKey);
167
426
  const res = await this.db.prepare(q).run();
168
427
  if (!res.success) {
@@ -172,7 +431,7 @@ export class Orm {
172
431
  const includeTree = includeTreeKey === null
173
432
  ? null
174
433
  : ast.models[ctor.name].data_sources[includeTreeKey.toString()].tree;
175
- const fromSqlRes = fromSql(ctor, res.results, includeTree);
434
+ const fromSqlRes = mapSql(ctor, res.results, includeTree);
176
435
  if (!fromSqlRes.ok) {
177
436
  return fromSqlRes;
178
437
  }
@@ -182,7 +441,7 @@ export class Orm {
182
441
  * Executes a query of the form `SELECT * FROM [Model.DataSource] WHERE [Model.PrimaryKey] = ?`
183
442
  * returning all results as instantiated models.
184
443
  */
185
- async get(ctor, id, includeTreeKey) {
444
+ async get(ctor, id, includeTreeKey = null) {
186
445
  const q = Orm.getQuery(ctor, includeTreeKey);
187
446
  const res = await this.db.prepare(q).bind(id).run();
188
447
  if (!res.success) {
@@ -192,7 +451,7 @@ export class Orm {
192
451
  const includeTree = includeTreeKey === null
193
452
  ? null
194
453
  : ast.models[ctor.name].data_sources[includeTreeKey.toString()].tree;
195
- const fromSqlRes = fromSql(ctor, res.results, includeTree);
454
+ const fromSqlRes = mapSql(ctor, res.results, includeTree);
196
455
  if (!fromSqlRes.ok) {
197
456
  return fromSqlRes;
198
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloesce",
3
- "version": "0.0.4-unstable.3",
3
+ "version": "0.0.4-unstable.5",
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",