astro 7.2.2 → 7.2.4
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/dist/actions/handler.d.ts +6 -12
- package/dist/actions/handler.js +30 -40
- package/dist/actions/load.d.ts +12 -0
- package/dist/actions/load.js +55 -0
- package/dist/actions/runtime/entrypoints/server.js +5 -4
- package/dist/actions/runtime/server.js +8 -6
- package/dist/assets/fonts/providers/index.d.ts +1 -1
- package/dist/assets/fonts/providers/index.js +2 -12
- package/dist/assets/utils/vendor/image-size/types/heif.js +8 -3
- package/dist/assets/utils/vendor/image-size/types/icns.js +10 -2
- package/dist/assets/utils/vendor/image-size/types/jp2.js +2 -2
- package/dist/assets/utils/vendor/image-size/types/jxl.js +5 -2
- package/dist/assets/utils/vendor/image-size/types/utils.d.ts +1 -1
- package/dist/assets/utils/vendor/image-size/types/utils.js +20 -6
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/container/environment.d.ts +20 -0
- package/dist/container/environment.js +106 -0
- package/dist/container/index.js +73 -37
- package/dist/content/content-layer.js +3 -3
- package/dist/content/data-store-writer.d.ts +7 -10
- package/dist/content/data-store-writer.js +62 -27
- package/dist/content/data-store.d.ts +20 -3
- package/dist/content/data-store.js +28 -7
- package/dist/content/mutable-data-store.js +15 -10
- package/dist/content/runtime.d.ts +3 -2
- package/dist/content/runtime.js +43 -26
- package/dist/core/app/app.d.ts +0 -2
- package/dist/core/app/app.js +0 -7
- package/dist/core/app/base.d.ts +32 -20
- package/dist/core/app/base.js +79 -73
- package/dist/core/app/dev-facade.d.ts +27 -0
- package/dist/core/app/dev-facade.js +51 -0
- package/dist/core/app/entrypoints/index.d.ts +0 -1
- package/dist/core/app/entrypoints/index.js +0 -2
- package/dist/core/app/entrypoints/virtual/dev.js +24 -19
- package/dist/core/app/node.js +13 -14
- package/dist/core/app/prepare-response.d.ts +1 -1
- package/dist/core/app/validate-headers.js +7 -3
- package/dist/core/build/app.d.ts +31 -6
- package/dist/core/build/app.js +31 -13
- package/dist/core/build/environment.d.ts +25 -0
- package/dist/core/build/environment.js +157 -0
- package/dist/core/cache/handler.d.ts +13 -7
- package/dist/core/cache/handler.js +55 -51
- package/dist/core/cache/provider.d.ts +4 -0
- package/dist/core/cache/provider.js +15 -0
- package/dist/core/constants.d.ts +0 -11
- package/dist/core/constants.js +1 -5
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/environment/dev-nonrunnable.d.ts +6 -0
- package/dist/core/environment/dev-nonrunnable.js +170 -0
- package/dist/core/environment/index.d.ts +90 -0
- package/dist/core/environment/index.js +12 -0
- package/dist/core/environment/production.d.ts +6 -0
- package/dist/core/environment/production.js +117 -0
- package/dist/core/errors/build-handler.d.ts +5 -10
- package/dist/core/errors/build-handler.js +11 -17
- package/dist/core/errors/default-handler.d.ts +4 -9
- package/dist/core/errors/default-handler.js +92 -107
- package/dist/core/errors/dev-handler.d.ts +7 -11
- package/dist/core/errors/dev-handler.js +71 -69
- package/dist/core/errors/errors-data.d.ts +18 -0
- package/dist/core/errors/errors-data.js +7 -0
- package/dist/core/errors/handler.d.ts +22 -3
- package/dist/core/errors/handler.js +25 -0
- package/dist/core/fetch/default-handler.d.ts +15 -8
- package/dist/core/fetch/default-handler.js +12 -34
- package/dist/core/fetch/features.d.ts +23 -0
- package/dist/core/fetch/features.js +27 -0
- package/dist/core/fetch/fetch-state.d.ts +52 -7
- package/dist/core/fetch/fetch-state.js +99 -92
- package/dist/core/fetch/index.d.ts +9 -2
- package/dist/core/fetch/index.js +18 -79
- package/dist/core/fetch/vite-plugin.js +2 -1
- package/dist/core/i18n/domain.js +4 -11
- package/dist/core/i18n/handler.d.ts +28 -13
- package/dist/core/i18n/handler.js +100 -100
- package/dist/core/logger/manifest-logger.d.ts +31 -0
- package/dist/core/logger/manifest-logger.js +37 -0
- package/dist/core/manifest/ambient-source.d.ts +2 -0
- package/dist/core/manifest/ambient-source.js +4 -0
- package/dist/core/manifest/ambient.d.ts +16 -0
- package/dist/core/manifest/ambient.js +22 -0
- package/dist/core/manifest/derived.d.ts +3 -0
- package/dist/core/manifest/derived.js +10 -0
- package/dist/core/manifest/memo.d.ts +31 -0
- package/dist/core/manifest/memo.js +47 -0
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/astro-middleware.d.ts +25 -35
- package/dist/core/middleware/astro-middleware.js +61 -81
- package/dist/core/middleware/load.d.ts +19 -0
- package/dist/core/middleware/load.js +36 -0
- package/dist/core/middleware/sequence.js +15 -7
- package/dist/core/pages/handler.d.ts +18 -25
- package/dist/core/pages/handler.js +68 -84
- package/dist/core/redirects/render.js +3 -8
- package/dist/core/render/index.d.ts +0 -1
- package/dist/core/render/index.js +0 -2
- package/dist/core/render/route-cache.d.ts +6 -1
- package/dist/core/render/route-cache.js +11 -1
- package/dist/core/rewrites/handler.d.ts +4 -6
- package/dist/core/rewrites/handler.js +19 -20
- package/dist/core/routing/default.d.ts +2 -0
- package/dist/core/routing/default.js +7 -1
- package/dist/core/routing/dev.d.ts +1 -3
- package/dist/core/routing/dev.js +12 -5
- package/dist/core/routing/handler.d.ts +6 -16
- package/dist/core/routing/handler.js +126 -164
- package/dist/core/routing/helpers.d.ts +7 -1
- package/dist/core/routing/helpers.js +21 -0
- package/dist/core/routing/match-request.d.ts +10 -0
- package/dist/core/routing/match-request.js +46 -0
- package/dist/core/routing/route-table.d.ts +34 -0
- package/dist/core/routing/route-table.js +41 -0
- package/dist/core/routing/trailing-slash-handler.d.ts +4 -14
- package/dist/core/routing/trailing-slash-handler.js +39 -50
- package/dist/core/server-islands/mappings.d.ts +6 -0
- package/dist/core/server-islands/mappings.js +12 -0
- package/dist/core/session/driver.d.ts +4 -0
- package/dist/core/session/driver.js +14 -0
- package/dist/core/session/handler.d.ts +1 -1
- package/dist/core/session/handler.js +8 -8
- package/dist/core/session/provider-disabled.js +2 -2
- package/dist/entrypoints/prerender.js +5 -1
- package/dist/i18n/middleware.d.ts +4 -4
- package/dist/i18n/middleware.js +3 -3
- package/dist/manifest/serialized.d.ts +1 -0
- package/dist/manifest/serialized.js +9 -2
- package/dist/runtime/prerender/static-paths.d.ts +14 -2
- package/dist/runtime/prerender/static-paths.js +4 -3
- package/dist/types/public/config.d.ts +433 -241
- package/dist/vite-plugin-app/createAstroServerApp.js +22 -18
- package/dist/vite-plugin-app/environment.d.ts +18 -0
- package/dist/vite-plugin-app/environment.js +210 -0
- package/dist/vite-plugin-app/handle-request.d.ts +30 -0
- package/dist/vite-plugin-app/handle-request.js +129 -0
- package/dist/vite-plugin-app/index.d.ts +1 -1
- package/dist/vite-plugin-app/index.js +1 -1
- package/package.json +14 -7
- package/src/core/README.md +37 -0
- package/src/core/manifest/ambient-source.ts +9 -0
- package/src/types/README.md +5 -0
- package/dist/container/pipeline.d.ts +0 -14
- package/dist/container/pipeline.js +0 -78
- package/dist/core/app/dev/app.d.ts +0 -31
- package/dist/core/app/dev/app.js +0 -80
- package/dist/core/app/dev/pipeline.d.ts +0 -17
- package/dist/core/app/dev/pipeline.js +0 -133
- package/dist/core/app/pipeline.d.ts +0 -14
- package/dist/core/app/pipeline.js +0 -123
- package/dist/core/base-pipeline.d.ts +0 -183
- package/dist/core/base-pipeline.js +0 -293
- package/dist/core/build/pipeline.d.ts +0 -38
- package/dist/core/build/pipeline.js +0 -202
- package/dist/vite-plugin-app/app.d.ts +0 -62
- package/dist/vite-plugin-app/app.js +0 -237
- package/dist/vite-plugin-app/pipeline.d.ts +0 -25
- package/dist/vite-plugin-app/pipeline.js +0 -189
package/dist/container/index.js
CHANGED
|
@@ -2,17 +2,21 @@ import { getDefaultClientDirectives } from "../core/client-directive/default.js"
|
|
|
2
2
|
import { ASTRO_CONFIG_DEFAULTS } from "../core/config/schemas/defaults.js";
|
|
3
3
|
import { createKey } from "../core/encryption.js";
|
|
4
4
|
import { FetchState } from "../core/fetch/fetch-state.js";
|
|
5
|
-
import {
|
|
5
|
+
import { handleMiddleware } from "../core/middleware/astro-middleware.js";
|
|
6
6
|
import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
|
|
7
|
-
import {
|
|
7
|
+
import { handlePages } from "../core/pages/handler.js";
|
|
8
8
|
import { removeLeadingForwardSlash } from "../core/path.js";
|
|
9
9
|
import { getParts } from "../core/routing/parts.js";
|
|
10
10
|
import { getPattern } from "../core/routing/pattern.js";
|
|
11
11
|
import { validateSegment } from "../core/routing/segment.js";
|
|
12
12
|
import { SlotString } from "../runtime/server/render/slot.js";
|
|
13
|
-
import {
|
|
13
|
+
import { createContainerEnvironment } from "./environment.js";
|
|
14
|
+
import { setEnvironment } from "../core/environment/index.js";
|
|
14
15
|
import { createConsoleLogger } from "../core/logger/impls/console.js";
|
|
15
|
-
|
|
16
|
+
import { setLogger } from "../core/logger/manifest-logger.js";
|
|
17
|
+
import { peekMiddleware } from "../core/middleware/load.js";
|
|
18
|
+
import { getRouteTable } from "../core/routing/route-table.js";
|
|
19
|
+
function createManifest(manifest, renderers, middleware, site) {
|
|
16
20
|
function middlewareInstance() {
|
|
17
21
|
return {
|
|
18
22
|
onRequest: middleware ?? NOOP_MIDDLEWARE_FN
|
|
@@ -50,6 +54,7 @@ function createManifest(manifest, renderers, middleware) {
|
|
|
50
54
|
componentMetadata: manifest?.componentMetadata ?? /* @__PURE__ */ new Map(),
|
|
51
55
|
inlinedScripts: manifest?.inlinedScripts ?? /* @__PURE__ */ new Map(),
|
|
52
56
|
i18n: manifest?.i18n,
|
|
57
|
+
site: site ?? manifest?.site,
|
|
53
58
|
checkOrigin: false,
|
|
54
59
|
allowedDomains: manifest?.allowedDomains ?? [],
|
|
55
60
|
actionBodySizeLimit: 1024 * 1024,
|
|
@@ -69,9 +74,17 @@ function createManifest(manifest, renderers, middleware) {
|
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
class experimental_AstroContainer {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The container's fabricated manifest — the source of truth all the
|
|
79
|
+
* functional-core accessors key off. The container never touches the
|
|
80
|
+
* ambient manifest, so multiple containers in one process stay isolated.
|
|
81
|
+
*/
|
|
82
|
+
#manifest;
|
|
83
|
+
/**
|
|
84
|
+
* The route → module interner, shared between the environment record
|
|
85
|
+
* (lookups) and the `insertRoute` writes below.
|
|
86
|
+
*/
|
|
87
|
+
#interner;
|
|
75
88
|
/**
|
|
76
89
|
* Internally used to check if the container was created with a manifest.
|
|
77
90
|
* @private
|
|
@@ -81,25 +94,33 @@ class experimental_AstroContainer {
|
|
|
81
94
|
streaming = false,
|
|
82
95
|
manifest,
|
|
83
96
|
renderers,
|
|
84
|
-
resolve
|
|
97
|
+
resolve,
|
|
98
|
+
site
|
|
85
99
|
}) {
|
|
86
|
-
const ssrManifest = createManifest(manifest, renderers);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (this.#withManifest) {
|
|
94
|
-
return this.#containerResolve(specifier, ssrManifest);
|
|
95
|
-
} else if (resolve) {
|
|
96
|
-
return resolve(specifier);
|
|
97
|
-
}
|
|
98
|
-
return specifier;
|
|
100
|
+
const ssrManifest = createManifest(manifest, renderers, void 0, site);
|
|
101
|
+
const containerRenderers = renderers ?? manifest?.renderers ?? [];
|
|
102
|
+
const containerResolve = async (specifier) => {
|
|
103
|
+
if (this.#withManifest) {
|
|
104
|
+
return this.#containerResolve(specifier, ssrManifest);
|
|
105
|
+
} else if (resolve) {
|
|
106
|
+
return resolve(specifier);
|
|
99
107
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
return specifier;
|
|
109
|
+
};
|
|
110
|
+
const interner = /* @__PURE__ */ new WeakMap();
|
|
111
|
+
setLogger(ssrManifest, createConsoleLogger({ level: "error" }));
|
|
112
|
+
setEnvironment(
|
|
113
|
+
ssrManifest,
|
|
114
|
+
createContainerEnvironment({
|
|
115
|
+
interner,
|
|
116
|
+
resolve: containerResolve,
|
|
117
|
+
renderers: containerRenderers,
|
|
118
|
+
streaming
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
getRouteTable(ssrManifest);
|
|
122
|
+
this.#manifest = ssrManifest;
|
|
123
|
+
this.#interner = interner;
|
|
103
124
|
}
|
|
104
125
|
async #containerResolve(specifier, manifest) {
|
|
105
126
|
const found = manifest.entryModules[specifier];
|
|
@@ -114,12 +135,13 @@ class experimental_AstroContainer {
|
|
|
114
135
|
* @param {AstroContainerOptions=} containerOptions
|
|
115
136
|
*/
|
|
116
137
|
static async create(containerOptions = {}) {
|
|
117
|
-
const { streaming = false, manifest, renderers = [], resolve } = containerOptions;
|
|
138
|
+
const { streaming = false, manifest, renderers = [], resolve, astroConfig } = containerOptions;
|
|
118
139
|
return new experimental_AstroContainer({
|
|
119
140
|
streaming,
|
|
120
141
|
manifest,
|
|
121
142
|
renderers,
|
|
122
|
-
resolve
|
|
143
|
+
resolve,
|
|
144
|
+
site: astroConfig?.site ?? manifest?.site
|
|
123
145
|
});
|
|
124
146
|
}
|
|
125
147
|
/**
|
|
@@ -153,12 +175,12 @@ class experimental_AstroContainer {
|
|
|
153
175
|
);
|
|
154
176
|
}
|
|
155
177
|
if (isNamedRenderer(renderer)) {
|
|
156
|
-
this.#
|
|
178
|
+
this.#manifest.renderers.push({
|
|
157
179
|
name: renderer.name,
|
|
158
180
|
ssr: renderer
|
|
159
181
|
});
|
|
160
182
|
} else if ("name" in options) {
|
|
161
|
-
this.#
|
|
183
|
+
this.#manifest.renderers.push({
|
|
162
184
|
name: options.name,
|
|
163
185
|
ssr: renderer
|
|
164
186
|
});
|
|
@@ -193,15 +215,15 @@ class experimental_AstroContainer {
|
|
|
193
215
|
*/
|
|
194
216
|
addClientRenderer(options) {
|
|
195
217
|
const { entrypoint, name } = options;
|
|
196
|
-
const rendererIndex = this.#
|
|
218
|
+
const rendererIndex = this.#manifest.renderers.findIndex((r) => r.name === name);
|
|
197
219
|
if (rendererIndex === -1) {
|
|
198
220
|
throw new Error(
|
|
199
221
|
"You tried to add the " + name + " client renderer, but its server renderer wasn't added. You must add the server renderer first. Use the `addServerRenderer` function."
|
|
200
222
|
);
|
|
201
223
|
}
|
|
202
|
-
const renderer = this.#
|
|
224
|
+
const renderer = this.#manifest.renderers[rendererIndex];
|
|
203
225
|
renderer.clientEntrypoint = entrypoint;
|
|
204
|
-
this.#
|
|
226
|
+
this.#manifest.renderers[rendererIndex] = renderer;
|
|
205
227
|
}
|
|
206
228
|
// NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
|
|
207
229
|
// @ts-expect-error @ematipico: I plan to use it for a possible integration that could help people
|
|
@@ -212,6 +234,20 @@ class experimental_AstroContainer {
|
|
|
212
234
|
container.#withManifest = true;
|
|
213
235
|
return container;
|
|
214
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Associates a runtime-inserted route with its component module in the
|
|
239
|
+
* interner shared with the container environment record. Snapshots the
|
|
240
|
+
* already-resolved middleware synchronously via `peekMiddleware` —
|
|
241
|
+
* `undefined` when `getMiddleware` has not settled yet.
|
|
242
|
+
*/
|
|
243
|
+
#internRoute(routeData, componentInstance) {
|
|
244
|
+
this.#interner.set(routeData, {
|
|
245
|
+
page() {
|
|
246
|
+
return Promise.resolve(componentInstance);
|
|
247
|
+
},
|
|
248
|
+
onRequest: peekMiddleware(this.#manifest)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
215
251
|
#insertRoute({
|
|
216
252
|
path,
|
|
217
253
|
componentInstance,
|
|
@@ -220,14 +256,14 @@ class experimental_AstroContainer {
|
|
|
220
256
|
}) {
|
|
221
257
|
const pathUrl = new URL(path, "https://example.com");
|
|
222
258
|
const routeData = this.#createRoute(pathUrl, params, type);
|
|
223
|
-
this.#
|
|
259
|
+
this.#manifest.routes.push({
|
|
224
260
|
routeData,
|
|
225
261
|
file: "",
|
|
226
262
|
links: [],
|
|
227
263
|
styles: [],
|
|
228
264
|
scripts: []
|
|
229
265
|
});
|
|
230
|
-
this.#
|
|
266
|
+
this.#internRoute(routeData, componentInstance);
|
|
231
267
|
return routeData;
|
|
232
268
|
}
|
|
233
269
|
/**
|
|
@@ -286,7 +322,7 @@ class experimental_AstroContainer {
|
|
|
286
322
|
params: options.params,
|
|
287
323
|
type: routeType
|
|
288
324
|
});
|
|
289
|
-
const state = new FetchState(this.#
|
|
325
|
+
const state = new FetchState(this.#manifest, request);
|
|
290
326
|
state.routeData = routeData;
|
|
291
327
|
state.pathname = url.pathname;
|
|
292
328
|
state.clientAddress = "";
|
|
@@ -300,7 +336,7 @@ class experimental_AstroContainer {
|
|
|
300
336
|
if (options.props) {
|
|
301
337
|
state.initialProps = options.props;
|
|
302
338
|
}
|
|
303
|
-
return
|
|
339
|
+
return handleMiddleware(state, handlePages);
|
|
304
340
|
}
|
|
305
341
|
/**
|
|
306
342
|
* It stores an Astro **page** route. The first argument, `route`, gets associated to the `component`.
|
|
@@ -315,7 +351,7 @@ class experimental_AstroContainer {
|
|
|
315
351
|
insertPageRoute(route, component, params) {
|
|
316
352
|
const url = new URL(route, "https://example.com/");
|
|
317
353
|
const routeData = this.#createRoute(url, params ?? {}, "page");
|
|
318
|
-
this.#
|
|
354
|
+
this.#manifest.routes.push({
|
|
319
355
|
routeData,
|
|
320
356
|
file: "",
|
|
321
357
|
links: [],
|
|
@@ -323,7 +359,7 @@ class experimental_AstroContainer {
|
|
|
323
359
|
scripts: []
|
|
324
360
|
});
|
|
325
361
|
const componentInstance = this.#wrapComponent(component, params);
|
|
326
|
-
this.#
|
|
362
|
+
this.#internRoute(routeData, componentInstance);
|
|
327
363
|
}
|
|
328
364
|
#createRoute(url, params, type) {
|
|
329
365
|
const segments = removeLeadingForwardSlash(url.pathname).split("/").filter(Boolean).map((s) => {
|
|
@@ -196,7 +196,7 @@ ${contentConfig.error.message}`
|
|
|
196
196
|
logger.info("Content config changed");
|
|
197
197
|
shouldClear = true;
|
|
198
198
|
}
|
|
199
|
-
if (previousAstroVersion && previousAstroVersion !== "7.2.
|
|
199
|
+
if (previousAstroVersion && previousAstroVersion !== "7.2.4") {
|
|
200
200
|
logger.info("Astro version changed");
|
|
201
201
|
shouldClear = true;
|
|
202
202
|
}
|
|
@@ -204,8 +204,8 @@ ${contentConfig.error.message}`
|
|
|
204
204
|
logger.info("Clearing content store");
|
|
205
205
|
this.#store.clearAll();
|
|
206
206
|
}
|
|
207
|
-
if ("7.2.
|
|
208
|
-
this.#store.metaStore().set("astro-version", "7.2.
|
|
207
|
+
if ("7.2.4") {
|
|
208
|
+
this.#store.metaStore().set("astro-version", "7.2.4");
|
|
209
209
|
}
|
|
210
210
|
if (currentConfigDigest) {
|
|
211
211
|
this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { type PathLike } from 'node:fs';
|
|
2
|
-
/**
|
|
3
|
-
* A chunked store manifest: each collection maps to the list of part file names
|
|
4
|
-
* whose contents concatenate back into that collection's serialized string.
|
|
5
|
-
*/
|
|
2
|
+
/** A chunked store manifest: each collection maps to its content-addressed part files. */
|
|
6
3
|
export type DataStoreManifest = Record<string, string[]>;
|
|
7
4
|
/**
|
|
8
5
|
* Persists the content collection data produced by the content layer. This is
|
|
@@ -26,9 +23,9 @@ export declare function serializeDataStore(collections: Map<string, Map<string,
|
|
|
26
23
|
* on UTF-16 code-unit boundaries (e.g. `String.prototype.slice`) can cut a
|
|
27
24
|
* surrogate pair in half; encoding a lone surrogate to UTF-8 substitutes U+FFFD,
|
|
28
25
|
* so concatenating the re-read parts would corrupt any astral-plane character
|
|
29
|
-
* (emoji, some CJK, etc.).
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* (emoji, some CJK, etc.). `getChunkEnd()` advances by Unicode code point, so
|
|
27
|
+
* `str.slice` is only ever called on code-point boundaries and the parts always
|
|
28
|
+
* rejoin to the exact original string.
|
|
32
29
|
*/
|
|
33
30
|
export declare function chunkString(str: string, maxBytes: number): string[];
|
|
34
31
|
/**
|
|
@@ -52,9 +49,9 @@ export declare class FileWriter implements DataStoreWriter {
|
|
|
52
49
|
* A {@link DataStoreWriter} that splits the store across many content-addressed
|
|
53
50
|
* files inside a directory, described by a manifest.
|
|
54
51
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
52
|
+
* Entries are serialized independently into parts no larger than a fixed byte
|
|
53
|
+
* size, so writing a collection does not require a collection-wide serialized
|
|
54
|
+
* string. Each part file is named by the xxhash of its contents, so unchanged
|
|
58
55
|
* parts keep the same name across builds and their writes are skipped, and two
|
|
59
56
|
* identical parts are naturally deduplicated. The manifest is written last as
|
|
60
57
|
* the commit point, and stale files are pruned afterwards. This is the inverse
|
|
@@ -15,23 +15,31 @@ function serializeDataStore(collections) {
|
|
|
15
15
|
return devalue.stringify(sortCollections(collections));
|
|
16
16
|
}
|
|
17
17
|
const ENCODER = new TextEncoder();
|
|
18
|
+
function getChunkEnd(str, startIndex, maxBytes) {
|
|
19
|
+
let index = startIndex;
|
|
20
|
+
let bytes = 0;
|
|
21
|
+
while (index < str.length) {
|
|
22
|
+
const codePoint = str.codePointAt(index);
|
|
23
|
+
const charLength = codePoint > 65535 ? 2 : 1;
|
|
24
|
+
const charBytes = ENCODER.encode(str.slice(index, index + charLength)).length;
|
|
25
|
+
if (bytes + charBytes > maxBytes && index > startIndex) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
index += charLength;
|
|
29
|
+
bytes += charBytes;
|
|
30
|
+
if (bytes >= maxBytes) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { endIndex: index, bytes };
|
|
35
|
+
}
|
|
18
36
|
function chunkString(str, maxBytes) {
|
|
19
37
|
const chunks = [];
|
|
20
38
|
let startIndex = 0;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (currentBytes + charBytes > maxBytes && index > startIndex) {
|
|
26
|
-
chunks.push(str.slice(startIndex, index));
|
|
27
|
-
startIndex = index;
|
|
28
|
-
currentBytes = 0;
|
|
29
|
-
}
|
|
30
|
-
index += char.length;
|
|
31
|
-
currentBytes += charBytes;
|
|
32
|
-
}
|
|
33
|
-
if (startIndex < str.length) {
|
|
34
|
-
chunks.push(str.slice(startIndex));
|
|
39
|
+
while (startIndex < str.length) {
|
|
40
|
+
const { endIndex } = getChunkEnd(str, startIndex, maxBytes);
|
|
41
|
+
chunks.push(str.slice(startIndex, endIndex));
|
|
42
|
+
startIndex = endIndex;
|
|
35
43
|
}
|
|
36
44
|
return chunks;
|
|
37
45
|
}
|
|
@@ -58,6 +66,7 @@ class ChunkedWriter {
|
|
|
58
66
|
#manifestFile;
|
|
59
67
|
#chunkSize;
|
|
60
68
|
#hasher;
|
|
69
|
+
#writtenFiles = /* @__PURE__ */ new Set();
|
|
61
70
|
constructor(dir, chunkSize) {
|
|
62
71
|
this.#dir = dir;
|
|
63
72
|
this.#manifestFile = new URL(`./${DATA_STORE_MANIFEST_FILE}`, dir);
|
|
@@ -67,23 +76,49 @@ class ChunkedWriter {
|
|
|
67
76
|
if (!this.#hasher) {
|
|
68
77
|
this.#hasher = await xxhash();
|
|
69
78
|
}
|
|
70
|
-
|
|
71
|
-
const writtenFiles = /* @__PURE__ */ new Set();
|
|
79
|
+
this.#writtenFiles = /* @__PURE__ */ new Set();
|
|
72
80
|
const manifest = {};
|
|
73
81
|
for (const [collectionName, entries] of sortCollections(collections)) {
|
|
74
|
-
|
|
75
|
-
const parts = [];
|
|
76
|
-
for (const part of chunkString(stringified, this.#chunkSize)) {
|
|
77
|
-
const fileName = `${h64ToString(part)}.txt`;
|
|
78
|
-
await writeFileAtomic(new URL(`./${fileName}`, this.#dir), part);
|
|
79
|
-
parts.push(fileName);
|
|
80
|
-
writtenFiles.add(fileName);
|
|
81
|
-
}
|
|
82
|
-
manifest[collectionName] = parts;
|
|
82
|
+
manifest[collectionName] = await this.#writeCollection(entries);
|
|
83
83
|
}
|
|
84
84
|
await writeFileAtomic(this.#manifestFile, JSON.stringify(manifest));
|
|
85
|
-
writtenFiles.add(DATA_STORE_MANIFEST_FILE);
|
|
86
|
-
emptyDir(this.#dir, writtenFiles);
|
|
85
|
+
this.#writtenFiles.add(DATA_STORE_MANIFEST_FILE);
|
|
86
|
+
emptyDir(this.#dir, this.#writtenFiles);
|
|
87
|
+
}
|
|
88
|
+
async #writeCollection(entries) {
|
|
89
|
+
const parts = [];
|
|
90
|
+
let chunk = "";
|
|
91
|
+
let chunkBytes = 0;
|
|
92
|
+
const writeChunk = async () => {
|
|
93
|
+
const fileName = `${this.#hasher.h64ToString(chunk)}.txt`;
|
|
94
|
+
await writeFileAtomic(new URL(`./${fileName}`, this.#dir), chunk);
|
|
95
|
+
parts.push(fileName);
|
|
96
|
+
this.#writtenFiles.add(fileName);
|
|
97
|
+
chunk = "";
|
|
98
|
+
chunkBytes = 0;
|
|
99
|
+
};
|
|
100
|
+
for (const [id, entry] of entries) {
|
|
101
|
+
const serialized = `${devalue.stringify([id, entry])}
|
|
102
|
+
`;
|
|
103
|
+
let startIndex = 0;
|
|
104
|
+
while (startIndex < serialized.length) {
|
|
105
|
+
const { endIndex, bytes } = getChunkEnd(
|
|
106
|
+
serialized,
|
|
107
|
+
startIndex,
|
|
108
|
+
this.#chunkSize - chunkBytes
|
|
109
|
+
);
|
|
110
|
+
chunk += serialized.slice(startIndex, endIndex);
|
|
111
|
+
chunkBytes += bytes;
|
|
112
|
+
startIndex = endIndex;
|
|
113
|
+
if (chunkBytes >= this.#chunkSize) {
|
|
114
|
+
await writeChunk();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (chunk) {
|
|
119
|
+
await writeChunk();
|
|
120
|
+
}
|
|
121
|
+
return parts;
|
|
87
122
|
}
|
|
88
123
|
}
|
|
89
124
|
export {
|
|
@@ -31,6 +31,24 @@ export interface DataEntry<TData extends Record<string, unknown> = Record<string
|
|
|
31
31
|
*/
|
|
32
32
|
deferredRender?: boolean;
|
|
33
33
|
assetImports?: Array<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Locations of image fields within `data`, recorded when the entry is stored.
|
|
36
|
+
* Each path is the sequence of keys from `data` to a field that holds an image
|
|
37
|
+
* src string. At read time these fields are resolved to `ImageMetadata` without
|
|
38
|
+
* traversing or cloning the rest of `data`, so sibling values that devalue can
|
|
39
|
+
* serialize but `structuredClone` cannot (e.g. class instances) are left
|
|
40
|
+
* untouched.
|
|
41
|
+
*/
|
|
42
|
+
imageImports?: (string | number)[][];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Rebuilds a collection from ordered parts containing newline-delimited
|
|
46
|
+
* `devalue.stringify([id, entry])` records.
|
|
47
|
+
*/
|
|
48
|
+
export declare class ChunkedCollectionParser {
|
|
49
|
+
#private;
|
|
50
|
+
add(part: string): void;
|
|
51
|
+
finish(): Map<string, any>;
|
|
34
52
|
}
|
|
35
53
|
/**
|
|
36
54
|
* A read-only data store for content collections. This is used to retrieve data from the content layer at runtime.
|
|
@@ -52,9 +70,8 @@ export declare class ImmutableDataStore {
|
|
|
52
70
|
*
|
|
53
71
|
* Each collection maps to a list of parts. A part is either a raw string
|
|
54
72
|
* (when the store is loaded from disk) or an ESM namespace from a virtual
|
|
55
|
-
* chunk import (`{ default: string }`, when emitted at runtime).
|
|
56
|
-
*
|
|
57
|
-
* serialized string, then parsed with devalue. This is the inverse of
|
|
73
|
+
* chunk import (`{ default: string }`, when emitted at runtime). Each part
|
|
74
|
+
* contains independently serialized entry records. This is the inverse of
|
|
58
75
|
* {@link import('./data-store-writer.js').ChunkedWriter} and stays free of
|
|
59
76
|
* Node built-ins so it can run at runtime.
|
|
60
77
|
*/
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import * as devalue from "devalue";
|
|
2
2
|
import { InMemorySource } from "./data-store-source.js";
|
|
3
|
+
class ChunkedCollectionParser {
|
|
4
|
+
#entries = /* @__PURE__ */ new Map();
|
|
5
|
+
#remainder = "";
|
|
6
|
+
add(part) {
|
|
7
|
+
const content = this.#remainder + part;
|
|
8
|
+
const records = content.split("\n");
|
|
9
|
+
this.#remainder = records.pop();
|
|
10
|
+
for (const record of records) {
|
|
11
|
+
const parsed = devalue.parse(record);
|
|
12
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || typeof parsed[0] !== "string") {
|
|
13
|
+
throw new Error("Invalid chunked data store entry");
|
|
14
|
+
}
|
|
15
|
+
this.#entries.set(parsed[0], parsed[1]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
finish() {
|
|
19
|
+
if (this.#remainder) {
|
|
20
|
+
throw new Error("Invalid chunked data store entry");
|
|
21
|
+
}
|
|
22
|
+
return this.#entries;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
3
25
|
class ImmutableDataStore {
|
|
4
26
|
_collections = /* @__PURE__ */ new Map();
|
|
5
27
|
constructor() {
|
|
@@ -39,21 +61,19 @@ class ImmutableDataStore {
|
|
|
39
61
|
*
|
|
40
62
|
* Each collection maps to a list of parts. A part is either a raw string
|
|
41
63
|
* (when the store is loaded from disk) or an ESM namespace from a virtual
|
|
42
|
-
* chunk import (`{ default: string }`, when emitted at runtime).
|
|
43
|
-
*
|
|
44
|
-
* serialized string, then parsed with devalue. This is the inverse of
|
|
64
|
+
* chunk import (`{ default: string }`, when emitted at runtime). Each part
|
|
65
|
+
* contains independently serialized entry records. This is the inverse of
|
|
45
66
|
* {@link import('./data-store-writer.js').ChunkedWriter} and stays free of
|
|
46
67
|
* Node built-ins so it can run at runtime.
|
|
47
68
|
*/
|
|
48
69
|
static manifestToMap(manifest) {
|
|
49
70
|
const collections = /* @__PURE__ */ new Map();
|
|
50
71
|
for (const [collectionName, parts] of Object.entries(manifest)) {
|
|
51
|
-
|
|
72
|
+
const parser = new ChunkedCollectionParser();
|
|
52
73
|
for (const part of parts) {
|
|
53
|
-
|
|
74
|
+
parser.add(typeof part === "string" ? part : part.default);
|
|
54
75
|
}
|
|
55
|
-
|
|
56
|
-
collections.set(collectionName, entries);
|
|
76
|
+
collections.set(collectionName, parser.finish());
|
|
57
77
|
}
|
|
58
78
|
return collections;
|
|
59
79
|
}
|
|
@@ -100,6 +120,7 @@ function dataStoreSingleton() {
|
|
|
100
120
|
}
|
|
101
121
|
const globalDataStore = dataStoreSingleton();
|
|
102
122
|
export {
|
|
123
|
+
ChunkedCollectionParser,
|
|
103
124
|
ImmutableDataStore,
|
|
104
125
|
globalDataStore
|
|
105
126
|
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
FileWriter,
|
|
11
11
|
serializeDataStore
|
|
12
12
|
} from "./data-store-writer.js";
|
|
13
|
-
import { ImmutableDataStore } from "./data-store.js";
|
|
13
|
+
import { ChunkedCollectionParser, ImmutableDataStore } from "./data-store.js";
|
|
14
14
|
import { contentModuleToId } from "./utils.js";
|
|
15
15
|
const SAVE_DEBOUNCE_MS = 500;
|
|
16
16
|
const MAX_DEPTH = 10;
|
|
@@ -276,10 +276,13 @@ ${lines.join(",\n")}]);
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
const foundAssets = new Set(assetImports);
|
|
279
|
-
|
|
279
|
+
const imageImports = [];
|
|
280
|
+
forEach(data, function(ctx, val) {
|
|
280
281
|
if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
|
|
281
282
|
const src = val.replace(IMAGE_IMPORT_PREFIX, "");
|
|
282
283
|
foundAssets.add(src);
|
|
284
|
+
imageImports.push(ctx.path.map((segment) => segment));
|
|
285
|
+
ctx.update(src);
|
|
283
286
|
}
|
|
284
287
|
});
|
|
285
288
|
const entry = {
|
|
@@ -299,6 +302,9 @@ ${lines.join(",\n")}]);
|
|
|
299
302
|
entry.assetImports = Array.from(foundAssets);
|
|
300
303
|
this.addAssetImports(entry.assetImports, filePath);
|
|
301
304
|
}
|
|
305
|
+
if (imageImports.length) {
|
|
306
|
+
entry.imageImports = imageImports;
|
|
307
|
+
}
|
|
302
308
|
if (digest) {
|
|
303
309
|
entry.digest = digest;
|
|
304
310
|
}
|
|
@@ -424,16 +430,15 @@ ${lines.join(",\n")}]);
|
|
|
424
430
|
try {
|
|
425
431
|
const manifestData = await fs.readFile(manifestFile, "utf-8");
|
|
426
432
|
const manifest = JSON.parse(manifestData);
|
|
427
|
-
const
|
|
433
|
+
const collections = /* @__PURE__ */ new Map();
|
|
428
434
|
for (const collectionName in manifest) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
435
|
+
const parser = new ChunkedCollectionParser();
|
|
436
|
+
for (const fileName of manifest[collectionName]) {
|
|
437
|
+
parser.add(await fs.readFile(new URL(`./${fileName}`, dirPath), "utf-8"));
|
|
438
|
+
}
|
|
439
|
+
collections.set(collectionName, parser.finish());
|
|
434
440
|
}
|
|
435
|
-
const
|
|
436
|
-
const store2 = await MutableDataStore.fromMap(map);
|
|
441
|
+
const store2 = await MutableDataStore.fromMap(collections);
|
|
437
442
|
store2.#writer = new ChunkedWriter(dirPath, chunkSize);
|
|
438
443
|
return store2;
|
|
439
444
|
} catch (err) {
|
|
@@ -4,7 +4,7 @@ import type * as zCore from 'zod/v4/core';
|
|
|
4
4
|
import type { ImageMetadata } from '../assets/types.js';
|
|
5
5
|
import { type AstroComponentFactory } from '../runtime/server/index.js';
|
|
6
6
|
import type { LiveDataCollectionResult, LiveDataEntryResult } from '../types/public/content.js';
|
|
7
|
-
import {
|
|
7
|
+
import type { LIVE_CONTENT_TYPE } from './consts.js';
|
|
8
8
|
import { type DataEntry } from './data-store.js';
|
|
9
9
|
import { LiveCollectionCacheHintError, LiveCollectionError, LiveCollectionValidationError, LiveEntryNotFoundError } from './loaders/errors.js';
|
|
10
10
|
import type { LiveLoader } from './loaders/types.js';
|
|
@@ -26,6 +26,7 @@ export declare function createGetCollection({ liveCollections, }: {
|
|
|
26
26
|
rendered?: import("./data-store.js").RenderedContent;
|
|
27
27
|
deferredRender?: boolean;
|
|
28
28
|
assetImports?: Array<string>;
|
|
29
|
+
imageImports?: (string | number)[][];
|
|
29
30
|
}[]>;
|
|
30
31
|
type ContentEntryResult = {
|
|
31
32
|
id: string;
|
|
@@ -70,7 +71,7 @@ type RenderResult = {
|
|
|
70
71
|
headings: MarkdownHeading[];
|
|
71
72
|
remarkPluginFrontmatter: Record<string, any>;
|
|
72
73
|
};
|
|
73
|
-
export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata
|
|
74
|
+
export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>, imageImports?: (string | number)[][]): T;
|
|
74
75
|
export declare function resolveEntryData<T extends Record<string, unknown>>(entry: DataEntry<T>, imageAssetMap?: Map<string, ImageMetadata>): T;
|
|
75
76
|
export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
|
|
76
77
|
export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<string, number>>, z.ZodString, z.ZodObject<{
|