cloesce 0.0.3-fix.6 → 0.0.4-unstable.0
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/README.md +154 -70
- package/dist/cli.js +65 -85
- package/dist/common.d.ts +49 -11
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +72 -11
- package/dist/extractor/extract.d.ts +2 -0
- package/dist/extractor/extract.d.ts.map +1 -1
- package/dist/extractor/extract.js +242 -43
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.d.ts +22 -0
- package/dist/router/crud.d.ts.map +1 -0
- package/dist/router/crud.js +65 -0
- package/dist/router/router.d.ts +77 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/{runtime/runtime.js → router/router.js} +119 -161
- package/dist/router/wasm.d.ts +37 -0
- package/dist/router/wasm.d.ts.map +1 -0
- package/dist/router/wasm.js +98 -0
- package/dist/ui/backend.d.ts +124 -0
- package/dist/ui/backend.d.ts.map +1 -0
- package/dist/ui/backend.js +201 -0
- package/dist/{index → ui}/client.d.ts +1 -1
- package/dist/ui/client.d.ts.map +1 -0
- package/package.json +15 -13
- package/dist/LICENSE +0 -201
- package/dist/index/backend.d.ts +0 -22
- package/dist/index/backend.d.ts.map +0 -1
- package/dist/index/backend.js +0 -17
- package/dist/index/client.d.ts.map +0 -1
- package/dist/runtime/runtime.d.ts +0 -112
- package/dist/runtime/runtime.d.ts.map +0 -1
- package/dist/runtime.wasm +0 -0
- /package/dist/{index → ui}/client.js +0 -0
package/dist/README.md
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
# cloesce (
|
|
1
|
+
# cloesce (unstable, `v0.0.4`)
|
|
2
2
|
|
|
3
|
-
Cloesce is a full stack compiler for the Cloudflare developer platform, allowing class definitions in high level languages to serve as
|
|
3
|
+
Cloesce is a full stack compiler for the Cloudflare developer platform, allowing class definitions in high level languages to serve as a metadata basis to create a database schema, backend REST API, frontend API client, and Cloudflare infrastructure (as of v0.0.4, D1 + Workers).
|
|
4
4
|
|
|
5
5
|
Cloesce is working towards an alpha MVP (v0.1.0), with the general milestones being [here](https://cloesce.pages.dev/schreiber/v0.1.0_milestones/).
|
|
6
6
|
|
|
7
7
|
Internal documentation going over design decisions and general thoughts for each milestone can be found [here](https://cloesce.pages.dev/).
|
|
8
8
|
|
|
9
|
-
# Documentation `v0.0.
|
|
10
|
-
|
|
11
|
-
Note that this version is very unstable (ie, it passes our set of happy-path tests).
|
|
9
|
+
# Documentation `v0.0.4`
|
|
12
10
|
|
|
13
11
|
## Getting Started
|
|
14
12
|
|
|
15
|
-
`v0.0.
|
|
13
|
+
`v0.0.4` supports only Typescript-to-Typescript projects. An example project is shown [here](https://github.com/bens-schreiber/cloesce/tree/main/examples).
|
|
16
14
|
|
|
17
15
|
1. NPM
|
|
18
16
|
|
|
19
17
|
- Create an NPM project and install cloesce
|
|
20
18
|
|
|
21
19
|
```sh
|
|
22
|
-
npm i cloesce@0.0.
|
|
20
|
+
npm i cloesce@0.0.4-unstable.0
|
|
23
21
|
```
|
|
24
22
|
|
|
25
23
|
2. TypeScript
|
|
@@ -42,19 +40,19 @@ npm i cloesce@0.0.3-fix.3
|
|
|
42
40
|
|
|
43
41
|
3. Cloesce Config
|
|
44
42
|
|
|
45
|
-
- Create a `cloesce.config.json` with the following
|
|
43
|
+
- Create a `cloesce.config.json` with the following keys:
|
|
46
44
|
|
|
47
45
|
```json
|
|
48
46
|
{
|
|
49
|
-
"source": "./src",
|
|
50
|
-
"workersUrl": "http://localhost:
|
|
51
|
-
"clientUrl": "http://localhost:
|
|
47
|
+
"source": "./src",
|
|
48
|
+
"workersUrl": "http://localhost:5000/api",
|
|
49
|
+
"clientUrl": "http://localhost:5173/api"
|
|
52
50
|
}
|
|
53
51
|
```
|
|
54
52
|
|
|
55
53
|
4. Vite
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
To prevent CORS issues, a Vite proxy can be used for the frontend:
|
|
58
56
|
|
|
59
57
|
```ts
|
|
60
58
|
import { defineConfig } from "vite";
|
|
@@ -74,7 +72,7 @@ export default defineConfig({
|
|
|
74
72
|
|
|
75
73
|
5. Wrangler Config
|
|
76
74
|
|
|
77
|
-
- `v0.0.
|
|
75
|
+
- `v0.0.4` will generate the required areas of your wrangler config. A full config looks like this:
|
|
78
76
|
|
|
79
77
|
```toml
|
|
80
78
|
compatibility_date = "2025-10-02"
|
|
@@ -89,12 +87,19 @@ database_name = "example"
|
|
|
89
87
|
|
|
90
88
|
## A Simple Model
|
|
91
89
|
|
|
92
|
-
A model is a type which represents
|
|
90
|
+
A model is a type which represents:
|
|
91
|
+
|
|
92
|
+
- a database table,
|
|
93
|
+
- database views
|
|
94
|
+
- REST API
|
|
95
|
+
- Client API
|
|
96
|
+
- Cloudflare infrastructure (D1 + Workers)
|
|
93
97
|
|
|
94
|
-
Suprisingly, it's pretty compact. A basic
|
|
98
|
+
Suprisingly, it's pretty compact. A basic model looks like this:
|
|
95
99
|
|
|
96
100
|
```ts
|
|
97
101
|
// horse.cloesce.ts
|
|
102
|
+
import { D1, GET, POST, PrimaryKey } from "cloesce/backend";
|
|
98
103
|
|
|
99
104
|
@D1
|
|
100
105
|
export class Horse {
|
|
@@ -104,7 +109,7 @@ export class Horse {
|
|
|
104
109
|
name: string | null;
|
|
105
110
|
|
|
106
111
|
@POST
|
|
107
|
-
|
|
112
|
+
neigh(): string {
|
|
108
113
|
return `i am ${this.name}, this is my horse noise`;
|
|
109
114
|
}
|
|
110
115
|
}
|
|
@@ -113,25 +118,34 @@ export class Horse {
|
|
|
113
118
|
- `@D1` denotes that this is a SQL Table
|
|
114
119
|
- `@PrimaryKey` sets the SQL primary key. All models require a primary key.
|
|
115
120
|
- `@POST` reveals the method as an API endpoint with the `POST` HTTP Verb.
|
|
121
|
+
- All Cloesce models need to be under a `.cloesce.ts` file.
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
To compile this model into a working full stack application, Cloesce must undergo both **compilation** and **migrations**. Compilation is the process of extracting the metadata language that powers Cloesce, ensuring it is a valid program, and then producing code to orchestrate the program across different domains (database, backend, frontend, cloud). Migrations utilize the history of validated metadata to create SQL code, translating the evolution of your models.
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
To compile, run `npx cloesce compile`.
|
|
126
|
+
|
|
127
|
+
To create a migration, run `npx cloesce migrate <name>`.
|
|
122
128
|
|
|
123
|
-
|
|
124
|
-
npx wrangler build
|
|
129
|
+
After running the above commands, you will have a full project capable of being ran with:
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
```sh
|
|
132
|
+
# Apply the generated migrations
|
|
133
|
+
npx wrangler d1 migrations apply <db-name>
|
|
134
|
+
|
|
135
|
+
# Run the backend
|
|
136
|
+
npx wrangler dev
|
|
128
137
|
```
|
|
129
138
|
|
|
130
|
-
Note the output in the `.generated/` dir
|
|
139
|
+
Note the output in the `.generated/` dir. These values should not be committed to git, as they depend on the file system of the machine running it.
|
|
131
140
|
|
|
132
141
|
- `client.ts` is an importable API with all of your backend types and endpoints
|
|
133
|
-
- `migrations/*.sql` is the generated SQL code (note, migrations beyond the initial aren't really supported yet)
|
|
134
142
|
- `workers.ts` is the workers entrypoint.
|
|
143
|
+
- `cidl.json` is the working metadata for the project
|
|
144
|
+
|
|
145
|
+
Note the output in `migrations`, ex after running `npx cloesce migrate Initial`
|
|
146
|
+
|
|
147
|
+
- `<date>_Initial.json` contains all model information necessary from SQL
|
|
148
|
+
- `<date>_Initial.sql` contains the acual SQL migrations. In this early version of Cloesce, it's important to check migrations every time.
|
|
135
149
|
|
|
136
150
|
## Features
|
|
137
151
|
|
|
@@ -142,6 +156,8 @@ In order to interact with your database, you will need to define a WranglerEnv
|
|
|
142
156
|
```ts
|
|
143
157
|
// horse.cloesce.ts
|
|
144
158
|
|
|
159
|
+
import { WranglerEnv } from "cloesce/backend";
|
|
160
|
+
|
|
145
161
|
@WranglerEnv
|
|
146
162
|
export class Env {
|
|
147
163
|
db: D1Database; // only one DB is supported for now-- make sure it matches the name in `wrangler.toml`
|
|
@@ -161,118 +177,117 @@ export class Horse {
|
|
|
161
177
|
|
|
162
178
|
@POST
|
|
163
179
|
async neigh(@Inject env: WranglerEnv): Promise<string> {
|
|
164
|
-
env.db.prepare(...);
|
|
180
|
+
await env.db.prepare(...);
|
|
165
181
|
|
|
166
182
|
return `i am ${this.name}, this is my horse noise`;
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
```
|
|
170
186
|
|
|
171
|
-
|
|
187
|
+
### Foreign Keys, One to One, Data Sources
|
|
172
188
|
|
|
173
|
-
|
|
189
|
+
Complex model relationships are permitted via the `@ForeignKey`, `@OneToOne / @OneToMany @ManyToMany` and `@DataSource` decorators.
|
|
174
190
|
Foreign keys are scalar attributes which must reference some other model's primary key:
|
|
175
191
|
|
|
176
192
|
```ts
|
|
177
193
|
@D1
|
|
178
|
-
export class
|
|
194
|
+
export class Dog {
|
|
179
195
|
@PrimaryKey
|
|
180
196
|
id: number;
|
|
181
197
|
}
|
|
182
198
|
|
|
183
199
|
@D1
|
|
184
|
-
export class
|
|
200
|
+
export class Person {
|
|
185
201
|
@PrimaryKey
|
|
186
202
|
id: number;
|
|
187
203
|
|
|
188
|
-
@ForeignKey(
|
|
189
|
-
|
|
204
|
+
@ForeignKey(Dog)
|
|
205
|
+
dogId: number;
|
|
190
206
|
}
|
|
191
207
|
```
|
|
192
208
|
|
|
193
|
-
This representation is true to the underlying SQL table: `
|
|
209
|
+
This representation is true to the underlying SQL table: `Person` has a column `dogId` which is a foreign key to `Dog`. Cloesce allows you to actually join these tables together in your model representation:
|
|
194
210
|
|
|
195
211
|
```ts
|
|
196
212
|
@D1
|
|
197
|
-
export class
|
|
213
|
+
export class Dog {
|
|
198
214
|
@PrimaryKey
|
|
199
215
|
id: number;
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
@D1
|
|
203
|
-
export class
|
|
219
|
+
export class Person {
|
|
204
220
|
@PrimaryKey
|
|
205
221
|
id: number;
|
|
206
222
|
|
|
207
|
-
@ForeignKey(
|
|
208
|
-
|
|
223
|
+
@ForeignKey(Dog)
|
|
224
|
+
dogId: number;
|
|
209
225
|
|
|
210
|
-
@OneToOne("
|
|
211
|
-
|
|
226
|
+
@OneToOne("dogId") // references Person.dogId
|
|
227
|
+
dog: Dog | undefined; // This value is a "navigation property", which may or may not exist at runtime
|
|
212
228
|
}
|
|
213
229
|
```
|
|
214
230
|
|
|
215
|
-
In `v0.0.
|
|
231
|
+
In `v0.0.4`, there are no defaults, only very explicit decisons. Because of that, navigation properties won't exist at runtime unless you tell them to. Cloesce does this via a `DataSource`, which describes the foreign key dependencies you wish to include. All scalar properties are included by default and cannot be excluded.
|
|
216
232
|
|
|
217
233
|
```ts
|
|
218
234
|
@D1
|
|
219
|
-
export class
|
|
235
|
+
export class Dog {
|
|
220
236
|
@PrimaryKey
|
|
221
237
|
id: number;
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
@D1
|
|
225
|
-
export class
|
|
241
|
+
export class Person {
|
|
226
242
|
@PrimaryKey
|
|
227
243
|
id: number;
|
|
228
244
|
|
|
229
|
-
@ForeignKey(
|
|
230
|
-
|
|
245
|
+
@ForeignKey(Dog)
|
|
246
|
+
dogId: number;
|
|
231
247
|
|
|
232
|
-
@OneToOne("
|
|
233
|
-
|
|
248
|
+
@OneToOne("dogId")
|
|
249
|
+
dog: Dog | undefined;
|
|
234
250
|
|
|
235
251
|
@DataSource
|
|
236
|
-
static readonly default: IncludeTree<
|
|
237
|
-
|
|
252
|
+
static readonly default: IncludeTree<Person> = {
|
|
253
|
+
dog: {}, // says: on model population, join Persons's Dog
|
|
238
254
|
};
|
|
239
255
|
}
|
|
240
256
|
```
|
|
241
257
|
|
|
242
|
-
|
|
258
|
+
Data sources are just SQL views and can be invoked in your queries. They are aliased in such a way that its similiar to object properties. The frontend chooses which datasource to use in it's API client. `null` is a valid option, meaning no joins will occur.
|
|
243
259
|
|
|
244
260
|
```ts
|
|
245
261
|
@D1
|
|
246
|
-
export class
|
|
262
|
+
export class Person {
|
|
247
263
|
@PrimaryKey
|
|
248
264
|
id: number;
|
|
249
265
|
|
|
250
|
-
@ForeignKey(
|
|
251
|
-
|
|
266
|
+
@ForeignKey(Dog)
|
|
267
|
+
dogId: number;
|
|
252
268
|
|
|
253
|
-
@OneToOne("
|
|
254
|
-
|
|
269
|
+
@OneToOne("dogId")
|
|
270
|
+
dog: Dog | undefined;
|
|
255
271
|
|
|
256
272
|
@DataSource
|
|
257
|
-
static readonly default: IncludeTree<
|
|
258
|
-
|
|
273
|
+
static readonly default: IncludeTree<Person> = {
|
|
274
|
+
dog: {},
|
|
259
275
|
};
|
|
260
276
|
|
|
261
277
|
@GET
|
|
262
|
-
static async get(id: number, @Inject env: WranglerEnv): Promise<
|
|
278
|
+
static async get(id: number, @Inject env: WranglerEnv): Promise<Person> {
|
|
263
279
|
let records = await env.db
|
|
264
|
-
.prepare("SELECT * FROM [
|
|
280
|
+
.prepare("SELECT * FROM [Person.default] WHERE [Person.id] = ?") // Person.default is the SQL view generated from the IncludeTree
|
|
265
281
|
.bind(id)
|
|
266
282
|
.run();
|
|
267
283
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return modelsFromSql(A, records.results, A.default)[0] as A;
|
|
284
|
+
let persons = Orm.fromSql(Person, records.results, Person.default);
|
|
285
|
+
return persons.value[0];
|
|
271
286
|
}
|
|
272
287
|
}
|
|
273
288
|
```
|
|
274
289
|
|
|
275
|
-
Note
|
|
290
|
+
Note that the `get` code can be simplified using CRUD methods or ORM primitives.
|
|
276
291
|
|
|
277
292
|
### One to Many
|
|
278
293
|
|
|
@@ -316,8 +331,6 @@ export class Dog {
|
|
|
316
331
|
|
|
317
332
|
### Many to Many
|
|
318
333
|
|
|
319
|
-
NOTE: `M:M` relationships have a [bug](https://github.com/bens-schreiber/cloesce/issues/88) in `v0.0.3`, but the syntax is as follows:
|
|
320
|
-
|
|
321
334
|
```ts
|
|
322
335
|
@D1
|
|
323
336
|
export class Student {
|
|
@@ -333,11 +346,74 @@ export class Course {
|
|
|
333
346
|
@PrimaryKey
|
|
334
347
|
id: number;
|
|
335
348
|
|
|
336
|
-
@ManyToMany("StudentsCourses")
|
|
349
|
+
@ManyToMany("StudentsCourses") // same unique id => same jct table.
|
|
337
350
|
students: Student[];
|
|
338
351
|
}
|
|
339
352
|
```
|
|
340
353
|
|
|
354
|
+
### ORM Methods
|
|
355
|
+
|
|
356
|
+
Cloesce provides a suite of ORM methods for getting, listing, updating and inserting models.
|
|
357
|
+
|
|
358
|
+
#### Upsert
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
@D1
|
|
362
|
+
class Horse {
|
|
363
|
+
// ...
|
|
364
|
+
|
|
365
|
+
@POST
|
|
366
|
+
static async post(@Inject { db }: Env, horse: Horse): Promise<Horse> {
|
|
367
|
+
const orm = Orm.fromD1(db);
|
|
368
|
+
await orm.upsert(Horse, horse, null);
|
|
369
|
+
return (await orm.get(Horse, horse.id, null)).value;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
#### List, Get
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
@D1
|
|
378
|
+
class Horse {
|
|
379
|
+
// ...
|
|
380
|
+
@GET
|
|
381
|
+
static async get(@Inject { db }: Env, id: number): Promise<Horse> {
|
|
382
|
+
const orm = Orm.fromD1(db);
|
|
383
|
+
return (await orm.get(Horse, id, "default")).value;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@GET
|
|
387
|
+
static async list(@Inject { db }: Env): Promise<Horse[]> {
|
|
388
|
+
const orm = Orm.fromD1(db);
|
|
389
|
+
return (await orm.list(Horse, "default")).value;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### CRUD Methods
|
|
395
|
+
|
|
396
|
+
Generic GET, POST, PATCH (and in a future version, DEL) boilerplate methods do not need to be copied around. Cloesce supports CRUD generation, a syntactic sugar that adds the methods to the compiler output.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
@CRUD(["POST", "GET", "LIST"])
|
|
400
|
+
@D1
|
|
401
|
+
export class CrudHaver {
|
|
402
|
+
@PrimaryKey
|
|
403
|
+
id: number;
|
|
404
|
+
name: string;
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
which will generate client API methods like:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
static async get(
|
|
412
|
+
id: number,
|
|
413
|
+
dataSource: "none" = "none",
|
|
414
|
+
): Promise<HttpResult<CrudHaver>>
|
|
415
|
+
```
|
|
416
|
+
|
|
341
417
|
### Plain Old Objects
|
|
342
418
|
|
|
343
419
|
Simple non-model objects can be returned and serialized from a model method:
|
|
@@ -355,7 +431,7 @@ export class Cat {
|
|
|
355
431
|
id: number;
|
|
356
432
|
|
|
357
433
|
@GET
|
|
358
|
-
|
|
434
|
+
query(): CatStuff {
|
|
359
435
|
return {
|
|
360
436
|
catFacts: ["cats r cool"],
|
|
361
437
|
catNames: ["reginald"]
|
|
@@ -384,16 +460,24 @@ class Foo {
|
|
|
384
460
|
|
|
385
461
|
## Unit Tests
|
|
386
462
|
|
|
387
|
-
- `src/
|
|
463
|
+
- `src/frontend/ts` run `npm test`
|
|
388
464
|
- `src/generator` run `cargo test`
|
|
389
465
|
|
|
390
466
|
## Integration Tests
|
|
391
467
|
|
|
392
|
-
-
|
|
393
|
-
-
|
|
468
|
+
- Regression tests: `cargo run --bin test regression`
|
|
469
|
+
- Pass fail extractor tests: `cargo run --bin test run-fail`
|
|
394
470
|
|
|
395
471
|
Optionally, pass `--check` if new snapshots should not be created.
|
|
396
472
|
|
|
473
|
+
To update integration snapshots, run:
|
|
474
|
+
|
|
475
|
+
- `cargo run --bin update`
|
|
476
|
+
|
|
477
|
+
To delete any generated snapshots run:
|
|
478
|
+
|
|
479
|
+
- `cargo run --bin update -- -d`
|
|
480
|
+
|
|
397
481
|
## E2E
|
|
398
482
|
|
|
399
483
|
- `tests/e2e` run `npm test`
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { WASI } from "node:wasi";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { command, run, subcommands, flag, option, optional,
|
|
6
|
+
import { command, run, subcommands, flag, string, positional, option, optional, } from "cmd-ts";
|
|
7
7
|
import { Project } from "ts-morph";
|
|
8
8
|
import { CidlExtractor } from "./extractor/extract.js";
|
|
9
9
|
import { ExtractorError, ExtractorErrorCode, getErrorInfo } from "./common.js";
|
|
@@ -35,9 +35,9 @@ const cmds = subcommands({
|
|
|
35
35
|
args: [
|
|
36
36
|
"generate",
|
|
37
37
|
"all",
|
|
38
|
+
path.join(outputDir, "cidl.pre.json"),
|
|
38
39
|
path.join(outputDir, "cidl.json"),
|
|
39
40
|
"wrangler.toml",
|
|
40
|
-
"migrations/migrations.sql",
|
|
41
41
|
path.join(outputDir, "workers.ts"),
|
|
42
42
|
path.join(outputDir, "client.ts"),
|
|
43
43
|
config.clientUrl,
|
|
@@ -48,89 +48,9 @@ const cmds = subcommands({
|
|
|
48
48
|
await generate(allConfig);
|
|
49
49
|
},
|
|
50
50
|
}),
|
|
51
|
-
wrangler: command({
|
|
52
|
-
name: "wrangler",
|
|
53
|
-
description: "Generate wrangler.toml configuration",
|
|
54
|
-
args: {},
|
|
55
|
-
handler: async () => {
|
|
56
|
-
await generate({
|
|
57
|
-
name: "wrangler",
|
|
58
|
-
wasmFile: "generator.wasm",
|
|
59
|
-
args: ["generate", "wrangler", "wrangler.toml"],
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
}),
|
|
63
|
-
d1: command({
|
|
64
|
-
name: "d1",
|
|
65
|
-
description: "Generate database schema",
|
|
66
|
-
args: {},
|
|
67
|
-
handler: async () => {
|
|
68
|
-
const config = loadCloesceConfig(process.cwd());
|
|
69
|
-
const outputDir = config.outputDir ?? ".generated";
|
|
70
|
-
await generate({
|
|
71
|
-
name: "d1",
|
|
72
|
-
wasmFile: "generator.wasm",
|
|
73
|
-
args: [
|
|
74
|
-
"generate",
|
|
75
|
-
"d1",
|
|
76
|
-
path.join(outputDir, "cidl.json"),
|
|
77
|
-
"migrations/migrations.sql",
|
|
78
|
-
],
|
|
79
|
-
});
|
|
80
|
-
},
|
|
81
|
-
}),
|
|
82
|
-
workers: command({
|
|
83
|
-
name: "workers",
|
|
84
|
-
description: "Generate workers TypeScript",
|
|
85
|
-
args: {},
|
|
86
|
-
handler: async () => {
|
|
87
|
-
const config = loadCloesceConfig(process.cwd());
|
|
88
|
-
const outputDir = config.outputDir ?? ".generated";
|
|
89
|
-
if (!config.workersUrl) {
|
|
90
|
-
console.error("Error: workersUrl must be defined in cloesce.config.json");
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
await generate({
|
|
94
|
-
name: "workers",
|
|
95
|
-
wasmFile: "generator.wasm",
|
|
96
|
-
args: [
|
|
97
|
-
"generate",
|
|
98
|
-
"workers",
|
|
99
|
-
path.join(outputDir, "cidl.json"),
|
|
100
|
-
path.join(outputDir, "workers.ts"),
|
|
101
|
-
"wrangler.toml",
|
|
102
|
-
config.workersUrl,
|
|
103
|
-
],
|
|
104
|
-
});
|
|
105
|
-
},
|
|
106
|
-
}),
|
|
107
|
-
client: command({
|
|
108
|
-
name: "client",
|
|
109
|
-
description: "Generate client TypeScript",
|
|
110
|
-
args: {},
|
|
111
|
-
handler: async () => {
|
|
112
|
-
const config = loadCloesceConfig(process.cwd());
|
|
113
|
-
const outputDir = config.outputDir ?? ".generated";
|
|
114
|
-
if (!config.clientUrl) {
|
|
115
|
-
console.error("Error: clientUrl must be defined in cloesce-config.json");
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
await generate({
|
|
119
|
-
name: "client",
|
|
120
|
-
wasmFile: "generator.wasm",
|
|
121
|
-
args: [
|
|
122
|
-
"generate",
|
|
123
|
-
"client",
|
|
124
|
-
path.join(outputDir, "cidl.json"),
|
|
125
|
-
path.join(outputDir, "client.ts"),
|
|
126
|
-
config.clientUrl,
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
},
|
|
130
|
-
}),
|
|
131
51
|
extract: command({
|
|
132
52
|
name: "extract",
|
|
133
|
-
description: "Extract models and write cidl.json
|
|
53
|
+
description: "Extract models and write cidl.pre.json",
|
|
134
54
|
args: {
|
|
135
55
|
projectName: option({
|
|
136
56
|
long: "project-name",
|
|
@@ -141,7 +61,6 @@ const cmds = subcommands({
|
|
|
141
61
|
long: "out",
|
|
142
62
|
short: "o",
|
|
143
63
|
type: optional(string),
|
|
144
|
-
description: "Output path (default: .generated/cidl.json)",
|
|
145
64
|
}),
|
|
146
65
|
inp: option({
|
|
147
66
|
long: "in",
|
|
@@ -169,6 +88,54 @@ const cmds = subcommands({
|
|
|
169
88
|
await extract({ ...args });
|
|
170
89
|
},
|
|
171
90
|
}),
|
|
91
|
+
migrate: command({
|
|
92
|
+
name: "migrate",
|
|
93
|
+
description: "Creates a database migration.",
|
|
94
|
+
args: {
|
|
95
|
+
name: positional({ type: string, displayName: "name" }),
|
|
96
|
+
debug: flag({
|
|
97
|
+
long: "debug",
|
|
98
|
+
short: "d",
|
|
99
|
+
description: "Show debug output",
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
handler: async (args) => {
|
|
103
|
+
const config = loadCloesceConfig(process.cwd(), args.debug);
|
|
104
|
+
const cidlPath = path.join(config.outputDir ?? ".generated", "cidl.json");
|
|
105
|
+
if (!fs.existsSync(cidlPath)) {
|
|
106
|
+
console.error("Err: No cloesce file found, have you ran `cloesce compile`?");
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const migrationsPath = "./migrations";
|
|
110
|
+
if (!fs.existsSync(migrationsPath)) {
|
|
111
|
+
fs.mkdirSync(migrationsPath);
|
|
112
|
+
}
|
|
113
|
+
const migrationPrefix = path.join(migrationsPath, `${timestamp()}_${args.name}`);
|
|
114
|
+
let wasmArgs = [
|
|
115
|
+
"migrations",
|
|
116
|
+
cidlPath,
|
|
117
|
+
`${migrationPrefix}.json`,
|
|
118
|
+
`${migrationPrefix}.sql`,
|
|
119
|
+
];
|
|
120
|
+
// Add last migration if exists
|
|
121
|
+
{
|
|
122
|
+
const files = fs.readdirSync(migrationsPath);
|
|
123
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
124
|
+
// Sort descending by filename
|
|
125
|
+
jsonFiles.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
126
|
+
if (jsonFiles.length > 0) {
|
|
127
|
+
wasmArgs.push(path.join(migrationsPath, jsonFiles[0]));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const migrateConfig = {
|
|
131
|
+
name: "migrations",
|
|
132
|
+
wasmFile: "generator.wasm",
|
|
133
|
+
args: wasmArgs,
|
|
134
|
+
};
|
|
135
|
+
// Runs a generator command. Exits the process on failure.
|
|
136
|
+
await generate(migrateConfig);
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
172
139
|
},
|
|
173
140
|
});
|
|
174
141
|
async function extract(opts) {
|
|
@@ -177,7 +144,7 @@ async function extract(opts) {
|
|
|
177
144
|
const config = loadCloesceConfig(projectRoot, opts.debug);
|
|
178
145
|
const searchPaths = opts.inp ? [opts.inp] : (config.paths ?? [root]);
|
|
179
146
|
const outputDir = config.outputDir ?? ".generated";
|
|
180
|
-
const outPath = opts.out ?? path.join(outputDir, "cidl.json");
|
|
147
|
+
const outPath = opts.out ?? path.join(outputDir, "cidl.pre.json");
|
|
181
148
|
const truncate = opts.truncateSourcePaths ?? config.truncateSourcePaths ?? false;
|
|
182
149
|
const cloesceProjectName = opts.projectName ??
|
|
183
150
|
config.projectName ??
|
|
@@ -205,6 +172,9 @@ async function extract(opts) {
|
|
|
205
172
|
if (truncate) {
|
|
206
173
|
ast.wrangler_env.source_path =
|
|
207
174
|
"./" + path.basename(ast.wrangler_env.source_path);
|
|
175
|
+
if (ast.app_source) {
|
|
176
|
+
ast.app_source = "./" + path.basename(ast.app_source);
|
|
177
|
+
}
|
|
208
178
|
for (const model of Object.values(ast.models)) {
|
|
209
179
|
model.source_path =
|
|
210
180
|
"./" + path.basename(model.source_path);
|
|
@@ -267,6 +237,16 @@ function loadCloesceConfig(root, debug = false) {
|
|
|
267
237
|
}
|
|
268
238
|
return {};
|
|
269
239
|
}
|
|
240
|
+
function timestamp() {
|
|
241
|
+
const d = new Date();
|
|
242
|
+
return (d.getFullYear().toString() +
|
|
243
|
+
String(d.getMonth() + 1).padStart(2, "0") +
|
|
244
|
+
String(d.getDate()).padStart(2, "0") +
|
|
245
|
+
"T" +
|
|
246
|
+
String(d.getHours()).padStart(2, "0") +
|
|
247
|
+
String(d.getMinutes()).padStart(2, "0") +
|
|
248
|
+
String(d.getSeconds()).padStart(2, "0"));
|
|
249
|
+
}
|
|
270
250
|
function readPackageJsonProjectName(cwd) {
|
|
271
251
|
const pkgPath = path.join(cwd, "package.json");
|
|
272
252
|
let projectName = path.basename(cwd);
|