firebase-contract 0.1.0-rc.5 → 0.1.0-rc.7
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 +165 -81
- package/dist/lib/_internal/compiler/compiler.d.ts +5 -4
- package/dist/lib/_internal/compiler/compiler.d.ts.map +1 -1
- package/dist/lib/_internal/compiler/compiler.js +207 -23
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts +0 -5
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-dto-generator.js +23 -13
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts +0 -5
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-types-generator.js +27 -17
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts +0 -4
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-validation-generator.js +15 -6
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts +0 -6
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/task-payload-generator.js +30 -16
- package/dist/lib/_internal/generators/config/config-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/config/config-generator.js +2 -1
- package/dist/lib/_internal/generators/data-connect/adapter-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/adapter-generator.js +2 -1
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/graphql-generator.js +2 -1
- package/dist/lib/_internal/generators/data-connect/operations-generator.js +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.js +2 -1
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.js +2 -1
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.js +2 -1
- package/dist/lib/_internal/generators/generator.d.ts +36 -0
- package/dist/lib/_internal/generators/generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/generator.js +7 -1
- package/dist/lib/_internal/generators/id/id-codec-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/id/id-codec-generator.js +2 -1
- package/dist/lib/_internal/generators/sql/sql-migration-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/sql/sql-migration-generator.js +2 -1
- package/dist/lib/_internal/generators/support/naming.d.ts +1 -0
- package/dist/lib/_internal/generators/support/naming.d.ts.map +1 -1
- package/dist/lib/_internal/generators/support/naming.js +3 -0
- package/dist/lib/_internal/generators/support/templates.d.ts +36 -0
- package/dist/lib/_internal/generators/support/templates.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/templates.js +50 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-generator.js +2 -1
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.js +2 -1
- package/dist/lib/_internal/generators/union/union-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/union/union-generator.js +2 -1
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/zod/zod-generator.js +2 -1
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/zod/zod-split-generator.js +2 -1
- package/dist/lib/_internal/ir/build-ir.d.ts.map +1 -1
- package/dist/lib/_internal/ir/build-ir.js +2 -0
- package/dist/lib/_internal/ir/ir.d.ts +2 -0
- package/dist/lib/_internal/ir/ir.d.ts.map +1 -1
- package/dist/lib/_internal/parser/parse.d.ts.map +1 -1
- package/dist/lib/_internal/parser/parse.js +194 -17
- package/dist/lib/_internal/parser/raw-document.d.ts +46 -7
- package/dist/lib/_internal/parser/raw-document.d.ts.map +1 -1
- package/dist/lib/cli/commands/generate.js +1 -1
- package/dist/lib/cli/commands/init.d.ts.map +1 -1
- package/dist/lib/cli/commands/init.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,36 +32,93 @@ Provides the `firebase-contract` (alias `fbc`) CLI and a programmatic API.
|
|
|
32
32
|
```bash
|
|
33
33
|
fbc init # scaffold contract.yml + firebase-contract.json
|
|
34
34
|
fbc validate # parse, resolve imports, semantically validate
|
|
35
|
-
fbc generate # run every
|
|
35
|
+
fbc generate # run every generator declared across the contract graph
|
|
36
36
|
fbc inspect # print the normalized IR (debugging)
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### One-command generation
|
|
40
40
|
|
|
41
|
-
Each contract file
|
|
42
|
-
|
|
41
|
+
Each contract file declares the generators it uses in a top-level
|
|
42
|
+
`generators:` block — a name plus an output template. Declaring a
|
|
43
|
+
**document-scoped** generator (typescript-split, zod-split, …) runs it once for
|
|
44
|
+
that yml; **api-scoped** generators (api-types, api-validation, api-dto,
|
|
45
|
+
task-payloads) run for the entries that opt in (see below):
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
# contract.yml (root)
|
|
49
|
+
project:
|
|
50
|
+
aliases:
|
|
51
|
+
"#contracts/*": libs/contracts/src/* # out templates may target aliases
|
|
52
|
+
generators:
|
|
53
|
+
- { generator: typescript-split, out: "#contracts" }
|
|
54
|
+
- { generator: api-types, out: "#contracts/api-types/{api-name}" }
|
|
55
|
+
imports:
|
|
56
|
+
- ./apps/shop/data-connect/schema.yml
|
|
57
|
+
```
|
|
43
58
|
|
|
44
59
|
```yaml
|
|
45
60
|
# apps/shop/data-connect/schema.yml
|
|
46
|
-
|
|
47
|
-
- {
|
|
48
|
-
- {
|
|
61
|
+
generators:
|
|
62
|
+
- { generator: data-connect-graphql-split, out: src }
|
|
63
|
+
- { generator: sql-migrations, out: . }
|
|
49
64
|
```
|
|
50
65
|
|
|
66
|
+
- `out` resolves relative to the declaring yml; `#alias/...` prefixes resolve
|
|
67
|
+
through the **root** yml's `project.aliases` (relative to the root).
|
|
68
|
+
- Api-scoped templates may use `{api-name}` (kebab-cased name) and `{path}`
|
|
69
|
+
(REST path with `{param}` segments dropped).
|
|
70
|
+
- Entries reference declarations by name — nearest first (same yml → root).
|
|
71
|
+
|
|
72
|
+
#### Output settings: `file`, `split`, `options`, `header`
|
|
73
|
+
|
|
74
|
+
Every declaration (and any entry-level application) can reshape the output —
|
|
75
|
+
the defaults reproduce each generator's built-in layout, so omitting them
|
|
76
|
+
changes nothing:
|
|
77
|
+
|
|
51
78
|
```yaml
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
79
|
+
generators:
|
|
80
|
+
- generator: api-dto
|
|
81
|
+
out: "src/entries/{path}/dto"
|
|
82
|
+
file: "{api-name}.dto.ts" # file-name template (default shown)
|
|
83
|
+
split: true # one file per api (api-dto's default)
|
|
84
|
+
- generator: api-types
|
|
85
|
+
out: "#contracts/api-types"
|
|
86
|
+
file: "{api-name}.types.ts" # override the bundled default with per-api files
|
|
87
|
+
split: true
|
|
88
|
+
options:
|
|
89
|
+
typesImport: "../types" # where `import type { … } from '…'` points
|
|
90
|
+
- generator: typescript
|
|
91
|
+
out: "#contracts"
|
|
92
|
+
file: models.ts # rename a single-file/barrel output
|
|
93
|
+
split: true # document scope: switches to typescript-split
|
|
58
94
|
```
|
|
59
95
|
|
|
60
|
-
|
|
61
|
-
|
|
96
|
+
- **`file`** — output file name. Api scope: a template (`{api-name}`/`{path}`
|
|
97
|
+
allowed when `split: true`). Document scope: renames the single output file,
|
|
98
|
+
or the barrel for `-split` variants.
|
|
99
|
+
- **`split`** — api scope: `true` emits one file per api (the `file` template
|
|
100
|
+
must contain a placeholder), `false` bundles everything into one `file`.
|
|
101
|
+
Document scope: `true` swaps to the generator's `-split` variant
|
|
102
|
+
(`typescript` → `typescript-split`, …) and errors when none exists.
|
|
103
|
+
- **`options`** — free-form string map passed through to the generator.
|
|
104
|
+
Currently: `typesImport` (api-types, task-payloads, data-connect-operations)
|
|
105
|
+
overrides the `./types` import path.
|
|
106
|
+
- **`header`** — per-generator banner override: `default` for the built-in
|
|
107
|
+
AUTO-GENERATED banner, any text for a custom comment, `""` to suppress the
|
|
108
|
+
banner for this generator. Wins over the contract-level `header:` and the
|
|
109
|
+
CLI `--header` flag.
|
|
110
|
+
- Defaults per generator: `api-types` → `api-types.ts` bundled, `api-validation`
|
|
111
|
+
→ `api-validation.ts` bundled, `task-payloads` → `task-payloads.ts` bundled,
|
|
112
|
+
`api-dto` → `{api-name}.dto.ts` split.
|
|
113
|
+
- Entry-level settings override the declaration, which overrides the
|
|
114
|
+
generator's default (same nearest-first rule as `out`).
|
|
115
|
+
|
|
116
|
+
`fbc generate contract.yml` materializes **all** declared outputs across the
|
|
117
|
+
whole import graph in one run. Passing `-o`/`-g` switches to single-target mode
|
|
62
118
|
(`fbc generate <entry> -o <dir> -g typescript,zod`), and
|
|
63
119
|
`firebase-contract.json` (`entry`, `outDir`, `generators`) can hold defaults.
|
|
64
120
|
|
|
121
|
+
|
|
65
122
|
### Generated-file headers
|
|
66
123
|
|
|
67
124
|
By default no banner is emitted. Opt in per run or per contract:
|
|
@@ -162,24 +219,24 @@ models:
|
|
|
162
219
|
|
|
163
220
|
| option | meaning |
|
|
164
221
|
| ----------------------------------- | -------------------------------------------------------- |
|
|
165
|
-
| `type` | scalar name, or an enum/model name |
|
|
166
|
-
| `optional` | value may be absent (`field?`) |
|
|
167
|
-
| `list` | value is an array of `type` |
|
|
168
|
-
| `id` | marks the (single) primary identifier |
|
|
169
|
-
| `unique` | field-level unique → `@unique` |
|
|
170
|
-
| `relation` | model-typed field is a foreign-key relation (see below) |
|
|
171
|
-
| `col` | Data Connect column dataType (Int64 PKs default to `bigserial`) |
|
|
172
|
-
| `default` | `@default(expr: …)` on the DC column |
|
|
173
|
-
| `literal` | pin to one literal value (union discriminant tags) |
|
|
174
|
-
| `nullable` | value may be `null` (`.nullable()` / `\| null`) |
|
|
175
|
-
| `description` | doc comment carried into generated output |
|
|
176
|
-
| `jsdoc` | render the description as a JSDoc block (Firestore fields) |
|
|
222
|
+
| `type` | document | scalar name, or an enum/model name |
|
|
223
|
+
| `optional` | document | value may be absent (`field?`) |
|
|
224
|
+
| `list` | document | value is an array of `type` |
|
|
225
|
+
| `id` | document | marks the (single) primary identifier |
|
|
226
|
+
| `unique` | document | field-level unique → `@unique` |
|
|
227
|
+
| `relation` | document | model-typed field is a foreign-key relation (see below) |
|
|
228
|
+
| `col` | document | Data Connect column dataType (Int64 PKs default to `bigserial`) |
|
|
229
|
+
| `default` | document | `@default(expr: …)` on the DC column |
|
|
230
|
+
| `literal` | document | pin to one literal value (union discriminant tags) |
|
|
231
|
+
| `nullable` | document | value may be `null` (`.nullable()` / `\| null`) |
|
|
232
|
+
| `description` | document | doc comment carried into generated output |
|
|
233
|
+
| `jsdoc` | document | render the description as a JSDoc block (Firestore fields) |
|
|
177
234
|
| **constraints** → | |
|
|
178
|
-
| `min` / `max` | numeric bounds |
|
|
179
|
-
| `minLength` / `maxLength` | string/array length bounds |
|
|
180
|
-
| `nonempty` | non-empty string/array |
|
|
181
|
-
| `pattern` | regex the string must match |
|
|
182
|
-
| `email` / `url` | string format |
|
|
235
|
+
| `min` / `max` | document | numeric bounds |
|
|
236
|
+
| `minLength` / `maxLength` | document | string/array length bounds |
|
|
237
|
+
| `nonempty` | document | non-empty string/array |
|
|
238
|
+
| `pattern` | document | regex the string must match |
|
|
239
|
+
| `email` / `url` | document | string format |
|
|
183
240
|
|
|
184
241
|
Constraints flow into the Zod schemas, the API request validation, and the
|
|
185
242
|
class-validator DTOs.
|
|
@@ -188,14 +245,14 @@ class-validator DTOs.
|
|
|
188
245
|
|
|
189
246
|
| option | meaning |
|
|
190
247
|
| ------------ | ------------------------------------------------------------------ |
|
|
191
|
-
| `key` | composite primary key field names (else the `id: true` field) |
|
|
192
|
-
| `table` | table name override (default: snake_case pluralization) |
|
|
193
|
-
| `gqlName` | GraphQL type name override |
|
|
194
|
-
| `fsName` | Firestore-side rename (avoid collisions with table models) |
|
|
195
|
-
| `directives` | `multi` = each type-level directive on its own line |
|
|
196
|
-
| `footer` | trailing comment block after the closing `}` in the schema file |
|
|
197
|
-
| `indexes` | composite `@index`/`@unique` (`{ fields, name?, unique?, expand? }` — `expand` renders args one per line) |
|
|
198
|
-
| `sql` | raw SQL constraints (see [SQL migrations](#sql-migrations)) |
|
|
248
|
+
| `key` | document | composite primary key field names (else the `id: true` field) |
|
|
249
|
+
| `table` | document | table name override (default: snake_case pluralization) |
|
|
250
|
+
| `gqlName` | document | GraphQL type name override |
|
|
251
|
+
| `fsName` | document | Firestore-side rename (avoid collisions with table models) |
|
|
252
|
+
| `directives` | document | `multi` = each type-level directive on its own line |
|
|
253
|
+
| `footer` | document | trailing comment block after the closing `}` in the schema file |
|
|
254
|
+
| `indexes` | document | composite `@index`/`@unique` (`{ fields, name?, unique?, expand? }` — `expand` renders args one per line) |
|
|
255
|
+
| `sql` | document | raw SQL constraints (see [SQL migrations](#sql-migrations)) |
|
|
199
256
|
|
|
200
257
|
### Embedded vs relation
|
|
201
258
|
|
|
@@ -273,14 +330,14 @@ Everything below exists to reproduce real hand-written `.gql` files byte-for-byt
|
|
|
273
330
|
|
|
274
331
|
| option | meaning |
|
|
275
332
|
| --- | --- |
|
|
276
|
-
| `gqlName` | rendered operation name (the yml key stays unique across services) |
|
|
277
|
-
| `footer` | trailing `# …` comment block after the operation |
|
|
278
|
-
| `raw` | emit the operation body verbatim (multi-model queries, `_or` keyset cursors — field checks are skipped) |
|
|
279
|
-
| `single: id \| key` | single-row lookup (`product(id: $id)` / `product(key: { … })`) |
|
|
280
|
-
| `keyArg` / `keyVars` | update/delete key argument form and variable renames |
|
|
281
|
-
| `whereAnd: true` | render conditions as an `_and: [ … ]` array |
|
|
282
|
-
| `entityDir` | override the `operations/<entity>/` output directory |
|
|
283
|
-
| `style` | formatting hints: `signature`, `args`, `data`, `orderBy`, `auth`, `key`, `and`, `where` (`inline`/`multi`/`compact`/`bare`) |
|
|
333
|
+
| `gqlName` | document | rendered operation name (the yml key stays unique across services) |
|
|
334
|
+
| `footer` | document | trailing `# …` comment block after the operation |
|
|
335
|
+
| `raw` | document | emit the operation body verbatim (multi-model queries, `_or` keyset cursors — field checks are skipped) |
|
|
336
|
+
| `single: id \| document | key` | single-row lookup (`product(id: $id)` / `product(key: { … })`) |
|
|
337
|
+
| `keyArg` / `keyVars` | document | update/delete key argument form and variable renames |
|
|
338
|
+
| `whereAnd: true` | document | render conditions as an `_and: [ … ]` array |
|
|
339
|
+
| `entityDir` | document | override the `operations/<entity>/` output directory |
|
|
340
|
+
| `style` | document | formatting hints: `signature`, `args`, `data`, `orderBy`, `auth`, `key`, `and`, `where` (`inline`/`multi`/`compact`/`bare`) |
|
|
284
341
|
|
|
285
342
|
Inputs support `var` (variable rename), `required` (override optionality),
|
|
286
343
|
`literal` (fixed value, no variable), `flat` (write the FK column directly),
|
|
@@ -296,11 +353,13 @@ name as long as their connector sets don't overlap.
|
|
|
296
353
|
|
|
297
354
|
## API endpoints
|
|
298
355
|
|
|
299
|
-
Model application endpoints
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
(`
|
|
303
|
-
|
|
356
|
+
Model application endpoints in three kind-implied sections — `apis:` (https /
|
|
357
|
+
callable, keyed by REST path), `tasks:` (Cloud Tasks), and `events:` (Pub/Sub).
|
|
358
|
+
The api-scoped generators — request/response types (`api-types`),
|
|
359
|
+
request-validation Zod (`api-validation`), class-validator DTOs (`api-dto`),
|
|
360
|
+
and payload contracts (`task-payloads`) — run for the entries that opt in via
|
|
361
|
+
the section `defaults` or the entry's own `generators:`. A payload references a
|
|
362
|
+
model or declares inline fields; a void response maps to `void`.
|
|
304
363
|
|
|
305
364
|
```yaml
|
|
306
365
|
envelopes:
|
|
@@ -310,9 +369,10 @@ envelopes:
|
|
|
310
369
|
opId: string
|
|
311
370
|
enqueuedAt: int
|
|
312
371
|
|
|
313
|
-
|
|
372
|
+
tasks: # Cloud Tasks (kind implied)
|
|
373
|
+
defaults:
|
|
374
|
+
generators: [task-payloads] # section default: applies to entries below
|
|
314
375
|
createCatalog:
|
|
315
|
-
kind: task # product | callable | https | pubsub
|
|
316
376
|
envelope: RetryTaskPayload # wraps the product data
|
|
317
377
|
maxAttempts: 3
|
|
318
378
|
request:
|
|
@@ -322,17 +382,31 @@ apis:
|
|
|
322
382
|
response:
|
|
323
383
|
void: true
|
|
324
384
|
|
|
385
|
+
events: # Pub/Sub (kind implied)
|
|
325
386
|
generateAiResponse:
|
|
326
|
-
kind: pubsub
|
|
327
387
|
topic: ai-review-generate-response
|
|
328
388
|
timeoutSeconds: 540
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
389
|
+
generators: [task-payloads]
|
|
390
|
+
|
|
391
|
+
apis: # https/callable, keyed by REST path
|
|
392
|
+
defaults:
|
|
393
|
+
generators: [api-types, api-validation]
|
|
394
|
+
/shops/{slug}:
|
|
395
|
+
operationId: getShopBySlug
|
|
396
|
+
kind: callable # https (default) | callable
|
|
332
397
|
request: { fields: { slug: { type: string, nonempty: true } } }
|
|
333
398
|
response: { model: Shop }
|
|
399
|
+
generators: # entry-level wins over section defaults;
|
|
400
|
+
- api-types # an inline out overrides the declared template
|
|
401
|
+
- { generator: api-dto, out: "src/entries/{path}" }
|
|
334
402
|
```
|
|
335
403
|
|
|
404
|
+
Generator application resolves in two tiers — the section `defaults`
|
|
405
|
+
(`apis:/tasks:/events:` → `defaults.generators`) and the entry's own
|
|
406
|
+
`generators:` (which replaces the defaults). `apis:` keys must be REST paths
|
|
407
|
+
(`/...` or `METHOD /...` so the same route can appear once per verb) with an
|
|
408
|
+
`operationId`; tasks and Pub/Sub events live in their own sections.
|
|
409
|
+
|
|
336
410
|
For `createCatalog` this yields `CreateCatalogTaskData`, `CreateCatalogTaskPayload =
|
|
337
411
|
RetryTaskPayload<CreateCatalogTaskData>`, and `CREATE_CATALOG_MAX_ATTEMPTS`, plus the
|
|
338
412
|
request types, Zod schema, and DTO.
|
|
@@ -446,6 +520,8 @@ project:
|
|
|
446
520
|
idCodec:
|
|
447
521
|
minLength: 8
|
|
448
522
|
alphabet: <your-shuffled-sqids-alphabet>
|
|
523
|
+
aliases:
|
|
524
|
+
"#contracts/*": libs/contracts/src/*
|
|
449
525
|
```
|
|
450
526
|
|
|
451
527
|
- `services` (`name`/`database`) feed the `FIRESTORE_DATABASES` constants in the
|
|
@@ -454,6 +530,9 @@ project:
|
|
|
454
530
|
`id-codecs` generator also emits a self-contained `id-core.ts` (the
|
|
455
531
|
encode/decode primitives), so nothing about id encoding lives outside the
|
|
456
532
|
contract.
|
|
533
|
+
- `aliases` maps `#alias/...` out-template prefixes to paths relative to the
|
|
534
|
+
**root** yml, so imported contracts can target shared libs without relative
|
|
535
|
+
path gymnastics (longest prefix wins).
|
|
457
536
|
- `codebases` / service `location` + `connectors` are consumed by the optional
|
|
458
537
|
`config` generator (preview), which emits per-service `dataconnect.yaml`,
|
|
459
538
|
per-connector `connector.yaml`, and the `FIRESTORE_DATABASES` /
|
|
@@ -465,32 +544,37 @@ project:
|
|
|
465
544
|
|
|
466
545
|
# Generators
|
|
467
546
|
|
|
468
|
-
| name | output | notes |
|
|
469
|
-
| ------------------------ | ----------------------------- | ------------------------------------------------------ |
|
|
470
|
-
| `typescript` | `types.ts` | interfaces + const/union enums |
|
|
471
|
-
| `typescript-split` | `types/<table>.ts` + `types.ts` barrel | one file per table; enums/embedded objects co-located |
|
|
472
|
-
| `zod` | `schemas.ts` | `z.object` schemas (constraints, `z.lazy` model refs) |
|
|
473
|
-
| `zod-split` | `schemas/<table>.ts` + `schemas.ts` barrel | split variant of `zod` |
|
|
474
|
-
| `data-connect-graphql` | `schema.gql` | `type … @table(name, key)`; `@col`/`@unique`/`@index`/`@default` |
|
|
475
|
-
| `data-connect-graphql-split` | `schema/<table>.gql` | same schema, one file per table (enums co-located) |
|
|
476
|
-
| `data-connect-operations`| `<connector>/operations.gql` + `.ts` | queries/mutations (where ops, orderBy, limit, aggregate, exprs, inc), per connector |
|
|
477
|
-
| `data-connect-operations-split` | `<connector>/operations/<entity>/{queries,mutations}.gql` | real Data Connect repo layout; shared types stay in `typescript`/`zod` outputs |
|
|
478
|
-
| `data-connect-adapter` | `data-connect-adapters.ts` | convert `Any` rows ⇄ logical types |
|
|
479
|
-
| `firestore-types` | `firestore-types.ts` | naive TS types with Firestore `Timestamp` (all models) |
|
|
480
|
-
| `firestore` | `firestore.ts` | Firestore projection Zod schemas (derived, denormalized) |
|
|
481
|
-
| `firestore-split` | `firestore/<collection>.ts` + `firestore.ts` barrel | one file per projection; `_meta_` under `firestore/_/`; DC-schema `.pick()` reuse; `FIRESTORE_DATABASES` constants in the barrel |
|
|
482
|
-
| `api-types` | `api-types.ts` | endpoint request/response types |
|
|
483
|
-
| `api-validation` | `api-validation.ts` | endpoint request-validation Zod |
|
|
484
|
-
| `api-dto` |
|
|
485
|
-
| `task-payloads` | `task-payloads.ts` | envelopes + `*TaskData`/`*TaskPayload` + constants |
|
|
486
|
-
| `sql-migrations` | `migrations/constraints.sql` | composite FK / CHECK / index SQL |
|
|
487
|
-
| `id-codecs` | `id-codecs.ts` (+ `id-core.ts`) | typed per-entity id encode/decode wrappers; emits the Sqids primitives when `project.idCodec` is set |
|
|
488
|
-
| `unions` | `unions.ts` | Zod discriminated unions + TS union types |
|
|
489
|
-
| `config` | `dataconnect.yaml`, `connector.yaml`, `constants.ts` | DC configs + sync constants (preview — see [Project config](#project-config)) |
|
|
547
|
+
| name | scope | output | notes |
|
|
548
|
+
| ------------------------ | -------- | ----------------------------- | ------------------------------------------------------ |
|
|
549
|
+
| `typescript` | document | `types.ts` | interfaces + const/union enums |
|
|
550
|
+
| `typescript-split` | document | `types/<table>.ts` + `types.ts` barrel | one file per table; enums/embedded objects co-located |
|
|
551
|
+
| `zod` | document | `schemas.ts` | `z.object` schemas (constraints, `z.lazy` model refs) |
|
|
552
|
+
| `zod-split` | document | `schemas/<table>.ts` + `schemas.ts` barrel | split variant of `zod` |
|
|
553
|
+
| `data-connect-graphql` | document | `schema.gql` | `type … @table(name, key)`; `@col`/`@unique`/`@index`/`@default` |
|
|
554
|
+
| `data-connect-graphql-split` | document | `schema/<table>.gql` | same schema, one file per table (enums co-located) |
|
|
555
|
+
| `data-connect-operations`| document | `<connector>/operations.gql` + `.ts` | queries/mutations (where ops, orderBy, limit, aggregate, exprs, inc), per connector |
|
|
556
|
+
| `data-connect-operations-split` | document | `<connector>/operations/<entity>/{queries,mutations}.gql` | real Data Connect repo layout; shared types stay in `typescript`/`zod` outputs |
|
|
557
|
+
| `data-connect-adapter` | document | `data-connect-adapters.ts` | convert `Any` rows ⇄ logical types |
|
|
558
|
+
| `firestore-types` | document | `firestore-types.ts` | naive TS types with Firestore `Timestamp` (all models) |
|
|
559
|
+
| `firestore` | document | `firestore.ts` | Firestore projection Zod schemas (derived, denormalized) |
|
|
560
|
+
| `firestore-split` | document | `firestore/<collection>.ts` + `firestore.ts` barrel | one file per projection; `_meta_` under `firestore/_/`; DC-schema `.pick()` reuse; `FIRESTORE_DATABASES` constants in the barrel |
|
|
561
|
+
| `api-types` | api | `api-types.ts` | endpoint request/response types |
|
|
562
|
+
| `api-validation` | api | `api-validation.ts` | endpoint request-validation Zod |
|
|
563
|
+
| `api-dto` | api | `<operation>.dto.ts` per api | class-validator DTO classes (NestJS `dto/` convention) |
|
|
564
|
+
| `task-payloads` | api | `task-payloads.ts` | envelopes + `*TaskData`/`*TaskPayload` + constants |
|
|
565
|
+
| `sql-migrations` | document | `migrations/constraints.sql` | composite FK / CHECK / index SQL |
|
|
566
|
+
| `id-codecs` | document | `id-codecs.ts` (+ `id-core.ts`) | typed per-entity id encode/decode wrappers; emits the Sqids primitives when `project.idCodec` is set |
|
|
567
|
+
| `unions` | document | `unions.ts` | Zod discriminated unions + TS union types |
|
|
568
|
+
| `config` | document | `dataconnect.yaml`, `connector.yaml`, `constants.ts` | DC configs + sync constants (preview — see [Project config](#project-config)) |
|
|
490
569
|
|
|
491
570
|
Each generator emits nothing when its section is absent, so a small contract
|
|
492
571
|
produces a small output. Select a subset with `-g`.
|
|
493
572
|
|
|
573
|
+
**Scope** decides how a `generators:` declaration takes effect: `document`
|
|
574
|
+
generators run once for the yml that declares them; `api` generators run only
|
|
575
|
+
for the entries that apply them (section `defaults.generators` → entry
|
|
576
|
+
`generators:`, entry wins).
|
|
577
|
+
|
|
494
578
|
### The `Any` boundary
|
|
495
579
|
|
|
496
580
|
Data Connect stores JSON and embedded objects as the `Any` scalar, erasing the
|
|
@@ -518,7 +602,7 @@ const result = generate('contract.yml', {
|
|
|
518
602
|
write: true,
|
|
519
603
|
})
|
|
520
604
|
|
|
521
|
-
// Every
|
|
605
|
+
// Every generator declared across the import graph:
|
|
522
606
|
const all = generateAll('contract.yml', { write: true })
|
|
523
607
|
```
|
|
524
608
|
|
|
@@ -43,10 +43,11 @@ export interface GenerateAllResult {
|
|
|
43
43
|
ok: boolean;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Run every
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
46
|
+
* Run every generator declared across the entry contract and its (transitive)
|
|
47
|
+
* imports — the one-command flow: each contract file declares its generators
|
|
48
|
+
* (`generators:` + section defaults / entry applications) and
|
|
49
|
+
* `fbc generate <root>` materializes all of them. Output dirs resolve relative
|
|
50
|
+
* to the declaring yml (or the root yml for `#alias/...` templates).
|
|
50
51
|
*/
|
|
51
52
|
export declare const generateAll: (entryPath: string, options?: {
|
|
52
53
|
registry?: GeneratorRegistry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/compiler/compiler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/compiler/compiler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EAAU,WAAW,EAAqC,MAAM,2BAA2B,CAAA;AAElG,OAAO,EAAkB,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAG3E,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAI5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAE7D,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,EAAE,CAAA;IACN,SAAS,EAAE,WAAW,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,WAAW,MAAM,EAAE,UAAS,cAAmB,KAAG,aAUzE,CAAA;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,oBAAoB,EAAE,CAAA;IAC/B,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,EAAE,EAAE,OAAO,CAAA;CACZ;AAmND;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,WAAW,MAAM,EACjB,UAAS;IAAE,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,KACjH,iBAuDF,CAAA;AAED,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,aAAa,EAAE,KAAG,SAUlD,CAAA;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,EAAE,CAAA;IACN,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,oFAAoF;IACpF,KAAK,EAAE,aAAa,EAAE,CAAA;IACtB,0DAA0D;IAC1D,EAAE,EAAE,OAAO,CAAA;CACZ;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,WAAW,MAAM,EAAE,SAAS,eAAe,KAAG,cAuCtE,CAAA"}
|
|
@@ -7,6 +7,7 @@ import { createFsLoader } from '../resolver/module-loader.js';
|
|
|
7
7
|
import { validateIr } from '../validation/validate.js';
|
|
8
8
|
import { createDefaultRegistry } from '../generators/index.js';
|
|
9
9
|
import { toHeaderComment } from '../generators/support/header.js';
|
|
10
|
+
import { API_PLACEHOLDER_RE, expandApiPlaceholders } from '../generators/support/templates.js';
|
|
10
11
|
/**
|
|
11
12
|
* Run the front end of the pipeline: resolve imports, build the IR, and run
|
|
12
13
|
* semantic validation. Generators are intentionally not involved here — the CLI
|
|
@@ -24,11 +25,175 @@ export const compile = (entryPath, options = {}) => {
|
|
|
24
25
|
diagnostics: [...importDiagnostics, ...buildDiagnostics, ...validationDiagnostics],
|
|
25
26
|
};
|
|
26
27
|
};
|
|
28
|
+
/** `apis:/tasks:/events:` section a raw api belongs to (drives the section defaults lookup). */
|
|
29
|
+
const sectionOf = (api) => {
|
|
30
|
+
if (api.kind === 'task')
|
|
31
|
+
return 'tasks';
|
|
32
|
+
if (api.kind === 'pubsub')
|
|
33
|
+
return 'events';
|
|
34
|
+
return 'apis';
|
|
35
|
+
};
|
|
36
|
+
const expandOutTemplate = (template, api, diagnostics, source) => {
|
|
37
|
+
if (!API_PLACEHOLDER_RE.test(template))
|
|
38
|
+
return template;
|
|
39
|
+
if (!api) {
|
|
40
|
+
diagnostics.push(error('INVALID_OUT_TEMPLATE', `"${template}" uses api placeholders but the generator is document-scoped (${source})`));
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const out = expandApiPlaceholders(template, api);
|
|
44
|
+
if (out === undefined) {
|
|
45
|
+
diagnostics.push(error('INVALID_OUT_TEMPLATE', `"${template}" uses {path} but api "${api.name}" has no REST path (${source})`));
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Resolve an out template to an absolute directory. `#alias/...` prefixes are
|
|
51
|
+
* matched against the root document's `project.aliases` (longest prefix wins)
|
|
52
|
+
* and resolve relative to the root yml; everything else resolves relative to
|
|
53
|
+
* the declaring yml — same as the legacy `generate:` form.
|
|
54
|
+
*/
|
|
55
|
+
const resolveOutDir = (out, declaringDoc, rootDoc, diagnostics) => {
|
|
56
|
+
if (!out.startsWith('#')) {
|
|
57
|
+
return join(dirname(declaringDoc.filePath), out);
|
|
58
|
+
}
|
|
59
|
+
const aliases = rootDoc.project?.aliases ?? {};
|
|
60
|
+
const candidates = Object.entries(aliases)
|
|
61
|
+
.map(([pattern, target]) => ({ prefix: pattern.endsWith('/*') ? pattern.slice(0, -2) : pattern, pattern, target }))
|
|
62
|
+
.filter(({ prefix }) => out === prefix || out.startsWith(`${prefix}/`))
|
|
63
|
+
.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
64
|
+
const match = candidates[0];
|
|
65
|
+
if (!match) {
|
|
66
|
+
diagnostics.push(error('UNKNOWN_ALIAS', `No project.aliases entry matches "${out}" (declare it in the root contract)`));
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const rest = out.slice(match.prefix.length).replace(/^\//, '');
|
|
70
|
+
const targetBase = match.target.endsWith('/*') ? match.target.slice(0, -2) : match.target;
|
|
71
|
+
return join(dirname(rootDoc.filePath), targetBase, rest);
|
|
72
|
+
};
|
|
73
|
+
/** Find a generator declaration by name — nearest first: the declaring yml, then the root yml. */
|
|
74
|
+
const findGeneratorDecl = (name, doc, rootDoc) => doc.generatorDecls?.find(decl => decl.generator === name) ??
|
|
75
|
+
rootDoc.generatorDecls?.find(decl => decl.generator === name);
|
|
27
76
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
77
|
+
* Build generator jobs from the declaration/application DSL:
|
|
78
|
+
* - api-scoped generators run for entries that opt in (section `defaults` →
|
|
79
|
+
* entry `generators:`, entry wins; no file-level tier by design)
|
|
80
|
+
* - document-scoped generators run once per yml that declares them
|
|
81
|
+
*/
|
|
82
|
+
const buildGeneratorJobs = (doc, rootDoc, registry, diagnostics) => {
|
|
83
|
+
const jobs = [];
|
|
84
|
+
const grouped = new Map();
|
|
85
|
+
for (const [apiName, rawApi] of Object.entries(doc.apis)) {
|
|
86
|
+
const uses = rawApi.generators ?? doc.sectionDefaults?.[sectionOf(rawApi)];
|
|
87
|
+
for (const use of uses ?? []) {
|
|
88
|
+
const decl = findGeneratorDecl(use.generator, doc, rootDoc);
|
|
89
|
+
const template = use.out ?? decl?.out;
|
|
90
|
+
if (template === undefined) {
|
|
91
|
+
diagnostics.push(error('UNKNOWN_GENERATOR_DECL', `Api "${apiName}" applies generator "${use.generator}" but no declaration with an "out" was found (${doc.filePath})`));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Output settings resolve nearest-first: entry use → declaration → the
|
|
95
|
+
// generator's built-in default (applied by the generator itself).
|
|
96
|
+
const generator = registry.get(use.generator);
|
|
97
|
+
const output = {};
|
|
98
|
+
const file = use.file ?? decl?.file;
|
|
99
|
+
const split = use.split ?? decl?.split ?? generator?.defaultOutput?.split;
|
|
100
|
+
const options = use.options ?? decl?.options;
|
|
101
|
+
const header = use.header ?? decl?.header;
|
|
102
|
+
if (file !== undefined)
|
|
103
|
+
output.file = file;
|
|
104
|
+
if (split !== undefined)
|
|
105
|
+
output.split = split;
|
|
106
|
+
if (options !== undefined)
|
|
107
|
+
output.options = options;
|
|
108
|
+
// Split mode needs a per-api file name; bundled mode cannot expand one.
|
|
109
|
+
const effectiveFile = file ?? generator?.defaultOutput?.file;
|
|
110
|
+
if (effectiveFile !== undefined && split === false && API_PLACEHOLDER_RE.test(effectiveFile)) {
|
|
111
|
+
diagnostics.push(error('INVALID_OUT_TEMPLATE', `file "${effectiveFile}" uses api placeholders but split is false for generator "${use.generator}" — set a plain file name (${doc.filePath})`));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (effectiveFile !== undefined && split === true && !API_PLACEHOLDER_RE.test(effectiveFile)) {
|
|
115
|
+
diagnostics.push(error('INVALID_OUT_TEMPLATE', `split is true for generator "${use.generator}" but file "${effectiveFile}" has no {api-name}/{path} placeholder, so per-api files would overwrite each other (${doc.filePath})`));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const api = { name: apiName, ...(rawApi.path !== undefined ? { path: rawApi.path } : {}) };
|
|
119
|
+
const expanded = expandOutTemplate(template, api, diagnostics, doc.filePath);
|
|
120
|
+
if (expanded === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
const outDir = resolveOutDir(expanded, doc, rootDoc, diagnostics);
|
|
123
|
+
if (outDir === undefined)
|
|
124
|
+
continue;
|
|
125
|
+
const key = [use.generator, outDir, file ?? '', String(split ?? ''), JSON.stringify(options ?? null), header ?? '\u0001'].join('\u0000');
|
|
126
|
+
const existing = grouped.get(key);
|
|
127
|
+
if (existing) {
|
|
128
|
+
existing.apiNames?.push(apiName);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const job = { source: doc.filePath, generatorName: use.generator, outDir, apiNames: [apiName], output };
|
|
132
|
+
if (header !== undefined)
|
|
133
|
+
job.header = header;
|
|
134
|
+
grouped.set(key, job);
|
|
135
|
+
jobs.push(job);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const decl of doc.generatorDecls ?? []) {
|
|
140
|
+
const generator = registry.get(decl.generator);
|
|
141
|
+
if (!generator) {
|
|
142
|
+
diagnostics.push(error('UNKNOWN_GENERATOR', `Unknown generator "${decl.generator}" (${doc.filePath})`));
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if ((generator.scope ?? 'document') === 'api')
|
|
146
|
+
continue; // applied via entries, not by declaration
|
|
147
|
+
// Document-scoped `split` toggles between a generator and its `-split`
|
|
148
|
+
// variant (e.g. typescript ⇄ typescript-split) instead of reshaping output.
|
|
149
|
+
let generatorName = decl.generator;
|
|
150
|
+
if (decl.split === true && !generatorName.endsWith('-split')) {
|
|
151
|
+
const splitName = `${generatorName}-split`;
|
|
152
|
+
if (registry.get(splitName)) {
|
|
153
|
+
generatorName = splitName;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
diagnostics.push(error('UNSUPPORTED_SPLIT', `Generator "${decl.generator}" has no split variant (${doc.filePath})`));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (decl.split === false && generatorName.endsWith('-split')) {
|
|
161
|
+
const baseName = generatorName.slice(0, -'-split'.length);
|
|
162
|
+
if (registry.get(baseName))
|
|
163
|
+
generatorName = baseName;
|
|
164
|
+
}
|
|
165
|
+
const output = {};
|
|
166
|
+
if (decl.file !== undefined) {
|
|
167
|
+
if (API_PLACEHOLDER_RE.test(decl.file)) {
|
|
168
|
+
diagnostics.push(error('INVALID_OUT_TEMPLATE', `file "${decl.file}" uses api placeholders but generator "${decl.generator}" is document-scoped (${doc.filePath})`));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
output.file = decl.file;
|
|
172
|
+
}
|
|
173
|
+
if (decl.options !== undefined)
|
|
174
|
+
output.options = decl.options;
|
|
175
|
+
const expanded = expandOutTemplate(decl.out, undefined, diagnostics, doc.filePath);
|
|
176
|
+
if (expanded === undefined)
|
|
177
|
+
continue;
|
|
178
|
+
const outDir = resolveOutDir(expanded, doc, rootDoc, diagnostics);
|
|
179
|
+
if (outDir === undefined)
|
|
180
|
+
continue;
|
|
181
|
+
jobs.push({
|
|
182
|
+
source: doc.filePath,
|
|
183
|
+
generatorName,
|
|
184
|
+
outDir,
|
|
185
|
+
...(Object.keys(output).length > 0 ? { output } : {}),
|
|
186
|
+
...(decl.header !== undefined ? { header: decl.header } : {}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return jobs;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Run every generator declared across the entry contract and its (transitive)
|
|
193
|
+
* imports — the one-command flow: each contract file declares its generators
|
|
194
|
+
* (`generators:` + section defaults / entry applications) and
|
|
195
|
+
* `fbc generate <root>` materializes all of them. Output dirs resolve relative
|
|
196
|
+
* to the declaring yml (or the root yml for `#alias/...` templates).
|
|
32
197
|
*/
|
|
33
198
|
export const generateAll = (entryPath, options = {}) => {
|
|
34
199
|
const { documents, diagnostics } = compile(entryPath, options);
|
|
@@ -38,27 +203,46 @@ export const generateAll = (entryPath, options = {}) => {
|
|
|
38
203
|
const collected = [...diagnostics];
|
|
39
204
|
const targets = [];
|
|
40
205
|
let ok = true;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
...(options.context ? { context: options.context } : {}),
|
|
50
|
-
...(options.loader ? { loader: options.loader } : {}),
|
|
51
|
-
});
|
|
52
|
-
// Per-target compiles revisit shared imports; keep only new diagnostics.
|
|
53
|
-
for (const diagnostic of result.diagnostics) {
|
|
54
|
-
if (!collected.some(d => d.code === diagnostic.code && d.message === diagnostic.message)) {
|
|
55
|
-
collected.push(diagnostic);
|
|
56
|
-
}
|
|
206
|
+
const registry = options.registry ?? createDefaultRegistry();
|
|
207
|
+
// Import order puts dependencies first, so the entry (root) document is last.
|
|
208
|
+
const rootDoc = documents[documents.length - 1];
|
|
209
|
+
const mergeDiagnostics = (incoming) => {
|
|
210
|
+
// Per-target compiles revisit shared imports; keep only new diagnostics.
|
|
211
|
+
for (const diagnostic of incoming) {
|
|
212
|
+
if (!collected.some(d => d.code === diagnostic.code && d.message === diagnostic.message)) {
|
|
213
|
+
collected.push(diagnostic);
|
|
57
214
|
}
|
|
58
|
-
if (!result.ok)
|
|
59
|
-
ok = false;
|
|
60
|
-
targets.push({ source: doc.filePath, outDir, generators: output.generators, files: result.files });
|
|
61
215
|
}
|
|
216
|
+
};
|
|
217
|
+
const runTarget = (doc, outDir, generators, apiNames, output, header) => {
|
|
218
|
+
const result = generate(doc.filePath, {
|
|
219
|
+
outDir,
|
|
220
|
+
generators,
|
|
221
|
+
registry,
|
|
222
|
+
write: options.write,
|
|
223
|
+
context: {
|
|
224
|
+
...options.context,
|
|
225
|
+
...(apiNames ? { apiNames } : {}),
|
|
226
|
+
...(output ? { output } : {}),
|
|
227
|
+
// Per-generator header beats the CLI/contract-level one ('' disables it).
|
|
228
|
+
...(header !== undefined ? { header: toHeaderComment(header) } : {}),
|
|
229
|
+
},
|
|
230
|
+
...(options.loader ? { loader: options.loader } : {}),
|
|
231
|
+
});
|
|
232
|
+
mergeDiagnostics(result.diagnostics);
|
|
233
|
+
if (!result.ok)
|
|
234
|
+
ok = false;
|
|
235
|
+
targets.push({ source: doc.filePath, outDir, generators, files: result.files });
|
|
236
|
+
};
|
|
237
|
+
for (const doc of documents) {
|
|
238
|
+
// Declaration/application DSL (`generators:` + section defaults / entry `generators:`).
|
|
239
|
+
const jobDiagnostics = [];
|
|
240
|
+
for (const job of buildGeneratorJobs(doc, rootDoc, registry, jobDiagnostics)) {
|
|
241
|
+
runTarget(doc, job.outDir, [job.generatorName], job.apiNames, job.output, job.header);
|
|
242
|
+
}
|
|
243
|
+
mergeDiagnostics(jobDiagnostics);
|
|
244
|
+
if (hasErrors(jobDiagnostics))
|
|
245
|
+
ok = false;
|
|
62
246
|
}
|
|
63
247
|
return { targets, diagnostics: collected, ok };
|
|
64
248
|
};
|