@aventara/client 1.0.0-rc.4 → 1.0.0-rc.9

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,80 @@
1
1
  # @aventara/client
2
2
 
3
+ ## 1.0.0-rc.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 99278e4: `aventara-build` reads a Next.js project's files from `src/aventara/` and, with `--client`, writes its browser client.
8
+
9
+ - A project whose `package.json` depends on `@aventara/next` keeps its configuration and adapter in
10
+ `src/aventara/config.ts` and `src/aventara/adapter.ts`; the build runs them there, and a configuration that imports no
11
+ Resource tree has its generated files in `src/aventara/generated/`. Every other project keeps
12
+ `src/aventara.config.ts` and `src/aventara.adapter.ts`, unchanged. A missing file is named with where the project's
13
+ host keeps it, and a project depending on both `@aventara/nest` and `@aventara/next` is refused in one sentence.
14
+ - `aventara-build --client` also writes the browser client from the ClientContract it has just compiled, with no
15
+ running server: `src/aventara/client.ts` and the client's modules under `src/aventara/generated/`, the same modules
16
+ `avclient generate` writes for that ClientContract except that the entrypoint is the app's own `api` on the page's
17
+ origin (`/api`) and the regenerate instructions. Its imports are spelled as the project's `tsconfig.json` says, and nothing it writes imports
18
+ `@aventara/client`.
19
+ - `src/aventara/client.ts` exports `avClient` and `avHydrate`, and re-exports every name `avclient generate`'s
20
+ `AvClient.ts` exports besides its default (`AvClient`, the Resource types, the enums, `AvDecimal`, the error
21
+ classes), so `import { AvDecimal } from "@/aventara/client"` works. The generated files and the contract-mismatch
22
+ error say to regenerate with `aventara-build --client` (the project's `aventara:prepare` script). `avClient` calls the API from the browser; a call made
23
+ on the server (a Client Component rendering there) rejects with one sentence pointing to `@/aventara/server` and
24
+ `@/aventara/universal`, and sends nothing. `avHydrate` revives data `avDehydrate` sends from the server: tagged
25
+ dates, `bigint`s, byte arrays and decimals become `Date`, `bigint`, `Uint8Array` and the client's own `AvDecimal`,
26
+ for TanStack Query's `deserializeData` or an SWR `fallback`.
27
+ - `aventara-build --check --client` checks those files too: a missing or stale one, or one an earlier ClientContract's
28
+ client left, exits 1 naming it. `--client` is refused in a NestJS project, whose frontends keep using
29
+ `avclient generate`, which is unchanged.
30
+ - `@aventara/client` gains an `@aventara/client/internal` entry, which `@aventara/build` reads the generator through.
31
+ It is not public API.
32
+
33
+ - Updated dependencies [205a379]
34
+ - Updated dependencies [a0bb05e]
35
+ - Updated dependencies [86ab319]
36
+ - Updated dependencies [fdf52e6]
37
+ - @aventara/core@1.0.0-rc.9
38
+
39
+ ## 1.0.0-rc.8
40
+
41
+ 1.0.0-rc.5, rc.6 and rc.7 were tagged but never published; 1.0.0-rc.8 contains everything listed under them and the changes below.
42
+
43
+ ### Patch Changes
44
+
45
+ - A generated client's results read as their value types in an editor: `body: string | null`, `published: boolean`,
46
+ `publishedAt: Date | null`, where a nullable, enum or `boolean` field was shown as `AvScalarValueType<…>` with the
47
+ client's Contract as its argument, and a result as `ProjectedFields<…>`. Run `avclient generate` again to pick it up.
48
+ What the client accepts and returns is unchanged.
49
+ - Updated dependencies [1c8e4ee]
50
+ - @aventara/core@1.0.0-rc.8
51
+
52
+ ## 1.0.0-rc.7
53
+
54
+ 1.0.0-rc.5 and 1.0.0-rc.6 were tagged but never published; 1.0.0-rc.7 contains everything listed under them and the changes below.
55
+
56
+ ### Patch Changes
57
+
58
+ - Updated dependencies [f917c1c]
59
+ - @aventara/core@1.0.0-rc.7
60
+
61
+ ## 1.0.0-rc.6
62
+
63
+ 1.0.0-rc.5 was tagged but never published. 1.0.0-rc.6 contains everything listed under 1.0.0-rc.5 and the fix below.
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies [1cf72e0]
68
+ - @aventara/core@1.0.0-rc.6
69
+
70
+ ## 1.0.0-rc.5
71
+
72
+ ### Patch Changes
73
+
74
+ - Updated dependencies [4795ff1]
75
+ - Updated dependencies [9e178ca]
76
+ - @aventara/core@1.0.0-rc.5
77
+
3
78
  ## 1.0.0-rc.4
4
79
 
5
80
  ### Patch Changes
@@ -44,6 +44,14 @@ export interface ClientEntrypoint {
44
44
  readonly deployment: URL;
45
45
  readonly path: EntrypointPath;
46
46
  }
47
+ /** An entrypoint on the page's own origin: its mount path alone, such as `/api`. */
48
+ export interface SameOriginEntrypoint {
49
+ /** Absent: the deployment is whichever origin served the page. */
50
+ readonly deployment?: undefined;
51
+ readonly path: EntrypointPath;
52
+ }
53
+ /** The entrypoint a generated client embeds as its default: a deployment's, or the page's own origin. */
54
+ export type EmittedEntrypoint = ClientEntrypoint | SameOriginEntrypoint;
47
55
  export interface ResolvedClientConfig {
48
56
  readonly entrypoint: ClientEntrypoint;
49
57
  /** `generateAt`, made absolute. */
@@ -1,4 +1,4 @@
1
- import type { AvClientConfigInput, AvEnvReference, ClientEntrypoint, ResolvedClientConfig } from "./client-config.interface.js";
1
+ import type { AvClientConfigInput, AvEnvReference, ClientEntrypoint, EmittedEntrypoint, ResolvedClientConfig, SameOriginEntrypoint } from "./client-config.interface.js";
2
2
  import type { EnvCascadeResolution } from "./env.cascade.js";
3
3
  /** Gives a `framework.client.ts` its type: returns `config` unchanged. */
4
4
  export declare function defineClientConfig(config: AvClientConfigInput): AvClientConfigInput;
@@ -26,4 +26,10 @@ export declare function resolveClientConfig(input: ClientConfigResolutionInput):
26
26
  * @throws AvClientConfigError in one sentence that does not echo the value.
27
27
  */
28
28
  export declare function resolveEntrypoint(value: string): ClientEntrypoint;
29
- export declare function entrypointHref(entrypoint: ClientEntrypoint): string;
29
+ /**
30
+ * The same-origin entrypoint of a framework mounted at `api`.
31
+ *
32
+ * @throws AvClientConfigError when `api` is not a mount path.
33
+ */
34
+ export declare function sameOriginEntrypoint(api: string): SameOriginEntrypoint;
35
+ export declare function entrypointHref(entrypoint: EmittedEntrypoint): string;
@@ -77,7 +77,17 @@ export function resolveEntrypoint(value) {
77
77
  function writtenMountPath(value) {
78
78
  return value.replace(/^[A-Za-z][A-Za-z0-9+.-]*:[/\\]*[^/\\?#]*/u, "");
79
79
  }
80
+ export function sameOriginEntrypoint(api) {
81
+ const normalized = AvProtocol.normalizeEntrypoint(api);
82
+ if (!normalized.ok) {
83
+ throw new AvClientConfigError(`api ${normalized.message}.`);
84
+ }
85
+ return { path: normalized.path };
86
+ }
80
87
  export function entrypointHref(entrypoint) {
88
+ if (entrypoint.deployment === undefined) {
89
+ return entrypoint.path;
90
+ }
81
91
  const url = new URL(entrypoint.deployment.href);
82
92
  url.pathname = entrypoint.path === "" ? "/" : entrypoint.path;
83
93
  return url.href.replace(/\/$/, "");
@@ -1,7 +1,13 @@
1
1
  export declare const GENERATED_OWNERSHIP_LINE = "/* !!! Generated by @aventara/client. Do not edit. !!! */";
2
+ /** The command a generated file says to regenerate it with: a command in backticks, then any words about it. */
3
+ export type RegenerateCommand = `\`${string}\`${string}`;
4
+ /** `avclient generate`'s own: what its banner and errors say. */
5
+ export declare const AVCLIENT_REGENERATE: RegenerateCommand;
6
+ export declare function generatedBanner(regenerate?: RegenerateCommand): readonly string[];
7
+ /** `avclient generate`'s banner. */
2
8
  export declare const GENERATED_BANNER: readonly string[];
3
9
  /** `source` with the banner before it and one blank line between. */
4
- export declare function withGeneratedBanner(source: string): string;
10
+ export declare function withGeneratedBanner(source: string, regenerate?: RegenerateCommand): string;
5
11
  /**
6
12
  * How far into a file the ownership line is looked for. The banner opens every
7
13
  * emitted file, so the line sits well inside this; a reader never has to read a
@@ -1,12 +1,16 @@
1
1
  export const GENERATED_OWNERSHIP_LINE = "/* !!! Generated by @aventara/client. Do not edit. !!! */";
2
- export const GENERATED_BANNER = [
3
- "// biome-ignore-all format: generated output",
4
- "// biome-ignore-all lint: generated output",
5
- GENERATED_OWNERSHIP_LINE,
6
- "/* Regenerate with `avclient generate`. */",
7
- ];
8
- export function withGeneratedBanner(source) {
9
- return `${GENERATED_BANNER.join("\n")}\n\n${source}`;
2
+ export const AVCLIENT_REGENERATE = "`avclient generate`";
3
+ export function generatedBanner(regenerate = AVCLIENT_REGENERATE) {
4
+ return [
5
+ "// biome-ignore-all format: generated output",
6
+ "// biome-ignore-all lint: generated output",
7
+ GENERATED_OWNERSHIP_LINE,
8
+ `/* Regenerate with ${regenerate}. */`,
9
+ ];
10
+ }
11
+ export const GENERATED_BANNER = generatedBanner();
12
+ export function withGeneratedBanner(source, regenerate = AVCLIENT_REGENERATE) {
13
+ return `${generatedBanner(regenerate).join("\n")}\n\n${source}`;
10
14
  }
11
15
  export const GENERATED_OWNERSHIP_HEAD_BYTES = 1024;
12
16
  export function carriesGeneratedOwnership(head) {
@@ -1,8 +1,13 @@
1
1
  import type { AvContractClient } from "@aventara/core";
2
- import type { ClientEntrypoint } from "../config/client-config.interface.js";
2
+ import type { EmittedEntrypoint } from "../config/client-config.interface.js";
3
+ import { type RegenerateCommand } from "./banner.emitter.js";
3
4
  import { type ClientEmission } from "./emitted-tree.interface.js";
4
5
  import { type ClientModuleStyle } from "./module-style.interface.js";
5
6
  /**
6
7
  * @throws AvGeneratedNameError when a name cannot be emitted, even renamed.
7
8
  */
8
- export declare function emitClientTree(contract: AvContractClient, entrypoint: ClientEntrypoint, style: ClientModuleStyle): ClientEmission;
9
+ export declare function emitClientTree(contract: AvContractClient, entrypoint: EmittedEntrypoint, style: ClientModuleStyle, regenerate?: RegenerateCommand): ClientEmission;
10
+ /** How an entry re-exports `AvClient`: the class itself, or its type alone. */
11
+ export type ClientClassExport = "value" | "type";
12
+ /** The entry's re-exports, its default aside, as source: one statement per module. */
13
+ export declare function clientEntryReExports(style: ClientModuleStyle, clientClass?: ClientClassExport): string;
@@ -1,4 +1,4 @@
1
- import { withGeneratedBanner } from "./banner.emitter.js";
1
+ import { AVCLIENT_REGENERATE, withGeneratedBanner, } from "./banner.emitter.js";
2
2
  import { emitClientSurfaceModule } from "./client-surface.emitter.js";
3
3
  import { emitContractCarrierModule } from "./contract-carrier.emitter.js";
4
4
  import { emitDerivationModules } from "./derivation.emitter.js";
@@ -12,7 +12,7 @@ import { emitNamedTypesModule } from "./named-type.emitter.js";
12
12
  import { emitDecimalModule, emitErrorsModule, emitMetadataModule, emitTransportModule, errorsModuleExports, } from "./runtime.emitter.js";
13
13
  import { emitScalarCodecModule } from "./scalar.codec.js";
14
14
  import { emitTransactionModules } from "./transaction.emitter.js";
15
- export function emitClientTree(contract, entrypoint, style) {
15
+ export function emitClientTree(contract, entrypoint, style, regenerate = AVCLIENT_REGENERATE) {
16
16
  const names = deriveEmittedNames(contract);
17
17
  const derivation = emitDerivationModules();
18
18
  const generated = [
@@ -24,7 +24,7 @@ export function emitClientTree(contract, entrypoint, style) {
24
24
  emitMetadataModule(contract.protocol, entrypoint),
25
25
  emitScalarCodecModule(style),
26
26
  emitDecimalModule(),
27
- emitErrorsModule(),
27
+ emitErrorsModule(regenerate),
28
28
  emitTransportModule(style),
29
29
  emitNamedTypesModule(contract, names, style),
30
30
  ...(contract.transactions === "interactive"
@@ -44,15 +44,22 @@ export function emitClientTree(contract, entrypoint, style) {
44
44
  .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
45
45
  .map((file) => ({
46
46
  path: file.path,
47
- bytes: encoder.encode(withGeneratedBanner(file.source)),
47
+ bytes: encoder.encode(withGeneratedBanner(file.source, regenerate)),
48
48
  }));
49
49
  return { tree, style, warnings: names.warnings };
50
50
  }
51
51
  function clientEntrySource(style) {
52
- const specifier = moduleSpecifierWriter(style);
53
- const from = (module) => specifier(`./${GENERATED_DIRECTORY}/${module.replace(/\.d\.ts$|\.ts$/, "")}`);
54
- return (`export { default } from ${from("client.ts")};\n` +
55
- `export {\n\tAvClient,\n\ttype AvClientOptions,\n\ttype AvCallOptions,\n} from ${from("client.ts")};\n` +
52
+ return (`export { default } from ${generatedModuleSpecifier(style, "client.ts")};\n` +
53
+ clientEntryReExports(style));
54
+ }
55
+ function generatedModuleSpecifier(style, module) {
56
+ return moduleSpecifierWriter(style)(`./${GENERATED_DIRECTORY}/${module.replace(/\.d\.ts$|\.ts$/, "")}`);
57
+ }
58
+ export function clientEntryReExports(style, clientClass = "value") {
59
+ const from = (module) => generatedModuleSpecifier(style, module);
60
+ return ((clientClass === "value"
61
+ ? `export {\n\tAvClient,\n\ttype AvClientOptions,\n\ttype AvCallOptions,\n} from ${from("client.ts")};\n`
62
+ : `export type { AvClient, AvClientOptions, AvCallOptions } from ${from("client.ts")};\n`) +
56
63
  `export type { AvFetch } from ${from("runtime/transport.ts")};\n` +
57
64
  `export type { AvOperation } from ${from("derivation/transactions/deferred-operation.d.ts")};\n` +
58
65
  `export type * from ${from("types.d.ts")};\n` +
@@ -1,10 +1,11 @@
1
1
  import type { AvContractClient } from "@aventara/core";
2
- import type { ClientEntrypoint } from "../config/client-config.interface.js";
2
+ import type { EmittedEntrypoint } from "../config/client-config.interface.js";
3
+ import { type RegenerateCommand } from "./banner.emitter.js";
3
4
  import type { EmittedModule } from "./emitted-tree.interface.js";
4
5
  import { type ClientModuleStyle } from "./module-style.interface.js";
5
6
  export declare function emitDecimalModule(): EmittedModule;
6
- export declare function emitMetadataModule(protocol: AvContractClient["protocol"], entrypoint: ClientEntrypoint): EmittedModule;
7
+ export declare function emitMetadataModule(protocol: AvContractClient["protocol"], entrypoint: EmittedEntrypoint): EmittedModule;
7
8
  export declare const FRAMEWORK_ERROR_CLASS_NAMES: readonly string[];
8
9
  export declare function errorsModuleExports(): readonly string[];
9
- export declare function emitErrorsModule(): EmittedModule;
10
+ export declare function emitErrorsModule(regenerate?: RegenerateCommand): EmittedModule;
10
11
  export declare function emitTransportModule(style: ClientModuleStyle): EmittedModule;
@@ -1,6 +1,7 @@
1
1
  import { FRAMEWORK_ERROR_CLASS_OF, OPERATION_CODES, VALIDATION_CODES, } from "@aventara/core/internal";
2
2
  import { AvProtocol } from "@aventara/core/protocol";
3
3
  import { entrypointHref } from "../config/config.resolver.js";
4
+ import { AVCLIENT_REGENERATE, } from "./banner.emitter.js";
4
5
  import { moduleSpecifierWriter, } from "./module-style.interface.js";
5
6
  import { wireGrammarLiteral } from "./scalar.codec.js";
6
7
  export function emitDecimalModule() {
@@ -51,10 +52,13 @@ export function emitMetadataModule(protocol, entrypoint) {
51
52
  `export const DEFAULT_ENTRYPOINT = ${JSON.stringify(entrypointHref(entrypoint))};\n`,
52
53
  };
53
54
  }
55
+ function contractMismatchSummary(regenerate) {
56
+ return `This client was generated against a ClientContract other than the one the server serves. Regenerate it with ${regenerate}.`;
57
+ }
54
58
  const FRAMEWORK_ERROR_CLASSES = {
55
59
  AvAuthError: "The caller is not authenticated, or is not permitted to run the operation.",
56
60
  AvConflictError: "The operation conflicted with stored state, and nothing was written. A2008 fails the same way if sent again; A2013 and A2014 may succeed if sent again, and no retry is automatic.",
57
- AvContractMismatchError: "This client was generated against a ClientContract other than the one the server serves. Regenerate it with `avclient generate`.",
61
+ AvContractMismatchError: contractMismatchSummary(AVCLIENT_REGENERATE),
58
62
  AvInternalError: "The server failed. Nothing in its cause is actionable; the diagnostics are in the server's logs.",
59
63
  AvNotFoundError: "A strict unique target does not exist.",
60
64
  AvProtocolError: "The request was not one the server could route or interpret: an unknown Resource or operation, a protocol version it does not speak, or a body, media type, size or method it refuses.",
@@ -152,8 +156,12 @@ export class AvTransportError extends Error {
152
156
  }
153
157
  }
154
158
  `;
155
- export function emitErrorsModule() {
156
- const classes = Object.entries(FRAMEWORK_ERROR_CLASSES).map(([name, summary]) => {
159
+ export function emitErrorsModule(regenerate = AVCLIENT_REGENERATE) {
160
+ const summaries = {
161
+ ...FRAMEWORK_ERROR_CLASSES,
162
+ AvContractMismatchError: contractMismatchSummary(regenerate),
163
+ };
164
+ const classes = Object.entries(summaries).map(([name, summary]) => {
157
165
  const codes = ERROR_CODES.filter((code) => FRAMEWORK_ERROR_CLASS_OF[code] === name);
158
166
  return (`\n/** ${summary} Thrown for ${codes.join(", ")}. */\n` +
159
167
  `export class ${name} extends AvError<${codes.map((code) => JSON.stringify(code)).join(" | ")}> {\n` +
@@ -0,0 +1,11 @@
1
+ /** The tagged form a dehydrated value takes: `{ [tagKey]: <tag>, [valueKey]: <wire form> }`. */
2
+ export declare const HYDRATION_FORMAT: {
3
+ /** The key a tag is under. */
4
+ readonly tagKey: "$av";
5
+ /** The key the tagged value's wire form is under. */
6
+ readonly valueKey: "v";
7
+ /** The tag of a plain object that holds `tagKey` as data. */
8
+ readonly objectTag: "object";
9
+ /** The scalars a value is tagged as: those whose runtime values JSON does not carry. */
10
+ readonly scalarTags: readonly ["bigint", "bytes", "datetime", "decimal"];
11
+ };
@@ -0,0 +1,6 @@
1
+ export const HYDRATION_FORMAT = {
2
+ tagKey: "$av",
3
+ valueKey: "v",
4
+ objectTag: "object",
5
+ scalarTags: ["bigint", "bytes", "datetime", "decimal"],
6
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `@aventara/client/internal` is not public API.
3
+ *
4
+ * It holds what Aventara's own packages need from the client generator and an
5
+ * application does not. It carries no stability promise: any export here may
6
+ * change or disappear in any release, including a patch release.
7
+ */
8
+ export type { ClientEntrypoint, EmittedEntrypoint, SameOriginEntrypoint, } from "./config/client-config.interface.js";
9
+ export { AvClientConfigError, sameOriginEntrypoint, } from "./config/config.resolver.js";
10
+ export { resolveModuleStyle } from "./config/module-style.resolver.js";
11
+ export type { ContractAcceptance } from "./contract/contract.acceptance.js";
12
+ export { acceptClientContract } from "./contract/contract.acceptance.js";
13
+ export type { RegenerateCommand } from "./emit/banner.emitter.js";
14
+ export { carriesGeneratedOwnership } from "./emit/banner.emitter.js";
15
+ export type { ClientClassExport } from "./emit/client-tree.emitter.js";
16
+ export { clientEntryReExports, emitClientTree, } from "./emit/client-tree.emitter.js";
17
+ export type { ClientEmission, EmittedFile, EmittedTree, } from "./emit/emitted-tree.interface.js";
18
+ export { GENERATED_DIRECTORY } from "./emit/emitted-tree.interface.js";
19
+ export type { ClientModuleStyle } from "./emit/module-style.interface.js";
20
+ export { importSpecifier } from "./emit/module-style.interface.js";
21
+ export { AvGeneratedNameError } from "./emit/name.deriver.js";
22
+ export { HYDRATION_FORMAT } from "./hydration/hydration-tag.format.js";
@@ -0,0 +1,9 @@
1
+ export { AvClientConfigError, sameOriginEntrypoint, } from "./config/config.resolver.js";
2
+ export { resolveModuleStyle } from "./config/module-style.resolver.js";
3
+ export { acceptClientContract } from "./contract/contract.acceptance.js";
4
+ export { carriesGeneratedOwnership } from "./emit/banner.emitter.js";
5
+ export { clientEntryReExports, emitClientTree, } from "./emit/client-tree.emitter.js";
6
+ export { GENERATED_DIRECTORY } from "./emit/emitted-tree.interface.js";
7
+ export { importSpecifier } from "./emit/module-style.interface.js";
8
+ export { AvGeneratedNameError } from "./emit/name.deriver.js";
9
+ export { HYDRATION_FORMAT } from "./hydration/hydration-tag.format.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/client",
3
- "version": "1.0.0-rc.4",
3
+ "version": "1.0.0-rc.9",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Development-time generator for Aventara typed remote clients.",
6
6
  "type": "module",
@@ -16,6 +16,10 @@
16
16
  ".": {
17
17
  "types": "./dist/index.d.ts",
18
18
  "import": "./dist/index.js"
19
+ },
20
+ "./internal": {
21
+ "types": "./dist/internal.d.ts",
22
+ "import": "./dist/internal.js"
19
23
  }
20
24
  },
21
25
  "files": [
@@ -24,7 +28,7 @@
24
28
  "CHANGELOG.md"
25
29
  ],
26
30
  "dependencies": {
27
- "@aventara/core": "1.0.0-rc.4",
31
+ "@aventara/core": "1.0.0-rc.9",
28
32
  "c12": "3.3.4",
29
33
  "get-tsconfig": "4.10.0"
30
34
  },
@@ -40,7 +44,7 @@
40
44
  "access": "public"
41
45
  },
42
46
  "devDependencies": {
43
- "@aventara/testing": "1.0.0-rc.4",
47
+ "@aventara/testing": "1.0.0-rc.9",
44
48
  "@types/node": "24.10.1",
45
49
  "typescript": "^5.9.2"
46
50
  },