firebase-contract 0.1.0-rc.9 → 0.1.1
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 +140 -63
- package/dist/lib/_internal/compiler/compiler.d.ts.map +1 -1
- package/dist/lib/_internal/compiler/compiler.js +50 -21
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-dto-generator.js +1 -8
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-types-generator.js +11 -5
- package/dist/lib/_internal/generators/api/task-payload-generator.js +3 -3
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts +0 -7
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/graphql-generator.js +5 -4
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts +0 -7
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/operations-generator.js +9 -8
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.js +7 -22
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts +1 -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 +69 -47
- package/dist/lib/_internal/generators/generator.d.ts +2 -0
- package/dist/lib/_internal/generators/generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/index.d.ts +2 -5
- package/dist/lib/_internal/generators/index.d.ts.map +1 -1
- package/dist/lib/_internal/generators/index.js +4 -15
- package/dist/lib/_internal/generators/support/jsdoc.d.ts +3 -0
- package/dist/lib/_internal/generators/support/jsdoc.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/jsdoc.js +9 -0
- package/dist/lib/_internal/generators/support/split-variant.d.ts +9 -0
- package/dist/lib/_internal/generators/support/split-variant.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/split-variant.js +13 -0
- package/dist/lib/_internal/generators/support/split.d.ts +1 -1
- package/dist/lib/_internal/generators/support/split.d.ts.map +1 -1
- package/dist/lib/_internal/generators/support/split.js +3 -2
- package/dist/lib/_internal/generators/typescript/render.d.ts +15 -0
- package/dist/lib/_internal/generators/typescript/render.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/render.js +92 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts +2 -1
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-generator.js +12 -78
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts +1 -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 +5 -85
- package/dist/lib/_internal/generators/zod/render.d.ts +12 -0
- package/dist/lib/_internal/generators/zod/render.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/render.js +59 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts +2 -1
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/zod/zod-generator.js +11 -55
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts +1 -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 +4 -56
- package/dist/lib/_internal/ir/build-ir.d.ts.map +1 -1
- package/dist/lib/_internal/ir/build-ir.js +93 -8
- package/dist/lib/_internal/ir/ir.d.ts +18 -5
- package/dist/lib/_internal/ir/ir.d.ts.map +1 -1
- package/dist/lib/_internal/ir/ir.js +1 -0
- package/dist/lib/_internal/parser/parse.d.ts +5 -3
- package/dist/lib/_internal/parser/parse.d.ts.map +1 -1
- package/dist/lib/_internal/parser/parse.js +103 -14
- package/dist/lib/_internal/parser/raw-document.d.ts +18 -2
- package/dist/lib/_internal/parser/raw-document.d.ts.map +1 -1
- package/dist/lib/_internal/resolver/import-resolver.js +1 -1
- package/dist/lib/_internal/validation/rules.d.ts +39 -0
- package/dist/lib/_internal/validation/rules.d.ts.map +1 -1
- package/dist/lib/_internal/validation/rules.js +133 -1
- package/dist/lib/cli/commands/generate.d.ts.map +1 -1
- package/dist/lib/cli/commands/generate.js +6 -4
- package/dist/lib/cli/commands/index.d.ts.map +1 -1
- package/dist/lib/cli/commands/index.js +2 -0
- package/dist/lib/cli/commands/init.d.ts.map +1 -1
- package/dist/lib/cli/commands/init.js +2 -18
- package/dist/lib/cli/commands/where.d.ts +3 -0
- package/dist/lib/cli/commands/where.d.ts.map +1 -0
- package/dist/lib/cli/commands/where.js +57 -0
- package/dist/lib/cli/config.d.ts +3 -2
- package/dist/lib/cli/config.d.ts.map +1 -1
- package/dist/lib/cli/config.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ From one contract, generate:
|
|
|
10
10
|
- **Zod** validation schemas
|
|
11
11
|
- **Data Connect** GraphQL schema (`@table`, keys, directives), query/mutation
|
|
12
12
|
operations (routed per connector), and `Any`↔logical adapters
|
|
13
|
-
- **Firestore** projection schemas (denormalized read model
|
|
13
|
+
- **Firestore** projection schemas (denormalized read model; shared field groups via fragments)
|
|
14
14
|
- **API** request/response types, request-validation Zod, and class-validator DTOs
|
|
15
15
|
- **Cloud Task / Pub/Sub** payload envelopes + delivery constants
|
|
16
16
|
- **SQL migrations** for constraints Data Connect can't express (composite FKs,
|
|
@@ -34,13 +34,14 @@ fbc init # scaffold contract.yml + firebase-contract.json
|
|
|
34
34
|
fbc validate # parse, resolve imports, semantically validate
|
|
35
35
|
fbc generate # run every generator declared across the contract graph
|
|
36
36
|
fbc inspect # print the normalized IR (debugging)
|
|
37
|
+
fbc where <Name> # locate the yml defining a type (logical / gqlName / fsName / table)
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
### One-command generation
|
|
40
41
|
|
|
41
42
|
Each contract file declares the generators it uses in a top-level
|
|
42
43
|
`generators:` block — a name plus an output template. Declaring a
|
|
43
|
-
**document-scoped** generator (typescript
|
|
44
|
+
**document-scoped** generator (typescript, zod, …) runs it once for
|
|
44
45
|
that yml; **api-scoped** generators (api-types, api-validation, api-dto,
|
|
45
46
|
task-payloads) run for the entries that opt in (see below):
|
|
46
47
|
|
|
@@ -50,7 +51,7 @@ project:
|
|
|
50
51
|
aliases:
|
|
51
52
|
"#contracts/*": libs/contracts/src/* # out templates may target aliases
|
|
52
53
|
generators:
|
|
53
|
-
- { generator: typescript
|
|
54
|
+
- { generator: typescript, out: "#contracts", split: true }
|
|
54
55
|
- { generator: api-types, out: "#contracts/api-types/{api-name}" }
|
|
55
56
|
imports:
|
|
56
57
|
- ./apps/shop/data-connect/schema.yml
|
|
@@ -59,7 +60,7 @@ imports:
|
|
|
59
60
|
```yaml
|
|
60
61
|
# apps/shop/data-connect/schema.yml
|
|
61
62
|
generators:
|
|
62
|
-
- { generator: data-connect-graphql
|
|
63
|
+
- { generator: data-connect-graphql, out: src, split: true }
|
|
63
64
|
- { generator: sql-migrations, out: . }
|
|
64
65
|
```
|
|
65
66
|
|
|
@@ -90,19 +91,26 @@ generators:
|
|
|
90
91
|
- generator: typescript
|
|
91
92
|
out: "#contracts"
|
|
92
93
|
file: models.ts # rename a single-file/barrel output
|
|
93
|
-
split: true # document scope:
|
|
94
|
+
split: true # document scope: per-table split layout
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
- **`file`** — output file name. Api scope: a template (`{api-name}`/`{path}`
|
|
97
98
|
allowed when `split: true`). Document scope: renames the single output file,
|
|
98
|
-
or the barrel
|
|
99
|
+
or the barrel of a split layout.
|
|
99
100
|
- **`split`** — api scope: `true` emits one file per api (the `file` template
|
|
100
101
|
must contain a placeholder), `false` bundles everything into one `file`.
|
|
101
|
-
Document scope: `true`
|
|
102
|
-
(
|
|
102
|
+
Document scope: `true` selects the generator's per-item split layout
|
|
103
|
+
(typescript, zod, firestore, data-connect-graphql, data-connect-operations)
|
|
104
|
+
and errors for generators without one.
|
|
103
105
|
- **`options`** — free-form string map passed through to the generator.
|
|
104
106
|
Currently: `typesImport` (api-types, task-payloads, data-connect-operations)
|
|
105
|
-
overrides the
|
|
107
|
+
overrides the types import path. For api-types and task-payloads this is
|
|
108
|
+
normally unnecessary: when the option is absent and a `typescript` generator
|
|
109
|
+
is declared (nearest-first: same yml → root), the import specifier is derived
|
|
110
|
+
automatically as the relative path from the output file to the declared
|
|
111
|
+
barrel — moving or renaming the barrel (`out` / `file`) re-derives it. An
|
|
112
|
+
explicit `typesImport` always wins; without a typescript declaration the
|
|
113
|
+
generator's built-in default (`./types`) applies.
|
|
106
114
|
- **`header`** — per-generator banner override: `default` for the built-in
|
|
107
115
|
AUTO-GENERATED banner, any text for a custom comment, `""` to suppress the
|
|
108
116
|
banner for this generator. Wins over the contract-level `header:` and the
|
|
@@ -115,8 +123,9 @@ generators:
|
|
|
115
123
|
|
|
116
124
|
`fbc generate contract.yml` materializes **all** declared outputs across the
|
|
117
125
|
whole import graph in one run. Passing `-o`/`-g` switches to single-target mode
|
|
118
|
-
(`fbc generate <entry> -o <dir> -g typescript,zod`)
|
|
119
|
-
|
|
126
|
+
(`fbc generate <entry> -o <dir> -g typescript,zod`). `firebase-contract.json`
|
|
127
|
+
holds only a default `entry` — output routing lives in the yml `generators:`
|
|
128
|
+
declarations, so a config file can never silently override them.
|
|
120
129
|
|
|
121
130
|
|
|
122
131
|
### Generated-file headers
|
|
@@ -173,8 +182,33 @@ in other contracts (relative paths or npm packages). Imports are resolved
|
|
|
173
182
|
transitively with cycle/duplicate detection and diamond dedup, so you can split
|
|
174
183
|
a large contract across files that mirror your repo layout.
|
|
175
184
|
|
|
185
|
+
**Type names are globally unique.** The namespace is flat across the whole
|
|
186
|
+
import graph: defining the same name twice — same kind (`DUPLICATE_DEFINITION`)
|
|
187
|
+
or across kinds/derived identifiers, e.g. an enum and a model both named
|
|
188
|
+
`Status`, or two enums whose `CONSTANT_CASE` consts collide (`NAME_COLLISION`)
|
|
189
|
+
— aborts generation. Uniqueness is enforced per output namespace with its
|
|
190
|
+
*effective* names, so `fsName`/`gqlName` renames legitimately reuse a name in a
|
|
191
|
+
different output: GraphQL names are checked per `data-connect-graphql` scope
|
|
192
|
+
(services may map distinct logical names onto the same gqlName), and the
|
|
193
|
+
firestore module is checked under `fsName ?? name` alongside doc names. A bare
|
|
194
|
+
type reference therefore always resolves to exactly one definition — use
|
|
195
|
+
`fbc where <Name>` to find it.
|
|
196
|
+
|
|
197
|
+
**Unknown keys are errors.** Every key the parser does not consume is an
|
|
198
|
+
`UNKNOWN_KEY` error (all reported in one pass), never silently dropped — a
|
|
199
|
+
typo like `optionnal:` must not quietly turn into a required field. There is
|
|
200
|
+
no `version:` field: nothing consumes one, so accepting it would only fake a
|
|
201
|
+
compatibility mechanism that does not exist.
|
|
202
|
+
|
|
203
|
+
**Out-of-vocabulary values are errors too.** A value outside a field's fixed
|
|
204
|
+
set is `INVALID_VALUE` rather than a silent coercion: `auth: PUBILC` does not
|
|
205
|
+
quietly become `NO_ACCESS`, `dir: Dsec` does not become `ASC`, and a mistyped
|
|
206
|
+
`action` / `single` / `keyArg` / `style.*` is not dropped. The one exception is
|
|
207
|
+
`where.op`, which is warned (`UNKNOWN_WHERE_OP`) not errored — it is emitted
|
|
208
|
+
verbatim into GraphQL and the recognized-operator list may lag Data Connect, so
|
|
209
|
+
a typo is surfaced without rejecting a valid-but-unlisted operator.
|
|
210
|
+
|
|
176
211
|
```yaml
|
|
177
|
-
version: 1
|
|
178
212
|
imports:
|
|
179
213
|
- ./apps/shop/data-connect/schema.yml
|
|
180
214
|
- some-shared-package/contract.yml
|
|
@@ -211,8 +245,10 @@ export type ProductStatusKey = keyof typeof PRODUCT_STATUS
|
|
|
211
245
|
export type ProductStatus = (typeof PRODUCT_STATUS)[ProductStatusKey]
|
|
212
246
|
```
|
|
213
247
|
|
|
214
|
-
|
|
215
|
-
|
|
248
|
+
For a plain `'OPEN' | 'DONE' | 'DISABLED'` union instead, construct the
|
|
249
|
+
TypeScript generator with `enumStyle: 'union'` via the programmatic API
|
|
250
|
+
(`createTypeScriptGenerator({ enumStyle: 'union' })`). This is a build-time
|
|
251
|
+
generator option, not a YAML contract field.
|
|
216
252
|
|
|
217
253
|
## Models
|
|
218
254
|
|
|
@@ -247,24 +283,24 @@ models:
|
|
|
247
283
|
|
|
248
284
|
| option | meaning |
|
|
249
285
|
| ----------------------------------- | -------------------------------------------------------- |
|
|
250
|
-
| `type` |
|
|
251
|
-
| `optional` |
|
|
252
|
-
| `list` |
|
|
253
|
-
| `id` |
|
|
254
|
-
| `unique` |
|
|
255
|
-
| `relation` |
|
|
256
|
-
| `col` |
|
|
257
|
-
| `default` |
|
|
258
|
-
| `literal` |
|
|
259
|
-
| `nullable` |
|
|
260
|
-
| `description` |
|
|
261
|
-
| `jsdoc` |
|
|
286
|
+
| `type` | scalar name, or an enum/model name |
|
|
287
|
+
| `optional` | value may be absent (`field?`) |
|
|
288
|
+
| `list` | value is an array of `type` |
|
|
289
|
+
| `id` | marks the (single) primary identifier |
|
|
290
|
+
| `unique` | field-level unique → `@unique` |
|
|
291
|
+
| `relation` | model-typed field is a foreign-key relation (see below) |
|
|
292
|
+
| `col` | Data Connect column dataType (Int64 PKs default to `bigserial`) |
|
|
293
|
+
| `default` | `@default(expr: …)` on the DC column |
|
|
294
|
+
| `literal` | pin to one literal value (union discriminant tags) |
|
|
295
|
+
| `nullable` | value may be `null` (`.nullable()` / `\| null`) |
|
|
296
|
+
| `description` | doc comment carried into generated output |
|
|
297
|
+
| `jsdoc` | render the description as a JSDoc block (Firestore fields) |
|
|
262
298
|
| **constraints** → | |
|
|
263
|
-
| `min` / `max` |
|
|
264
|
-
| `minLength` / `maxLength` |
|
|
265
|
-
| `nonempty` |
|
|
266
|
-
| `pattern` |
|
|
267
|
-
| `email` / `url` |
|
|
299
|
+
| `min` / `max` | numeric bounds |
|
|
300
|
+
| `minLength` / `maxLength` | string/array length bounds |
|
|
301
|
+
| `nonempty` | non-empty string/array |
|
|
302
|
+
| `pattern` | regex the string must match |
|
|
303
|
+
| `email` / `url` | string format |
|
|
268
304
|
|
|
269
305
|
Constraints flow into the Zod schemas, the API request validation, and the
|
|
270
306
|
class-validator DTOs.
|
|
@@ -273,14 +309,14 @@ class-validator DTOs.
|
|
|
273
309
|
|
|
274
310
|
| option | meaning |
|
|
275
311
|
| ------------ | ------------------------------------------------------------------ |
|
|
276
|
-
| `key` |
|
|
277
|
-
| `table` |
|
|
278
|
-
| `gqlName` |
|
|
279
|
-
| `fsName` |
|
|
280
|
-
| `directives` |
|
|
281
|
-
| `footer` |
|
|
282
|
-
| `indexes` |
|
|
283
|
-
| `sql` |
|
|
312
|
+
| `key` | composite primary key field names (else the `id: true` field) |
|
|
313
|
+
| `table` | table name override (default: snake_case pluralization) |
|
|
314
|
+
| `gqlName` | GraphQL type name override |
|
|
315
|
+
| `fsName` | Firestore-side rename (avoid collisions with table models) |
|
|
316
|
+
| `directives` | `multi` = each type-level directive on its own line |
|
|
317
|
+
| `footer` | trailing comment block after the closing `}` in the schema file |
|
|
318
|
+
| `indexes` | composite `@index`/`@unique` (`{ fields, name?, unique?, expand? }` — `expand` renders args one per line) |
|
|
319
|
+
| `sql` | raw SQL constraints (see [SQL migrations](#sql-migrations)) |
|
|
284
320
|
|
|
285
321
|
### Embedded vs relation
|
|
286
322
|
|
|
@@ -358,14 +394,14 @@ Everything below exists to reproduce real hand-written `.gql` files byte-for-byt
|
|
|
358
394
|
|
|
359
395
|
| option | meaning |
|
|
360
396
|
| --- | --- |
|
|
361
|
-
| `gqlName` |
|
|
362
|
-
| `footer` |
|
|
363
|
-
| `raw` |
|
|
364
|
-
| `single: id \|
|
|
365
|
-
| `keyArg` / `keyVars` |
|
|
366
|
-
| `whereAnd: true` |
|
|
367
|
-
| `entityDir` |
|
|
368
|
-
| `style` |
|
|
397
|
+
| `gqlName` | rendered operation name (the yml key stays unique across services) |
|
|
398
|
+
| `footer` | trailing `# …` comment block after the operation |
|
|
399
|
+
| `raw` | emit the operation body verbatim (multi-model queries, `_or` keyset cursors — field checks are skipped) |
|
|
400
|
+
| `single: id \| key` | single-row lookup (`product(id: $id)` / `product(key: { … })`) |
|
|
401
|
+
| `keyArg` / `keyVars` | update/delete key argument form and variable renames |
|
|
402
|
+
| `whereAnd: true` | render conditions as an `_and: [ … ]` array |
|
|
403
|
+
| `entityDir` | override the `operations/<entity>/` output directory |
|
|
404
|
+
| `style` | formatting hints: `signature`, `args`, `data`, `orderBy`, `auth`, `key`, `and`, `where` (`inline`/`multi`/`compact`/`bare`) |
|
|
369
405
|
|
|
370
406
|
Inputs support `var` (variable rename), `required` (override optionality),
|
|
371
407
|
`literal` (fixed value, no variable), `flat` (write the FK column directly),
|
|
@@ -447,9 +483,9 @@ reality automatically:
|
|
|
447
483
|
|
|
448
484
|
- **relations → resolved string ids** (`ownerUser` → `ownerUserId: z.string()`)
|
|
449
485
|
- **timestamps → `z.date()`**, optional fields → `.nullable()`
|
|
450
|
-
- a **`_meta_`** consistency envelope is attached (opt out with `meta: false`)
|
|
451
486
|
- `pick` / `omit` select which base fields survive
|
|
452
487
|
- inline `fields` add denormalized data (flattened DAG edges, stringified JSON)
|
|
488
|
+
- `extends: [<fragment>…]` splices shared fields in (see **Fragments** below)
|
|
453
489
|
|
|
454
490
|
```yaml
|
|
455
491
|
firestore:
|
|
@@ -467,10 +503,13 @@ firestore:
|
|
|
467
503
|
```
|
|
468
504
|
|
|
469
505
|
Emits Zod schemas + inferred types, mirroring a hand-written Firestore schema
|
|
470
|
-
library. (
|
|
471
|
-
|
|
506
|
+
library. (`firestore-types` is the zero-config entry point for projects that
|
|
507
|
+
have not written projections yet: it re-types *every* model with the Firestore
|
|
508
|
+
`Timestamp`, no `firestore:` section needed. Once you declare projections,
|
|
509
|
+
prefer the `firestore` generator — it is the one that understands
|
|
510
|
+
pick/omit/fsName/denormalized fields.)
|
|
472
511
|
|
|
473
|
-
With `firestore
|
|
512
|
+
With `firestore` + `split: true`, fields whose Zod chain is **identical** to the Data
|
|
474
513
|
Connect schema are reused via `.pick()` — only representation changes are
|
|
475
514
|
restated:
|
|
476
515
|
|
|
@@ -480,7 +519,6 @@ export const ShopSchema = DcShopSchema.pick({
|
|
|
480
519
|
}).extend({
|
|
481
520
|
createdAt: z.date(),
|
|
482
521
|
updatedAt: z.date().nullable(),
|
|
483
|
-
_meta_: _Meta_Schema,
|
|
484
522
|
})
|
|
485
523
|
```
|
|
486
524
|
|
|
@@ -497,6 +535,50 @@ Additional projection options:
|
|
|
497
535
|
- FS-only enums render as frozen consts (`REVIEW_STATE` + `ReviewStateSchema`),
|
|
498
536
|
co-located with the first projection that references them
|
|
499
537
|
|
|
538
|
+
### Fragments (`fragments:` + `extends:`)
|
|
539
|
+
|
|
540
|
+
A **fragment** is a named group of fields declared once and spliced into
|
|
541
|
+
consumers via `extends: [<name>…]` — the way to share a common field across
|
|
542
|
+
many projections (a consistency `_meta_` envelope, audit columns, …) without a
|
|
543
|
+
tool-imposed convention. The fragment field's name, type, and which docs carry
|
|
544
|
+
it are all yours.
|
|
545
|
+
|
|
546
|
+
The value objects a fragment references can be **pinned** to a fixed split
|
|
547
|
+
output via `out` (+ optional `file`) on the model declaration. A pinned value
|
|
548
|
+
object is *placement-scoped*: emitted only at that location (dependency-ordered,
|
|
549
|
+
with an `index.ts` barrel), imported by consumers via that directory, and
|
|
550
|
+
excluded from the default `typescript`/`zod` barrels — so it lives purely in the
|
|
551
|
+
firestore output. This is what lets a shared envelope sit under `firestore/_/`.
|
|
552
|
+
|
|
553
|
+
```yaml
|
|
554
|
+
models:
|
|
555
|
+
MetaOp:
|
|
556
|
+
out: firestore/_ # → firestore/_/_meta_.ts (+ firestore/_/index.ts)
|
|
557
|
+
file: _meta_.ts
|
|
558
|
+
fields:
|
|
559
|
+
id: { type: string }
|
|
560
|
+
Meta:
|
|
561
|
+
out: firestore/_
|
|
562
|
+
file: _meta_.ts
|
|
563
|
+
fields:
|
|
564
|
+
scope: { type: float, nullable: true }
|
|
565
|
+
op: { type: MetaOp, nullable: true }
|
|
566
|
+
|
|
567
|
+
fragments:
|
|
568
|
+
meta:
|
|
569
|
+
fields:
|
|
570
|
+
_meta_: { type: Meta }
|
|
571
|
+
|
|
572
|
+
firestore:
|
|
573
|
+
Shop:
|
|
574
|
+
from: Shop
|
|
575
|
+
extends: [meta] # appends `_meta_: MetaSchema` (imported from './_')
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
`extends` referencing a fragment that is not declared is an `UNKNOWN_FRAGMENT`
|
|
579
|
+
error, not a silent no-op — a typo like `extends: [metaa]` fails generation
|
|
580
|
+
rather than quietly dropping the shared fields.
|
|
581
|
+
|
|
500
582
|
## Discriminated unions
|
|
501
583
|
|
|
502
584
|
```yaml
|
|
@@ -553,7 +635,7 @@ project:
|
|
|
553
635
|
```
|
|
554
636
|
|
|
555
637
|
- `services` (`name`/`database`) feed the `FIRESTORE_DATABASES` constants in the
|
|
556
|
-
`firestore
|
|
638
|
+
split `firestore` barrel.
|
|
557
639
|
- `idCodec` pins the Sqids settings as a contract constant; when present, the
|
|
558
640
|
`id-codecs` generator also emits a self-contained `id-core.ts` (the
|
|
559
641
|
encode/decode primitives), so nothing about id encoding lives outside the
|
|
@@ -574,18 +656,13 @@ project:
|
|
|
574
656
|
|
|
575
657
|
| name | scope | output | notes |
|
|
576
658
|
| ------------------------ | -------- | ----------------------------- | ------------------------------------------------------ |
|
|
577
|
-
| `typescript` | document | `types.ts`
|
|
578
|
-
| `
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
581
|
-
| `data-connect-graphql` | document | `schema.gql` | `type … @table(name, key)`; `@col`/`@unique`/`@index`/`@default` |
|
|
582
|
-
| `data-connect-graphql-split` | document | `schema/<table>.gql` | same schema, one file per table (enums co-located) |
|
|
583
|
-
| `data-connect-operations`| document | `<connector>/operations.gql` + `.ts` | queries/mutations (where ops, orderBy, limit, aggregate, exprs, inc), per connector |
|
|
584
|
-
| `data-connect-operations-split` | document | `<connector>/operations/<entity>/{queries,mutations}.gql` | real Data Connect repo layout; shared types stay in `typescript`/`zod` outputs |
|
|
659
|
+
| `typescript` | document | `types.ts`; `split: true` → `types/<table>.ts` + barrel | interfaces + const/union enums; split co-locates enums/embedded objects per table |
|
|
660
|
+
| `zod` | document | `schemas.ts`; `split: true` → `schemas/<table>.ts` + barrel | `z.object` schemas (constraints, `z.lazy` model refs) |
|
|
661
|
+
| `data-connect-graphql` | document | `schema.gql`; `split: true` → `schema/<table>.gql` | `type … @table(name, key)`; `@col`/`@unique`/`@index`/`@default` |
|
|
662
|
+
| `data-connect-operations`| document | `<connector>/operations.gql` + `.ts`; `split: true` → `<connector>/operations/<entity>/{queries,mutations}.gql` | queries/mutations (where ops, orderBy, limit, aggregate, exprs, inc), per connector; split matches the real Data Connect repo layout |
|
|
585
663
|
| `data-connect-adapter` | document | `data-connect-adapters.ts` | convert `Any` rows ⇄ logical types |
|
|
586
664
|
| `firestore-types` | document | `firestore-types.ts` | naive TS types with Firestore `Timestamp` (all models) |
|
|
587
|
-
| `firestore` | document | `firestore.ts`
|
|
588
|
-
| `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 |
|
|
665
|
+
| `firestore` | document | `firestore.ts`; `split: true` → `firestore/<collection>.ts` + barrel | Firestore projection Zod schemas (derived, denormalized); `extends` fragments, pinned value objects under their `out` (e.g. `firestore/_/`), DC-schema `.pick()` reuse, `FIRESTORE_DATABASES` constants in the barrel |
|
|
589
666
|
| `api-types` | api | `api-types.ts` | endpoint request/response types |
|
|
590
667
|
| `api-validation` | api | `api-validation.ts` | endpoint request-validation Zod |
|
|
591
668
|
| `api-dto` | api | `<operation>.dto.ts` per api | class-validator DTO classes (NestJS `dto/` convention) |
|
|
@@ -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,EAAU,WAAW,EAAqC,MAAM,2BAA2B,CAAA;AAElG,OAAO,EAAkB,YAAY,EAAE,MAAM,8BAA8B,CAAA;
|
|
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;AAI3E,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAI5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAEL,cAAc,EAGf,MAAM,kBAAkB,CAAA;AAEzB,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,aAkBzE,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;IAC1B,oFAAoF;IACpF,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,mFAAmF;IACnF,GAAG,CAAC,EAAE,IAAI,CAAA;CACX;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;AAyOD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,WAAW,MAAM,EACjB,UAAS;IACP,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,GAAG,CAAC,EAAE,IAAI,CAAA;CACN,KACL,iBA0FF,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,cAgEtE,CAAA"}
|
|
@@ -4,6 +4,7 @@ import { error, hasErrors } from '../diagnostics.js';
|
|
|
4
4
|
import { buildIr } from '../ir/build-ir.js';
|
|
5
5
|
import { resolveImports } from '../resolver/import-resolver.js';
|
|
6
6
|
import { createFsLoader } from '../resolver/module-loader.js';
|
|
7
|
+
import { graphqlNameCollisions } from '../validation/rules.js';
|
|
7
8
|
import { validateIr } from '../validation/validate.js';
|
|
8
9
|
import { createDefaultRegistry } from '../generators/index.js';
|
|
9
10
|
import { toHeaderComment } from '../generators/support/header.js';
|
|
@@ -20,10 +21,18 @@ export const compile = (entryPath, options = {}) => {
|
|
|
20
21
|
const { documents, diagnostics: importDiagnostics } = resolveImports(entryPath, loader);
|
|
21
22
|
const { ir, diagnostics: buildDiagnostics } = buildIr(documents);
|
|
22
23
|
const validationDiagnostics = validateIr(ir);
|
|
24
|
+
// A GraphQL schema is generated per data-connect-graphql declaration from the
|
|
25
|
+
// declaring yml's subtree, so its name uniqueness only holds at that scope —
|
|
26
|
+
// distinct services may reuse a gqlName. generateAll re-compiles each target
|
|
27
|
+
// with the declaring yml as entry, which is exactly when this check applies.
|
|
28
|
+
const entryDoc = documents[documents.length - 1];
|
|
29
|
+
const graphqlDiagnostics = entryDoc?.generatorDecls?.some(decl => decl.generator === 'data-connect-graphql')
|
|
30
|
+
? graphqlNameCollisions(ir)
|
|
31
|
+
: [];
|
|
23
32
|
return {
|
|
24
33
|
ir,
|
|
25
34
|
documents,
|
|
26
|
-
diagnostics: [...importDiagnostics, ...buildDiagnostics, ...validationDiagnostics],
|
|
35
|
+
diagnostics: [...importDiagnostics, ...buildDiagnostics, ...validationDiagnostics, ...graphqlDiagnostics],
|
|
27
36
|
};
|
|
28
37
|
};
|
|
29
38
|
/** `apis:/tasks:/events:` section a raw api belongs to (drives the section defaults lookup). */
|
|
@@ -74,6 +83,30 @@ const resolveOutDir = (out, declaringDoc, rootDoc, diagnostics) => {
|
|
|
74
83
|
/** Find a generator declaration by name — nearest first: the declaring yml, then the root yml. */
|
|
75
84
|
const findGeneratorDecl = (name, doc, rootDoc) => doc.generatorDecls?.find(decl => decl.generator === name) ??
|
|
76
85
|
rootDoc.generatorDecls?.find(decl => decl.generator === name);
|
|
86
|
+
/** Generators whose output imports the shared types barrel via `options.typesImport`. */
|
|
87
|
+
const TYPES_IMPORT_GENERATORS = new Set(['api-types', 'task-payloads']);
|
|
88
|
+
/**
|
|
89
|
+
* Derive the default `typesImport` specifier from the typescript declaration
|
|
90
|
+
* itself: the relative path from the job's output directory to the declared
|
|
91
|
+
* barrel (`out` + `file`, default `types.ts`). The declaration resolves
|
|
92
|
+
* nearest-first (same yml → root), mirroring how entries resolve declarations,
|
|
93
|
+
* so both outputs stay in sync when the barrel is moved or renamed. An explicit
|
|
94
|
+
* `options.typesImport` always wins; without a typescript declaration in scope
|
|
95
|
+
* the generator's built-in default applies.
|
|
96
|
+
*/
|
|
97
|
+
const deriveTypesImport = (outDir, doc, rootDoc) => {
|
|
98
|
+
const decl = findGeneratorDecl('typescript', doc, rootDoc);
|
|
99
|
+
if (!decl)
|
|
100
|
+
return undefined;
|
|
101
|
+
const declaringDoc = doc.generatorDecls?.includes(decl) ? doc : rootDoc;
|
|
102
|
+
// Alias problems are reported when the typescript job itself is built.
|
|
103
|
+
const barrelDir = resolveOutDir(decl.out, declaringDoc, rootDoc, []);
|
|
104
|
+
if (barrelDir === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const barrel = join(barrelDir, (decl.file ?? 'types.ts').replace(/\.ts$/, ''));
|
|
107
|
+
const specifier = relative(outDir, barrel);
|
|
108
|
+
return specifier.startsWith('.') ? specifier : `./${specifier}`;
|
|
109
|
+
};
|
|
77
110
|
/**
|
|
78
111
|
* Build generator jobs from the declaration/application DSL:
|
|
79
112
|
* - api-scoped generators run for entries that opt in (section `defaults` →
|
|
@@ -98,14 +131,12 @@ const buildGeneratorJobs = (doc, rootDoc, registry, diagnostics) => {
|
|
|
98
131
|
const output = {};
|
|
99
132
|
const file = use.file ?? decl?.file;
|
|
100
133
|
const split = use.split ?? decl?.split ?? generator?.defaultOutput?.split;
|
|
101
|
-
|
|
134
|
+
let options = use.options ?? decl?.options;
|
|
102
135
|
const header = use.header ?? decl?.header;
|
|
103
136
|
if (file !== undefined)
|
|
104
137
|
output.file = file;
|
|
105
138
|
if (split !== undefined)
|
|
106
139
|
output.split = split;
|
|
107
|
-
if (options !== undefined)
|
|
108
|
-
output.options = options;
|
|
109
140
|
// Split mode needs a per-api file name; bundled mode cannot expand one.
|
|
110
141
|
const effectiveFile = file ?? generator?.defaultOutput?.file;
|
|
111
142
|
if (effectiveFile !== undefined && split === false && API_PLACEHOLDER_RE.test(effectiveFile)) {
|
|
@@ -123,6 +154,13 @@ const buildGeneratorJobs = (doc, rootDoc, registry, diagnostics) => {
|
|
|
123
154
|
const outDir = resolveOutDir(expanded, doc, rootDoc, diagnostics);
|
|
124
155
|
if (outDir === undefined)
|
|
125
156
|
continue;
|
|
157
|
+
if (TYPES_IMPORT_GENERATORS.has(use.generator) && options?.['typesImport'] === undefined) {
|
|
158
|
+
const derived = deriveTypesImport(outDir, doc, rootDoc);
|
|
159
|
+
if (derived !== undefined)
|
|
160
|
+
options = { ...(options ?? {}), typesImport: derived };
|
|
161
|
+
}
|
|
162
|
+
if (options !== undefined)
|
|
163
|
+
output.options = options;
|
|
126
164
|
const key = [use.generator, outDir, file ?? '', String(split ?? ''), JSON.stringify(options ?? null), header ?? '\u0001'].join('\u0000');
|
|
127
165
|
const existing = grouped.get(key);
|
|
128
166
|
if (existing) {
|
|
@@ -145,25 +183,16 @@ const buildGeneratorJobs = (doc, rootDoc, registry, diagnostics) => {
|
|
|
145
183
|
}
|
|
146
184
|
if ((generator.scope ?? 'document') === 'api')
|
|
147
185
|
continue; // applied via entries, not by declaration
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
if (decl.split
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
generatorName = splitName;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
diagnostics.push(error('UNSUPPORTED_SPLIT', `Generator "${decl.generator}" has no split variant (${doc.filePath})`));
|
|
186
|
+
const output = {};
|
|
187
|
+
// Document scope: `split: true` selects the generator's per-item layout
|
|
188
|
+
// (dispatched inside the generator); reject it when none exists.
|
|
189
|
+
if (decl.split !== undefined) {
|
|
190
|
+
if (decl.split === true && generator.splittable !== true) {
|
|
191
|
+
diagnostics.push(error('UNSUPPORTED_SPLIT', `Generator "${decl.generator}" has no split layout (${doc.filePath})`));
|
|
158
192
|
continue;
|
|
159
193
|
}
|
|
194
|
+
output.split = decl.split;
|
|
160
195
|
}
|
|
161
|
-
else if (decl.split === false && generatorName.endsWith('-split')) {
|
|
162
|
-
const baseName = generatorName.slice(0, -'-split'.length);
|
|
163
|
-
if (registry.get(baseName))
|
|
164
|
-
generatorName = baseName;
|
|
165
|
-
}
|
|
166
|
-
const output = {};
|
|
167
196
|
if (decl.file !== undefined) {
|
|
168
197
|
if (API_PLACEHOLDER_RE.test(decl.file)) {
|
|
169
198
|
diagnostics.push(error('INVALID_OUT_TEMPLATE', `file "${decl.file}" uses api placeholders but generator "${decl.generator}" is document-scoped (${doc.filePath})`));
|
|
@@ -181,7 +210,7 @@ const buildGeneratorJobs = (doc, rootDoc, registry, diagnostics) => {
|
|
|
181
210
|
continue;
|
|
182
211
|
jobs.push({
|
|
183
212
|
source: doc.filePath,
|
|
184
|
-
generatorName,
|
|
213
|
+
generatorName: decl.generator,
|
|
185
214
|
outDir,
|
|
186
215
|
...(Object.keys(output).length > 0 ? { output } : {}),
|
|
187
216
|
...(decl.header !== undefined ? { header: decl.header } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-dto-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-dto-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAgC,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"api-dto-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-dto-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAgC,MAAM,iBAAiB,CAAA;AA8GxF,eAAO,MAAM,qBAAqB,QAAO,SAuBvC,CAAA"}
|
|
@@ -3,6 +3,7 @@ import { pascalCase, singleQuote } from '../support/naming.js';
|
|
|
3
3
|
import { isRelation, relationFkName } from '../support/relations.js';
|
|
4
4
|
import { selectApis } from '../generator.js';
|
|
5
5
|
import { headerBlocks } from '../support/header.js';
|
|
6
|
+
import { jsdoc } from '../support/jsdoc.js';
|
|
6
7
|
import { emitApiFiles, resolveOutput } from '../support/templates.js';
|
|
7
8
|
const SCALAR_TS = {
|
|
8
9
|
string: 'string',
|
|
@@ -96,14 +97,6 @@ const decoratorsFor = (ir, field, used) => {
|
|
|
96
97
|
}
|
|
97
98
|
return out;
|
|
98
99
|
};
|
|
99
|
-
const jsdoc = (description, indent) => {
|
|
100
|
-
if (!description)
|
|
101
|
-
return '';
|
|
102
|
-
const lines = description.split('\n');
|
|
103
|
-
if (lines.length === 1)
|
|
104
|
-
return `${indent}/** ${lines[0]} */\n`;
|
|
105
|
-
return `${indent}/**\n${lines.map(line => `${indent} * ${line}`.trimEnd()).join('\n')}\n${indent} */\n`;
|
|
106
|
-
};
|
|
107
100
|
const renderDto = (ir, api, used) => {
|
|
108
101
|
const fields = payloadFields(ir, api.request);
|
|
109
102
|
const className = `${pascalCase(api.name)}Dto`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-types-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAgC,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"api-types-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAgC,MAAM,iBAAiB,CAAA;AA4FxF,eAAO,MAAM,uBAAuB,QAAO,SA+BzC,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { pascalCase } from '../support/naming.js';
|
|
|
2
2
|
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
3
3
|
import { selectApis } from '../generator.js';
|
|
4
4
|
import { headerBlocks } from '../support/header.js';
|
|
5
|
+
import { jsdoc } from '../support/jsdoc.js';
|
|
5
6
|
import { emitApiFiles, resolveOutput } from '../support/templates.js';
|
|
6
7
|
const SCALAR_TS = {
|
|
7
8
|
string: 'string',
|
|
@@ -34,8 +35,7 @@ const renderField = (ir, field) => {
|
|
|
34
35
|
const name = isRelation(field) ? relationFkName(field) : field.name;
|
|
35
36
|
const optional = field.optional ? '?' : '';
|
|
36
37
|
const nullable = field.nullable ? ' | null' : '';
|
|
37
|
-
|
|
38
|
-
return `${doc} ${name}${optional}: ${renderFieldTsType(ir, field)}${nullable}`;
|
|
38
|
+
return `${jsdoc(field.description, ' ')} ${name}${optional}: ${renderFieldTsType(ir, field)}${nullable}`;
|
|
39
39
|
};
|
|
40
40
|
const renderPayloadType = (ir, payload, typeName) => {
|
|
41
41
|
if (payload.isVoid) {
|
|
@@ -74,9 +74,15 @@ const apiComment = (api) => {
|
|
|
74
74
|
const bits = [`kind: ${api.kind}`];
|
|
75
75
|
if (api.method)
|
|
76
76
|
bits.push(`method: ${api.method}`);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
const description = api.description?.split('\n') ?? [];
|
|
78
|
+
// Single-line descriptions stay inline; multi-line ones get their own
|
|
79
|
+
// comment lines so literal-block yml descriptions survive verbatim.
|
|
80
|
+
if (description.length <= 1) {
|
|
81
|
+
if (description[0])
|
|
82
|
+
bits.unshift(description[0]);
|
|
83
|
+
return `// ${api.name} — ${bits.join(' | ')}`;
|
|
84
|
+
}
|
|
85
|
+
return [`// ${api.name} — ${bits.join(' | ')}`, ...description.map(line => `// ${line}`.trimEnd())].join('\n');
|
|
80
86
|
};
|
|
81
87
|
/**
|
|
82
88
|
* Generates request/response TypeScript types for each API endpoint. A payload
|
|
@@ -3,6 +3,7 @@ import { constantCase, pascalCase, singleQuote } from '../support/naming.js';
|
|
|
3
3
|
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
4
4
|
import { selectApis } from '../generator.js';
|
|
5
5
|
import { headerBlocks } from '../support/header.js';
|
|
6
|
+
import { jsdoc } from '../support/jsdoc.js';
|
|
6
7
|
import { emitApiFiles, resolveOutput } from '../support/templates.js';
|
|
7
8
|
const SCALAR_TS = {
|
|
8
9
|
string: 'string',
|
|
@@ -30,13 +31,12 @@ const fieldTs = (ir, field) => {
|
|
|
30
31
|
};
|
|
31
32
|
const renderMember = (ir, field) => {
|
|
32
33
|
const name = isRelation(field) ? relationFkName(field) : field.name;
|
|
33
|
-
const doc = field.description ? ` /** ${field.description} */\n` : '';
|
|
34
34
|
const nullable = field.nullable ? ' | null' : '';
|
|
35
|
-
return `${
|
|
35
|
+
return `${jsdoc(field.description, ' ')} ${name}${field.optional ? '?' : ''}: ${fieldTs(ir, field)}${nullable}`;
|
|
36
36
|
};
|
|
37
37
|
const payloadFields = (ir, payload) => payload.model ? (findModel(ir, payload.model)?.fields ?? []) : payload.fields;
|
|
38
38
|
const renderEnvelope = (env) => {
|
|
39
|
-
const doc = env.description
|
|
39
|
+
const doc = jsdoc(env.description, '');
|
|
40
40
|
const members = env.fields.map(field => ` ${field.name}${field.optional ? '?' : ''}: ${SCALAR_TS[field.type.kind === 'scalar' ? field.type.name : 'string']}`).join('\n');
|
|
41
41
|
return `${doc}export type ${env.name}<T> = T & {\n${members}\n}`;
|
|
42
42
|
};
|
|
@@ -5,11 +5,4 @@ import { Generator } from '../generator.js';
|
|
|
5
5
|
* comment, matching the "JSON is Any, logical type preserved" rule.
|
|
6
6
|
*/
|
|
7
7
|
export declare const createDataConnectGraphqlGenerator: () => Generator;
|
|
8
|
-
/**
|
|
9
|
-
* Split variant matching the real Data Connect layout: one file per table at
|
|
10
|
-
* `schema/<table_name>.gql`, with each enum co-located in the file of the first
|
|
11
|
-
* model that references it (leftover enums go to `schema/_enums.gql`). The `Any`
|
|
12
|
-
* scalar is not declared — Data Connect provides it built-in.
|
|
13
|
-
*/
|
|
14
|
-
export declare const createDataConnectGraphqlSplitGenerator: () => Generator;
|
|
15
8
|
//# sourceMappingURL=graphql-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/data-connect/graphql-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"graphql-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/data-connect/graphql-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAgJ5E;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,QAAO,SAmClD,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { headerBlocks } from '../support/header.js';
|
|
|
2
2
|
import { constantCase, pluralize, snakeCase } from '../support/naming.js';
|
|
3
3
|
import { isRelation } from '../support/relations.js';
|
|
4
4
|
import { outputFile } from '../support/templates.js';
|
|
5
|
+
import { withSplitVariant } from '../support/split-variant.js';
|
|
5
6
|
// Data Connect built-in scalars. `json` and embedded model objects are stored
|
|
6
7
|
// as `Any` — the logical type is preserved in a comment and restored by the
|
|
7
8
|
// Data Connect adapter generator.
|
|
@@ -131,7 +132,7 @@ const commentBanner = (header) => header
|
|
|
131
132
|
* model fields become the `Any` scalar while retaining their logical type as a
|
|
132
133
|
* comment, matching the "JSON is Any, logical type preserved" rule.
|
|
133
134
|
*/
|
|
134
|
-
export const createDataConnectGraphqlGenerator = () => ({
|
|
135
|
+
export const createDataConnectGraphqlGenerator = () => withSplitVariant({
|
|
135
136
|
name: 'data-connect-graphql',
|
|
136
137
|
description: 'Data Connect GraphQL schema',
|
|
137
138
|
generate(ir, context) {
|
|
@@ -154,7 +155,7 @@ export const createDataConnectGraphqlGenerator = () => ({
|
|
|
154
155
|
}
|
|
155
156
|
return [{ path: outputFile(context, 'schema.gql'), content: `${blocks.join('\n\n')}\n` }];
|
|
156
157
|
},
|
|
157
|
-
});
|
|
158
|
+
}, createDataConnectGraphqlSplitLayout());
|
|
158
159
|
const renderEnumGql = (irEnum, rename) => {
|
|
159
160
|
const lines = [...commentLines(irEnum.description, '')];
|
|
160
161
|
lines.push(`enum ${gqlOf(rename, irEnum.name)} {`);
|
|
@@ -171,8 +172,8 @@ const renderEnumGql = (irEnum, rename) => {
|
|
|
171
172
|
* model that references it (leftover enums go to `schema/_enums.gql`). The `Any`
|
|
172
173
|
* scalar is not declared — Data Connect provides it built-in.
|
|
173
174
|
*/
|
|
174
|
-
|
|
175
|
-
name: 'data-connect-graphql
|
|
175
|
+
const createDataConnectGraphqlSplitLayout = () => ({
|
|
176
|
+
name: 'data-connect-graphql',
|
|
176
177
|
description: 'Data Connect GraphQL schema, one file per table (schema/<table>.gql)',
|
|
177
178
|
generate(ir, context) {
|
|
178
179
|
const banner = headerBlocks(context, commentBanner);
|