firebase-contract 0.1.0-rc.15 → 0.1.0-rc.16

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 CHANGED
@@ -245,8 +245,10 @@ export type ProductStatusKey = keyof typeof PRODUCT_STATUS
245
245
  export type ProductStatus = (typeof PRODUCT_STATUS)[ProductStatusKey]
246
246
  ```
247
247
 
248
- Pass `enumStyle: 'union'` to the TypeScript generator for a plain
249
- `'OPEN' | 'DONE' | 'DISABLED'` union instead.
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.
250
252
 
251
253
  ## Models
252
254
 
@@ -281,24 +283,24 @@ models:
281
283
 
282
284
  | option | meaning |
283
285
  | ----------------------------------- | -------------------------------------------------------- |
284
- | `type` | document | scalar name, or an enum/model name |
285
- | `optional` | document | value may be absent (`field?`) |
286
- | `list` | document | value is an array of `type` |
287
- | `id` | document | marks the (single) primary identifier |
288
- | `unique` | document | field-level unique → `@unique` |
289
- | `relation` | document | model-typed field is a foreign-key relation (see below) |
290
- | `col` | document | Data Connect column dataType (Int64 PKs default to `bigserial`) |
291
- | `default` | document | `@default(expr: …)` on the DC column |
292
- | `literal` | document | pin to one literal value (union discriminant tags) |
293
- | `nullable` | document | value may be `null` (`.nullable()` / `\| null`) |
294
- | `description` | document | doc comment carried into generated output |
295
- | `jsdoc` | document | render the description as a JSDoc block (Firestore fields) |
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) |
296
298
  | **constraints** → | |
297
- | `min` / `max` | document | numeric bounds |
298
- | `minLength` / `maxLength` | document | string/array length bounds |
299
- | `nonempty` | document | non-empty string/array |
300
- | `pattern` | document | regex the string must match |
301
- | `email` / `url` | document | string format |
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 |
302
304
 
303
305
  Constraints flow into the Zod schemas, the API request validation, and the
304
306
  class-validator DTOs.
@@ -307,14 +309,14 @@ class-validator DTOs.
307
309
 
308
310
  | option | meaning |
309
311
  | ------------ | ------------------------------------------------------------------ |
310
- | `key` | document | composite primary key field names (else the `id: true` field) |
311
- | `table` | document | table name override (default: snake_case pluralization) |
312
- | `gqlName` | document | GraphQL type name override |
313
- | `fsName` | document | Firestore-side rename (avoid collisions with table models) |
314
- | `directives` | document | `multi` = each type-level directive on its own line |
315
- | `footer` | document | trailing comment block after the closing `}` in the schema file |
316
- | `indexes` | document | composite `@index`/`@unique` (`{ fields, name?, unique?, expand? }` — `expand` renders args one per line) |
317
- | `sql` | document | raw SQL constraints (see [SQL migrations](#sql-migrations)) |
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)) |
318
320
 
319
321
  ### Embedded vs relation
320
322
 
@@ -392,14 +394,14 @@ Everything below exists to reproduce real hand-written `.gql` files byte-for-byt
392
394
 
393
395
  | option | meaning |
394
396
  | --- | --- |
395
- | `gqlName` | document | rendered operation name (the yml key stays unique across services) |
396
- | `footer` | document | trailing `# …` comment block after the operation |
397
- | `raw` | document | emit the operation body verbatim (multi-model queries, `_or` keyset cursors — field checks are skipped) |
398
- | `single: id \| document | key` | single-row lookup (`product(id: $id)` / `product(key: { … })`) |
399
- | `keyArg` / `keyVars` | document | update/delete key argument form and variable renames |
400
- | `whereAnd: true` | document | render conditions as an `_and: [ … ]` array |
401
- | `entityDir` | document | override the `operations/<entity>/` output directory |
402
- | `style` | document | formatting hints: `signature`, `args`, `data`, `orderBy`, `auth`, `key`, `and`, `where` (`inline`/`multi`/`compact`/`bare`) |
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`) |
403
405
 
404
406
  Inputs support `var` (variable rename), `required` (override optionality),
405
407
  `literal` (fixed value, no variable), `flat` (write the FK column directly),
@@ -573,6 +575,10 @@ firestore:
573
575
  extends: [meta] # appends `_meta_: MetaSchema` (imported from './_')
574
576
  ```
575
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
+
576
582
  ## Discriminated unions
577
583
 
578
584
  ```yaml
@@ -28,6 +28,13 @@ export declare const apis: ValidationRule;
28
28
  export declare const firestore: ValidationRule;
29
29
  /** Union variants must be known models, each carrying the discriminant field. */
30
30
  export declare const unions: ValidationRule;
31
+ /**
32
+ * A firestore doc's `extends` must reference a declared fragment. An unknown
33
+ * name would otherwise resolve to no fields and silently drop the shared
34
+ * columns (`extends: [metaa]` → no `_meta_`), the exact silent-coercion class
35
+ * the contract-is-truth rules exist to kill.
36
+ */
37
+ export declare const fragmentRefs: ValidationRule;
31
38
  /**
32
39
  * Type names must be unique within every output namespace — the export surface
33
40
  * where generated identifiers converge (a split layout still merges into one
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/validation/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAA;AAG9D,OAAO,EAAa,EAAE,EAAgB,MAAM,aAAa,CAAA;AAEzD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,CAAA;AAIrD,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,cAU3B,CAAA;AAEH,6CAA6C;AAC7C,eAAO,MAAM,UAAU,EAAE,cAQpB,CAAA;AAEL,oCAAoC;AACpC,eAAO,MAAM,mBAAmB,EAAE,cAgB9B,CAAA;AAEJ,kFAAkF;AAClF,eAAO,MAAM,UAAU,EAAE,cAiCxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,cA+BnB,CAAA;AAEJ,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,cAkC9B,CAAA;AAEJ,mEAAmE;AACnE,eAAO,MAAM,cAAc,EAAE,cAU1B,CAAA;AAEH,mGAAmG;AACnG,eAAO,MAAM,UAAU,EAAE,cA+ErB,CAAA;AAEJ,uFAAuF;AACvF,eAAO,MAAM,IAAI,EAAE,cAYf,CAAA;AAEJ,wFAAwF;AACxF,eAAO,MAAM,SAAS,EAAE,cA+BpB,CAAA;AAEJ,iFAAiF;AACjF,eAAO,MAAM,MAAM,EAAE,cAoBjB,CAAA;AAaJ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,EAAE,cA+E5B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAmBnC,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,cAAc,EAazC,CAAA"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/validation/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAA;AAG9D,OAAO,EAA2B,EAAE,EAAgB,MAAM,aAAa,CAAA;AAEvE;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,CAAA;AAIrD,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,cAU3B,CAAA;AAEH,6CAA6C;AAC7C,eAAO,MAAM,UAAU,EAAE,cAQpB,CAAA;AAEL,oCAAoC;AACpC,eAAO,MAAM,mBAAmB,EAAE,cAgB9B,CAAA;AAEJ,kFAAkF;AAClF,eAAO,MAAM,UAAU,EAAE,cAiCxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,cA+BnB,CAAA;AAEJ,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,cAkC9B,CAAA;AAEJ,mEAAmE;AACnE,eAAO,MAAM,cAAc,EAAE,cAU1B,CAAA;AAEH,mGAAmG;AACnG,eAAO,MAAM,UAAU,EAAE,cA+ErB,CAAA;AAEJ,uFAAuF;AACvF,eAAO,MAAM,IAAI,EAAE,cAYf,CAAA;AAEJ,wFAAwF;AACxF,eAAO,MAAM,SAAS,EAAE,cA+BpB,CAAA;AAEJ,iFAAiF;AACjF,eAAO,MAAM,MAAM,EAAE,cAoBjB,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,cAUxB,CAAA;AAaH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,EAAE,cA+E5B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAmBnC,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,cAAc,EAczC,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { error, warning } from '../diagnostics.js';
2
2
  import { constantCase } from '../generators/support/naming.js';
3
3
  import { isTable } from '../generators/support/split.js';
4
- import { findModel } from '../ir/ir.js';
4
+ import { findFragment, findModel } from '../ir/ir.js';
5
5
  const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
6
6
  /** Every field type must resolve to a known scalar, enum, or model. */
7
7
  export const unresolvedTypes = ir => ir.models.flatMap(model => model.fields
@@ -269,6 +269,18 @@ export const unions = ir => ir.unions.flatMap(union => {
269
269
  }
270
270
  return diagnostics;
271
271
  });
272
+ /**
273
+ * A firestore doc's `extends` must reference a declared fragment. An unknown
274
+ * name would otherwise resolve to no fields and silently drop the shared
275
+ * columns (`extends: [metaa]` → no `_meta_`), the exact silent-coercion class
276
+ * the contract-is-truth rules exist to kill.
277
+ */
278
+ export const fragmentRefs = ir => ir.firestore.flatMap(doc => doc.extends
279
+ .filter(name => !findFragment(ir, name))
280
+ .map(name => error('UNKNOWN_FRAGMENT', `Firestore doc "${doc.name}" extends unknown fragment "${name}"`, {
281
+ file: doc.sourceFile,
282
+ path: `firestore.${doc.name}.extends`,
283
+ })));
272
284
  const RESERVED_OWNER = 'the generated runtime';
273
285
  /**
274
286
  * Type names must be unique within every output namespace — the export surface
@@ -397,5 +409,6 @@ export const DEFAULT_RULES = [
397
409
  apis,
398
410
  firestore,
399
411
  unions,
412
+ fragmentRefs,
400
413
  nameCollisions,
401
414
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-contract",
3
- "version": "0.1.0-rc.15",
3
+ "version": "0.1.0-rc.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "firebase-contract": "./dist/lib/cli/index.js",