@aventara/core 1.0.0-rc.11 → 1.0.0-rc.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @aventara/core
2
2
 
3
+ ## 1.0.0-rc.12
4
+
5
+ ### Patch Changes
6
+
7
+ - fdeee8a: A unique field a layer may not filter on is no longer an identifier of that layer. Exempting its `filter` (a write-only
8
+ `apiKey: ["select", "filter", "order"]`, an unsearchable `email: ["filter"]`, or a Pick that leaves `filter` out) used
9
+ to keep the field in the Contract's `identifiers`, so `find.unique`, `update.unique`, `upsert.unique`, `delete.unique`
10
+ and a nested `$connect`/`$connectOrCreate` still looked a record up by it and answered the row or `A2003`, over every
11
+ route including `/_all` and `/_transactions`. Such a selector is now refused `V1007` before the Adapter is called, a
12
+ compound identifier goes with any one of its fields, and the generated client and the server façades no longer offer
13
+ it. A layer that keeps a unique variant with no filterable identifier left fails at startup with
14
+ `COMPILER_MISSING_IDENTIFIER`, as hiding the identifier already did: under `exposed: false`, pick `id: ["select",
15
+ "filter"]`, not `id: ["select"]`, to keep `find.unique`. The agent skill's Pick example says so.
16
+
3
17
  ## 1.0.0-rc.11
4
18
 
5
19
  ### Patch Changes
package/ERRORS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Aventara error codes
2
2
 
3
- Every code `@aventara/core` 1.0.0-rc.11 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.
3
+ Every code `@aventara/core` 1.0.0-rc.12 answers, reports in a validation issue, or refuses to start with. Generated from `AvErrorCatalog` (exported by `@aventara/core`); do not edit.
4
4
 
5
5
  Branch on the code, never on the message. In code, `AvErrorCatalog[code]` gives the same entry: `{ code, kind, message, cause, fix, docs }`.
6
6
 
@@ -135,7 +135,7 @@ Each entry of `cause.issues[]` under `A2001`, `A2002`, `A2004`, `A2007` or `A200
135
135
  | [`COMPILER_MISSING_READ_COMPUTE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_read_compute) | A selectable virtual field has no compute.read. | A VIRTUAL field that can be selected has no pipelines.compute.read stage. | Add a compute.read stage, or make the field write-only with capabilities.select: false. |
136
136
  | [`COMPILER_INVALID_COMPUTE_DEPENDENCY`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_compute_dependency) | Invalid compute.read dependency. | A dependsOn entry that is not a stored scalar field of the same Resource. | List only stored scalar fields of the Resource in dependsOn. |
137
137
  | [`COMPILER_RESERVED_RESOURCE_NAME`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_reserved_resource_name) | Reserved Resource name. | A model name starts with $, which is reserved for the framework's own names (ctx.$sql, $ref), or is the name of a member beside the Resources: all on serverClient (and so on Next.js's avClient), and on a host that adds its own, one of those (on Next.js: tx and transaction on avClient, appTx and transaction on avApp). | Rename the model; the table it maps to can keep its name. A host member's name is also left free when the layer it belongs to hides the Resource. |
138
- | [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one. | Expose an identifier field (id, or every field of one unique), or exempt the unique variants. |
138
+ | [`COMPILER_MISSING_IDENTIFIER`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_missing_identifier) | A unique operation has no identifier. | A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource it may filter on: a Pick that did not pick one with its filter, or an Omit that hid every one or its filter. | Expose an identifier field with its filter (id, or every field of one unique), or exempt the unique variants. |
139
139
  | [`COMPILER_HIDDEN_REQUIRED_FIELD`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_hidden_required_field) | A layer that creates a Resource hides a required field. | A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it. | Expose the field, give it a compute.create stage, or exempt the layer's create operations. |
140
140
  | [`COMPILER_INVALID_VALIDATOR`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_validator) | A field validator cannot apply. | A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field. | Use an AvZ check that fits the field's type; a Json field's checks go in its json shape. |
141
141
  | [`COMPILER_INVALID_JSON_SHAPE`](https://aventara.razor34.dev/framework/docs/error-codes#compiler_invalid_json_shape) | A Json shape cannot apply. | A json shape on a field that is not a discovered Json field, a shape not built with AvZ, or one that admits null or absence at its top level. | Put the shape on a Json field, build it with AvZ, and keep its top level non-null (the column's nullability decides null). |
@@ -7,8 +7,9 @@ export type AvContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "CO
7
7
  | "COMPILER_RESERVED_RESOURCE_NAME"
8
8
  /**
9
9
  * A layer exposes a unique operation variant (`find.unique`, `update.unique`,
10
- * `delete.unique`, `upsert.unique`) and no complete identifier of its Resource:
11
- * a Pick that did not pick one, or an Omit that hid every one.
10
+ * `delete.unique`, `upsert.unique`) and no complete identifier of its Resource
11
+ * it may filter on: a Pick that did not pick one with its filter, or an Omit
12
+ * that hid every one or its filter.
12
13
  */
13
14
  | "COMPILER_MISSING_IDENTIFIER"
14
15
  /**
@@ -71,8 +71,8 @@ export const CONTRACT_COMPILER_DIAGNOSTIC_GUIDANCE = {
71
71
  },
72
72
  COMPILER_MISSING_IDENTIFIER: {
73
73
  message: "A unique operation has no identifier.",
74
- cause: "A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one.",
75
- fix: "Expose an identifier field (id, or every field of one unique), or exempt the unique variants.",
74
+ cause: "A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource it may filter on: a Pick that did not pick one with its filter, or an Omit that hid every one or its filter.",
75
+ fix: "Expose an identifier field with its filter (id, or every field of one unique), or exempt the unique variants.",
76
76
  },
77
77
  COMPILER_HIDDEN_REQUIRED_FIELD: {
78
78
  message: "A layer that creates a Resource hides a required field.",
@@ -866,9 +866,14 @@ function pruneEmptyOperationFamilies(operations) {
866
866
  function identifierLabel(identifier) {
867
867
  return identifier.map((name) => `\`${name}\``).join(" and ");
868
868
  }
869
+ function isAddressable(field, parentField) {
870
+ return (field?.kind === "scalar" &&
871
+ (capabilityIsEnabled(field.capabilities.filter) ||
872
+ !capabilityIsEnabled(parentField?.capabilities?.filter)));
873
+ }
869
874
  function reconcileIdentifiers(walk, resource, parent, path) {
870
875
  resource.identifiers = parent.identifiers
871
- .filter((identifier) => identifier.every((name) => resource.fields[name]?.kind === "scalar"))
876
+ .filter((identifier) => identifier.every((name) => isAddressable(resource.fields[name], parent.fields[name])))
872
877
  .map((identifier) => [...identifier]);
873
878
  const first = parent.identifiers[0];
874
879
  if (resource.identifiers.length > 0 || first === undefined) {
@@ -882,8 +887,8 @@ function reconcileIdentifiers(walk, resource, parent, path) {
882
887
  }
883
888
  const variantPath = `${path}.operations.${family}.${variant}`;
884
889
  walk.diagnostics.push(diagnostic("COMPILER_MISSING_IDENTIFIER", variantPath, walk.style === "pick"
885
- ? `${variantPath} needs an identifier field; pick ${identifierLabel(first)} (or another complete identifier) too`
886
- : `${variantPath} needs an identifier field, and the exemptions hide every complete identifier; keep ${identifierLabel(first)} (or another complete identifier) exposed, or exempt the operation too`));
890
+ ? `${variantPath} needs an identifier field; pick ${identifierLabel(first)} with its filter (or another complete identifier) too`
891
+ : `${variantPath} needs an identifier field, and the exemptions hide (or stop filtering on) every complete identifier; keep ${identifierLabel(first)} (or another complete identifier) exposed and filterable, or exempt the operation too`));
887
892
  }
888
893
  }
889
894
  function validateScalarReference(projection, field, path, diagnostics) {
@@ -319,6 +319,16 @@ type ExemptedFields<Fields, Block, Style> = Style extends "omit" ? [Block] exten
319
319
  };
320
320
  /** The fields an exemptions `fields` block leaves a Resource without. */
321
321
  type RemovedField<Fields, Block, Style> = Style extends "omit" ? WholeKey<Block> : Exclude<keyof Fields, GrantedFieldKey<Block>>;
322
+ /** Whether a field's capabilities offer a filter operator. */
323
+ type Filters<Field> = Field extends {
324
+ readonly capabilities: infer Caps;
325
+ } ? [MemberTokens<NonNullable<Caps[keyof Caps & "filter"]>>] extends [never] ? false : true : false;
326
+ /** The fields a `fields` block stops filtering on: the parent filters on them, the layer no longer does. */
327
+ type UnfilteredField<Fields, Block, Style> = [Block] extends [never] ? never : {
328
+ [F in keyof Fields & keyof Block]-?: Filters<Fields[F]> extends true ? Filters<ExemptedField<Fields[F], NonNullable<Block[F]>, Style>> extends true ? never : F : never;
329
+ }[keyof Fields & keyof Block];
330
+ /** The fields no identifier of the layer may name. */
331
+ type UnaddressableField<Fields, Block, Style> = RemovedField<Fields, Block, Style> | UnfilteredField<Fields, Block, Style>;
322
332
  /** One family's entry in an operations entry: `true` for a listed family. */
323
333
  type FamilyEntry<Operations, Family> = Operations extends readonly unknown[] ? Family extends Listed<Operations> ? true : unknown : [Operations] extends [boolean] ? Operations : Family extends keyof Operations ? NonNullable<Operations[Family]> : unknown;
324
334
  /** The keys of a per-variant object entry set to a literal `true`. */
@@ -342,7 +352,7 @@ type KeptOperations<Operations, Entry, Style, UniqueLost> = {
342
352
  readonly [V in KeptVariant<NonNullable<Operations[F]>, FamilyEntry<Entry, F>, Style, UniqueLost>]: NonNullable<Operations[F]>[V];
343
353
  };
344
354
  };
345
- /** A Resource's identifiers less those naming a removed field. */
355
+ /** A Resource's identifiers less those naming an unaddressable field. */
346
356
  type KeptIdentifiers<Identifiers, Removed> = [Removed] extends [never] ? Identifiers : Identifiers extends readonly (infer Identifier)[] ? readonly (Identifier extends readonly (infer Field)[] ? [Extract<Field, Removed>] extends [never] ? Identifier : never : never)[] : Identifiers;
347
357
  /** Whether removing fields left a Resource with no identifier at all. */
348
358
  type IdentifiersLost<Identifiers, Removed> = [Removed] extends [never] ? false : [
@@ -359,9 +369,9 @@ type OperationsEntryOf<Entry> = (Entry & {
359
369
  type ExemptedResourceObject<in out Resource, in out FieldsBlock, in out OperationsEntry, in out Style> = {
360
370
  readonly [P in keyof Resource]: P extends "fields" ? ExemptedFields<Resource[P], FieldsBlock, Style> : P extends "operations" ? ExemptedOperations<Resource[P], OperationsEntry, Style, IdentifiersLost<Resource extends {
361
371
  readonly identifiers: infer Ids;
362
- } ? Ids : never, RemovedField<Resource extends {
372
+ } ? Ids : never, UnaddressableField<Resource extends {
363
373
  readonly fields: infer Fields;
364
- } ? Fields : never, FieldsBlock, Style>>> : P extends "identifiers" ? KeptIdentifiers<Resource[P], RemovedField<Resource extends {
374
+ } ? Fields : never, FieldsBlock, Style>>> : P extends "identifiers" ? KeptIdentifiers<Resource[P], UnaddressableField<Resource extends {
365
375
  readonly fields: infer Fields;
366
376
  } ? Fields : never, FieldsBlock, Style>> : Resource[P];
367
377
  };
@@ -1,2 +1,2 @@
1
1
  /** The version of this `@aventara/core`. */
2
- export declare const CORE_VERSION = "1.0.0-rc.11";
2
+ export declare const CORE_VERSION = "1.0.0-rc.12";
@@ -1 +1 @@
1
- export const CORE_VERSION = "1.0.0-rc.11";
1
+ export const CORE_VERSION = "1.0.0-rc.12";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/core",
3
- "version": "1.0.0-rc.11",
3
+ "version": "1.0.0-rc.12",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Core runtime, contract compiler, and extension APIs for Aventara.",
6
6
  "type": "module",