@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.
Files changed (55) 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/chrome-provider.d.mts +107 -0
  5. package/dist/chrome-provider.d.mts.map +1 -0
  6. package/dist/chrome-provider.mjs +2 -0
  7. package/dist/chrome-provider.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +1 -1
  10. package/dist/client.mjs.map +1 -1
  11. package/dist/content-config-store-8omQF5p5.mjs +2 -0
  12. package/dist/content-config-store-8omQF5p5.mjs.map +1 -0
  13. package/dist/entities/index.d.mts +37 -11
  14. package/dist/entities/index.d.mts.map +1 -1
  15. package/dist/entities/index.mjs +1 -1
  16. package/dist/entities/index.mjs.map +1 -1
  17. package/dist/{index-D8nN7BFd.d.mts → index-Bsmju0UF.d.mts} +30 -3
  18. package/dist/index-Bsmju0UF.d.mts.map +1 -0
  19. package/dist/index.d.mts +4 -4
  20. package/dist/index.mjs +1 -1
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/locale-utils-DFEk4CE9.mjs +2 -0
  23. package/dist/{locale-utils-CuIBKeaz.mjs.map → locale-utils-DFEk4CE9.mjs.map} +1 -1
  24. package/dist/lock-service.d.mts +1 -1
  25. package/dist/plugin-BSxbiGc1.mjs +2 -0
  26. package/dist/plugin-BSxbiGc1.mjs.map +1 -0
  27. package/dist/plugin.d.mts +17 -6
  28. package/dist/plugin.d.mts.map +1 -1
  29. package/dist/plugin.mjs +1 -1
  30. package/dist/query-client.mjs +1 -1
  31. package/dist/resolve-context-BVk6uObB.mjs +2 -0
  32. package/dist/resolve-context-BVk6uObB.mjs.map +1 -0
  33. package/dist/resolve.d.mts +596 -0
  34. package/dist/resolve.d.mts.map +1 -0
  35. package/dist/resolve.mjs +9 -0
  36. package/dist/resolve.mjs.map +1 -0
  37. package/dist/routes-Bc9DcaPk.mjs +2 -0
  38. package/dist/routes-Bc9DcaPk.mjs.map +1 -0
  39. package/dist/{routing-strategy-TPSzrapD.d.mts → routing-strategy-D3Qowm3D.d.mts} +40 -6
  40. package/dist/routing-strategy-D3Qowm3D.d.mts.map +1 -0
  41. package/dist/routing-strategy-dkt2yO25.mjs +2 -0
  42. package/dist/routing-strategy-dkt2yO25.mjs.map +1 -0
  43. package/dist/schema-BJxAwPrI.mjs +2 -0
  44. package/dist/schema-BJxAwPrI.mjs.map +1 -0
  45. package/dist/{types-CUw2Pf0Y.d.mts → types-B3K2p9t8.d.mts} +22 -1
  46. package/dist/types-B3K2p9t8.d.mts.map +1 -0
  47. package/package.json +21 -7
  48. package/dist/index-D8nN7BFd.d.mts.map +0 -1
  49. package/dist/locale-utils-CuIBKeaz.mjs +0 -2
  50. package/dist/plugin-IW9SM9iO.mjs +0 -2
  51. package/dist/plugin-IW9SM9iO.mjs.map +0 -1
  52. package/dist/routing-strategy-DGx6Rq6F.mjs +0 -2
  53. package/dist/routing-strategy-DGx6Rq6F.mjs.map +0 -1
  54. package/dist/routing-strategy-TPSzrapD.d.mts.map +0 -1
  55. 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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"}
@@ -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,`&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