cloesce 0.0.4-unstable.0 → 0.0.4-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/README.md → README.md} +212 -67
- package/dist/cli.js +37 -21
- package/dist/common.d.ts +89 -41
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +93 -38
- package/dist/extractor/extract.d.ts +9 -9
- package/dist/extractor/extract.d.ts.map +1 -1
- package/dist/extractor/extract.js +128 -149
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.d.ts.map +1 -1
- package/dist/router/crud.js +25 -20
- package/dist/router/router.d.ts +71 -15
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +190 -109
- package/dist/router/wasm.d.ts +10 -3
- package/dist/router/wasm.d.ts.map +1 -1
- package/dist/router/wasm.js +12 -6
- package/dist/ui/backend.d.ts +431 -41
- package/dist/ui/backend.d.ts.map +1 -1
- package/dist/ui/backend.js +391 -90
- package/dist/ui/client.d.ts +2 -1
- package/dist/ui/client.d.ts.map +1 -1
- package/dist/ui/client.js +1 -0
- package/package.json +2 -2
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
|
|
3
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
|
-
Cloesce is working towards
|
|
5
|
+
Cloesce is working towards a stable 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
|
|
9
|
+
# Documentation
|
|
10
10
|
|
|
11
11
|
## Getting Started
|
|
12
12
|
|
|
13
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).
|
|
14
14
|
|
|
15
|
-
1
|
|
15
|
+
### 1) NPM
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Create an NPM project and install cloesce
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
npm i cloesce@0.0.4-unstable.
|
|
20
|
+
npm i cloesce@0.0.4-unstable.10
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
2
|
|
23
|
+
### 2) TypeScript
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Create a `tsconfig.json` with the following values:
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
28
|
{
|
|
@@ -38,9 +38,9 @@ npm i cloesce@0.0.4-unstable.0
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
3
|
|
41
|
+
### 3) Cloesce Config
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Create a `cloesce.config.json` with your desired configuration:
|
|
44
44
|
|
|
45
45
|
```json
|
|
46
46
|
{
|
|
@@ -50,7 +50,7 @@ npm i cloesce@0.0.4-unstable.0
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
4
|
|
53
|
+
### 4) Vite
|
|
54
54
|
|
|
55
55
|
To prevent CORS issues, a Vite proxy can be used for the frontend:
|
|
56
56
|
|
|
@@ -70,9 +70,11 @@ export default defineConfig({
|
|
|
70
70
|
});
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Middleware support for CORS is also supported (see Middleware section).
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
### 5) Wrangler Config
|
|
76
|
+
|
|
77
|
+
Cloesce will generate any missing `wrangler.toml` values (or the file if missing). A minimal `wrangler.toml` looks like this:
|
|
76
78
|
|
|
77
79
|
```toml
|
|
78
80
|
compatibility_date = "2025-10-02"
|
|
@@ -85,12 +87,11 @@ database_id = "..."
|
|
|
85
87
|
database_name = "example"
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
##
|
|
90
|
+
## Cloesce Models
|
|
89
91
|
|
|
90
92
|
A model is a type which represents:
|
|
91
93
|
|
|
92
94
|
- a database table,
|
|
93
|
-
- database views
|
|
94
95
|
- REST API
|
|
95
96
|
- Client API
|
|
96
97
|
- Cloudflare infrastructure (D1 + Workers)
|
|
@@ -120,13 +121,18 @@ export class Horse {
|
|
|
120
121
|
- `@POST` reveals the method as an API endpoint with the `POST` HTTP Verb.
|
|
121
122
|
- All Cloesce models need to be under a `.cloesce.ts` file.
|
|
122
123
|
|
|
123
|
-
To compile this model into a working full stack application, Cloesce must undergo both **compilation** and **migrations**.
|
|
124
|
+
To compile this model into a working full stack application, Cloesce must undergo both **compilation** and **migrations**.
|
|
125
|
+
|
|
126
|
+
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).
|
|
124
127
|
|
|
125
|
-
|
|
128
|
+
Migrations utilize the history of validated metadata to create SQL code, translating the evolution of your models.
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
### Compiling
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
- `npx cloesce compile`
|
|
133
|
+
- `npx cloesce migrate <name>`.
|
|
134
|
+
|
|
135
|
+
After running the above commands, you will have a full project capable of being ran with Wrangler:
|
|
130
136
|
|
|
131
137
|
```sh
|
|
132
138
|
# Apply the generated migrations
|
|
@@ -136,17 +142,36 @@ npx wrangler d1 migrations apply <db-name>
|
|
|
136
142
|
npx wrangler dev
|
|
137
143
|
```
|
|
138
144
|
|
|
139
|
-
|
|
145
|
+
### Compiled Artifacts
|
|
146
|
+
|
|
147
|
+
#### `.generated/`
|
|
148
|
+
|
|
149
|
+
These values should not be committed to git, as they depend on the file system of the machine running it.
|
|
140
150
|
|
|
141
151
|
- `client.ts` is an importable API with all of your backend types and endpoints
|
|
142
152
|
- `workers.ts` is the workers entrypoint.
|
|
143
153
|
- `cidl.json` is the working metadata for the project
|
|
144
154
|
|
|
145
|
-
|
|
155
|
+
#### `migrations`
|
|
156
|
+
|
|
157
|
+
After running `npx cloesce migrate <name>`, a new migration will be created in the `migrations/` folder. For example, after creating a migration called `Initial`, you will see:
|
|
146
158
|
|
|
147
159
|
- `<date>_Initial.json` contains all model information necessary from SQL
|
|
148
160
|
- `<date>_Initial.sql` contains the acual SQL migrations. In this early version of Cloesce, it's important to check migrations every time.
|
|
149
161
|
|
|
162
|
+
#### Supported Column Types
|
|
163
|
+
|
|
164
|
+
Model columns must directly map to SQLite columns. The supported TypeScript types are:
|
|
165
|
+
|
|
166
|
+
- `number` => `Real` not null
|
|
167
|
+
- `string` => `Text` not null
|
|
168
|
+
- `boolean` and `Boolean` => `Integer` not null
|
|
169
|
+
- `Integer` => `Integer` not null
|
|
170
|
+
- `Date` => `Text` (ISO formatted) not null
|
|
171
|
+
- `N | null` => `N` (nullable)
|
|
172
|
+
|
|
173
|
+
Blob types will be added in v0.0.5 when R2 support is added.
|
|
174
|
+
|
|
150
175
|
## Features
|
|
151
176
|
|
|
152
177
|
### Wrangler Environment
|
|
@@ -154,12 +179,10 @@ Note the output in `migrations`, ex after running `npx cloesce migrate Initial`
|
|
|
154
179
|
In order to interact with your database, you will need to define a WranglerEnv
|
|
155
180
|
|
|
156
181
|
```ts
|
|
157
|
-
// horse.cloesce.ts
|
|
158
|
-
|
|
159
182
|
import { WranglerEnv } from "cloesce/backend";
|
|
160
183
|
|
|
161
184
|
@WranglerEnv
|
|
162
|
-
export class
|
|
185
|
+
export class MyEnv {
|
|
163
186
|
db: D1Database; // only one DB is supported for now-- make sure it matches the name in `wrangler.toml`
|
|
164
187
|
|
|
165
188
|
// you can also define values in the toml under [[vars]]
|
|
@@ -167,7 +190,7 @@ export class Env {
|
|
|
167
190
|
}
|
|
168
191
|
```
|
|
169
192
|
|
|
170
|
-
|
|
193
|
+
Your WranglerEnv can then be injected into any model method using the `@Inject` decorator:
|
|
171
194
|
|
|
172
195
|
```ts
|
|
173
196
|
@D1
|
|
@@ -176,7 +199,7 @@ export class Horse {
|
|
|
176
199
|
id: number;
|
|
177
200
|
|
|
178
201
|
@POST
|
|
179
|
-
async neigh(@Inject env:
|
|
202
|
+
async neigh(@Inject env: MyEnv): Promise<string> {
|
|
180
203
|
await env.db.prepare(...);
|
|
181
204
|
|
|
182
205
|
return `i am ${this.name}, this is my horse noise`;
|
|
@@ -184,10 +207,9 @@ export class Horse {
|
|
|
184
207
|
}
|
|
185
208
|
```
|
|
186
209
|
|
|
187
|
-
### Foreign
|
|
210
|
+
### Foreign Key Column
|
|
188
211
|
|
|
189
|
-
|
|
190
|
-
Foreign keys are scalar attributes which must reference some other model's primary key:
|
|
212
|
+
Reference another model via a foreign key using the `@ForeignKey` decorator:
|
|
191
213
|
|
|
192
214
|
```ts
|
|
193
215
|
@D1
|
|
@@ -206,7 +228,9 @@ export class Person {
|
|
|
206
228
|
}
|
|
207
229
|
```
|
|
208
230
|
|
|
209
|
-
|
|
231
|
+
### One to One
|
|
232
|
+
|
|
233
|
+
Cloesce allows you to relate models via `1:1` relationships using the `@OneToOne` decorator. It requires that a foreign key already exists on the model.
|
|
210
234
|
|
|
211
235
|
```ts
|
|
212
236
|
@D1
|
|
@@ -228,7 +252,7 @@ export class Person {
|
|
|
228
252
|
}
|
|
229
253
|
```
|
|
230
254
|
|
|
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 `
|
|
255
|
+
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 `Data Source`, which describes the foreign key dependencies you wish to include. All scalar properties are included by default and cannot be excluded.
|
|
232
256
|
|
|
233
257
|
```ts
|
|
234
258
|
@D1
|
|
@@ -255,39 +279,9 @@ export class Person {
|
|
|
255
279
|
}
|
|
256
280
|
```
|
|
257
281
|
|
|
258
|
-
Data sources
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
@D1
|
|
262
|
-
export class Person {
|
|
263
|
-
@PrimaryKey
|
|
264
|
-
id: number;
|
|
265
|
-
|
|
266
|
-
@ForeignKey(Dog)
|
|
267
|
-
dogId: number;
|
|
268
|
-
|
|
269
|
-
@OneToOne("dogId")
|
|
270
|
-
dog: Dog | undefined;
|
|
271
|
-
|
|
272
|
-
@DataSource
|
|
273
|
-
static readonly default: IncludeTree<Person> = {
|
|
274
|
-
dog: {},
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
@GET
|
|
278
|
-
static async get(id: number, @Inject env: WranglerEnv): Promise<Person> {
|
|
279
|
-
let records = await env.db
|
|
280
|
-
.prepare("SELECT * FROM [Person.default] WHERE [Person.id] = ?") // Person.default is the SQL view generated from the IncludeTree
|
|
281
|
-
.bind(id)
|
|
282
|
-
.run();
|
|
283
|
-
|
|
284
|
-
let persons = Orm.fromSql(Person, records.results, Person.default);
|
|
285
|
-
return persons.value[0];
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
282
|
+
Data sources describe how foreign keys should be joined on model hydration (i.e. when invoking any instantiated method). They are composed of an `IncludeTree<T>`, a recursive type composed of the relationships you wish to include. All scalar properties are always included.
|
|
289
283
|
|
|
290
|
-
Note that
|
|
284
|
+
Note that `DataSourceOf` is added implicitly to all instantiated methods if no data source parameter is defined.
|
|
291
285
|
|
|
292
286
|
### One to Many
|
|
293
287
|
|
|
@@ -351,6 +345,24 @@ export class Course {
|
|
|
351
345
|
}
|
|
352
346
|
```
|
|
353
347
|
|
|
348
|
+
### DataSourceOf<T>
|
|
349
|
+
|
|
350
|
+
If it is important to determine what data source the frontend called the instantiated method with, the type `DataSourceOf<T>` allows explicit data source parameters:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
@D1
|
|
354
|
+
class Foo {
|
|
355
|
+
...
|
|
356
|
+
|
|
357
|
+
@POST
|
|
358
|
+
bar(ds: DataSourceOf<Foo>) {
|
|
359
|
+
// ds = "DataSource1" | "DataSource2" | ... | "none"
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Data sources are implicitly added to all instantiated methods if no data source parameter is defined.
|
|
365
|
+
|
|
354
366
|
### ORM Methods
|
|
355
367
|
|
|
356
368
|
Cloesce provides a suite of ORM methods for getting, listing, updating and inserting models.
|
|
@@ -373,6 +385,44 @@ class Horse {
|
|
|
373
385
|
|
|
374
386
|
#### List, Get
|
|
375
387
|
|
|
388
|
+
Both methods take an optional `IncludeTree<T>` parameter to specify what relationships in the generated CTE.
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
@D1
|
|
392
|
+
export class Person {
|
|
393
|
+
@PrimaryKey
|
|
394
|
+
id: number;
|
|
395
|
+
|
|
396
|
+
@ForeignKey(Dog)
|
|
397
|
+
dogId: number;
|
|
398
|
+
|
|
399
|
+
@OneToOne("dogId")
|
|
400
|
+
dog: Dog | undefined;
|
|
401
|
+
|
|
402
|
+
@DataSource
|
|
403
|
+
static readonly default: IncludeTree<Person> = {
|
|
404
|
+
dog: {},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Running `Orm.listQuery` with the data source `Person.default` would produce the CTE:
|
|
410
|
+
|
|
411
|
+
```sql
|
|
412
|
+
WITH "Person_view" AS (
|
|
413
|
+
SELECT
|
|
414
|
+
"Person"."id" AS "id",
|
|
415
|
+
"Person"."dogId" AS "dogId",
|
|
416
|
+
"Dog"."id" AS "dog.id"
|
|
417
|
+
FROM
|
|
418
|
+
"Person"
|
|
419
|
+
LEFT JOIN
|
|
420
|
+
"Dog" ON "Person"."dogId" = "Dog"."id"
|
|
421
|
+
) SELECT * FROM "Person_view"
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Example usages:
|
|
425
|
+
|
|
376
426
|
```ts
|
|
377
427
|
@D1
|
|
378
428
|
class Horse {
|
|
@@ -380,23 +430,47 @@ class Horse {
|
|
|
380
430
|
@GET
|
|
381
431
|
static async get(@Inject { db }: Env, id: number): Promise<Horse> {
|
|
382
432
|
const orm = Orm.fromD1(db);
|
|
383
|
-
return (await orm.get(Horse, id,
|
|
433
|
+
return (await orm.get(Horse, id, Horse.default)).value;
|
|
384
434
|
}
|
|
385
435
|
|
|
386
436
|
@GET
|
|
387
437
|
static async list(@Inject { db }: Env): Promise<Horse[]> {
|
|
388
438
|
const orm = Orm.fromD1(db);
|
|
389
|
-
return (await orm.list(Horse,
|
|
439
|
+
return (await orm.list(Horse, {})).value;
|
|
390
440
|
}
|
|
391
441
|
}
|
|
392
442
|
```
|
|
393
443
|
|
|
444
|
+
`list` takes an optional `from` parameter to modify the source of the list query. This is useful in filtering / limiting results.
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
await orm.list(
|
|
448
|
+
Horse,
|
|
449
|
+
Horse.default,
|
|
450
|
+
"SELECT * FROM Horse ORDER BY name LIMIT 10"
|
|
451
|
+
);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
produces SQL
|
|
455
|
+
|
|
456
|
+
```sql
|
|
457
|
+
WITH "Horse_view" AS (
|
|
458
|
+
SELECT
|
|
459
|
+
"Horse"."id" AS "id",
|
|
460
|
+
"Horse"."name" AS "name"
|
|
461
|
+
FROM
|
|
462
|
+
(SELECT * FROM Horse ORDER BY name LIMIT 10) as "Horse"
|
|
463
|
+
) SELECT * FROM "Horse_view"
|
|
464
|
+
```
|
|
465
|
+
|
|
394
466
|
### CRUD Methods
|
|
395
467
|
|
|
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.
|
|
468
|
+
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.
|
|
469
|
+
|
|
470
|
+
The `SAVE` method is an `upsert`, meaning it both inserts and updates in the same query.
|
|
397
471
|
|
|
398
472
|
```ts
|
|
399
|
-
@CRUD(["
|
|
473
|
+
@CRUD(["SAVE", "GET", "LIST"])
|
|
400
474
|
@D1
|
|
401
475
|
export class CrudHaver {
|
|
402
476
|
@PrimaryKey
|
|
@@ -414,6 +488,76 @@ static async get(
|
|
|
414
488
|
): Promise<HttpResult<CrudHaver>>
|
|
415
489
|
```
|
|
416
490
|
|
|
491
|
+
### Middleware
|
|
492
|
+
|
|
493
|
+
Cloesce supports middleware at the global level (before routing to a model+method), the model level (before validation) and the method level (before hydration). Middleware also exposes read/write access to the dependency injection instance that all models use.
|
|
494
|
+
|
|
495
|
+
Middleware is capable of exiting from the Cloesce Router early with an HTTP Result.
|
|
496
|
+
|
|
497
|
+
An example of all levels of middleware is below. All middleware must be defined in the file `app.cloesce.ts` which exports a `CloesceApp` instance as default.
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
@PlainOldObject
|
|
501
|
+
export class InjectedThing {
|
|
502
|
+
value: string;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@WranglerEnv
|
|
506
|
+
export class Env {
|
|
507
|
+
db: D1Database;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
@D1
|
|
511
|
+
@CRUD(["POST"])
|
|
512
|
+
export class Model {
|
|
513
|
+
@PrimaryKey
|
|
514
|
+
id: number;
|
|
515
|
+
|
|
516
|
+
@GET
|
|
517
|
+
static blockedMethod() {}
|
|
518
|
+
|
|
519
|
+
@GET
|
|
520
|
+
static getInjectedThing(@Inject thing: InjectedThing): InjectedThing {
|
|
521
|
+
return thing;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const app: CloesceApp = new CloesceApp();
|
|
526
|
+
|
|
527
|
+
app.onRequest((request: Request, env, ir) => {
|
|
528
|
+
if (request.method === "POST") {
|
|
529
|
+
return { ok: false, status: 401, message: "POST methods aren't allowed." };
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
app.onModel(Model, (request, env, ir) => {
|
|
534
|
+
ir.set(InjectedThing.name, {
|
|
535
|
+
value: "hello world",
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
app.onMethod(Model, "blockedMethod", (request, env, ir) => {
|
|
540
|
+
return { ok: false, status: 401, message: "Blocked method" };
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
app.onResponse(async (request, env, di, response: Response) => {
|
|
544
|
+
// basic CORS, allow all origins
|
|
545
|
+
response.headers.set("Access-Control-Allow-Origin", "*");
|
|
546
|
+
response.headers.set(
|
|
547
|
+
"Access-Control-Allow-Methods",
|
|
548
|
+
"GET, POST, PUT, DELETE, OPTIONS"
|
|
549
|
+
);
|
|
550
|
+
response.headers.set(
|
|
551
|
+
"Access-Control-Allow-Headers",
|
|
552
|
+
"Content-Type, Authorization"
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
export default app;
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
With this middleware, all POST methods will be blocked, and all methods for the model `Model` will be able to inject `InjectedThing`,and `blockedMethod` will return a 401. Additionally, all responses will have CORS headers.
|
|
560
|
+
|
|
417
561
|
### Plain Old Objects
|
|
418
562
|
|
|
419
563
|
Simple non-model objects can be returned and serialized from a model method:
|
|
@@ -466,10 +610,11 @@ class Foo {
|
|
|
466
610
|
## Integration Tests
|
|
467
611
|
|
|
468
612
|
- Regression tests: `cargo run --bin test regression`
|
|
469
|
-
- Pass fail extractor tests: `cargo run --bin test run-fail`
|
|
470
613
|
|
|
471
614
|
Optionally, pass `--check` if new snapshots should not be created.
|
|
472
615
|
|
|
616
|
+
To target a specific fixture, pass `--fixture folder_name`
|
|
617
|
+
|
|
473
618
|
To update integration snapshots, run:
|
|
474
619
|
|
|
475
620
|
- `cargo run --bin update`
|
package/dist/cli.js
CHANGED
|
@@ -22,30 +22,28 @@ const cmds = subcommands({
|
|
|
22
22
|
},
|
|
23
23
|
handler: async (args) => {
|
|
24
24
|
const config = loadCloesceConfig(process.cwd(), args.debug);
|
|
25
|
-
if (!config.workersUrl
|
|
26
|
-
console.error("Error: `workersUrl
|
|
25
|
+
if (!config.workersUrl) {
|
|
26
|
+
console.error("Error: `workersUrl`` must be defined in cloesce.config.json");
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
// Creates a `cidl.json` file. Exits the process on failure.
|
|
30
30
|
await extract({ debug: args.debug });
|
|
31
31
|
const outputDir = config.outputDir ?? ".generated";
|
|
32
|
-
const
|
|
33
|
-
name: "
|
|
32
|
+
const generateConfig = {
|
|
33
|
+
name: "generate",
|
|
34
34
|
wasmFile: "generator.wasm",
|
|
35
35
|
args: [
|
|
36
36
|
"generate",
|
|
37
|
-
"all",
|
|
38
37
|
path.join(outputDir, "cidl.pre.json"),
|
|
39
38
|
path.join(outputDir, "cidl.json"),
|
|
40
39
|
"wrangler.toml",
|
|
41
40
|
path.join(outputDir, "workers.ts"),
|
|
42
41
|
path.join(outputDir, "client.ts"),
|
|
43
|
-
config.clientUrl,
|
|
44
42
|
config.workersUrl,
|
|
45
43
|
],
|
|
46
44
|
};
|
|
47
45
|
// Runs a generator command. Exits the process on failure.
|
|
48
|
-
await generate(
|
|
46
|
+
await generate(generateConfig);
|
|
49
47
|
},
|
|
50
48
|
}),
|
|
51
49
|
extract: command({
|
|
@@ -83,6 +81,10 @@ const cmds = subcommands({
|
|
|
83
81
|
short: "d",
|
|
84
82
|
description: "Show debug output",
|
|
85
83
|
}),
|
|
84
|
+
skipTsCheck: flag({
|
|
85
|
+
long: "skipTsCheck",
|
|
86
|
+
description: "Skip TypeScript compilation checks",
|
|
87
|
+
}),
|
|
86
88
|
},
|
|
87
89
|
handler: async (args) => {
|
|
88
90
|
await extract({ ...args });
|
|
@@ -138,20 +140,22 @@ const cmds = subcommands({
|
|
|
138
140
|
}),
|
|
139
141
|
},
|
|
140
142
|
});
|
|
141
|
-
async function extract(
|
|
143
|
+
async function extract(args) {
|
|
142
144
|
const root = process.cwd();
|
|
143
145
|
const projectRoot = process.cwd();
|
|
144
|
-
const config = loadCloesceConfig(projectRoot,
|
|
145
|
-
const searchPaths =
|
|
146
|
+
const config = loadCloesceConfig(projectRoot, args.debug);
|
|
147
|
+
const searchPaths = args.inp ? [args.inp] : (config.paths ?? [root]);
|
|
146
148
|
const outputDir = config.outputDir ?? ".generated";
|
|
147
|
-
const outPath =
|
|
148
|
-
const truncate =
|
|
149
|
-
const cloesceProjectName =
|
|
149
|
+
const outPath = args.out ?? path.join(outputDir, "cidl.pre.json");
|
|
150
|
+
const truncate = args.truncateSourcePaths ?? config.truncateSourcePaths ?? false;
|
|
151
|
+
const cloesceProjectName = args.projectName ??
|
|
150
152
|
config.projectName ??
|
|
151
153
|
readPackageJsonProjectName(projectRoot);
|
|
152
154
|
const project = new Project({
|
|
153
155
|
compilerOptions: {
|
|
154
156
|
strictNullChecks: true,
|
|
157
|
+
experimentalDecorators: true,
|
|
158
|
+
emitDecoratorMetadata: true,
|
|
155
159
|
},
|
|
156
160
|
});
|
|
157
161
|
findCloesceProject(root, searchPaths, project);
|
|
@@ -159,16 +163,25 @@ async function extract(opts) {
|
|
|
159
163
|
if (fileCount === 0) {
|
|
160
164
|
new ExtractorError(ExtractorErrorCode.MissingFile);
|
|
161
165
|
}
|
|
162
|
-
if (
|
|
166
|
+
if (args.debug)
|
|
163
167
|
console.log(`Found ${fileCount} .cloesce.ts files`);
|
|
168
|
+
// Run typescript compiler checks to before extraction
|
|
169
|
+
if (!args.skipTsCheck) {
|
|
170
|
+
const diagnostics = project.getPreEmitDiagnostics();
|
|
171
|
+
if (diagnostics.length > 0) {
|
|
172
|
+
console.error("TypeScript errors detected in provided files:");
|
|
173
|
+
console.error(project.formatDiagnosticsWithColorAndContext(diagnostics));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
164
177
|
try {
|
|
165
|
-
const extractor = new CidlExtractor(cloesceProjectName, "v0.0.
|
|
178
|
+
const extractor = new CidlExtractor(cloesceProjectName, "v0.0.4");
|
|
166
179
|
const result = extractor.extract(project);
|
|
167
|
-
if (
|
|
180
|
+
if (result.isLeft()) {
|
|
168
181
|
console.error(formatErr(result.value));
|
|
169
182
|
process.exit(1);
|
|
170
183
|
}
|
|
171
|
-
let ast = result.
|
|
184
|
+
let ast = result.unwrap();
|
|
172
185
|
if (truncate) {
|
|
173
186
|
ast.wrangler_env.source_path =
|
|
174
187
|
"./" + path.basename(ast.wrangler_env.source_path);
|
|
@@ -189,11 +202,11 @@ async function extract(opts) {
|
|
|
189
202
|
const json = JSON.stringify(ast, null, 4);
|
|
190
203
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
191
204
|
fs.writeFileSync(outPath, json);
|
|
192
|
-
console.log(`CIDL
|
|
205
|
+
console.log(`CIDL extracted to ${outPath}`);
|
|
193
206
|
return { outPath, projectName: cloesceProjectName };
|
|
194
207
|
}
|
|
195
208
|
catch (err) {
|
|
196
|
-
console.error("Critical uncaught error.
|
|
209
|
+
console.error("Critical uncaught error in generator. \nSubmit a ticket to https://github.com/bens-schreiber/cloesce\n\n", err?.message ?? "No error message.", "\n", err?.stack ?? "No error stack.");
|
|
197
210
|
process.exit(1);
|
|
198
211
|
}
|
|
199
212
|
}
|
|
@@ -293,10 +306,13 @@ function formatErr(e) {
|
|
|
293
306
|
const { description, suggestion } = getErrorInfo(e.code);
|
|
294
307
|
const contextLine = e.context ? `Context: ${e.context}\n` : "";
|
|
295
308
|
const snippetLine = e.snippet ? `${e.snippet}\n\n` : "";
|
|
296
|
-
return
|
|
309
|
+
return `
|
|
310
|
+
==== CLOESCE ERROR ====
|
|
297
311
|
Error [${ExtractorErrorCode[e.code]}]: ${description}
|
|
298
312
|
Phase: TypeScript IDL Extraction
|
|
299
|
-
${contextLine}${snippetLine}Suggested fix: ${suggestion}
|
|
313
|
+
${contextLine}${snippetLine}Suggested fix: ${suggestion}
|
|
314
|
+
|
|
315
|
+
`;
|
|
300
316
|
}
|
|
301
317
|
run(cmds, process.argv.slice(2)).catch((err) => {
|
|
302
318
|
console.error(err);
|