@murumets-ee/content 0.36.0 → 0.38.0
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/admin.d.mts +32 -0
- package/dist/admin.d.mts.map +1 -0
- package/dist/admin.mjs +1 -0
- package/dist/chrome-provider.d.mts +107 -0
- package/dist/chrome-provider.d.mts.map +1 -0
- package/dist/chrome-provider.mjs +2 -0
- package/dist/chrome-provider.mjs.map +1 -0
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/content-config-store-8omQF5p5.mjs +2 -0
- package/dist/content-config-store-8omQF5p5.mjs.map +1 -0
- package/dist/entities/index.d.mts +37 -11
- package/dist/entities/index.d.mts.map +1 -1
- package/dist/entities/index.mjs +1 -1
- package/dist/entities/index.mjs.map +1 -1
- package/dist/{index-D8nN7BFd.d.mts → index-Bsmju0UF.d.mts} +30 -3
- package/dist/index-Bsmju0UF.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/locale-utils-DFEk4CE9.mjs +2 -0
- package/dist/{locale-utils-CuIBKeaz.mjs.map → locale-utils-DFEk4CE9.mjs.map} +1 -1
- package/dist/lock-service.d.mts +1 -1
- package/dist/plugin-BSxbiGc1.mjs +2 -0
- package/dist/plugin-BSxbiGc1.mjs.map +1 -0
- package/dist/plugin.d.mts +17 -6
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -1
- package/dist/query-client.mjs +1 -1
- package/dist/resolve-context-BVk6uObB.mjs +2 -0
- package/dist/resolve-context-BVk6uObB.mjs.map +1 -0
- package/dist/resolve.d.mts +596 -0
- package/dist/resolve.d.mts.map +1 -0
- package/dist/resolve.mjs +9 -0
- package/dist/resolve.mjs.map +1 -0
- package/dist/routes-Bc9DcaPk.mjs +2 -0
- package/dist/routes-Bc9DcaPk.mjs.map +1 -0
- package/dist/{routing-strategy-TPSzrapD.d.mts → routing-strategy-D3Qowm3D.d.mts} +40 -6
- package/dist/routing-strategy-D3Qowm3D.d.mts.map +1 -0
- package/dist/routing-strategy-dkt2yO25.mjs +2 -0
- package/dist/routing-strategy-dkt2yO25.mjs.map +1 -0
- package/dist/schema-BJxAwPrI.mjs +2 -0
- package/dist/schema-BJxAwPrI.mjs.map +1 -0
- package/dist/{types-CUw2Pf0Y.d.mts → types-B3K2p9t8.d.mts} +22 -1
- package/dist/types-B3K2p9t8.d.mts.map +1 -0
- package/package.json +21 -7
- package/dist/index-D8nN7BFd.d.mts.map +0 -1
- package/dist/locale-utils-CuIBKeaz.mjs +0 -2
- package/dist/plugin-IW9SM9iO.mjs +0 -2
- package/dist/plugin-IW9SM9iO.mjs.map +0 -1
- package/dist/routing-strategy-DGx6Rq6F.mjs +0 -2
- package/dist/routing-strategy-DGx6Rq6F.mjs.map +0 -1
- package/dist/routing-strategy-TPSzrapD.d.mts.map +0 -1
- package/dist/types-CUw2Pf0Y.d.mts.map +0 -1
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { Entity } from "@murumets-ee/entity";
|
|
2
|
+
import { ToolkitApp } from "@murumets-ee/core";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
5
|
+
|
|
6
|
+
//#region src/redirects/target.d.ts
|
|
7
|
+
/** The stored shape of `toolkit_redirects.target`. */
|
|
8
|
+
type RedirectTarget = {
|
|
9
|
+
kind: 'entity';
|
|
10
|
+
entity: string;
|
|
11
|
+
id: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'url';
|
|
14
|
+
url: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'path';
|
|
17
|
+
path: string;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/paths/index-reader.d.ts
|
|
21
|
+
/**
|
|
22
|
+
* Narrow read surface over `toolkit_paths` (plan/routing PR06).
|
|
23
|
+
*
|
|
24
|
+
* The resolution API (`./resolve.ts`) and the historical-tier helper
|
|
25
|
+
* (`./redirect-target.ts`) both read the path index, but neither needs the full
|
|
26
|
+
* `ToolkitPathsClient` — only `findOne` + `findMany`. Injecting a narrow
|
|
27
|
+
* structural interface (the same pattern `../redirects/validation.ts` uses for
|
|
28
|
+
* its readers) lets unit tests pass a trivial cast-free stub while the real
|
|
29
|
+
* `ToolkitPathsClient` still satisfies it (proven at compile time by the
|
|
30
|
+
* integration tests, which pass the real client straight in).
|
|
31
|
+
*
|
|
32
|
+
* The interface is deliberately a STRUCTURAL SUPERTYPE the real client is
|
|
33
|
+
* assignable to: every `where` field is a plain value (⊆ the client's richer
|
|
34
|
+
* `ColumnOperators` where-clause), `orderBy` only names `updatedAt` (a real
|
|
35
|
+
* column), and the projected {@link PathIndexRow} is a subset of the full row —
|
|
36
|
+
* so the assignment holds under TypeScript's method-parameter bivariance with no
|
|
37
|
+
* `as` casts on either side.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The `toolkit_paths` fields the resolution + historical tiers actually read.
|
|
41
|
+
*
|
|
42
|
+
* Declared as a `type` (not `interface`) deliberately: the real
|
|
43
|
+
* `ToolkitPathsClient`'s `findOne`/`findMany` are typed against `WhereClause`,
|
|
44
|
+
* whose template-literal index signature (`[k: \`${string}.${string}\`]`) a
|
|
45
|
+
* named `interface` can't satisfy as a supertype — a `type`-alias object literal
|
|
46
|
+
* gets the implicit index signature and stays assignable. (Same reason
|
|
47
|
+
* `../redirects/validation.ts` uses inline anonymous reader params.)
|
|
48
|
+
*/
|
|
49
|
+
type PathIndexRow = {
|
|
50
|
+
entityType: string;
|
|
51
|
+
entityId: string;
|
|
52
|
+
locale: string;
|
|
53
|
+
path: string;
|
|
54
|
+
isCanonical: boolean;
|
|
55
|
+
};
|
|
56
|
+
/** The (plain-value) where shapes those tiers query by. `type`, per {@link PathIndexRow}. */
|
|
57
|
+
type PathIndexWhere = {
|
|
58
|
+
entityType?: string;
|
|
59
|
+
entityId?: string;
|
|
60
|
+
locale?: string;
|
|
61
|
+
path?: string;
|
|
62
|
+
isCanonical?: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** The read surface `getCanonicalPath` / `resolvePath` / `findRedirectTarget` need. */
|
|
65
|
+
type PathIndexReader = {
|
|
66
|
+
findOne(where: PathIndexWhere): Promise<PathIndexRow | null>;
|
|
67
|
+
findMany(options: {
|
|
68
|
+
where?: PathIndexWhere; /** Only `updatedAt` ordering is used (historical-collision tie-break). */
|
|
69
|
+
orderBy?: Array<{
|
|
70
|
+
column: 'updatedAt';
|
|
71
|
+
dir?: 'asc' | 'desc';
|
|
72
|
+
}>;
|
|
73
|
+
limit?: number;
|
|
74
|
+
}): Promise<PathIndexRow[]>;
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/paths/homepage.d.ts
|
|
78
|
+
/** A resolved pointer at the document that serves `/`. */
|
|
79
|
+
interface HomepagePointer {
|
|
80
|
+
entity: string;
|
|
81
|
+
id: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Shape of the `site.homeDocument` JSONB value. `id` is a UUID — entity row ids
|
|
85
|
+
* are UUIDs across this codebase (only USER ids are varchar; CLAUDE.md).
|
|
86
|
+
*/
|
|
87
|
+
declare const homepagePointerSchema: z.ZodObject<{
|
|
88
|
+
entity: z.ZodString;
|
|
89
|
+
id: z.ZodString;
|
|
90
|
+
}, "strip", z.ZodTypeAny, {
|
|
91
|
+
id: string;
|
|
92
|
+
entity: string;
|
|
93
|
+
}, {
|
|
94
|
+
id: string;
|
|
95
|
+
entity: string;
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Parse a raw `site.homeDocument` value into a {@link HomepagePointer}, or `null`
|
|
99
|
+
* when it's unset / malformed. Never throws.
|
|
100
|
+
*/
|
|
101
|
+
declare function parseHomepagePointer(value: unknown): HomepagePointer | null;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/paths/resolve.d.ts
|
|
104
|
+
/** The document that serves the requested path. */
|
|
105
|
+
interface ResolveDocumentResult {
|
|
106
|
+
kind: 'document';
|
|
107
|
+
entityType: string;
|
|
108
|
+
id: string;
|
|
109
|
+
}
|
|
110
|
+
/** A redirect to another URL/path. `301` permanent, `302` temporary. */
|
|
111
|
+
interface ResolveRedirectResult {
|
|
112
|
+
kind: 'redirect';
|
|
113
|
+
to: string;
|
|
114
|
+
status: 301 | 302;
|
|
115
|
+
}
|
|
116
|
+
/** Nothing routes here (404). */
|
|
117
|
+
interface ResolveNoneResult {
|
|
118
|
+
kind: 'none';
|
|
119
|
+
}
|
|
120
|
+
type ResolveResult = ResolveDocumentResult | ResolveRedirectResult | ResolveNoneResult;
|
|
121
|
+
/**
|
|
122
|
+
* Is `(entityType, entityId)` published in `locale`? Backed by
|
|
123
|
+
* `QueryClient.findById`, which applies the publish filter for any principal
|
|
124
|
+
* WITHOUT `view_unpublished` (D017) — so the answer is only "published" if the
|
|
125
|
+
* injected client's context withholds that action, which the real one
|
|
126
|
+
* (`resolve-context.ts`'s reach context) does. See {@link createPublishChecker}.
|
|
127
|
+
* `resolveDraftPath` bypasses this entirely.
|
|
128
|
+
*/
|
|
129
|
+
interface PublishChecker {
|
|
130
|
+
isPublished(entityType: string, entityId: string, locale: string): Promise<boolean>;
|
|
131
|
+
}
|
|
132
|
+
/** Resolve an entity TYPE NAME to its `Entity`, or `undefined` when unregistered. */
|
|
133
|
+
type EntityResolver = (entityType: string) => Entity | undefined;
|
|
134
|
+
/** The `QueryClient.findById` surface the publish checker needs (publish filter is internal). */
|
|
135
|
+
interface PublishQueryClient {
|
|
136
|
+
findById(id: string, options?: {
|
|
137
|
+
locale?: string | undefined;
|
|
138
|
+
}): Promise<unknown>;
|
|
139
|
+
}
|
|
140
|
+
/** Build a publish-filtering read client for an entity (real: `new QueryClient(...)`). */
|
|
141
|
+
type QueryClientFactory = (entity: Entity) => PublishQueryClient;
|
|
142
|
+
/** Read the current homepage pointer, or `null` when unset/malformed. */
|
|
143
|
+
type HomepageReader = () => Promise<HomepagePointer | null>;
|
|
144
|
+
/** The manual-redirect lookup surface (locale-specific + catch-all rows). */
|
|
145
|
+
interface RedirectLookupReader {
|
|
146
|
+
findOne(where: {
|
|
147
|
+
fromPath: string; /** A concrete locale, or `null` for the catch-all (all-locales) row. */
|
|
148
|
+
locale: string | null;
|
|
149
|
+
}): Promise<{
|
|
150
|
+
target: RedirectTarget;
|
|
151
|
+
status: number;
|
|
152
|
+
} | null>;
|
|
153
|
+
}
|
|
154
|
+
/** Deps for {@link getCanonicalPath} — the minimal index read. */
|
|
155
|
+
interface CanonicalPathDeps {
|
|
156
|
+
pathsClient: PathIndexReader;
|
|
157
|
+
}
|
|
158
|
+
/** Deps for {@link resolvePath} / {@link resolveDraftPath}. */
|
|
159
|
+
interface ResolvePathDeps extends CanonicalPathDeps {
|
|
160
|
+
redirectsClient: RedirectLookupReader;
|
|
161
|
+
publishChecker: PublishChecker;
|
|
162
|
+
homepageReader: HomepageReader;
|
|
163
|
+
}
|
|
164
|
+
/** Deps for {@link getAlternates}. */
|
|
165
|
+
interface AlternatesDeps extends CanonicalPathDeps {
|
|
166
|
+
publishChecker: PublishChecker;
|
|
167
|
+
/** The locales to enumerate (configured content locales). */
|
|
168
|
+
locales: readonly string[];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Hop cap on manual-redirect `path`-chain following at REQUEST time (F021 /
|
|
172
|
+
* F027 point 4). `collapsePathTarget` only collapses ONE hop at save time, so a
|
|
173
|
+
* chain created out-of-order can still need re-hopping here; exceeding the cap
|
|
174
|
+
* resolves to `none` rather than looping or 500ing.
|
|
175
|
+
*/
|
|
176
|
+
declare const MAX_REDIRECT_HOPS = 5;
|
|
177
|
+
/**
|
|
178
|
+
* The current canonical path for `(entityType, entityId, locale)`, or `null`
|
|
179
|
+
* when the document has no canonical row in that locale.
|
|
180
|
+
*
|
|
181
|
+
* **No publish filter — deliberate.** This matches the CURRENT behavior of
|
|
182
|
+
* `resolveEntityHref` / search's URL builder (both call `AdminClient.findById`,
|
|
183
|
+
* which does zero publish filtering); Task 2 re-points those consumers onto this
|
|
184
|
+
* function, so changing the filtering here would be an unplanned behavior change.
|
|
185
|
+
* Callers that need a publish gate compose one explicitly (resolution's canonical
|
|
186
|
+
* tier does).
|
|
187
|
+
*/
|
|
188
|
+
declare function getCanonicalPath(entityType: string, entityId: string, locale: string, deps: CanonicalPathDeps): Promise<string | null>;
|
|
189
|
+
/**
|
|
190
|
+
* Build a {@link PublishChecker} from an entity resolver + a query-client
|
|
191
|
+
* factory. `isPublished` is `false` for an unknown entity type, else
|
|
192
|
+
* `findById(id, { locale }) !== null` — a non-null result means "published in
|
|
193
|
+
* that locale" because `QueryClient` applies the publish filter (and excludes
|
|
194
|
+
* trashed rows) for every principal without `view_unpublished` (D017). The
|
|
195
|
+
* factory therefore has to hand back a client whose context withholds that
|
|
196
|
+
* action; `resolve-context.ts`'s reach context is the one in-repo
|
|
197
|
+
* implementation and does. F027 point 1.
|
|
198
|
+
*/
|
|
199
|
+
declare function createPublishChecker(deps: {
|
|
200
|
+
entityResolver: EntityResolver;
|
|
201
|
+
queryClientFactory: QueryClientFactory;
|
|
202
|
+
}): PublishChecker;
|
|
203
|
+
/**
|
|
204
|
+
* Resolve `(path, locale)` to a document / redirect / none, publish-checking at
|
|
205
|
+
* every document-resolving tier. Public request surface (phase 05 §2).
|
|
206
|
+
*/
|
|
207
|
+
declare function resolvePath(path: string, locale: string, deps: ResolvePathDeps): Promise<ResolveResult>;
|
|
208
|
+
/**
|
|
209
|
+
* Admin/preview resolution — identical tier structure to {@link resolvePath} but
|
|
210
|
+
* SKIPS every publish check (draft and published documents both resolve). Per
|
|
211
|
+
* phase 05 §2 this surface is admin-only. Exactly ONE HTTP route may call it —
|
|
212
|
+
* `./routes.ts`'s `GET /api/admin/paths/resolve` (#775), which gates on
|
|
213
|
+
* `path:resolve` and re-checks the caller's `view` grant on whatever it
|
|
214
|
+
* resolves. NEVER wire it to `content-api` (public, published-only).
|
|
215
|
+
*/
|
|
216
|
+
declare function resolveDraftPath(path: string, locale: string, deps: ResolvePathDeps): Promise<ResolveResult>;
|
|
217
|
+
/**
|
|
218
|
+
* The published per-locale canonical URLs for a document — the `hreflang`
|
|
219
|
+
* alternates set (phase 05 §2, "published locales only"). A locale is included
|
|
220
|
+
* only when the document has a canonical row AND is published there. Bounded by
|
|
221
|
+
* the (small, fixed) configured-locale count — no fan-out concern.
|
|
222
|
+
*/
|
|
223
|
+
declare function getAlternates(entityType: string, id: string, deps: AlternatesDeps): Promise<Array<{
|
|
224
|
+
locale: string;
|
|
225
|
+
path: string;
|
|
226
|
+
}>>;
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/paths/enumerate.d.ts
|
|
229
|
+
/**
|
|
230
|
+
* Path enumeration for SSG / sitemaps (plan/routing PR06; phase
|
|
231
|
+
* `05-resolution-and-enumeration-api.md` §2).
|
|
232
|
+
*
|
|
233
|
+
* `enumeratePaths` streams the PUBLISHED canonical `(path, entityType, id,
|
|
234
|
+
* updatedAt)` rows for one locale. It powers `getStaticPaths` + the sitemap
|
|
235
|
+
* helper, so it must NEVER leak an unpublished document (CLAUDE.md #1 — the
|
|
236
|
+
* output feeds public build artifacts).
|
|
237
|
+
*
|
|
238
|
+
* **Security + availability.** Two properties matter here:
|
|
239
|
+
* 1. *No unpublished leak.* `toolkit_paths` carries no publish-status column,
|
|
240
|
+
* so each page's rows are publish-checked against the live entity via the
|
|
241
|
+
* injected {@link EnumeratePathsDeps.filterPublished} — an unpublished (or
|
|
242
|
+
* trashed) document is dropped from the stream.
|
|
243
|
+
* 2. *No unbounded fan-out* (CLAUDE.md "no amplification"). Work is bounded by
|
|
244
|
+
* PAGE SIZE, not by total document count: rows are keyset-paginated on the
|
|
245
|
+
* immutable `id` PK (one index query per page), and the publish check is
|
|
246
|
+
* BATCHED — ONE call per distinct entity-type bucket per page (a handful of
|
|
247
|
+
* types at most; the routable set is config-time-fixed, never
|
|
248
|
+
* attacker-influenced), never one call per row.
|
|
249
|
+
*
|
|
250
|
+
* **Leaf module.** Every cross-package dependency (the index reader, the publish
|
|
251
|
+
* filter) is injected via `deps`; this module imports no `@murumets-ee/core` and
|
|
252
|
+
* no `server-only` graph. Concrete deps are built from an app in
|
|
253
|
+
* `./resolve-context.ts` (`buildEnumerateDeps`), keeping the resolution surface
|
|
254
|
+
* off the `lumi` CLI command graph (F027 guardrail).
|
|
255
|
+
*
|
|
256
|
+
* Function-form-path entities need NO special handling — their rows were already
|
|
257
|
+
* materialized into `toolkit_paths` at write time (F004 / phase 05 §2), so a
|
|
258
|
+
* plain index read includes them.
|
|
259
|
+
*/
|
|
260
|
+
/** The projected `toolkit_paths` row the enumeration + batch lookup read. */
|
|
261
|
+
type PathEnumRow = {
|
|
262
|
+
id: string;
|
|
263
|
+
entityType: string;
|
|
264
|
+
entityId: string;
|
|
265
|
+
locale: string;
|
|
266
|
+
path: string;
|
|
267
|
+
updatedAt: Date;
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* The (operator-carrying) where shapes enumeration issues: keyset `id > cursor`,
|
|
271
|
+
* `entityId IN (...)` for the batch lookup, plus plain-value equality filters.
|
|
272
|
+
* A subset of the real client's `ColumnOperators` where-clause, so the real
|
|
273
|
+
* `ToolkitPathsClient` is assignable to {@link PathEnumReader}.
|
|
274
|
+
*/
|
|
275
|
+
type PathEnumWhere = {
|
|
276
|
+
id?: {
|
|
277
|
+
gt: string;
|
|
278
|
+
};
|
|
279
|
+
entityType?: string;
|
|
280
|
+
entityId?: {
|
|
281
|
+
in: string[];
|
|
282
|
+
};
|
|
283
|
+
locale?: string;
|
|
284
|
+
path?: string;
|
|
285
|
+
isCanonical?: boolean;
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* The batched keyset read surface {@link enumeratePaths} / {@link getCanonicalPaths}
|
|
289
|
+
* need. NARROWER than Task 1's point-lookup `PathIndexReader` (which only orders
|
|
290
|
+
* by `updatedAt` and takes plain-value where): enumeration needs `id`-ordering,
|
|
291
|
+
* a `{ gt }` cursor, and `{ in }` batching. Construct the real client directly
|
|
292
|
+
* (`toolkitPathsTable.makeClient(db)`) — it satisfies this type.
|
|
293
|
+
*/
|
|
294
|
+
type PathEnumReader = {
|
|
295
|
+
findMany(options: {
|
|
296
|
+
where?: PathEnumWhere;
|
|
297
|
+
orderBy?: Array<{
|
|
298
|
+
column: 'id';
|
|
299
|
+
dir?: 'asc' | 'desc';
|
|
300
|
+
}>;
|
|
301
|
+
limit?: number;
|
|
302
|
+
}): Promise<PathEnumRow[]>;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Batch publish check: return the SUBSET of `entityIds` that is published in
|
|
306
|
+
* `locale`. Called ONCE per entity-type group per page. The concrete
|
|
307
|
+
* implementation (see `resolve-context.ts`) runs an elevated-context
|
|
308
|
+
* `QueryClient.findMany({ where: inArray(getTable().id, ids), locale })` — so no
|
|
309
|
+
* unpublished/trashed id comes back. Since D017 the publish filter is
|
|
310
|
+
* CONDITIONAL (it holds for any principal without `view_unpublished`); what
|
|
311
|
+
* keeps that true here is that the elevated context is a REACH context which
|
|
312
|
+
* withholds that action, not that the filter is unconditional.
|
|
313
|
+
*/
|
|
314
|
+
type PublishFilter = (entityType: string, entityIds: readonly string[], locale: string) => Promise<ReadonlySet<string>>;
|
|
315
|
+
/** One enumerated path row (phase 05 §2 — exact shape). */
|
|
316
|
+
interface PathEntry {
|
|
317
|
+
path: string;
|
|
318
|
+
entityType: string;
|
|
319
|
+
id: string;
|
|
320
|
+
updatedAt: Date;
|
|
321
|
+
}
|
|
322
|
+
/** Options for {@link enumeratePaths} (phase 05 §2 — exact shape). */
|
|
323
|
+
interface EnumeratePathsOptions {
|
|
324
|
+
/** Enumerate this locale's canonical rows only. */
|
|
325
|
+
locale: string;
|
|
326
|
+
/** Optionally narrow to a single routable entity type. */
|
|
327
|
+
entityType?: string;
|
|
328
|
+
/**
|
|
329
|
+
* Resume the keyset AFTER this `toolkit_paths` row id (exclusive); omit to
|
|
330
|
+
* start from the beginning. The value is the opaque `toolkit_paths` PK the
|
|
331
|
+
* internal page cursor advances on — NOT the yielded `PathEntry.id`, which is
|
|
332
|
+
* the DOCUMENT (entity) id. A cross-request paginating caller (e.g. the
|
|
333
|
+
* content-api `_paths` endpoint) resolves the last consumed entry's row PK and
|
|
334
|
+
* passes it here to continue exactly where the prior page stopped, with no row
|
|
335
|
+
* skipped or repeated. Fully backward-compatible: existing callers omit it and
|
|
336
|
+
* traverse from the start unchanged.
|
|
337
|
+
*/
|
|
338
|
+
afterId?: string;
|
|
339
|
+
}
|
|
340
|
+
/** Injected dependencies for {@link enumeratePaths}. */
|
|
341
|
+
interface EnumeratePathsDeps {
|
|
342
|
+
pathsClient: PathEnumReader;
|
|
343
|
+
filterPublished: PublishFilter;
|
|
344
|
+
}
|
|
345
|
+
/** Internal tuning seam (page size / batch concurrency). Real callers omit it. */
|
|
346
|
+
interface EnumeratePathsTuning {
|
|
347
|
+
/** Rows per index page. Defaults to {@link ENUMERATE_PAGE_SIZE}. Tests force a small value. */
|
|
348
|
+
pageSize?: number;
|
|
349
|
+
/** Peak concurrent publish-check groups. Defaults to {@link PUBLISH_CHECK_CONCURRENCY}. */
|
|
350
|
+
publishConcurrency?: number;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Stream the published canonical paths for `opts.locale` (optionally narrowed to
|
|
354
|
+
* `opts.entityType`), keyset-paginated with a per-page batched publish filter.
|
|
355
|
+
* See the module header for the security + fan-out guarantees.
|
|
356
|
+
*/
|
|
357
|
+
declare function enumeratePaths(opts: EnumeratePathsOptions, deps: EnumeratePathsDeps, tuning?: EnumeratePathsTuning): AsyncGenerator<PathEntry>;
|
|
358
|
+
/** Deps for {@link getCanonicalPaths}. */
|
|
359
|
+
interface CanonicalPathsBatchDeps {
|
|
360
|
+
pathsClient: PathEnumReader;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The current canonical paths for many `(entityType, entityId)` in ONE locale,
|
|
364
|
+
* as an `entityId → path` map — the batched sibling of `getCanonicalPath`.
|
|
365
|
+
*
|
|
366
|
+
* **No publish filter — deliberate**, matching `getCanonicalPath` (and the
|
|
367
|
+
* search url builder it re-points): a search result's url is display-only and
|
|
368
|
+
* the caller already gates row visibility. One index query per {@link
|
|
369
|
+
* CANONICAL_BATCH_SIZE}-sized chunk (one query total for a normal search page),
|
|
370
|
+
* so it never fans out per row.
|
|
371
|
+
*/
|
|
372
|
+
declare function getCanonicalPaths(entityType: string, entityIds: readonly string[], locale: string, deps: CanonicalPathsBatchDeps): Promise<Map<string, string>>;
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/paths/sitemap.d.ts
|
|
375
|
+
/** Options for {@link generateSitemap}. */
|
|
376
|
+
interface SitemapOptions {
|
|
377
|
+
/** The locales to emit a `<urlset>` for (configured content locales). */
|
|
378
|
+
locales: readonly string[];
|
|
379
|
+
/**
|
|
380
|
+
* The default locale — its URL is the `x-default` hreflang target. Explicit
|
|
381
|
+
* (NOT inferred from `locales` order) so callers stay unambiguous.
|
|
382
|
+
*/
|
|
383
|
+
defaultLocale: string;
|
|
384
|
+
/** Absolute site origin, e.g. `https://example.com` (trailing slash tolerated). */
|
|
385
|
+
baseUrl: string;
|
|
386
|
+
/**
|
|
387
|
+
* Per-locale URL prefix rule (e.g. `(l) => l === 'en' ? '' : '/' + l`). The
|
|
388
|
+
* frontend owns the locale-prefix convention (D002); omitted → no prefix.
|
|
389
|
+
*/
|
|
390
|
+
prefix?: (locale: string) => string;
|
|
391
|
+
/**
|
|
392
|
+
* Max `<url>` entries per file before splitting (sitemap protocol caps at
|
|
393
|
+
* 50 000). Defaults to {@link DEFAULT_MAX_URLS_PER_FILE}.
|
|
394
|
+
*/
|
|
395
|
+
maxUrlsPerFile?: number;
|
|
396
|
+
}
|
|
397
|
+
/** Injected dependencies for {@link generateSitemap}. */
|
|
398
|
+
interface SitemapDeps {
|
|
399
|
+
/**
|
|
400
|
+
* Deps for {@link enumeratePaths} — the `toolkit_paths` reader plus the
|
|
401
|
+
* batched `filterPublished`. `filterPublished` is REUSED for the cross-locale
|
|
402
|
+
* alternate publish gate (same batched shape), so no second publish surface is
|
|
403
|
+
* needed.
|
|
404
|
+
*/
|
|
405
|
+
enumerate: EnumeratePathsDeps;
|
|
406
|
+
/**
|
|
407
|
+
* Deps for {@link getCanonicalPaths} — the batched cross-locale forward index
|
|
408
|
+
* read (NO publish filter; this module applies `enumerate.filterPublished` on
|
|
409
|
+
* top).
|
|
410
|
+
*/
|
|
411
|
+
canonicalBatch: CanonicalPathsBatchDeps;
|
|
412
|
+
/**
|
|
413
|
+
* Optional: batched `noIndex` check, same shape as `filterPublished` but
|
|
414
|
+
* semantics-inverted — returns the subset of `entityIds` that ARE `noIndex`
|
|
415
|
+
* in `locale` (PR 11's `seoMeta()`). Called at most once per (entity-type
|
|
416
|
+
* group, batch), never once per entry — see `excludeNoIndexed`. Omitted →
|
|
417
|
+
* nothing excluded (current default).
|
|
418
|
+
*/
|
|
419
|
+
filterNoIndex?: PublishFilter;
|
|
420
|
+
}
|
|
421
|
+
/** Internal tuning seam (batch size / enumerate page size). Real callers omit it. */
|
|
422
|
+
interface SitemapTuning {
|
|
423
|
+
/**
|
|
424
|
+
* Enumerated rows per cross-locale alternate batch. Defaults to
|
|
425
|
+
* {@link SITEMAP_ALTERNATES_BATCH_SIZE}. Tests force a small value to span
|
|
426
|
+
* multiple batches. Bounds the per-batch fan-out (see module header).
|
|
427
|
+
*/
|
|
428
|
+
batchSize?: number;
|
|
429
|
+
/** Passed through to {@link enumeratePaths} for its own paging/publish tuning. */
|
|
430
|
+
enumerate?: EnumeratePathsTuning;
|
|
431
|
+
}
|
|
432
|
+
/** One emitted sitemap document. */
|
|
433
|
+
interface SitemapFile {
|
|
434
|
+
/** `'urlset'` for a per-locale URL set, `'index'` for the sitemap index. */
|
|
435
|
+
kind: 'urlset' | 'index';
|
|
436
|
+
/**
|
|
437
|
+
* Suggested filename served at `${baseUrl}/${filename}`. Per-locale urlsets are
|
|
438
|
+
* `sitemap-<locale>.xml` (first) / `sitemap-<locale>-<n>.xml` (splits, n≥2);
|
|
439
|
+
* the index is {@link SITEMAP_INDEX_FILENAME}.
|
|
440
|
+
*/
|
|
441
|
+
filename: string;
|
|
442
|
+
/** The complete, well-formed XML document text (trailing newline included). */
|
|
443
|
+
xml: string;
|
|
444
|
+
}
|
|
445
|
+
/** Default `<url>` cap per file — under the sitemap protocol's 50 000 hard limit. */
|
|
446
|
+
declare const DEFAULT_MAX_URLS_PER_FILE = 40000;
|
|
447
|
+
/** Filename of the emitted sitemap index (referenced by the mounting route). */
|
|
448
|
+
declare const SITEMAP_INDEX_FILENAME = "sitemap.xml";
|
|
449
|
+
/**
|
|
450
|
+
* Stream the sitemap document set for `opts.locales`. See the module header for
|
|
451
|
+
* the security + fan-out guarantees. Yields each `<urlset>` file as it fills,
|
|
452
|
+
* then a `<sitemapindex>` LAST iff more than one file was produced.
|
|
453
|
+
*/
|
|
454
|
+
declare function generateSitemap(opts: SitemapOptions, deps: SitemapDeps, tuning?: SitemapTuning): AsyncGenerator<SitemapFile>;
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/paths/redirects-list.d.ts
|
|
457
|
+
/** The projected `toolkit_redirects` row the manual-redirect enumeration reads. */
|
|
458
|
+
type RedirectListRow = {
|
|
459
|
+
id: string;
|
|
460
|
+
fromPath: string; /** BCP47 locale, or `null` for the catch-all (all-locales) row. */
|
|
461
|
+
locale: string | null;
|
|
462
|
+
target: RedirectTarget;
|
|
463
|
+
status: number;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* The (operator-carrying) where shapes the manual enumeration issues: a keyset
|
|
467
|
+
* `id > cursor` plus a `locale` equality (a concrete locale, or `null` for the
|
|
468
|
+
* catch-all stratum). A subset of the real client's `WhereClause`, so the real
|
|
469
|
+
* `ToolkitRedirectsClient` is assignable to {@link RedirectListReader}.
|
|
470
|
+
*/
|
|
471
|
+
type RedirectListWhere = {
|
|
472
|
+
id?: {
|
|
473
|
+
gt: string;
|
|
474
|
+
};
|
|
475
|
+
locale?: string | null;
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* The batched keyset read surface the manual half needs over `toolkit_redirects`.
|
|
479
|
+
* Construct the real client directly (`toolkitRedirectsTable.makeClient(db)`) —
|
|
480
|
+
* it satisfies this type.
|
|
481
|
+
*/
|
|
482
|
+
type RedirectListReader = {
|
|
483
|
+
findMany(options: {
|
|
484
|
+
where?: RedirectListWhere;
|
|
485
|
+
orderBy?: Array<{
|
|
486
|
+
column: 'id';
|
|
487
|
+
dir?: 'asc' | 'desc';
|
|
488
|
+
}>;
|
|
489
|
+
limit?: number;
|
|
490
|
+
}): Promise<RedirectListRow[]>;
|
|
491
|
+
};
|
|
492
|
+
/** One emitted redirect. `301` permanent, `302` temporary (phase 05 §2 shape). */
|
|
493
|
+
interface RedirectEntry {
|
|
494
|
+
from: string;
|
|
495
|
+
to: string;
|
|
496
|
+
status: 301 | 302;
|
|
497
|
+
}
|
|
498
|
+
/** Options for {@link getRedirects}. */
|
|
499
|
+
interface RedirectsListOptions {
|
|
500
|
+
/** Emit this locale's redirects only (mirrors `enumeratePaths` / `generateSitemap`). */
|
|
501
|
+
locale: string;
|
|
502
|
+
}
|
|
503
|
+
/** Injected dependencies for {@link getRedirects}. */
|
|
504
|
+
interface RedirectsListDeps {
|
|
505
|
+
/** Keyset reader over `toolkit_redirects` (manual redirects). */
|
|
506
|
+
redirectsClient: RedirectListReader;
|
|
507
|
+
/**
|
|
508
|
+
* Keyset reader over `toolkit_paths` — the historical (non-canonical) rows AND
|
|
509
|
+
* the `path`-kind existence check. Reuses `enumeratePaths`'s exact reader shape.
|
|
510
|
+
*/
|
|
511
|
+
pathsClient: PathEnumReader;
|
|
512
|
+
/** Batched forward index read for owning-document current-canonical resolution. */
|
|
513
|
+
canonicalBatch: CanonicalPathsBatchDeps;
|
|
514
|
+
/** The SAME batched publish filter `enumeratePaths` / `generateSitemap` use. */
|
|
515
|
+
filterPublished: PublishFilter;
|
|
516
|
+
}
|
|
517
|
+
/** Internal tuning seam (page size). Real callers omit it. */
|
|
518
|
+
interface RedirectsListTuning {
|
|
519
|
+
/** Rows per keyset page. Defaults to {@link REDIRECTS_PAGE_SIZE}. Tests force a small value. */
|
|
520
|
+
pageSize?: number;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Stream the redirect list for `opts.locale`: manual redirects (locale-specific
|
|
524
|
+
* then catch-all, deduped by precedence) followed by historical redirects
|
|
525
|
+
* (skipping any `from` a manual redirect already claimed). See the module header
|
|
526
|
+
* for the security + fan-out + precedence guarantees.
|
|
527
|
+
*/
|
|
528
|
+
declare function getRedirects(opts: RedirectsListOptions, deps: RedirectsListDeps, tuning?: RedirectsListTuning): AsyncGenerator<RedirectEntry>;
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/paths/resolve-context.d.ts
|
|
531
|
+
/**
|
|
532
|
+
* Build the minimal deps for {@link import('./resolve.js').getCanonicalPath} —
|
|
533
|
+
* just the `toolkit_paths` reader. Task 2's `resolveEntityHref` / search
|
|
534
|
+
* url-builder re-points use this (no publish checker / settings machinery).
|
|
535
|
+
*/
|
|
536
|
+
declare function buildCanonicalPathDeps(app: ToolkitApp): CanonicalPathDeps;
|
|
537
|
+
/**
|
|
538
|
+
* Build a live {@link PublishChecker} that reads through an elevated-context
|
|
539
|
+
* `QueryClient` per entity (publish filter auto-applied). See file header.
|
|
540
|
+
*/
|
|
541
|
+
declare function buildPublishChecker(app: ToolkitApp): PublishChecker;
|
|
542
|
+
/**
|
|
543
|
+
* Build the homepage reader — a single indexed `toolkit_settings` lookup for
|
|
544
|
+
* `site.homeDocument` (global scope, default locale), zod-parsed defensively
|
|
545
|
+
* (unset / malformed → `null`, never a throw). No cache in v1 (phase 05 §5).
|
|
546
|
+
*/
|
|
547
|
+
declare function createHomepageReader(db: PostgresJsDatabase): HomepageReader;
|
|
548
|
+
/**
|
|
549
|
+
* Build the deps for {@link import('./enumerate.js').enumeratePaths} — the
|
|
550
|
+
* `toolkit_paths` reader plus a BATCHED publish filter. `filterPublished` runs
|
|
551
|
+
* ONE elevated-context `QueryClient.findMany({ where: inArray(getTable().id,
|
|
552
|
+
* ids), locale })` per entity-type group (not per row) — the trash filter
|
|
553
|
+
* applies unconditionally, and the publish filter applies because the elevated
|
|
554
|
+
* context WITHHOLDS `view_unpublished` (D017; see file header), so no
|
|
555
|
+
* unpublished/trashed id is returned. An unknown/unregistered entity type yields the empty set (its
|
|
556
|
+
* rows are treated as unpublished → dropped, fail-closed).
|
|
557
|
+
*/
|
|
558
|
+
declare function buildEnumerateDeps(app: ToolkitApp): EnumeratePathsDeps;
|
|
559
|
+
/**
|
|
560
|
+
* Build the deps for {@link import('./enumerate.js').getCanonicalPaths} — just
|
|
561
|
+
* the `toolkit_paths` reader (batched, NO publish filter, like
|
|
562
|
+
* {@link buildCanonicalPathDeps}). Task 2's search url re-point uses this to
|
|
563
|
+
* resolve a search page's result urls from the index in one query.
|
|
564
|
+
*/
|
|
565
|
+
declare function buildCanonicalPathsBatchDeps(app: ToolkitApp): CanonicalPathsBatchDeps;
|
|
566
|
+
/**
|
|
567
|
+
* Build the deps for {@link import('./sitemap.js').generateSitemap} — the
|
|
568
|
+
* one-stop bundle of {@link buildEnumerateDeps} (per-locale enumeration + its
|
|
569
|
+
* BATCHED publish filter, reused for the cross-locale alternate publish gate),
|
|
570
|
+
* {@link buildCanonicalPathsBatchDeps} (the batched cross-locale forward index
|
|
571
|
+
* read), and {@link buildNoIndexFilter} (PR 11's batched `noIndex` exclusion).
|
|
572
|
+
* Mirrors `buildResolveDeps`'s one-stop-deps pattern.
|
|
573
|
+
*/
|
|
574
|
+
declare function buildSitemapDeps(app: ToolkitApp): SitemapDeps;
|
|
575
|
+
/**
|
|
576
|
+
* Build the deps for {@link import('./redirects-list.js').getRedirects} — the
|
|
577
|
+
* one-stop bundle for the build-time redirect list (PR08). Composes
|
|
578
|
+
* {@link buildEnumerateDeps} (its `toolkit_paths` reader powers both the
|
|
579
|
+
* historical keyset and the `path`-kind existence check; its BATCHED
|
|
580
|
+
* `filterPublished` is the shared publish gate), {@link buildCanonicalPathsBatchDeps}
|
|
581
|
+
* (the batched owning-document current-canonical lookup), and a
|
|
582
|
+
* `toolkit_redirects` reader for the manual-redirect keyset. Mirrors
|
|
583
|
+
* {@link buildSitemapDeps}'s one-stop-deps style.
|
|
584
|
+
*/
|
|
585
|
+
declare function buildRedirectsListDeps(app: ToolkitApp): RedirectsListDeps;
|
|
586
|
+
/**
|
|
587
|
+
* Build the full {@link ResolvePathDeps} (+ {@link AlternatesDeps}) from an app —
|
|
588
|
+
* the one-stop deps for `resolvePath` / `resolveDraftPath` / `getAlternates`.
|
|
589
|
+
* `locales` are the statically-configured content locales (v1 — matches the
|
|
590
|
+
* redirect routes' own configured-locale source; DB-merged locale overrides are
|
|
591
|
+
* a later refinement).
|
|
592
|
+
*/
|
|
593
|
+
declare function buildResolveDeps(app: ToolkitApp): ResolvePathDeps & AlternatesDeps;
|
|
594
|
+
//#endregion
|
|
595
|
+
export { type AlternatesDeps, type CanonicalPathDeps, type CanonicalPathsBatchDeps, DEFAULT_MAX_URLS_PER_FILE, type EntityResolver, type EnumeratePathsDeps, type EnumeratePathsOptions, type EnumeratePathsTuning, type HomepagePointer, type HomepageReader, MAX_REDIRECT_HOPS, type PathEntry, type PathEnumReader, type PathEnumRow, type PathEnumWhere, type PathIndexReader, type PathIndexRow, type PathIndexWhere, type PublishChecker, type PublishFilter, type PublishQueryClient, type QueryClientFactory, type RedirectEntry, type RedirectListReader, type RedirectListRow, type RedirectListWhere, type RedirectLookupReader, type RedirectsListDeps, type RedirectsListOptions, type RedirectsListTuning, type ResolveDocumentResult, type ResolveNoneResult, type ResolvePathDeps, type ResolveRedirectResult, type ResolveResult, SITEMAP_INDEX_FILENAME, type SitemapDeps, type SitemapFile, type SitemapOptions, type SitemapTuning, buildCanonicalPathDeps, buildCanonicalPathsBatchDeps, buildEnumerateDeps, buildPublishChecker, buildRedirectsListDeps, buildResolveDeps, buildSitemapDeps, createHomepageReader, createPublishChecker, enumeratePaths, generateSitemap, getAlternates, getCanonicalPath, getCanonicalPaths, getRedirects, homepagePointerSchema, parseHomepagePointer, resolveDraftPath, resolvePath };
|
|
596
|
+
//# sourceMappingURL=resolve.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.mts","names":[],"sources":["../src/redirects/target.ts","../src/paths/index-reader.ts","../src/paths/homepage.ts","../src/paths/resolve.ts","../src/paths/enumerate.ts","../src/paths/sitemap.ts","../src/paths/redirects-list.ts","../src/paths/resolve-context.ts"],"mappings":"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"}
|
package/dist/resolve.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{a as e,c as t,d as n,i as r,n as i,o as a,r as o,s,t as c,u as l}from"./resolve-context-BVk6uObB.mjs";import{a as u,c as d,g as f,i as p,o as m,p as h,r as g,s as _}from"./schema-BJxAwPrI.mjs";async function*v(e,t,n={}){let r=n.pageSize??200,i=n.publishConcurrency??5,{locale:a,entityType:o}=e,s=e.afterId;for(;;){let e={locale:a,isCanonical:!0};o!==void 0&&(e.entityType=o),s!==void 0&&(e.id={gt:s});let n=await t.pathsClient.findMany({where:e,orderBy:[{column:`id`,dir:`asc`}],limit:r});if(n.length===0)break;let c=new Map;for(let e of n){let t=c.get(e.entityType);t?t.push(e):c.set(e.entityType,[e])}let l=new Map;await f([...c.entries()],i,async([e,n])=>{let r=await t.filterPublished(e,n.map(e=>e.entityId),a);l.set(e,r)});for(let e of n)l.get(e.entityType)?.has(e.entityId)&&(yield{path:e.path,entityType:e.entityType,id:e.entityId,updatedAt:e.updatedAt});let u=n[n.length-1];if(!u||(s=u.id,n.length<r))break}}async function y(e,t,n,r){let i=new Map;for(let a=0;a<t.length;a+=500){let o=t.slice(a,a+500),s=await r.pathsClient.findMany({where:{entityType:e,locale:n,isCanonical:!0,entityId:{in:[...o]}},limit:o.length});for(let e of s)i.set(e.entityId,e.path)}return i}const b=4e4,x=`sitemap.xml`;async function*S(e,t,n={}){let r=N(e.maxUrlsPerFile,b),i=N(n.batchSize,200),a=A(e.baseUrl),o=[];for(let s of e.locales){let c=[],l=null,u=0,d=()=>{if(c.length===0)return null;let e=M(s,u),t=(l??new Date).toISOString(),n={kind:`urlset`,filename:e,xml:T(c)};return o.push({filename:e,lastmod:t}),c=[],l=null,u+=1,n},f=v({locale:s},t.enumerate,n.enumerate);for await(let n of P(f,i)){let i=await w(n,s,t);if(i.length===0)continue;let o=await C(i,s,e,t);for(let t of i){let n=o.get(t.id)??[{locale:s,path:t.path}];if(c.push(E(t,s,n,e,a)),(l===null||t.updatedAt>l)&&(l=t.updatedAt),c.length>=r){let e=d();e&&(yield e)}}}let p=d();p&&(yield p)}o.length>1&&(yield{kind:`index`,filename:x,xml:D(o,a)})}async function C(e,t,n,r){let i=new Map;for(let n of e)i.set(n.id,[{locale:t,path:n.path}]);let a=n.locales.filter(e=>e!==t);if(a.length===0)return i;let o=new Map;for(let t of e){let e=o.get(t.entityType);e?e.push(t.id):o.set(t.entityType,[t.id])}for(let e of a)for(let[t,n]of o){let a=await y(t,n,e,r.canonicalBatch);if(a.size===0)continue;let o=[...a.keys()],s=await r.enumerate.filterPublished(t,o,e),c=r.filterNoIndex===void 0?void 0:await r.filterNoIndex(t,o,e);for(let[t,n]of a){if(!s.has(t)||c?.has(t))continue;let r=i.get(t);r&&r.push({locale:e,path:n})}}return i}async function w(e,t,n){let r=n.filterNoIndex;if(r===void 0)return[...e];let i=new Map;for(let t of e){let e=i.get(t.entityType);e?e.push(t.id):i.set(t.entityType,[t.id])}let a=new Map;return await f([...i.entries()],5,async([e,n])=>{a.set(e,await r(e,n,t))}),e.filter(e=>!a.get(e.entityType)?.has(e.id))}function T(e){return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">\n${e.join(`
|
|
3
|
+
`)}\n</urlset>\n`}function E(e,t,n,r,i){let a=k(i,r.prefix,t,e.path),o=n.map(e=>` ${O(e.locale,k(i,r.prefix,e.locale,e.path))}`),s=n.find(e=>e.locale===r.defaultLocale);return s&&o.push(` ${O(`x-default`,k(i,r.prefix,s.locale,s.path))}`),` <url>
|
|
4
|
+
<loc>${j(a)}</loc>\n <lastmod>${e.updatedAt.toISOString()}</lastmod>\n${o.join(`
|
|
5
|
+
`)}\n </url>`}function D(e,t){return`<?xml version="1.0" encoding="UTF-8"?>
|
|
6
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${e.map(e=>` <sitemap>
|
|
7
|
+
<loc>${j(`${t}/${e.filename}`)}</loc>\n <lastmod>${e.lastmod}</lastmod>\n </sitemap>`).join(`
|
|
8
|
+
`)}\n</sitemapindex>\n`}function O(e,t){return`<xhtml:link rel="alternate" hreflang="${j(e)}" href="${j(t)}"/>`}function k(e,t,n,r){return`${e}${t?t(n):``}${r}`}function A(e){return e.replace(/\/+$/,``)}function j(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}function M(e,t){return t===0?`sitemap-${e}.xml`:`sitemap-${e}-${t+1}.xml`}function N(e,t){if(e===void 0||!Number.isFinite(e))return t;let n=Math.floor(e);return n>=1?n:t}async function*P(e,t){let n=[];for await(let r of e)n.push(r),n.length>=t&&(yield n,n=[]);n.length>0&&(yield n)}async function*F(e,t,n={}){let{locale:r}=e,i=n.pageSize??200,a=new Set;yield*I(r,r,t,i,a),yield*I(r,null,t,i,a),yield*z(r,t,i,a)}async function*I(e,t,n,r,i){let a;for(;;){let o={locale:t};a!==void 0&&(o.id={gt:a});let s=await n.redirectsClient.findMany({where:o,orderBy:[{column:`id`,dir:`asc`}],limit:r});if(s.length===0)break;let c=[];for(let e of s)i.has(e.fromPath)||(i.add(e.fromPath),c.push(e));let l=await L(c,e,n);for(let t of c){let r=B(t.status),i=t.target;if(i.kind===`url`){yield{from:t.fromPath,to:i.url,status:r};continue}if(i.kind===`entity`){let e=l.get(t.id);e!==void 0&&e!==t.fromPath&&(yield{from:t.fromPath,to:e,status:r});continue}let a=h(i.path);a!==t.fromPath&&await R(n.pathsClient,e,a)&&(yield{from:t.fromPath,to:a,status:r})}let u=s[s.length-1];if(!u||(a=u.id,s.length<r))break}}async function L(e,t,n){let r=new Map,i=new Map;for(let t of e){if(t.target.kind!==`entity`)continue;let e={rowId:t.id,targetId:t.target.id},n=i.get(t.target.entity);n?n.push(e):i.set(t.target.entity,[e])}for(let[e,a]of i){let i=await y(e,a.map(e=>e.targetId),t,n.canonicalBatch),o=[...i.keys()];if(o.length===0)continue;let s=await n.filterPublished(e,o,t);for(let{rowId:e,targetId:t}of a){let n=i.get(t);n!==void 0&&s.has(t)&&r.set(e,n)}}return r}async function R(e,t,n){return(await e.findMany({where:{locale:t,path:n,isCanonical:!0},limit:1})).length>0}async function*z(e,t,n,r){let i=new Set,a;for(;;){let o={locale:e,isCanonical:!1};a!==void 0&&(o.id={gt:a});let s=await t.pathsClient.findMany({where:o,orderBy:[{column:`id`,dir:`asc`}],limit:n});if(s.length===0)break;let c=new Map;for(let e of s){let t=c.get(e.entityType);t?t.push(e):c.set(e.entityType,[e])}let l=new Map;for(let[n,r]of c){let i=await y(n,r.map(e=>e.entityId),e,t.canonicalBatch),a=[...i.keys()];if(a.length===0)continue;let o=await t.filterPublished(n,a,e);for(let[e,t]of i)o.has(e)&&l.set(e,t)}for(let e of s){if(r.has(e.path)||i.has(e.path))continue;let t=l.get(e.entityId);t!==void 0&&t!==e.path&&(i.add(e.path),yield{from:e.path,to:t,status:301})}let u=s[s.length-1];if(!u||(a=u.id,s.length<n))break}}function B(e){return e===302?302:301}export{b as DEFAULT_MAX_URLS_PER_FILE,g as MAX_REDIRECT_HOPS,x as SITEMAP_INDEX_FILENAME,c as buildCanonicalPathDeps,i as buildCanonicalPathsBatchDeps,o as buildEnumerateDeps,r as buildPublishChecker,e as buildRedirectsListDeps,a as buildResolveDeps,s as buildSitemapDeps,t as createHomepageReader,p as createPublishChecker,v as enumeratePaths,S as generateSitemap,u as getAlternates,m as getCanonicalPath,y as getCanonicalPaths,F as getRedirects,l as homepagePointerSchema,n as parseHomepagePointer,_ as resolveDraftPath,d as resolvePath};
|
|
9
|
+
//# sourceMappingURL=resolve.mjs.map
|