cloesce 0.0.4-unstable.1 → 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/README.md +212 -68
- 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 +1 -1
package/README.md
CHANGED
|
@@ -2,28 +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
|
-
|
|
21
|
-
npm i cloesce@0.0.4-unstable.0
|
|
20
|
+
npm i cloesce@0.0.4-unstable.10
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
2
|
|
23
|
+
### 2) TypeScript
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Create a `tsconfig.json` with the following values:
|
|
27
26
|
|
|
28
27
|
```json
|
|
29
28
|
{
|
|
@@ -39,9 +38,9 @@ npm i cloesce@0.0.4-unstable.0
|
|
|
39
38
|
}
|
|
40
39
|
```
|
|
41
40
|
|
|
42
|
-
3
|
|
41
|
+
### 3) Cloesce Config
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
Create a `cloesce.config.json` with your desired configuration:
|
|
45
44
|
|
|
46
45
|
```json
|
|
47
46
|
{
|
|
@@ -51,7 +50,7 @@ npm i cloesce@0.0.4-unstable.0
|
|
|
51
50
|
}
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
4
|
|
53
|
+
### 4) Vite
|
|
55
54
|
|
|
56
55
|
To prevent CORS issues, a Vite proxy can be used for the frontend:
|
|
57
56
|
|
|
@@ -71,9 +70,11 @@ export default defineConfig({
|
|
|
71
70
|
});
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
Middleware support for CORS is also supported (see Middleware section).
|
|
75
74
|
|
|
76
|
-
|
|
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:
|
|
77
78
|
|
|
78
79
|
```toml
|
|
79
80
|
compatibility_date = "2025-10-02"
|
|
@@ -86,12 +87,11 @@ database_id = "..."
|
|
|
86
87
|
database_name = "example"
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
##
|
|
90
|
+
## Cloesce Models
|
|
90
91
|
|
|
91
92
|
A model is a type which represents:
|
|
92
93
|
|
|
93
94
|
- a database table,
|
|
94
|
-
- database views
|
|
95
95
|
- REST API
|
|
96
96
|
- Client API
|
|
97
97
|
- Cloudflare infrastructure (D1 + Workers)
|
|
@@ -121,13 +121,18 @@ export class Horse {
|
|
|
121
121
|
- `@POST` reveals the method as an API endpoint with the `POST` HTTP Verb.
|
|
122
122
|
- All Cloesce models need to be under a `.cloesce.ts` file.
|
|
123
123
|
|
|
124
|
-
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).
|
|
125
127
|
|
|
126
|
-
|
|
128
|
+
Migrations utilize the history of validated metadata to create SQL code, translating the evolution of your models.
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
### Compiling
|
|
129
131
|
|
|
130
|
-
|
|
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:
|
|
131
136
|
|
|
132
137
|
```sh
|
|
133
138
|
# Apply the generated migrations
|
|
@@ -137,17 +142,36 @@ npx wrangler d1 migrations apply <db-name>
|
|
|
137
142
|
npx wrangler dev
|
|
138
143
|
```
|
|
139
144
|
|
|
140
|
-
|
|
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.
|
|
141
150
|
|
|
142
151
|
- `client.ts` is an importable API with all of your backend types and endpoints
|
|
143
152
|
- `workers.ts` is the workers entrypoint.
|
|
144
153
|
- `cidl.json` is the working metadata for the project
|
|
145
154
|
|
|
146
|
-
|
|
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:
|
|
147
158
|
|
|
148
159
|
- `<date>_Initial.json` contains all model information necessary from SQL
|
|
149
160
|
- `<date>_Initial.sql` contains the acual SQL migrations. In this early version of Cloesce, it's important to check migrations every time.
|
|
150
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
|
+
|
|
151
175
|
## Features
|
|
152
176
|
|
|
153
177
|
### Wrangler Environment
|
|
@@ -155,12 +179,10 @@ Note the output in `migrations`, ex after running `npx cloesce migrate Initial`
|
|
|
155
179
|
In order to interact with your database, you will need to define a WranglerEnv
|
|
156
180
|
|
|
157
181
|
```ts
|
|
158
|
-
// horse.cloesce.ts
|
|
159
|
-
|
|
160
182
|
import { WranglerEnv } from "cloesce/backend";
|
|
161
183
|
|
|
162
184
|
@WranglerEnv
|
|
163
|
-
export class
|
|
185
|
+
export class MyEnv {
|
|
164
186
|
db: D1Database; // only one DB is supported for now-- make sure it matches the name in `wrangler.toml`
|
|
165
187
|
|
|
166
188
|
// you can also define values in the toml under [[vars]]
|
|
@@ -168,7 +190,7 @@ export class Env {
|
|
|
168
190
|
}
|
|
169
191
|
```
|
|
170
192
|
|
|
171
|
-
|
|
193
|
+
Your WranglerEnv can then be injected into any model method using the `@Inject` decorator:
|
|
172
194
|
|
|
173
195
|
```ts
|
|
174
196
|
@D1
|
|
@@ -177,7 +199,7 @@ export class Horse {
|
|
|
177
199
|
id: number;
|
|
178
200
|
|
|
179
201
|
@POST
|
|
180
|
-
async neigh(@Inject env:
|
|
202
|
+
async neigh(@Inject env: MyEnv): Promise<string> {
|
|
181
203
|
await env.db.prepare(...);
|
|
182
204
|
|
|
183
205
|
return `i am ${this.name}, this is my horse noise`;
|
|
@@ -185,10 +207,9 @@ export class Horse {
|
|
|
185
207
|
}
|
|
186
208
|
```
|
|
187
209
|
|
|
188
|
-
### Foreign
|
|
210
|
+
### Foreign Key Column
|
|
189
211
|
|
|
190
|
-
|
|
191
|
-
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:
|
|
192
213
|
|
|
193
214
|
```ts
|
|
194
215
|
@D1
|
|
@@ -207,7 +228,9 @@ export class Person {
|
|
|
207
228
|
}
|
|
208
229
|
```
|
|
209
230
|
|
|
210
|
-
|
|
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.
|
|
211
234
|
|
|
212
235
|
```ts
|
|
213
236
|
@D1
|
|
@@ -229,7 +252,7 @@ export class Person {
|
|
|
229
252
|
}
|
|
230
253
|
```
|
|
231
254
|
|
|
232
|
-
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.
|
|
233
256
|
|
|
234
257
|
```ts
|
|
235
258
|
@D1
|
|
@@ -256,39 +279,9 @@ export class Person {
|
|
|
256
279
|
}
|
|
257
280
|
```
|
|
258
281
|
|
|
259
|
-
Data sources
|
|
260
|
-
|
|
261
|
-
```ts
|
|
262
|
-
@D1
|
|
263
|
-
export class Person {
|
|
264
|
-
@PrimaryKey
|
|
265
|
-
id: number;
|
|
266
|
-
|
|
267
|
-
@ForeignKey(Dog)
|
|
268
|
-
dogId: number;
|
|
269
|
-
|
|
270
|
-
@OneToOne("dogId")
|
|
271
|
-
dog: Dog | undefined;
|
|
272
|
-
|
|
273
|
-
@DataSource
|
|
274
|
-
static readonly default: IncludeTree<Person> = {
|
|
275
|
-
dog: {},
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
@GET
|
|
279
|
-
static async get(id: number, @Inject env: WranglerEnv): Promise<Person> {
|
|
280
|
-
let records = await env.db
|
|
281
|
-
.prepare("SELECT * FROM [Person.default] WHERE [Person.id] = ?") // Person.default is the SQL view generated from the IncludeTree
|
|
282
|
-
.bind(id)
|
|
283
|
-
.run();
|
|
284
|
-
|
|
285
|
-
let persons = Orm.fromSql(Person, records.results, Person.default);
|
|
286
|
-
return persons.value[0];
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
```
|
|
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.
|
|
290
283
|
|
|
291
|
-
Note that
|
|
284
|
+
Note that `DataSourceOf` is added implicitly to all instantiated methods if no data source parameter is defined.
|
|
292
285
|
|
|
293
286
|
### One to Many
|
|
294
287
|
|
|
@@ -352,6 +345,24 @@ export class Course {
|
|
|
352
345
|
}
|
|
353
346
|
```
|
|
354
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
|
+
|
|
355
366
|
### ORM Methods
|
|
356
367
|
|
|
357
368
|
Cloesce provides a suite of ORM methods for getting, listing, updating and inserting models.
|
|
@@ -374,6 +385,44 @@ class Horse {
|
|
|
374
385
|
|
|
375
386
|
#### List, Get
|
|
376
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
|
+
|
|
377
426
|
```ts
|
|
378
427
|
@D1
|
|
379
428
|
class Horse {
|
|
@@ -381,23 +430,47 @@ class Horse {
|
|
|
381
430
|
@GET
|
|
382
431
|
static async get(@Inject { db }: Env, id: number): Promise<Horse> {
|
|
383
432
|
const orm = Orm.fromD1(db);
|
|
384
|
-
return (await orm.get(Horse, id,
|
|
433
|
+
return (await orm.get(Horse, id, Horse.default)).value;
|
|
385
434
|
}
|
|
386
435
|
|
|
387
436
|
@GET
|
|
388
437
|
static async list(@Inject { db }: Env): Promise<Horse[]> {
|
|
389
438
|
const orm = Orm.fromD1(db);
|
|
390
|
-
return (await orm.list(Horse,
|
|
439
|
+
return (await orm.list(Horse, {})).value;
|
|
391
440
|
}
|
|
392
441
|
}
|
|
393
442
|
```
|
|
394
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
|
+
|
|
395
466
|
### CRUD Methods
|
|
396
467
|
|
|
397
|
-
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.
|
|
398
471
|
|
|
399
472
|
```ts
|
|
400
|
-
@CRUD(["
|
|
473
|
+
@CRUD(["SAVE", "GET", "LIST"])
|
|
401
474
|
@D1
|
|
402
475
|
export class CrudHaver {
|
|
403
476
|
@PrimaryKey
|
|
@@ -415,6 +488,76 @@ static async get(
|
|
|
415
488
|
): Promise<HttpResult<CrudHaver>>
|
|
416
489
|
```
|
|
417
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
|
+
|
|
418
561
|
### Plain Old Objects
|
|
419
562
|
|
|
420
563
|
Simple non-model objects can be returned and serialized from a model method:
|
|
@@ -467,10 +610,11 @@ class Foo {
|
|
|
467
610
|
## Integration Tests
|
|
468
611
|
|
|
469
612
|
- Regression tests: `cargo run --bin test regression`
|
|
470
|
-
- Pass fail extractor tests: `cargo run --bin test run-fail`
|
|
471
613
|
|
|
472
614
|
Optionally, pass `--check` if new snapshots should not be created.
|
|
473
615
|
|
|
616
|
+
To target a specific fixture, pass `--fixture folder_name`
|
|
617
|
+
|
|
474
618
|
To update integration snapshots, run:
|
|
475
619
|
|
|
476
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);
|