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