@aventara/build 1.0.0-rc.13 → 1.0.0-rc.15
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 +46 -0
- package/README.md +4 -3
- package/dist/cli.js +28 -5
- package/dist/client/client-file.js +31 -10
- package/dist/computed/computed-fields-file.js +8 -3
- package/dist/contracts/prebuilt-contracts.compiler.js +1 -0
- package/dist/facts/config-facts-file.js +50 -0
- package/dist/project/generated-files.resolver.js +1 -0
- package/dist/resources/resources-file.js +18 -4
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @aventara/build
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- aab6d72: The client `avclient generate` writes now passes a default `biome check` as written, on Biome 2.4.0 and later (Biome's default rules; an opt-in rule or assist such as `useSortedKeys` is not covered): every generated file opens with `biome-ignore-all` lines for formatting, lint and import order (`organizeImports`), core's copied declarations have a blank line after their imports, the copied field-check evaluator carries no line suppression that the file-wide one makes unused, and each file ends without a final line break, which Biome 2.4.0 to 2.4.10 reported on every file under `biome-ignore-all format`. `aventara-build`'s own files pass too, in NestJS and Next.js projects: `resources.generated.ts`, `computed-fields.generated.ts` and `contracts.generated.ts` now open with the same suppressions and end without a final line break, and `--client`'s `src/aventara/client.ts` and `src/aventara/universal.ts` are written the same way. The discovery artifact `aventara-prisma7-generate` writes passes too. A file an earlier version wrote is reported stale by `aventara-build --check` and replaced by the next run. A client `avclient generate` wrote before is replaced on its next run. The client skill now says the generated files need no exclusion under Biome's default rules, and how to skip them in another linter or formatter.
|
|
8
|
+
- bf98da7: A pipeline stage's types now know the root's field definitions. `aventara-build` writes `src/generated/aventara/config-facts.generated.ts` after it compiles the Contracts, and the discovery artifact puts it on the model's type:
|
|
9
|
+
|
|
10
|
+
- A Json field the root shapes reads as its shape in a stage's `ctx` calls, in `tx` and in compute's read-only `ctx`: `ctx.Product.find.many({ select: ["name"] })` types `name` as `{ ar: string; en: string }`. A read gives the shape's own type, the same as `framework.application`'s.
|
|
11
|
+
- A `VIRTUAL` field the root adds is in a variant's typed `ctx.$args` (`ctx.$args.data.tenderedMinor`), and a `ctx` create must write a non-null added input it can create.
|
|
12
|
+
- A variant's hooks and interceptors are told resolved arguments: their `ctx.$args` holds no `$ref` (an `AvTransactionReference`), since references are resolved before they run. Guards, pipes and filters still may see one.
|
|
13
|
+
- A write takes a shaped Json value read-only, deep, on every surface (`framework.application`, `ctx`, the generated client too), so a frozen value, such as a stage's `ctx.$args`, fits as well as a mutable one.
|
|
14
|
+
- A compute stage on a shaped Json field returns the shape's type. A compute stage on an added field returns the type its generated fact states, read from the model instead of the configuration. Without the generated file, its return is not typed.
|
|
15
|
+
|
|
16
|
+
The server warns at startup (`AVENTARA_STALE_GENERATED_TYPES`) when the configuration's definitions differ from the ones the generated types were written with, naming each field. It is the same one warning as for stale computed fields: one stale build gives one warning, with a paragraph for each part that is stale, including a facts file it cannot read. `aventara-build --check` also fails on a stale file. Run `aventara:prepare` after you change a `definition`.
|
|
17
|
+
|
|
18
|
+
The discovery artifact's format is now 5: regenerate it with `aventara:prepare` (an artifact of format 4 is refused, as before). The Json Shape type emitter the client generator uses now lives in `@aventara/core`; the generated clients are unchanged. The CLI's agent skill says this in `SKILL.md` and `reference/stages.md`.
|
|
19
|
+
|
|
20
|
+
- `aventara-build` no longer logs `unchanged:` for `computed-fields.generated.ts` when it keeps that file because the configuration does not type-check: it logs `kept (configuration does not type-check): <file>`, after the type errors, since the kept file may be an earlier version's. The exit code is unchanged (0), so the `postinstall` that runs `aventara:prepare` still completes; fix the errors, then run `aventara:prepare` again.
|
|
21
|
+
- Updated dependencies [aab6d72]
|
|
22
|
+
- Updated dependencies [bf98da7]
|
|
23
|
+
- Updated dependencies [f8cd860]
|
|
24
|
+
- Updated dependencies [8a64af2]
|
|
25
|
+
- Updated dependencies [548881c]
|
|
26
|
+
- Updated dependencies [62fa91f]
|
|
27
|
+
- @aventara/client@1.0.0-rc.15
|
|
28
|
+
- @aventara/core@1.0.0-rc.15
|
|
29
|
+
|
|
30
|
+
## 1.0.0-rc.14
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- 0895a93: `avDehydrate` and `avHydrate` are typed: data handed from a Server Component to a Client Component as props keeps its type, with no cast.
|
|
35
|
+
|
|
36
|
+
- `avDehydrate(data)` (`@aventara/next`) returns an `AvDehydrated<T>` for data of type `T`, and `avHydrate(value)` (`@/aventara/client`) of an `AvDehydrated<T>` returns the `T`, its `Date`, `bigint`, `Uint8Array` and `AvDecimal` values included. Type an SWR `fallback` prop `AvDehydrated<T>` and pass `avHydrate(fallback)` to `SWRConfig` as it is.
|
|
37
|
+
- `AvDehydrated<T>` is new, exported by `@aventara/next` and by the `src/aventara/client.ts` that `aventara-build --client` writes; the two are one type. It is opaque: a plain object, the data itself, or the dehydrated form of another type is a compile error where an `AvDehydrated<T>` is expected.
|
|
38
|
+
- A Resource or enum named `AvDehydrated` or `avHydrate` is now emitted under a renamed identifier (`AvDehydratedModel`, `avHydrateModel`), with a warning, as other names the generated client owns are. Before, `src/aventara/client.ts`'s own declaration hid a Resource type of that name, which could not be imported from it.
|
|
39
|
+
- Read data you dehydrate through `@/aventara/universal`: `@/aventara/server` types a decimal as core's `AvDecimal`, while `avHydrate` revives it as the browser client's.
|
|
40
|
+
- Pass `avHydrate(fallback)` to `SWRConfig`, never the dehydrated value itself: SWR's `fallback` is typed `any`, so the compiler cannot catch it.
|
|
41
|
+
- `avHydrate` still takes `unknown` and returns `unknown`; `avDehydrate` of an `unknown` value returns an `AvDehydrated<unknown>`. `serializeData: avDehydrate` and `deserializeData: avHydrate` in TanStack Query are written as before.
|
|
42
|
+
- The agent skills show the SWR props pattern typed, with no cast. The frontend skill's refresh line no longer names `npx`, and says a file you edited is kept with a warning naming it, and replaced with `--force`.
|
|
43
|
+
|
|
44
|
+
- Updated dependencies [1c5eaa3]
|
|
45
|
+
- Updated dependencies [0895a93]
|
|
46
|
+
- @aventara/core@1.0.0-rc.14
|
|
47
|
+
- @aventara/client@1.0.0-rc.14
|
|
48
|
+
|
|
3
49
|
## 1.0.0-rc.13
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ aventara-prisma7-generate … && aventara-build
|
|
|
17
17
|
|
|
18
18
|
## What it reads and writes
|
|
19
19
|
|
|
20
|
-
Run it from the project's root. It writes
|
|
20
|
+
Run it from the project's root. It writes four files, in this order, each from what the one before wrote, so a fresh
|
|
21
21
|
install is right on its first `aventara:prepare`. They go to the directory your configuration imports
|
|
22
22
|
`aventaraResources` from, beside the discovery artifact: `src/generated/aventara/` normally, `src/aventara/` when
|
|
23
23
|
`aventara init` found Prisma's client generated into `src/generated` (which `prisma generate` would empty). A
|
|
@@ -27,6 +27,7 @@ configuration that imports no tree keeps `src/generated/aventara/`.
|
|
|
27
27
|
|---|---|
|
|
28
28
|
| `resources.generated.ts` | `aventaraResources`: every Resource your `src/resources/**/*.resource.ts` files define, one `resources` tree per layer |
|
|
29
29
|
| `computed-fields.generated.ts` | `computedFields` (typed `AvComputedFields`): which fields the configuration's write compute fills, read off its type |
|
|
30
|
+
| `config-facts.generated.ts` | `configFacts`: the Json shapes and the fields the root's definitions add, read off the compiled Contracts; the discovery artifact puts them on the model's type, so a pipeline stage's `ctx` and `ctx.$args` know them |
|
|
30
31
|
| `contracts.generated.ts` | `aventaraContracts` (typed `AvPrebuiltContracts`): the compiled Contracts |
|
|
31
32
|
|
|
32
33
|
### The Resource tree
|
|
@@ -135,7 +136,7 @@ so no running server is needed:
|
|
|
135
136
|
|
|
136
137
|
| File | What it holds |
|
|
137
138
|
|---|---|
|
|
138
|
-
| `src/aventara/client.ts` | `avClient`, the typed client calling the app's own `api` (`/api`) from the browser, and `
|
|
139
|
+
| `src/aventara/client.ts` | `avClient`, the typed client calling the app's own `api` (`/api`) from the browser, `avHydrate` and its `AvDehydrated<T>` type; and every name `avclient generate`'s `AvClient.ts` exports besides its default: `AvClient`, the Resource types, the enums, `AvDecimal` and the error classes |
|
|
139
140
|
| `src/aventara/universal.ts` | `avClient` for both sides: in the browser it calls the app's own `api`, on the server it calls the app in process, through what `ready()` from `@aventara/next` registers in `src/instrumentation.ts`, with that request's state |
|
|
140
141
|
| `src/aventara/generated/` (the client's modules) | the same modules `avclient generate` writes for the same ClientContract, but with the app's own `api` as the entrypoint, and saying to regenerate them with `aventara-build --client` |
|
|
141
142
|
|
|
@@ -189,7 +190,7 @@ aventara-build --check
|
|
|
189
190
|
aventara-build --check --client
|
|
190
191
|
```
|
|
191
192
|
|
|
192
|
-
Writes nothing. It exits 0 when the
|
|
193
|
+
Writes nothing. It exits 0 when the four files on disk are the ones a build would write now, and 1 on the first that is
|
|
193
194
|
missing or stale, naming it (and, for the Contracts, the Resources whose configuration or model changed). With
|
|
194
195
|
`--client`, the browser client's files are checked too, after the Contracts: a missing or stale file, or one an earlier
|
|
195
196
|
ClientContract's client left, exits 1. Run it in CI before a deploy.
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { readConfigComputedFields } from "./computed/computed-fields.reader.js";
|
|
|
6
6
|
import { emitComputedFieldsModule } from "./computed/computed-fields-file.js";
|
|
7
7
|
import { checkContractsFile } from "./contracts/contracts-file.js";
|
|
8
8
|
import { compilePrebuiltContracts } from "./contracts/prebuilt-contracts.compiler.js";
|
|
9
|
+
import { emitConfigFactsModule, emitUnreadConfigFactsModule, } from "./facts/config-facts-file.js";
|
|
9
10
|
import { acquireBuildLock } from "./project/build.lock.js";
|
|
10
11
|
import { generatedFileBatch, readGeneratedFile, } from "./project/generated-file.js";
|
|
11
12
|
import { DEFAULT_GENERATED_FILES, resolveGeneratedFiles, } from "./project/generated-files.resolver.js";
|
|
@@ -18,7 +19,7 @@ import { emitResourcesModule } from "./resources/resources-file.js";
|
|
|
18
19
|
const USAGE = `aventara-build — compile this project's Aventara Contracts ahead of time.
|
|
19
20
|
|
|
20
21
|
Usage:
|
|
21
|
-
aventara-build write the
|
|
22
|
+
aventara-build write the four files below
|
|
22
23
|
aventara-build --client also write a Next.js app's browser client
|
|
23
24
|
aventara-build --check [--client] write nothing; exit 1 when one is missing or stale
|
|
24
25
|
|
|
@@ -29,10 +30,13 @@ Run it from the project's root. It writes, in this order:
|
|
|
29
30
|
${RESOURCES_GLOB} export, one \`resources\` tree per layer;
|
|
30
31
|
${DEFAULT_GENERATED_FILES.computedFields}
|
|
31
32
|
which fields the configuration computes, read off its type;
|
|
33
|
+
${DEFAULT_GENERATED_FILES.configFacts}
|
|
34
|
+
the Json shapes and the fields the configuration's definitions add,
|
|
35
|
+
read off the compiled Contracts, which pipeline stages' types read;
|
|
32
36
|
${DEFAULT_GENERATED_FILES.contracts}
|
|
33
37
|
the compiled Contracts, \`aventaraContracts\`.
|
|
34
38
|
|
|
35
|
-
All
|
|
39
|
+
All four go to the directory the configuration imports \`aventaraResources\`
|
|
36
40
|
from (src/aventara/ when \`aventara init\` found the ORM's client generated into
|
|
37
41
|
src/generated), else to ${PROJECT_LAYOUTS.nest.generated}/ (${PROJECT_LAYOUTS.next.generated}/
|
|
38
42
|
in a Next.js project).
|
|
@@ -188,6 +192,14 @@ export async function runBuild(argv, projectDirectory, output) {
|
|
|
188
192
|
else {
|
|
189
193
|
noteWrite(wrote(batch.write(generated.resources, tree), generated.resources, counted(found.definitions.length)));
|
|
190
194
|
}
|
|
195
|
+
const hadFacts = readGeneratedFile(projectDirectory, generated.configFacts) !== undefined;
|
|
196
|
+
if (check && !hadFacts) {
|
|
197
|
+
output.stderr(refusal({ file: generated.configFacts, what: "is missing" }));
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
if (!check && !hadFacts) {
|
|
201
|
+
batch.write(generated.configFacts, emitUnreadConfigFactsModule(files.configFile, "the build has not compiled the Contracts yet"));
|
|
202
|
+
}
|
|
191
203
|
const hadComputed = readGeneratedFile(projectDirectory, generated.computedFields) !==
|
|
192
204
|
undefined;
|
|
193
205
|
if (check && !hadComputed) {
|
|
@@ -218,7 +230,7 @@ export async function runBuild(argv, projectDirectory, output) {
|
|
|
218
230
|
return 1;
|
|
219
231
|
}
|
|
220
232
|
if (kept) {
|
|
221
|
-
noteWrite(
|
|
233
|
+
noteWrite(`aventara-build: kept (configuration does not type-check): ${generated.computedFields}.`);
|
|
222
234
|
}
|
|
223
235
|
else if (check) {
|
|
224
236
|
const stale = checked(projectDirectory, generated.computedFields, computed, "the fields the configuration computes changed since it was written");
|
|
@@ -236,6 +248,17 @@ export async function runBuild(argv, projectDirectory, output) {
|
|
|
236
248
|
const project = await loadProjectModules(projectDirectory, files);
|
|
237
249
|
const compiled = await compilePrebuiltContracts(project);
|
|
238
250
|
const resources = Object.keys(compiled.prebuilt.fingerprints.resources).length;
|
|
251
|
+
const facts = emitConfigFactsModule(files.configFile, project.internal.configFactsOf(compiled.core), project.internal);
|
|
252
|
+
if (check) {
|
|
253
|
+
const stale = checked(projectDirectory, generated.configFacts, facts, "the Json shapes or the fields the configuration's definitions add changed since it was written");
|
|
254
|
+
if (stale !== undefined) {
|
|
255
|
+
output.stderr(refusal(stale));
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
noteWrite(wrote(batch.write(generated.configFacts, facts), generated.configFacts));
|
|
261
|
+
}
|
|
239
262
|
const browserClient = clientLayout === undefined
|
|
240
263
|
? undefined
|
|
241
264
|
: await emitBrowserClient(projectDirectory, clientLayout, {
|
|
@@ -268,8 +291,8 @@ export async function runBuild(argv, projectDirectory, output) {
|
|
|
268
291
|
return 1;
|
|
269
292
|
}
|
|
270
293
|
note(browserClient === undefined
|
|
271
|
-
? `aventara-build: ${generated.resources}, ${generated.computedFields} and ${generated.contracts} are up to date.`
|
|
272
|
-
: `aventara-build: ${generated.resources}, ${generated.computedFields}, ${generated.contracts} and the browser client (${browserClient.files[0]?.file}, ${browserClient.files[1]?.file} and ${browserClient.tree}/) are up to date.`);
|
|
294
|
+
? `aventara-build: ${generated.resources}, ${generated.computedFields}, ${generated.configFacts} and ${generated.contracts} are up to date.`
|
|
295
|
+
: `aventara-build: ${generated.resources}, ${generated.computedFields}, ${generated.configFacts}, ${generated.contracts} and the browser client (${browserClient.files[0]?.file}, ${browserClient.files[1]?.file} and ${browserClient.tree}/) are up to date.`);
|
|
273
296
|
return 0;
|
|
274
297
|
}
|
|
275
298
|
output.stderr(refusal({
|
|
@@ -8,6 +8,7 @@ export const REGENERATE = "`aventara-build --client` (the project's `aventara:pr
|
|
|
8
8
|
const ENTRY_BANNER = [
|
|
9
9
|
"// biome-ignore-all format: generated output",
|
|
10
10
|
"// biome-ignore-all lint: generated output",
|
|
11
|
+
"// biome-ignore-all assist/source/organizeImports: generated output",
|
|
11
12
|
"/* !!! Generated by @aventara/build. Do not edit. !!! */",
|
|
12
13
|
`/* Regenerate with ${REGENERATE}. */`,
|
|
13
14
|
].join("\n");
|
|
@@ -145,6 +146,9 @@ const HOOKS: Pick<AvClientOptions, "onCall" | "onSuccess" | "onError"> = {
|
|
|
145
146
|
onError: (call, error) => clientOptions()?.onError?.(call, error),
|
|
146
147
|
};
|
|
147
148
|
`;
|
|
149
|
+
function withoutFinalLineBreak(text) {
|
|
150
|
+
return text.replace(/\n+$/, "");
|
|
151
|
+
}
|
|
148
152
|
function optionsImport(options) {
|
|
149
153
|
return options === undefined
|
|
150
154
|
? ""
|
|
@@ -152,15 +156,15 @@ function optionsImport(options) {
|
|
|
152
156
|
}
|
|
153
157
|
function entrySource(style, accessVerbs, options) {
|
|
154
158
|
const from = (module) => JSON.stringify(importSpecifier(module, style));
|
|
155
|
-
const { tagKey, valueKey, objectTag, scalarTags } = HYDRATION_FORMAT;
|
|
159
|
+
const { tagKey, valueKey, objectTag, scalarTags, typeKey } = HYDRATION_FORMAT;
|
|
156
160
|
const tagged = [...scalarTags, objectTag]
|
|
157
161
|
.map((tag) => JSON.stringify(tag))
|
|
158
162
|
.join(" | ");
|
|
159
163
|
return `${ENTRY_BANNER}
|
|
160
164
|
|
|
161
|
-
import { AvClient${options === undefined ? "" : ", type AvClientOptions"} } from ${from(`./${GENERATED_DIRECTORY}/client`)};
|
|
165
|
+
${optionsImport(options)}import { AvClient${options === undefined ? "" : ", type AvClientOptions"} } from ${from(`./${GENERATED_DIRECTORY}/client`)};
|
|
162
166
|
import { type AvBuiltInScalar, decodeScalar } from ${from(`./${GENERATED_DIRECTORY}/runtime/codec`)};
|
|
163
|
-
|
|
167
|
+
|
|
164
168
|
${clientEntryReExports(style, "type", accessVerbs)}
|
|
165
169
|
/** What a call made on the server is refused with. */
|
|
166
170
|
const SERVER_CALL = ${JSON.stringify(SERVER_CALL_REFUSAL)};
|
|
@@ -221,17 +225,35 @@ function revivedMembers(record: Readonly<Record<string, unknown>>): Record<strin
|
|
|
221
225
|
return Object.fromEntries(Object.entries(record).map(([key, member]) => [key, avHydrate(member)]));
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Data of type \`T\` as \`avDehydrate\` (from \`@aventara/next\`) sends it from the
|
|
230
|
+
* server: opaque, read back as the \`T\` by \`avHydrate\`. Type a Client
|
|
231
|
+
* Component's prop with it to take server data with no cast. It is
|
|
232
|
+
* \`@aventara/next\`'s \`AvDehydrated\`, declared here again: its one member is a
|
|
233
|
+
* brand of the type only, never of the value.
|
|
234
|
+
*/
|
|
235
|
+
export type AvDehydrated<T> = {
|
|
236
|
+
/** The type the value was dehydrated from: the type's brand, no member of the value. */
|
|
237
|
+
readonly ${JSON.stringify(typeKey)}: T;
|
|
238
|
+
};
|
|
239
|
+
|
|
224
240
|
/**
|
|
225
241
|
* Revives data that \`avDehydrate\` (from \`@aventara/next\`) made safe to pass
|
|
226
242
|
* from the server to the browser: each tagged Date, bigint, Uint8Array and
|
|
227
243
|
* decimal becomes the value this client reads, a decimal this client's own
|
|
228
244
|
* AvDecimal.
|
|
229
245
|
* Arrays and plain objects are copied with their members revived; any other
|
|
230
|
-
* value is returned as it is.
|
|
231
|
-
*
|
|
246
|
+
* value is returned as it is. An \`AvDehydrated<T>\` revives as the \`T\`: call it
|
|
247
|
+
* on a prop of that type, such as an SWR \`fallback\`, and pass on what it
|
|
248
|
+
* returns, never the dehydrated value itself (SWR's \`fallback\` is typed
|
|
249
|
+
* \`any\`, so the type cannot catch it). Give it to TanStack Query as
|
|
250
|
+
* \`deserializeData\`.
|
|
232
251
|
*
|
|
233
252
|
* @throws TypeError when a tagged value is not one avDehydrate writes.
|
|
234
253
|
*/
|
|
254
|
+
export function avHydrate<T>(dehydrated: AvDehydrated<T>): T;
|
|
255
|
+
/** For a caller that cannot name the data's type: TanStack Query's \`deserializeData\`. */
|
|
256
|
+
export function avHydrate(dehydrated: unknown): unknown;
|
|
235
257
|
export function avHydrate(dehydrated: unknown): unknown {
|
|
236
258
|
if (Array.isArray(dehydrated)) {
|
|
237
259
|
return dehydrated.map((member: unknown) => avHydrate(member));
|
|
@@ -266,10 +288,9 @@ function universalSource(style, accessVerbs, options) {
|
|
|
266
288
|
|
|
267
289
|
import { io } from ${JSON.stringify(style.importFileExtension === "" ? "next/cache" : "next/cache.js")};
|
|
268
290
|
import { cache } from "react";
|
|
269
|
-
import { AvClient${options === undefined ? "" : ", type AvClientOptions"} } from ${from(`./${GENERATED_DIRECTORY}/client`)};
|
|
270
|
-
import type { AvFetch } from ${from(`./${GENERATED_DIRECTORY}/runtime/transport`)};
|
|
271
|
-
${optionsImport(options)}
|
|
291
|
+
${optionsImport(options)}import { AvClient${options === undefined ? "" : ", type AvClientOptions"} } from ${from(`./${GENERATED_DIRECTORY}/client`)};
|
|
272
292
|
import { avLazy as lazyCall } from ${from(`./${GENERATED_DIRECTORY}/runtime/operation-set`)};
|
|
293
|
+
import type { AvFetch } from ${from(`./${GENERATED_DIRECTORY}/runtime/transport`)};
|
|
273
294
|
|
|
274
295
|
/** The key the server's in-process path is registered under, on globalThis, by ready() from @aventara/next. */
|
|
275
296
|
const SERVER_PATH = Symbol.for(${JSON.stringify(UNIVERSAL_PATH_KEY)});
|
|
@@ -456,11 +477,11 @@ export async function emitBrowserClient(projectDirectory, layout, source) {
|
|
|
456
477
|
files: [
|
|
457
478
|
{
|
|
458
479
|
file: `${layout.folder}/${layout.entry}`,
|
|
459
|
-
text: entrySource(style, accessVerbs, options),
|
|
480
|
+
text: withoutFinalLineBreak(entrySource(style, accessVerbs, options)),
|
|
460
481
|
},
|
|
461
482
|
{
|
|
462
483
|
file: `${layout.folder}/${layout.universal}`,
|
|
463
|
-
text: universalSource(style, accessVerbs, options),
|
|
484
|
+
text: withoutFinalLineBreak(universalSource(style, accessVerbs, options)),
|
|
464
485
|
},
|
|
465
486
|
...tree,
|
|
466
487
|
],
|
|
@@ -9,7 +9,12 @@ function literal(computedFields) {
|
|
|
9
9
|
return JSON.stringify(ordered, null, "\t");
|
|
10
10
|
}
|
|
11
11
|
export function emitComputedFieldsModule(configFile, reading) {
|
|
12
|
-
const header =
|
|
12
|
+
const header = [
|
|
13
|
+
"// biome-ignore-all format: generated output",
|
|
14
|
+
"// biome-ignore-all lint: generated output",
|
|
15
|
+
"// biome-ignore-all assist/source/organizeImports: generated output",
|
|
16
|
+
`// Generated by aventara-build from the type of ${configFile}. Do not edit: regenerate it (aventara:prepare).`,
|
|
17
|
+
].join("\n");
|
|
13
18
|
const type = [
|
|
14
19
|
"",
|
|
15
20
|
"// What an adapter's discovery artifact carries on its model's type for the stages that run before compute.",
|
|
@@ -26,7 +31,7 @@ export function emitComputedFieldsModule(configFile, reading) {
|
|
|
26
31
|
"// What an adapter's discovery artifact carries on its model's type for the stages that run before compute:",
|
|
27
32
|
"// nothing, so they may leave any create field out.",
|
|
28
33
|
"export type AvComputedFields = undefined;",
|
|
29
|
-
].join("\n")}
|
|
34
|
+
].join("\n")}`;
|
|
30
35
|
}
|
|
31
36
|
return `${[
|
|
32
37
|
header,
|
|
@@ -36,5 +41,5 @@ export function emitComputedFieldsModule(configFile, reading) {
|
|
|
36
41
|
"// Resource. The framework compares it with the configuration at startup and warns when they differ.",
|
|
37
42
|
`export const computedFields = ${literal(reading.computedFields)} as const satisfies AvConfigComputedFields;`,
|
|
38
43
|
...type,
|
|
39
|
-
].join("\n")}
|
|
44
|
+
].join("\n")}`;
|
|
40
45
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const key = (name) => JSON.stringify(name);
|
|
2
|
+
function shapeTypes(json, jsonSchemaType) {
|
|
3
|
+
const resources = Object.keys(json);
|
|
4
|
+
if (resources.length === 0) {
|
|
5
|
+
return "Record<never, never>";
|
|
6
|
+
}
|
|
7
|
+
const blocks = resources.map((resource) => {
|
|
8
|
+
const fields = json[resource] ?? {};
|
|
9
|
+
const members = Object.keys(fields).map((field) => `\t\t\treadonly ${key(field)}: ${jsonSchemaType(fields[field])};`);
|
|
10
|
+
return `\t\treadonly ${key(resource)}: {\n${members.join("\n")}\n\t\t};`;
|
|
11
|
+
});
|
|
12
|
+
return `{\n${blocks.join("\n")}\n\t}`;
|
|
13
|
+
}
|
|
14
|
+
const headerOf = (configFile) => [
|
|
15
|
+
"// biome-ignore-all format: generated output",
|
|
16
|
+
"// biome-ignore-all lint: generated output",
|
|
17
|
+
"// biome-ignore-all assist/source/organizeImports: generated output",
|
|
18
|
+
`// Generated by aventara-build from the Contracts compiled from ${configFile}. Do not edit: regenerate it (aventara:prepare).`,
|
|
19
|
+
].join("\n");
|
|
20
|
+
export function emitUnreadConfigFactsModule(configFile, reason) {
|
|
21
|
+
return `${[
|
|
22
|
+
headerOf(configFile),
|
|
23
|
+
"",
|
|
24
|
+
`// Not read: ${reason.replaceAll("\n", " ")}. Pipeline stages see the model's own fields:`,
|
|
25
|
+
"// Json fields unshaped, no field the configuration adds.",
|
|
26
|
+
"export const configFacts = null;",
|
|
27
|
+
"",
|
|
28
|
+
"// What an adapter's discovery artifact carries on its model's type for pipeline stages' ctx and arguments:",
|
|
29
|
+
"// nothing.",
|
|
30
|
+
"export type AvConfigFacts = undefined;",
|
|
31
|
+
].join("\n")}`;
|
|
32
|
+
}
|
|
33
|
+
export function emitConfigFactsModule(configFile, facts, writers) {
|
|
34
|
+
const literal = JSON.stringify(JSON.parse(writers.canonicalJson(facts)), null, "\t");
|
|
35
|
+
return `${[
|
|
36
|
+
headerOf(configFile),
|
|
37
|
+
"",
|
|
38
|
+
"// The Json shapes and added fields the root's definitions state: per Resource, each shaped field's JSON Schema",
|
|
39
|
+
"// and each added field's Contract field. The framework compares it with the configuration at startup and warns",
|
|
40
|
+
"// when they differ.",
|
|
41
|
+
`export const configFacts = ${literal} as const;`,
|
|
42
|
+
"",
|
|
43
|
+
"// What an adapter's discovery artifact carries on its model's type for pipeline stages' ctx and arguments:",
|
|
44
|
+
"// each added field, and each shaped Json field's value type.",
|
|
45
|
+
"export type AvConfigFacts = {",
|
|
46
|
+
'\treadonly added: (typeof configFacts)["added"];',
|
|
47
|
+
`\treadonly json: ${shapeTypes(facts.json, writers.jsonSchemaType)};`,
|
|
48
|
+
"};",
|
|
49
|
+
].join("\n")}`;
|
|
50
|
+
}
|
|
@@ -8,6 +8,7 @@ function filesIn(directory) {
|
|
|
8
8
|
return {
|
|
9
9
|
resources: `${directory}/${TREE_MODULE}.ts`,
|
|
10
10
|
computedFields: `${directory}/computed-fields.generated.ts`,
|
|
11
|
+
configFacts: `${directory}/config-facts.generated.ts`,
|
|
11
12
|
contracts: `${directory}/contracts.generated.ts`,
|
|
12
13
|
};
|
|
13
14
|
}
|
|
@@ -18,13 +18,24 @@ function localNames(definitions) {
|
|
|
18
18
|
export function emitResourcesModule(projectDirectory, resourcesFile, definitions, suffix) {
|
|
19
19
|
const file = path.join(projectDirectory, resourcesFile);
|
|
20
20
|
const names = localNames(definitions);
|
|
21
|
-
const imports = definitions
|
|
21
|
+
const imports = definitions
|
|
22
|
+
.map((definition, index) => {
|
|
22
23
|
const local = names[index];
|
|
23
24
|
const imported = definition.exportName === local
|
|
24
25
|
? local
|
|
25
26
|
: `${definition.exportName} as ${local}`;
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const specifier = relativeSpecifier(file, path.join(projectDirectory, definition.file), suffix);
|
|
28
|
+
return {
|
|
29
|
+
specifier,
|
|
30
|
+
line: `import { ${imported} } from ${JSON.stringify(specifier)};`,
|
|
31
|
+
};
|
|
32
|
+
})
|
|
33
|
+
.sort((left, right) => left.specifier < right.specifier
|
|
34
|
+
? -1
|
|
35
|
+
: left.specifier > right.specifier
|
|
36
|
+
? 1
|
|
37
|
+
: 0)
|
|
38
|
+
.map(({ line }) => line);
|
|
28
39
|
const layers = DEFINITION_LAYERS.flatMap((layer) => {
|
|
29
40
|
const spreads = definitions.flatMap((definition, index) => definition.layers.includes(layer)
|
|
30
41
|
? [`...${names[index]}.${layer}`]
|
|
@@ -36,6 +47,9 @@ export function emitResourcesModule(projectDirectory, resourcesFile, definitions
|
|
|
36
47
|
: [];
|
|
37
48
|
});
|
|
38
49
|
return `${[
|
|
50
|
+
"// biome-ignore-all format: generated output",
|
|
51
|
+
"// biome-ignore-all lint: generated output",
|
|
52
|
+
"// biome-ignore-all assist/source/organizeImports: generated output",
|
|
39
53
|
`// Generated by aventara-build from ${RESOURCES_GLOB}. Do not edit: regenerate it (aventara:prepare).`,
|
|
40
54
|
...imports,
|
|
41
55
|
"",
|
|
@@ -44,5 +58,5 @@ export function emitResourcesModule(projectDirectory, resourcesFile, definitions
|
|
|
44
58
|
"export const aventaraResources = {",
|
|
45
59
|
...layers,
|
|
46
60
|
"} as const;",
|
|
47
|
-
].join("\n")}
|
|
61
|
+
].join("\n")}`;
|
|
48
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/build",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.15",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Compiles an Aventara application's Contracts at build time, for serverless and edge cold starts.",
|
|
6
6
|
"type": "module",
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
"CHANGELOG.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aventara/client": "1.0.0-rc.
|
|
19
|
+
"@aventara/client": "1.0.0-rc.15",
|
|
20
20
|
"jiti": "2.7.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@aventara/core": "1.0.0-rc.
|
|
23
|
+
"@aventara/core": "1.0.0-rc.15",
|
|
24
24
|
"typescript": ">=5.7.0 <7"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@aventara/core": "1.0.0-rc.
|
|
30
|
+
"@aventara/core": "1.0.0-rc.15",
|
|
31
31
|
"@types/node": "24.10.1",
|
|
32
32
|
"typescript": "^5.9.2"
|
|
33
33
|
},
|