@aventara/build 1.0.0-rc.10 → 1.0.0-rc.11
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 +43 -0
- package/dist/client/client-file.js +94 -20
- package/dist/project/project-modules.loader.js +2 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @aventara/build
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6197c08: `@/aventara/universal`'s calls are the generated client's own calls again. The entry no longer wraps each one in
|
|
8
|
+
`io().then(call)`, so its own `signal`, `avClient.all`'s `signal` and strict abort, and `avLazy` all reach it, in the
|
|
9
|
+
browser and on the server. Next's `io()` is now awaited before the request is sent.
|
|
10
|
+
|
|
11
|
+
In the browser, the calls made in one tick are still grouped into one `/_all`. On the server, none is grouped: each
|
|
12
|
+
call has a client of its own with grouping off, and it is sent from the async context of the code that made it. Before,
|
|
13
|
+
one queue served every request. Calls from two concurrent requests, or a call inside `unstable_cache` and one outside
|
|
14
|
+
it, could share a flush and run with the first call's headers and `state`.
|
|
15
|
+
|
|
16
|
+
`avLazy` no longer keeps one answer for every request on a server:
|
|
17
|
+
|
|
18
|
+
- The generated client's `avLazy` keeps its answer in a page. On a server (no `location`) it keeps none, and each await
|
|
19
|
+
makes the call again.
|
|
20
|
+
- `@aventara/core`'s `avLazy` keeps none: each await makes the call again.
|
|
21
|
+
- `@/aventara/universal` now exports its own `avLazy`, which keeps one answer per request inside a React render
|
|
22
|
+
(React's `cache`, which the entry now imports) and none where React keeps no request.
|
|
23
|
+
|
|
24
|
+
Before, an `avLazy` made at module scope handed the first request's answer to every request after it.
|
|
25
|
+
|
|
26
|
+
`ready()`'s in-process path now splits a cached read's GET URL into its path and its `?q=&v=` query, with an empty
|
|
27
|
+
body, so the read reaches its GET route instead of being answered as absent.
|
|
28
|
+
|
|
29
|
+
- 0b2f077: `aventara-build` reads `server-only` as an empty module, as a Next.js server bundle does. A `src/aventara/config.ts`
|
|
30
|
+
that imports it, or imports a module that does, now builds; before, the package threw outside the `react-server`
|
|
31
|
+
condition and the build refused the configuration ("could not be run: This module cannot be imported from a Client
|
|
32
|
+
Component module").
|
|
33
|
+
- Updated dependencies [6197c08]
|
|
34
|
+
- Updated dependencies [5ac10f7]
|
|
35
|
+
- Updated dependencies [e0e89c1]
|
|
36
|
+
- Updated dependencies [186bc29]
|
|
37
|
+
- Updated dependencies [33b5f83]
|
|
38
|
+
- Updated dependencies [6d8b589]
|
|
39
|
+
- Updated dependencies [11ece49]
|
|
40
|
+
- Updated dependencies [92fd093]
|
|
41
|
+
- Updated dependencies [e5e98e6]
|
|
42
|
+
- Updated dependencies [a05d970]
|
|
43
|
+
- @aventara/client@1.0.0-rc.11
|
|
44
|
+
- @aventara/core@1.0.0-rc.11
|
|
45
|
+
|
|
3
46
|
## 1.0.0-rc.10
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
|
@@ -12,7 +12,7 @@ const ENTRY_BANNER = [
|
|
|
12
12
|
].join("\n");
|
|
13
13
|
export const SERVER_CALL_REFUSAL = "avClient from @/aventara/client runs in the browser only, and this call ran on the server, where a Client Component renders first: on the server, call avClient from @/aventara/server, or from @/aventara/universal for data the browser then hydrates.";
|
|
14
14
|
const BROWSER_GUARD_SOURCE = `/** \`call\`, rejected instead of run when \`refusal()\` names why. */
|
|
15
|
-
function guarded(call:
|
|
15
|
+
function guarded(call: Call): Call {
|
|
16
16
|
return (...args) => {
|
|
17
17
|
const refused = refusal();
|
|
18
18
|
return refused === undefined ? call(...args) : Promise.reject(new Error(refused));
|
|
@@ -33,40 +33,55 @@ function ioFirst(): Promise<void> {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* \`call\`, made at once when it may run (its request awaits \`io()\`); when
|
|
38
|
+
* \`refusal()\` names why it may not, made once \`io()\` lets it if it may run
|
|
39
|
+
* then, and rejected instead otherwise.
|
|
40
|
+
*/
|
|
41
|
+
function guarded(call: Call): Call {
|
|
38
42
|
return (...args) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
refusal() === undefined
|
|
44
|
+
? call(...args)
|
|
45
|
+
: ioFirst().then(() => {
|
|
46
|
+
const refused = refusal();
|
|
47
|
+
return refused === undefined ? call(...args) : Promise.reject(new Error(refused));
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
`;
|
|
45
|
-
const GUARD_SOURCE = `/**
|
|
46
|
-
|
|
51
|
+
const GUARD_SOURCE = `/**
|
|
52
|
+
* A frozen copy of one Resource's families, or one family's variants: each call
|
|
53
|
+
* guarded, and made on what \`at\` reads of the \`current()\` client.
|
|
54
|
+
*/
|
|
55
|
+
function guardedTree(tree: object, at: (client: AvClient) => unknown): object {
|
|
47
56
|
const copy: Record<string, unknown> = Object.create(Object.getPrototypeOf(tree) as object | null);
|
|
48
57
|
for (const [key, value] of Object.entries(tree)) {
|
|
49
|
-
|
|
58
|
+
const member = (client: AvClient): unknown => (at(client) as Readonly<Record<string, unknown>>)[key];
|
|
59
|
+
copy[key] =
|
|
60
|
+
typeof value === "function"
|
|
61
|
+
? guarded((...args) => (member(current()) as Call)(...args))
|
|
62
|
+
: guardedTree(value as object, member);
|
|
50
63
|
}
|
|
51
64
|
return Object.freeze(copy);
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
68
|
+
* A client shaped as \`template\`, every call that sends a request guarded and
|
|
69
|
+
* made on the \`current()\` client; \`tx\` builds deferred operations and sends
|
|
70
|
+
* nothing, so the template's is kept as it is.
|
|
57
71
|
*/
|
|
58
|
-
function guardedClient(
|
|
72
|
+
function guardedClient(template: AvClient): AvClient {
|
|
59
73
|
const copy: AvClient = Object.create(AvClient.prototype) as AvClient;
|
|
60
|
-
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(
|
|
74
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(template))) {
|
|
61
75
|
const value: unknown = descriptor.value;
|
|
76
|
+
const member = (client: AvClient): unknown => (client as unknown as Readonly<Record<string, unknown>>)[key];
|
|
62
77
|
Object.defineProperty(copy, key, {
|
|
63
78
|
...descriptor,
|
|
64
79
|
value:
|
|
65
80
|
key === "tx"
|
|
66
81
|
? value
|
|
67
82
|
: typeof value === "function"
|
|
68
|
-
? guarded(
|
|
69
|
-
: guardedTree(value as object),
|
|
83
|
+
? guarded((...args) => (member(current()) as Call)(...args))
|
|
84
|
+
: guardedTree(value as object, member),
|
|
70
85
|
});
|
|
71
86
|
}
|
|
72
87
|
return copy;
|
|
@@ -100,6 +115,16 @@ function refusal(): string | undefined {
|
|
|
100
115
|
return onServer() ? SERVER_CALL : undefined;
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
type Call = (...args: unknown[]) => unknown;
|
|
119
|
+
|
|
120
|
+
/** The one client every call is made on. */
|
|
121
|
+
const CLIENT = new AvClient();
|
|
122
|
+
|
|
123
|
+
/** The client a call made here and now is made on: the one client. */
|
|
124
|
+
function current(): AvClient {
|
|
125
|
+
return CLIENT;
|
|
126
|
+
}
|
|
127
|
+
|
|
103
128
|
${BROWSER_GUARD_SOURCE}
|
|
104
129
|
${GUARD_SOURCE}
|
|
105
130
|
/**
|
|
@@ -107,7 +132,7 @@ ${GUARD_SOURCE}
|
|
|
107
132
|
* on the server (while a Client Component renders there) rejects instead: on the
|
|
108
133
|
* server, import \`avClient\` from \`@/aventara/server\` or \`@/aventara/universal\`.
|
|
109
134
|
*/
|
|
110
|
-
export const avClient: AvClient = guardedClient(
|
|
135
|
+
export const avClient: AvClient = guardedClient(CLIENT);
|
|
111
136
|
|
|
112
137
|
/** The key a dehydrated value's tag is under: \`{ ${JSON.stringify(tagKey)}: <scalar>, ${JSON.stringify(valueKey)}: <its wire form> }\`. */
|
|
113
138
|
const TAG = ${JSON.stringify(tagKey)};
|
|
@@ -177,9 +202,12 @@ function universalSource(style, accessVerbs) {
|
|
|
177
202
|
return `${ENTRY_BANNER}
|
|
178
203
|
|
|
179
204
|
import { io } from ${JSON.stringify(style.importFileExtension === "" ? "next/cache" : "next/cache.js")};
|
|
205
|
+
import { cache } from "react";
|
|
180
206
|
import { AvClient } from ${from(`./${GENERATED_DIRECTORY}/client`)};
|
|
181
207
|
import type { AvFetch } from ${from(`./${GENERATED_DIRECTORY}/runtime/transport`)};
|
|
182
208
|
|
|
209
|
+
import { avLazy as lazyCall } from ${from(`./${GENERATED_DIRECTORY}/runtime/operation-set`)};
|
|
210
|
+
|
|
183
211
|
/** The key the server's in-process path is registered under, on globalThis, by ready() from @aventara/next. */
|
|
184
212
|
const SERVER_PATH = Symbol.for(${JSON.stringify(UNIVERSAL_PATH_KEY)});
|
|
185
213
|
|
|
@@ -202,8 +230,9 @@ function refusal(): string | undefined {
|
|
|
202
230
|
return onServer() && serverPath() === undefined ? UNREGISTERED : undefined;
|
|
203
231
|
}
|
|
204
232
|
|
|
205
|
-
/**
|
|
206
|
-
function universalFetch(...request: unknown[]): unknown {
|
|
233
|
+
/** Once Next's \`io()\` lets it: in the browser, the platform's fetch; on the server, the in-process path. */
|
|
234
|
+
async function universalFetch(...request: unknown[]): Promise<unknown> {
|
|
235
|
+
await ioFirst();
|
|
207
236
|
const reach = onServer() ? serverPath() : (globalThis as { readonly fetch?: AvFetch }).fetch;
|
|
208
237
|
if (reach === undefined) {
|
|
209
238
|
throw new TypeError(onServer() ? UNREGISTERED : "No fetch is available here.");
|
|
@@ -211,6 +240,51 @@ function universalFetch(...request: unknown[]): unknown {
|
|
|
211
240
|
return (reach as (...request: unknown[]) => unknown)(...request);
|
|
212
241
|
}
|
|
213
242
|
|
|
243
|
+
type Call = (...args: unknown[]) => unknown;
|
|
244
|
+
|
|
245
|
+
/** The browser's one client, whose calls are grouped, and the shape \`avClient\` copies. */
|
|
246
|
+
const BROWSER = new AvClient({ fetch: universalFetch as AvFetch });
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The client a call made here and now is made on: the browser's one; on the
|
|
250
|
+
* server, a client of the call's own with grouping off, so the call is sent
|
|
251
|
+
* alone, from the async context it was made in (the request's, or a scope
|
|
252
|
+
* nested in it), and shares no queue with any other call.
|
|
253
|
+
*/
|
|
254
|
+
function current(): AvClient {
|
|
255
|
+
return onServer() ? new AvClient({ fetch: universalFetch as AvFetch, grouping: false }) : BROWSER;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** On the server, the calls each avLazy made in the request being rendered (React's \`cache\`). */
|
|
259
|
+
const requestCalls = cache((): WeakMap<object, Promise<unknown>> => new WeakMap());
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Describe a call now, make it later: the tree's avLazy, which avClient.all
|
|
263
|
+
* accepts on either side. In the browser it makes the call once and keeps its
|
|
264
|
+
* answer. On the server it keeps one answer per request: inside a React
|
|
265
|
+
* request scope (a render), each request makes the call once, in its own
|
|
266
|
+
* context; where React keeps no request (a route handler, a Server Action),
|
|
267
|
+
* it keeps nothing, and each await makes the call again.
|
|
268
|
+
*
|
|
269
|
+
* @example const posts = avLazy(() => avClient.Post.find.many({}));
|
|
270
|
+
*/
|
|
271
|
+
export function avLazy<T>(run: () => PromiseLike<T>): Promise<T> {
|
|
272
|
+
const key = {};
|
|
273
|
+
return lazyCall(() => {
|
|
274
|
+
if (!onServer()) {
|
|
275
|
+
return run();
|
|
276
|
+
}
|
|
277
|
+
const calls = requestCalls();
|
|
278
|
+
const made = calls.get(key);
|
|
279
|
+
if (made !== undefined) {
|
|
280
|
+
return made as Promise<T>;
|
|
281
|
+
}
|
|
282
|
+
const call = Promise.resolve(run());
|
|
283
|
+
calls.set(key, call);
|
|
284
|
+
return call;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
214
288
|
${UNIVERSAL_GUARD_SOURCE}
|
|
215
289
|
${GUARD_SOURCE}
|
|
216
290
|
/**
|
|
@@ -221,7 +295,7 @@ ${GUARD_SOURCE}
|
|
|
221
295
|
* prefetched on the server hydrates in the browser: pass it through
|
|
222
296
|
* \`avDehydrate\` (\`@aventara/next\`) and \`avHydrate\` (\`@/aventara/client\`).
|
|
223
297
|
*/
|
|
224
|
-
export const avClient: AvClient = ${accessVerbs ? "withServerAccess(" : ""}guardedClient(
|
|
298
|
+
export const avClient: AvClient = ${accessVerbs ? "withServerAccess(" : ""}guardedClient(BROWSER)${accessVerbs ? ")" : ""};
|
|
225
299
|
${accessVerbs ? SERVER_ACCESS_SOURCE : ""}`;
|
|
226
300
|
}
|
|
227
301
|
const UNIVERSAL_ACCESS_KEY = "aventara.next.universal.access";
|
|
@@ -5,6 +5,7 @@ import { createJiti } from "jiti";
|
|
|
5
5
|
import { AvBuildError } from "../build.error.js";
|
|
6
6
|
const EXTENSIONS = [".js", ".ts", ".mjs", ".cjs", ".mts", ".cts"];
|
|
7
7
|
const EXTENSIONS_AS_LISTED = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
8
|
+
const SERVER_MARKERS = { "server-only": {} };
|
|
8
9
|
const CONFIG_EXPORTS = ["aventaraConfig", "default"];
|
|
9
10
|
function projectFile(projectDirectory, module, layout) {
|
|
10
11
|
const present = EXTENSIONS.map((extension) => `${module}${extension}`).filter((file) => statSync(path.join(projectDirectory, file), {
|
|
@@ -107,6 +108,7 @@ export async function loadProjectModules(projectDirectory, { configFile, adapter
|
|
|
107
108
|
fsCache: false,
|
|
108
109
|
interopDefault: false,
|
|
109
110
|
nativeModules: ["@aventara/core"],
|
|
111
|
+
virtualModules: SERVER_MARKERS,
|
|
110
112
|
});
|
|
111
113
|
const project = await projectCore(jiti);
|
|
112
114
|
const config = configOf(configFile, await evaluate(jiti, projectDirectory, configFile));
|
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.11",
|
|
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.11",
|
|
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.11",
|
|
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.11",
|
|
31
31
|
"@types/node": "24.10.1",
|
|
32
32
|
"typescript": "^5.9.2"
|
|
33
33
|
},
|