smoodly 0.0.35 → 0.0.36
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/README.md +29 -38
- package/dist/admin/drift.js +3 -2
- package/dist/admin/editor/SeoSettings.d.ts +7 -1
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/field-label.d.ts +2 -0
- package/dist/admin/field-label.js +2 -0
- package/dist/admin/forms/FieldWidget.js +2 -2
- package/dist/admin/next/create-admin.d.ts +4 -4
- package/dist/admin/next/create-admin.js +5 -5
- package/dist/admin/next/index.d.ts +1 -1
- package/dist/admin/serialize.d.ts +8 -1
- package/dist/admin/serialize.js +23 -7
- package/dist/admin/shell/AdminApp.d.ts +3 -3
- package/dist/admin/shell/AdminApp.js +2 -2
- package/dist/admin/shell/EntryForm.js +8 -1
- package/dist/admin/shell/presence-realtime.d.ts +2 -2
- package/dist/cli/space-io.js +1 -1
- package/dist/collections.d.ts +4 -0
- package/dist/collections.js +9 -1
- package/dist/entry-page.d.ts +12 -10
- package/dist/entry-store.d.ts +29 -0
- package/dist/entry-store.js +53 -0
- package/dist/env.d.ts +5 -5
- package/dist/env.js +2 -2
- package/dist/fields.d.ts +13 -7
- package/dist/fields.js +2 -2
- package/dist/global.d.ts +4 -7
- package/dist/index.d.ts +10 -39
- package/dist/index.js +1 -1
- package/dist/migrations.js +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/meta.d.ts +6 -4
- package/dist/next/meta.js +14 -17
- package/dist/next/page-renderer.js +6 -3
- package/dist/next/queries.d.ts +10 -11
- package/dist/next/queries.js +7 -2
- package/dist/page.d.ts +5 -2
- package/dist/page.js +3 -1
- package/dist/pairing.d.ts +10 -10
- package/dist/path-index.d.ts +3 -0
- package/dist/path-index.js +7 -0
- package/dist/read-shapes.d.ts +64 -0
- package/dist/read-shapes.js +52 -0
- package/dist/render.d.ts +3 -3
- package/dist/resolve.d.ts +12 -0
- package/dist/resolve.js +29 -7
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +1 -1
- package/dist/schema.d.ts +22 -1
- package/dist/seo.d.ts +18 -0
- package/dist/seo.js +53 -0
- package/dist/site.d.ts +15 -16
- package/dist/site.js +64 -58
- package/dist/sql-space.d.ts +6 -5
- package/dist/sql-space.js +11 -24
- package/dist/sql.d.ts +6 -1
- package/dist/sql.js +60 -23
- package/dist/supabase-entry-store.d.ts +3 -1
- package/dist/supabase-entry-store.js +39 -0
- package/dist/supabase-path-index.d.ts +1 -0
- package/dist/supabase-path-index.js +16 -0
- package/migrations/20260930040956_smoodly_v7.sql +84 -0
- package/package.json +1 -1
package/dist/entry-store.js
CHANGED
|
@@ -172,6 +172,32 @@ export function compareEntries(order, seq) {
|
|
|
172
172
|
return d !== 0 ? d * dir : (seq(a) - seq(b)) * dir;
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
+
/** Comparator for `query`: like compareEntries, but a declared field reads the
|
|
176
|
+
* merged snapshot, `updatedAt` the locale row's, and an absent, null or empty-string value
|
|
177
|
+
* sorts last in both directions; ties break by creation order (ascending). */
|
|
178
|
+
export function compareSnapshots(order, locale, seq) {
|
|
179
|
+
return (a, b) => {
|
|
180
|
+
if (order === "manual") {
|
|
181
|
+
const d = (a.record.sort ?? Infinity) - (b.record.sort ?? Infinity);
|
|
182
|
+
return d !== 0 ? d : seq(a.record.id) - seq(b.record.id);
|
|
183
|
+
}
|
|
184
|
+
const dir = order.direction === "asc" ? 1 : -1;
|
|
185
|
+
const value = (r) => order.by === "createdAt" ? r.record.createdAt : order.by === "updatedAt" ? r.record.locales[locale]?.updatedAt : r.fields[order.by];
|
|
186
|
+
const empty = (v) => (v === "" ? undefined : v); // a cleared text or date is empty too
|
|
187
|
+
const va = empty(value(a)), vb = empty(value(b));
|
|
188
|
+
const tie = seq(a.record.id) - seq(b.record.id);
|
|
189
|
+
if (order.by === "createdAt")
|
|
190
|
+
return tie * dir; // the clock can tie within a millisecond; creation order cannot
|
|
191
|
+
if (va == null && vb == null)
|
|
192
|
+
return tie;
|
|
193
|
+
if (va == null)
|
|
194
|
+
return 1;
|
|
195
|
+
if (vb == null)
|
|
196
|
+
return -1;
|
|
197
|
+
const d = typeof va === "number" && typeof vb === "number" ? va - vb : String(va).localeCompare(String(vb));
|
|
198
|
+
return d !== 0 ? d * dir : tie;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
175
201
|
export class MemoryEntryStore {
|
|
176
202
|
constructor(collections,
|
|
177
203
|
// pass the SAME index to MemoryPageStore so cross-kind questions work
|
|
@@ -389,6 +415,29 @@ export class MemoryEntryStore {
|
|
|
389
415
|
rows = rows.slice(0, options.limit);
|
|
390
416
|
return rows.map((e) => this.snapshot(e));
|
|
391
417
|
}
|
|
418
|
+
async query(collection, q) {
|
|
419
|
+
const schema = this.schema(collection);
|
|
420
|
+
const wanted = q.ids ? new Set(q.ids) : null;
|
|
421
|
+
const rows = [];
|
|
422
|
+
for (const e of this.entries.values()) {
|
|
423
|
+
if (e.collection !== collection)
|
|
424
|
+
continue;
|
|
425
|
+
if (wanted && !wanted.has(e.id))
|
|
426
|
+
continue;
|
|
427
|
+
if (q.parent !== undefined && e.parentId !== q.parent)
|
|
428
|
+
continue;
|
|
429
|
+
const row = e.locales[q.locale];
|
|
430
|
+
if (!row || (!q.draft && row.status !== "published"))
|
|
431
|
+
continue;
|
|
432
|
+
const snapshot = !q.draft && row.publishedVersionId ? this.versions.get(row.publishedVersionId) : undefined;
|
|
433
|
+
const fields = snapshot ? structuredClone(snapshot.fields) : entryFieldsIn(e, q.locale);
|
|
434
|
+
if (q.where && !matchesWhere(fields, q.where))
|
|
435
|
+
continue;
|
|
436
|
+
rows.push({ record: this.snapshot(e), fields });
|
|
437
|
+
}
|
|
438
|
+
rows.sort(compareSnapshots(q.order ?? collectionOrder(schema), q.locale, (id) => this.order.get(id)));
|
|
439
|
+
return typeof q.limit === "number" ? rows.slice(0, q.limit) : rows;
|
|
440
|
+
}
|
|
392
441
|
async getMany(collection, ids, locale, options) {
|
|
393
442
|
this.schema(collection);
|
|
394
443
|
const out = {};
|
|
@@ -644,6 +693,10 @@ export class MemoryEntryStore {
|
|
|
644
693
|
resolvePath(locale, path) {
|
|
645
694
|
return this.paths.resolve(locale, path);
|
|
646
695
|
}
|
|
696
|
+
pathsIn(collection, ids, locale) {
|
|
697
|
+
this.schema(collection);
|
|
698
|
+
return this.paths.livePathsIn("entry", ids, locale);
|
|
699
|
+
}
|
|
647
700
|
pathsOf(collection, id) {
|
|
648
701
|
return this.row(collection, id) ? this.paths.pathsOf({ kind: "entry", id }) : Promise.resolve({});
|
|
649
702
|
}
|
package/dist/env.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type SmoodlyEnv = {
|
|
|
4
4
|
url: string;
|
|
5
5
|
/** Server-only: the service-role key (self-host) or the environment's write key (cloud). */
|
|
6
6
|
secretKey: string;
|
|
7
|
-
/** What a browser may hold for the content project: anon/publishable key, or the
|
|
7
|
+
/** What a browser may hold for the content project: anon/publishable key, or the cloud's per-environment key. It reads no content (sign-in and presence only). */
|
|
8
8
|
publishableKey: string;
|
|
9
9
|
/** Where editors sign in. Defaults to `url`. */
|
|
10
10
|
authUrl: string;
|
|
@@ -15,12 +15,12 @@ export declare function resolveSmoodlyEnv(env: Record<string, string | undefined
|
|
|
15
15
|
/** `resolveSmoodlyEnv(process.env)` — call lazily, never at module load,
|
|
16
16
|
* so `next build` without env still succeeds. */
|
|
17
17
|
export declare const smoodlyEnv: () => SmoodlyEnv;
|
|
18
|
-
/** What the browser needs to join the content project's
|
|
19
|
-
export type
|
|
18
|
+
/** What the browser needs to join the admin's presence channel: the content project's URL, publishable key and space, public values only — never for content reads (spec 2026-09-29 content API contract §8). */
|
|
19
|
+
export type PresenceConfig = {
|
|
20
20
|
url: string;
|
|
21
21
|
key: string;
|
|
22
22
|
spaceId: string;
|
|
23
23
|
};
|
|
24
|
-
/** The
|
|
24
|
+
/** The presence config, with the space the database says the secret key acts in —
|
|
25
25
|
* asked, never read out of the key, so it cannot disagree with where rows land. */
|
|
26
|
-
export declare function
|
|
26
|
+
export declare function presenceConfig(env: SmoodlyEnv, db?: Parameters<typeof readCurrentSpace>[0]): Promise<PresenceConfig>;
|
package/dist/env.js
CHANGED
|
@@ -25,9 +25,9 @@ export const smoodlyEnv = () => resolveSmoodlyEnv(process.env);
|
|
|
25
25
|
/** One lookup per process per content project and key: the space never
|
|
26
26
|
* changes for a key. A failed lookup is not cached, so the next call retries. */
|
|
27
27
|
const spaces = new Map();
|
|
28
|
-
/** The
|
|
28
|
+
/** The presence config, with the space the database says the secret key acts in —
|
|
29
29
|
* asked, never read out of the key, so it cannot disagree with where rows land. */
|
|
30
|
-
export function
|
|
30
|
+
export function presenceConfig(env, db) {
|
|
31
31
|
const cacheKey = `${env.url}\n${env.secretKey}`;
|
|
32
32
|
let space = spaces.get(cacheKey);
|
|
33
33
|
if (!space) {
|
package/dist/fields.d.ts
CHANGED
|
@@ -24,6 +24,14 @@ export declare function normalizeOptions<V extends string>(options: readonly Opt
|
|
|
24
24
|
export declare function optionsOf(options: unknown): Option[];
|
|
25
25
|
/** The values a stored choice may take. */
|
|
26
26
|
export declare const optionValues: (options: unknown) => string[];
|
|
27
|
+
/** A ref field's value at the type level: the target and how deep the ref site resolves it.
|
|
28
|
+
* Never a runtime value — `Values<M>` turns it into what a read returns. */
|
|
29
|
+
export type RefMode = "shallow" | "full" | (string & {});
|
|
30
|
+
export type RefTo<T, M extends RefMode = "shallow"> = {
|
|
31
|
+
readonly __refTo: T;
|
|
32
|
+
readonly __mode: M;
|
|
33
|
+
};
|
|
34
|
+
type Rebase<V, M extends RefMode> = V extends RefTo<infer T, any> ? RefTo<T, M> : V extends RefTo<infer T, any>[] ? RefTo<T, M>[] : V;
|
|
27
35
|
export type FieldBuilder<Value = unknown, Flags extends BuilderFlags = {}> = {
|
|
28
36
|
readonly descriptor: Descriptor;
|
|
29
37
|
optional(): FieldBuilder<Value, Flags & {
|
|
@@ -43,7 +51,9 @@ export type FieldBuilder<Value = unknown, Flags extends BuilderFlags = {}> = {
|
|
|
43
51
|
* the named refs, one level; object values nest deeper
|
|
44
52
|
* - no call — hydrate the entry itself; its refs stay ids
|
|
45
53
|
*/
|
|
46
|
-
resolve(
|
|
54
|
+
resolve(): FieldBuilder<Rebase<Value, "full">, Flags>;
|
|
55
|
+
resolve<const K extends string>(...keys: K[]): FieldBuilder<Rebase<Value, K>, Flags>;
|
|
56
|
+
resolve<const P extends Record<string, unknown>>(projection: P): FieldBuilder<Rebase<Value, keyof P & string>, Flags>;
|
|
47
57
|
/** Hide the field while the condition holds (spec 2026-09-14 §12): it leaves the form and validation skips it; the stored value is kept. */
|
|
48
58
|
hidden(condition: Condition): FieldBuilder<Value, Flags>;
|
|
49
59
|
/** phantom, type-level only */
|
|
@@ -105,14 +115,10 @@ export declare const f: {
|
|
|
105
115
|
* 300px sidebar cannot show one legibly (spec 2026-09-07, list field §1).
|
|
106
116
|
* `.min`/`.max` count items. */
|
|
107
117
|
list: <B extends FieldBuilder<any, any>>(item: B & NotLocalized<B>) => FieldBuilder<ValueOf<B>[], {}>;
|
|
108
|
-
ref: <T>(target: () => T) => FieldBuilder<
|
|
109
|
-
refList: <T>(target: () => T) => FieldBuilder<
|
|
118
|
+
ref: <T>(target: () => T) => FieldBuilder<RefTo<T, "shallow">, {}>;
|
|
119
|
+
refList: <T>(target: () => T) => FieldBuilder<RefTo<T, "shallow">[], {}>;
|
|
110
120
|
};
|
|
111
121
|
export type ValueOf<B> = B extends FieldBuilder<infer V, infer Flags> ? Flags extends {
|
|
112
122
|
optional: true;
|
|
113
123
|
} ? V | undefined : V : never;
|
|
114
|
-
/** Resolved entry for a ref target — refined when collections land. */
|
|
115
|
-
export type Resolved<T> = T extends {
|
|
116
|
-
__entry?: infer E;
|
|
117
|
-
} ? E : Record<string, unknown>;
|
|
118
124
|
export {};
|
package/dist/fields.js
CHANGED
|
@@ -37,13 +37,13 @@ function builder(descriptor) {
|
|
|
37
37
|
max: (n) => chain({ max: n }),
|
|
38
38
|
min: (n) => chain({ min: n }),
|
|
39
39
|
default: (v) => chain({ default: v }),
|
|
40
|
-
resolve: (...projection) => {
|
|
40
|
+
resolve: ((...projection) => {
|
|
41
41
|
if (projection.length === 0)
|
|
42
42
|
return chain({ resolve: "full" });
|
|
43
43
|
if (typeof projection[0] === "object")
|
|
44
44
|
return chain({ resolve: projection[0] });
|
|
45
45
|
return chain({ resolve: Object.fromEntries(projection.map((k) => [k, true])) });
|
|
46
|
-
},
|
|
46
|
+
}),
|
|
47
47
|
hidden: (condition) => chain({ hidden: condition }),
|
|
48
48
|
};
|
|
49
49
|
}
|
package/dist/global.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { CollectionSchema } from "./collections.ts";
|
|
2
2
|
import type { Descriptor } from "./fields.ts";
|
|
3
3
|
import type { PairedComponent } from "./pairing.tsx";
|
|
4
|
-
import type { BuilderMap,
|
|
5
|
-
import type {
|
|
4
|
+
import type { BuilderMap, SectionSchema, Values } from "./schema.ts";
|
|
5
|
+
import type { GlobalItem, VisualGlobalItem } from "./read-shapes.ts";
|
|
6
6
|
/** The reserved tree node type of a placement. */
|
|
7
7
|
export declare const GLOBAL_NODE_TYPE = "global";
|
|
8
8
|
/** The reserved keys a visual item's settings, styles steps and css live under in its row (spec 2026-09-14 §8). */
|
|
@@ -46,12 +46,9 @@ type GlobalInput<N extends string = string> = {
|
|
|
46
46
|
export declare function globalItem<F extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
47
47
|
fields: F;
|
|
48
48
|
section?: undefined;
|
|
49
|
-
}): GlobalSchema<
|
|
49
|
+
}): GlobalSchema<GlobalItem<Values<F>>, N>;
|
|
50
50
|
export declare function globalItem<F extends BuilderMap, St extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
51
51
|
section: PairedComponent<SectionSchema<F, St>, F, St>;
|
|
52
52
|
fields?: undefined;
|
|
53
|
-
}): GlobalSchema<
|
|
54
|
-
settings: NamespaceValues<St>;
|
|
55
|
-
styles: StyleProps;
|
|
56
|
-
}, N>;
|
|
53
|
+
}): GlobalSchema<VisualGlobalItem<Values<F>, Values<St>>, N>;
|
|
57
54
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type { PathIndex } from "./path-index.ts";
|
|
|
51
51
|
export { MemoryEntryStore } from "./entry-store.ts";
|
|
52
52
|
export { SupabaseEntryStore } from "./supabase-entry-store.ts";
|
|
53
53
|
export { SupabasePathIndex } from "./supabase-path-index.ts";
|
|
54
|
-
export type { EntryStore, EntryRecord, EntryLocale, EntryVersion, EntryScan, EntryUsage, ListOptions, CreateEntryOptions, AddEntryLocaleOptions, EntryStoreOptions, Where, WhereValue, } from "./entry-store.ts";
|
|
54
|
+
export type { EntryStore, EntryRecord, EntryLocale, EntryVersion, EntryScan, EntryUsage, ListOptions, CreateEntryOptions, AddEntryLocaleOptions, EntryStoreOptions, Where, WhereValue, SnapshotQuery, SnapshotRow, } from "./entry-store.ts";
|
|
55
55
|
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, matchesWhere, } from "./entry-store.ts";
|
|
56
56
|
export { affectedTargets, pageTag, entryTag, globalTag, pagesTreeTag, collectionTag } from "./revalidate.ts";
|
|
57
57
|
export type { AffectedTargets } from "./revalidate.ts";
|
|
@@ -66,7 +66,10 @@ export type { PageStore, PageRecord, PageLocale, PageVersion, PageScan, PageStor
|
|
|
66
66
|
export { pathOptions, pageErrors, localeRow, pageRefEdges, DEFAULT_LOCALES } from "./store.ts";
|
|
67
67
|
export type { SmoodlyConfig, ResolvedConfig } from "./config.ts";
|
|
68
68
|
export type { FieldBuilder, Descriptor, ValueOf, Option, OptionInput, OptionValue } from "./fields.ts";
|
|
69
|
-
export type { SectionSchema, ElementSchema, BuilderMap } from "./schema.ts";
|
|
69
|
+
export type { SectionSchema, ElementSchema, BuilderMap, Values } from "./schema.ts";
|
|
70
|
+
export type { CollectionSeo } from "./seo.ts";
|
|
71
|
+
export type { EntryRef, EntryItem, PageItem, GlobalItem, VisualGlobalItem, Seo } from "./read-shapes.ts";
|
|
72
|
+
export type { RefTo } from "./fields.ts";
|
|
70
73
|
export type { Props, PairedComponent } from "./pairing.tsx";
|
|
71
74
|
export type { CollectionSchema, EntryOrder } from "./collections.ts";
|
|
72
75
|
export { DEFAULT_TOOLSET, type Toolset, type ToolsetMark, type ToolsetNode } from "./toolset.ts";
|
|
@@ -94,23 +97,7 @@ export declare const smoodly: {
|
|
|
94
97
|
omit: string[];
|
|
95
98
|
};
|
|
96
99
|
css?: false;
|
|
97
|
-
sample?: Partial<{ [
|
|
98
|
-
__flags?: infer Fl;
|
|
99
|
-
} ? Fl extends {
|
|
100
|
-
optional: true;
|
|
101
|
-
} ? K : never : never; }[keyof F]>]: import("./fields.ts").ValueOf<F[K_1]>; } & { [K_2 in { [K in keyof F]: F[K] extends {
|
|
102
|
-
__flags?: infer Fl;
|
|
103
|
-
} ? Fl extends {
|
|
104
|
-
optional: true;
|
|
105
|
-
} ? K : never : never; }[keyof F]]?: import("./fields.ts").ValueOf<F[K_2]> | undefined; } & { [K_4 in Exclude<keyof S, { [K_3 in keyof S]: S[K_3] extends {
|
|
106
|
-
__flags?: infer Fl;
|
|
107
|
-
} ? Fl extends {
|
|
108
|
-
optional: true;
|
|
109
|
-
} ? K_3 : never : never; }[keyof S]>]: import("./fields.ts").ValueOf<S[K_4]>; } & { [K_5 in { [K_3 in keyof S]: S[K_3] extends {
|
|
110
|
-
__flags?: infer Fl;
|
|
111
|
-
} ? Fl extends {
|
|
112
|
-
optional: true;
|
|
113
|
-
} ? K_3 : never : never; }[keyof S]]?: import("./fields.ts").ValueOf<S[K_5]> | undefined; }> | undefined;
|
|
100
|
+
sample?: Partial<{ [K in keyof import("./schema.ts").NamespaceValues<F>]: import("./schema.ts").NamespaceValues<F>[K] extends import("./fields.ts").RefTo<any, any> ? string : import("./schema.ts").NamespaceValues<F>[K] extends import("./fields.ts").RefTo<any, any>[] ? string[] : import("./schema.ts").NamespaceValues<F>[K]; } & { [K_1 in keyof import("./schema.ts").NamespaceValues<S>]: import("./schema.ts").NamespaceValues<S>[K_1] extends import("./fields.ts").RefTo<any, any> ? string : import("./schema.ts").NamespaceValues<S>[K_1] extends import("./fields.ts").RefTo<any, any>[] ? string[] : import("./schema.ts").NamespaceValues<S>[K_1]; }> | undefined;
|
|
114
101
|
}): import("./schema.ts").SectionSchema<F, S>;
|
|
115
102
|
element<F extends import("./schema.ts").BuilderMap = {}, S extends import("./schema.ts").BuilderMap = {}>(input: {
|
|
116
103
|
name: string;
|
|
@@ -124,23 +111,7 @@ export declare const smoodly: {
|
|
|
124
111
|
omit: string[];
|
|
125
112
|
};
|
|
126
113
|
css?: false;
|
|
127
|
-
sample?: Partial<{ [
|
|
128
|
-
__flags?: infer Fl;
|
|
129
|
-
} ? Fl extends {
|
|
130
|
-
optional: true;
|
|
131
|
-
} ? K : never : never; }[keyof F]>]: import("./fields.ts").ValueOf<F[K_1]>; } & { [K_2 in { [K in keyof F]: F[K] extends {
|
|
132
|
-
__flags?: infer Fl;
|
|
133
|
-
} ? Fl extends {
|
|
134
|
-
optional: true;
|
|
135
|
-
} ? K : never : never; }[keyof F]]?: import("./fields.ts").ValueOf<F[K_2]> | undefined; } & { [K_4 in Exclude<keyof S, { [K_3 in keyof S]: S[K_3] extends {
|
|
136
|
-
__flags?: infer Fl;
|
|
137
|
-
} ? Fl extends {
|
|
138
|
-
optional: true;
|
|
139
|
-
} ? K_3 : never : never; }[keyof S]>]: import("./fields.ts").ValueOf<S[K_4]>; } & { [K_5 in { [K_3 in keyof S]: S[K_3] extends {
|
|
140
|
-
__flags?: infer Fl;
|
|
141
|
-
} ? Fl extends {
|
|
142
|
-
optional: true;
|
|
143
|
-
} ? K_3 : never : never; }[keyof S]]?: import("./fields.ts").ValueOf<S[K_5]> | undefined; }> | undefined;
|
|
114
|
+
sample?: Partial<{ [K in keyof import("./schema.ts").NamespaceValues<F>]: import("./schema.ts").NamespaceValues<F>[K] extends import("./fields.ts").RefTo<any, any> ? string : import("./schema.ts").NamespaceValues<F>[K] extends import("./fields.ts").RefTo<any, any>[] ? string[] : import("./schema.ts").NamespaceValues<F>[K]; } & { [K_1 in keyof import("./schema.ts").NamespaceValues<S>]: import("./schema.ts").NamespaceValues<S>[K_1] extends import("./fields.ts").RefTo<any, any> ? string : import("./schema.ts").NamespaceValues<S>[K_1] extends import("./fields.ts").RefTo<any, any>[] ? string[] : import("./schema.ts").NamespaceValues<S>[K_1]; }> | undefined;
|
|
144
115
|
} & {
|
|
145
116
|
contexts: ("richtext" | "column")[];
|
|
146
117
|
}): import("./schema.ts").ElementSchema<F, S>;
|
|
@@ -157,8 +128,8 @@ export declare const smoodly: {
|
|
|
157
128
|
};
|
|
158
129
|
export { NODE_ATTR, ZONE_ATTR, EMPTY_ATTR, BLANK_ATTR, FIELD_ATTR, ITEM_ATTR, EDITOR_ATTR, resolveMarkPath } from "./marks.ts";
|
|
159
130
|
export type { MarkNode, MarkPath, Marks, Scope } from "./marks.ts";
|
|
160
|
-
export { resolveSmoodlyEnv, smoodlyEnv,
|
|
161
|
-
export type { SmoodlyEnv,
|
|
131
|
+
export { resolveSmoodlyEnv, smoodlyEnv, presenceConfig } from "./env.ts";
|
|
132
|
+
export type { SmoodlyEnv, PresenceConfig } from "./env.ts";
|
|
162
133
|
export { clientOptions, createSmoodlyClient } from "./client.ts";
|
|
163
134
|
export { createSmoodlySite, pageContextOf } from "./site.ts";
|
|
164
|
-
export type { SiteOptions,
|
|
135
|
+
export type { SiteOptions, SitePageSchema, SmoodlySite, LoadOptions, PageTreeItem, EntryTreeItem, EntryQuery, } from "./site.ts";
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,6 @@ export const smoodly = {
|
|
|
66
66
|
marks,
|
|
67
67
|
};
|
|
68
68
|
export { NODE_ATTR, ZONE_ATTR, EMPTY_ATTR, BLANK_ATTR, FIELD_ATTR, ITEM_ATTR, EDITOR_ATTR, resolveMarkPath } from "./marks.js";
|
|
69
|
-
export { resolveSmoodlyEnv, smoodlyEnv,
|
|
69
|
+
export { resolveSmoodlyEnv, smoodlyEnv, presenceConfig } from "./env.js";
|
|
70
70
|
export { clientOptions, createSmoodlyClient } from "./client.js";
|
|
71
71
|
export { createSmoodlySite, pageContextOf } from "./site.js";
|
package/dist/migrations.js
CHANGED
|
@@ -43,7 +43,7 @@ export function viewsFile(collections, existing, at) {
|
|
|
43
43
|
const changed = collections.filter((c) => existing.get(c.name) !== collectionHash(c.sql));
|
|
44
44
|
if (changed.length === 0)
|
|
45
45
|
return null;
|
|
46
|
-
const text = "-- Smoodly: read views
|
|
46
|
+
const text = "-- Smoodly: read views for this app's collections, written by `npx smoodly migrate`\n" +
|
|
47
47
|
`-- (${at.toISOString()}). One block per collection; the marker's hash is the block's SQL, compared on the next run.\n` +
|
|
48
48
|
"-- Never edit a written file: change the collection and run migrate again.\n\n" +
|
|
49
49
|
changed.map((c) => `${collectionMarker(c)}\n${c.sql.trim()}`).join("\n\n") +
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createPageRenderer } from "./page-renderer.tsx";
|
|
2
2
|
export type { PageRendererOptions, PathRenderOptions, PageRegistration } from "./page-renderer.tsx";
|
|
3
|
-
export { metadataFrom } from "./meta.ts";
|
|
3
|
+
export { metadataFrom, metadataFromSeo } from "./meta.ts";
|
|
4
4
|
export { createSmoodlyQueries } from "./queries.ts";
|
|
5
5
|
export type { SmoodlyQueries, QueryOptions, EntriesQuery, TypedEntryItem, GlobalValue } from "./queries.ts";
|
|
6
6
|
export { previewRoute } from "./preview-route.ts";
|
package/dist/next/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createPageRenderer } from "./page-renderer.js";
|
|
2
|
-
export { metadataFrom } from "./meta.js";
|
|
2
|
+
export { metadataFrom, metadataFromSeo } from "./meta.js";
|
|
3
3
|
export { createSmoodlyQueries } from "./queries.js";
|
|
4
4
|
export { previewRoute } from "./preview-route.js";
|
|
5
5
|
export { previewPill } from "./preview-pill.js";
|
package/dist/next/meta.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* layout
|
|
2
|
+
import type { Seo } from "../read-shapes.ts";
|
|
3
|
+
/** Maps the effective SEO values onto Next metadata (spec 2026-09-10 page
|
|
4
|
+
* fields §4, 2026-09-29 content API contract §7). Absent values leave the
|
|
5
|
+
* app's layout defaults untouched: no seo at all returns {}. */
|
|
6
|
+
export declare function metadataFromSeo(seo: Seo | undefined): Metadata;
|
|
7
|
+
/** A page tree's stored meta as Next metadata; the SEO title falls back to the page's own title. */
|
|
6
8
|
export declare function metadataFrom(meta: unknown, page?: {
|
|
7
9
|
title?: string;
|
|
8
10
|
}): Metadata;
|
package/dist/next/meta.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { imageUrl } from "../image/loaders.js";
|
|
2
|
-
|
|
3
|
-
/** Maps the
|
|
4
|
-
* fields §4
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const m = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : {};
|
|
9
|
-
const str = (key) => (typeof m[key] === "string" && m[key] ? m[key] : undefined);
|
|
10
|
-
const title = str("title") ?? (page?.title ? page.title : undefined);
|
|
11
|
-
const description = str("description");
|
|
12
|
-
const ogImage = m.ogImage;
|
|
2
|
+
import { pageSeo } from "../seo.js";
|
|
3
|
+
/** Maps the effective SEO values onto Next metadata (spec 2026-09-10 page
|
|
4
|
+
* fields §4, 2026-09-29 content API contract §7). Absent values leave the
|
|
5
|
+
* app's layout defaults untouched: no seo at all returns {}. */
|
|
6
|
+
export function metadataFromSeo(seo) {
|
|
7
|
+
const { title, description, image, noindex } = seo ?? {};
|
|
13
8
|
const out = {};
|
|
14
9
|
if (title)
|
|
15
10
|
out.title = title;
|
|
@@ -20,16 +15,18 @@ export function metadataFrom(meta, page) {
|
|
|
20
15
|
og.title = title;
|
|
21
16
|
if (description)
|
|
22
17
|
og.description = description;
|
|
23
|
-
if (
|
|
18
|
+
if (image) {
|
|
24
19
|
// The delivery seam decides the URL; the cloud's transform loader (OQ 28) can serve a real 1200-wide render.
|
|
25
|
-
og.images = [{ url: imageUrl(
|
|
26
|
-
}
|
|
27
|
-
else if (typeof ogImage === "string" && ogImage) {
|
|
28
|
-
og.images = [ogImage];
|
|
20
|
+
og.images = [{ url: imageUrl(image, { width: 1200 }), width: image.width, height: image.height, ...(image.alt ? { alt: image.alt } : {}) }];
|
|
29
21
|
}
|
|
30
22
|
if (Object.keys(og).length > 0)
|
|
31
23
|
out.openGraph = og;
|
|
32
|
-
if (
|
|
24
|
+
if (noindex === true)
|
|
33
25
|
out.robots = { index: false, follow: false };
|
|
34
26
|
return out;
|
|
35
27
|
}
|
|
28
|
+
/** A page tree's stored meta as Next metadata; the SEO title falls back to the page's own title. */
|
|
29
|
+
export function metadataFrom(meta, page) {
|
|
30
|
+
const m = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : {};
|
|
31
|
+
return metadataFromSeo(pageSeo(m, page?.title ?? ""));
|
|
32
|
+
}
|
|
@@ -16,8 +16,9 @@ import { fixedPathFor } from "../localize.js";
|
|
|
16
16
|
import { decodeSegment, joinPath } from "../paths.js";
|
|
17
17
|
import { renderPage } from "../render.js";
|
|
18
18
|
import { entryTag, pageTag, globalTag } from "../revalidate.js";
|
|
19
|
+
import { toEntryItem } from "../read-shapes.js";
|
|
19
20
|
import { pageContextOf } from "../site.js";
|
|
20
|
-
import {
|
|
21
|
+
import { metadataFromSeo } from "./meta.js";
|
|
21
22
|
import { previewPill } from "./preview-pill.js";
|
|
22
23
|
/** Logged once per renderer when Next's cacheComponents is on (spec 2026-09-27 cache components §2). */
|
|
23
24
|
export const CACHE_COMPONENTS_WARNING = "smoodly: Next's cacheComponents is on, and Smoodly does not support it — a missing page answers 200 with noindex instead of 404, and a redirect answers 200 instead of 308. Turn cacheComponents off in next.config.ts; the Next.js guide's \"Cache Components\" section says why (https://smoodly.io/docs/frameworks/nextjs).";
|
|
@@ -73,7 +74,7 @@ export function createPageRenderer(options) {
|
|
|
73
74
|
console.warn(`smoodly: no entry page for collection "${hit.collection}" — the entry at ${hit.path} is a 404.`);
|
|
74
75
|
notFound();
|
|
75
76
|
}
|
|
76
|
-
return _jsx(View, { entry: hit.entry,
|
|
77
|
+
return _jsx(View, { entry: toEntryItem(hit.entry, hit.entry.fields, hit) });
|
|
77
78
|
}
|
|
78
79
|
/** hreflang from the SAME map a language switch reads (spec 2026-09-15 §3): the locales with a URL, through href. */
|
|
79
80
|
function alternates(hit) {
|
|
@@ -133,7 +134,9 @@ export function createPageRenderer(options) {
|
|
|
133
134
|
const hit = await load(target, locale, path, draft);
|
|
134
135
|
if (!hit || !accept(hit))
|
|
135
136
|
return {};
|
|
136
|
-
|
|
137
|
+
// A page type without seo: true still gets its page title as <title>; seo: true adds the panel's values on top.
|
|
138
|
+
const seo = hit.seo ?? (hit.kind === "page" ? { title: hit.title } : undefined);
|
|
139
|
+
return { ...metadataFromSeo(seo), ...alternates(hit) };
|
|
137
140
|
}
|
|
138
141
|
const any = () => true;
|
|
139
142
|
/** The escape hatch may only render ITS OWN registration's record. */
|
package/dist/next/queries.d.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
import type { CollectionSchema } from "../collections.ts";
|
|
2
|
-
import type { EntryRecord } from "../entry-store.ts";
|
|
3
2
|
import type { GlobalSchema } from "../global.ts";
|
|
4
|
-
import
|
|
3
|
+
import { type EntryItem, type PageItem } from "../read-shapes.ts";
|
|
4
|
+
import type { Materialize } from "../schema.ts";
|
|
5
|
+
import type { EntryQuery, PageTreeItem, SmoodlySite } from "../site.ts";
|
|
5
6
|
export type QueryOptions = {
|
|
6
7
|
locale?: string;
|
|
7
8
|
};
|
|
8
|
-
export type EntriesQuery = Omit<EntryQuery, "locale" | "draft"> & QueryOptions;
|
|
9
|
+
export type EntriesQuery = Omit<EntryQuery, "locale" | "draft" | "ids"> & QueryOptions;
|
|
9
10
|
type ByName<T, N> = Extract<T, {
|
|
10
11
|
name: N;
|
|
11
12
|
}>;
|
|
12
|
-
/** Fields typed from the schema; a widened `CollectionSchema[]` array (names are `string`) falls back to the untyped record instead of `never`. */
|
|
13
|
-
type EntryFields<C extends readonly CollectionSchema<any, string>[], N> = string extends C[number]["name"] ? Record<string, unknown> :
|
|
14
|
-
|
|
15
|
-
export type TypedEntryItem<C extends readonly CollectionSchema<any, string>[], N> = Omit<SiteEntryItem, "entry"> & {
|
|
16
|
-
entry: Omit<EntryRecord, "fields"> & {
|
|
17
|
-
fields: EntryFields<C, N>;
|
|
18
|
-
};
|
|
13
|
+
/** Fields typed from the schema, references materialized as `EntryRef`s; a widened `CollectionSchema[]` array (names are `string`) falls back to the untyped record instead of `never`. */
|
|
14
|
+
type EntryFields<C extends readonly CollectionSchema<any, string>[], N> = string extends C[number]["name"] ? Record<string, unknown> : {
|
|
15
|
+
[K in keyof NonNullable<ByName<C[number], N>["__entry"]>]: Materialize<NonNullable<ByName<C[number], N>["__entry"]>[K]>;
|
|
19
16
|
};
|
|
17
|
+
/** A read item whose `fields` are the collection's typed values. */
|
|
18
|
+
export type TypedEntryItem<C extends readonly CollectionSchema<any, string>[], N> = EntryItem<EntryFields<C, N>>;
|
|
20
19
|
export type GlobalValue<S extends readonly GlobalSchema<any, string>[], N> = string extends S[number]["name"] ? Record<string, unknown> : NonNullable<ByName<S[number], N>["__value"]>;
|
|
21
20
|
export type SmoodlyQueries<C extends readonly CollectionSchema<any, string>[], S extends readonly GlobalSchema<any, string>[]> = {
|
|
22
21
|
/** The published page tree in a locale, every item with its page fields. */
|
|
23
22
|
getPageTree(options?: QueryOptions): Promise<PageTreeItem[]>;
|
|
24
23
|
/** A page by locale-relative path ("/company/about-us") or id. */
|
|
25
|
-
getPage(pathOrId: string, options?: QueryOptions): Promise<
|
|
24
|
+
getPage(pathOrId: string, options?: QueryOptions): Promise<PageItem | null>;
|
|
26
25
|
/** Published entries of a collection: `where` (shared fields), `order`, `limit`, `parent` run in the store. */
|
|
27
26
|
getEntries<N extends C[number]["name"]>(collection: N, options?: EntriesQuery): Promise<TypedEntryItem<C, N>[]>;
|
|
28
27
|
getEntry<N extends C[number]["name"]>(collection: N, idOrPath: string, options?: QueryOptions): Promise<TypedEntryItem<C, N> | null>;
|
package/dist/next/queries.js
CHANGED
|
@@ -7,6 +7,7 @@ import { unstable_cache } from "next/cache";
|
|
|
7
7
|
import { draftMode } from "next/headers";
|
|
8
8
|
import { connection } from "next/server";
|
|
9
9
|
import { collectionTag, entryTag, pageTag, pagesTreeTag, globalTag } from "../revalidate.js";
|
|
10
|
+
import { referencedCollections } from "../read-shapes.js";
|
|
10
11
|
/** JSON with object keys sorted at every level and `undefined` dropped, so two spellings of one query share a unit. */
|
|
11
12
|
function stable(value) {
|
|
12
13
|
if (Array.isArray(value))
|
|
@@ -26,6 +27,7 @@ function entriesKey(o) {
|
|
|
26
27
|
}
|
|
27
28
|
export function createSmoodlyQueries(options) {
|
|
28
29
|
const { site } = options;
|
|
30
|
+
const collectionByName = new Map(options.collections.map((c) => [c.name, c]));
|
|
29
31
|
const globalByName = new Map((options.globals ?? []).map((s) => [s.name, s]));
|
|
30
32
|
const localeOf = (o) => o?.locale ?? site.locales.default;
|
|
31
33
|
/** Every query gates on the request (never a build prerender), then reads live in draft mode or cached under `tags`. */
|
|
@@ -59,7 +61,10 @@ export function createSmoodlyQueries(options) {
|
|
|
59
61
|
getEntries: (collection, o = {}) => {
|
|
60
62
|
const locale = localeOf(o);
|
|
61
63
|
const { locale: _locale, ...query } = o;
|
|
62
|
-
|
|
64
|
+
// The unit also depends on every collection its references resolve into (spec 2026-09-29 content API contract §6).
|
|
65
|
+
const schema = collectionByName.get(collection);
|
|
66
|
+
const tags = [collection, ...(schema ? referencedCollections(schema, [...options.collections]) : [])];
|
|
67
|
+
return run(["entries", collection, locale, entriesKey(o)], [...new Set(tags)].map(collectionTag), (draft) => site.getEntries(collection, { ...query, locale, draft }));
|
|
63
68
|
},
|
|
64
69
|
async getEntry(collection, idOrPath, o) {
|
|
65
70
|
const locale = localeOf(o);
|
|
@@ -70,7 +75,7 @@ export function createSmoodlyQueries(options) {
|
|
|
70
75
|
const hit = await site.getEntry(collection, idOrPath, { locale, draft });
|
|
71
76
|
// The site layer also answers a bare root slug, but the unit would sit under
|
|
72
77
|
// a tag no op ever fires — so a slug is refused here rather than cached stale.
|
|
73
|
-
if (hit && !idOrPath.startsWith("/") && hit.
|
|
78
|
+
if (hit && !idOrPath.startsWith("/") && hit.id !== idOrPath) {
|
|
74
79
|
throw new Error(`smoodly: getEntry("${collection}", "${idOrPath}") takes an id or a path — for a slug, pass the entry's path.`);
|
|
75
80
|
}
|
|
76
81
|
return hit;
|
package/dist/page.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export type PageSchema<Zones extends Record<string, unknown> = Record<string, un
|
|
|
13
13
|
* segment; the default locale's entry is required (see `fixedSlug`). */
|
|
14
14
|
slug?: string | Record<string, string>;
|
|
15
15
|
zones: Record<keyof Zones & string, ZonePolicy>;
|
|
16
|
-
|
|
16
|
+
/** The SEO & Social tab exists for this template; reads return `seo` and the route's metadata uses it (spec 2026-09-29 content API contract §7). */
|
|
17
|
+
seo?: true;
|
|
17
18
|
/** The template's own page-level fields (spec 2026-09-10 page fields §1):
|
|
18
19
|
* stored in the tree per locale, typed on the view's `page.fields`. */
|
|
19
20
|
fields: Record<string, Descriptor>;
|
|
@@ -27,7 +28,9 @@ export declare function pageSchema<Zones extends Record<string, object>, F exten
|
|
|
27
28
|
title: string;
|
|
28
29
|
slug?: string | Record<string, string>;
|
|
29
30
|
zones: Zones;
|
|
30
|
-
|
|
31
|
+
seo?: true;
|
|
32
|
+
/** Gone: `meta: { seo: true }` is `seo: true`. */
|
|
33
|
+
meta?: never;
|
|
31
34
|
fields?: F;
|
|
32
35
|
}): PageSchema<Zones, F>;
|
|
33
36
|
/** The default-locale segment of a fixed slug — what the admin lists and
|
package/dist/page.js
CHANGED
|
@@ -44,6 +44,8 @@ function normalizePolicy(p) {
|
|
|
44
44
|
export function pageSchema(input) {
|
|
45
45
|
if (!input?.name)
|
|
46
46
|
throw new Error("smoodly: a page requires a name.");
|
|
47
|
+
if (input.meta !== undefined)
|
|
48
|
+
throw new Error(`smoodly: page "${input.name}": meta: { seo: true } is now seo: true.`);
|
|
47
49
|
// A fixed slug is decided by `slug !== undefined` at every other hop, but
|
|
48
50
|
// the renderer's path lookup is truthiness-based: an empty string would read
|
|
49
51
|
// as fixed to the admin and blow up in the route. Reject it at construction.
|
|
@@ -62,7 +64,7 @@ export function pageSchema(input) {
|
|
|
62
64
|
title: input.title,
|
|
63
65
|
...(input.slug !== undefined ? { slug: input.slug } : {}),
|
|
64
66
|
zones: Object.fromEntries(Object.entries(input.zones).map(([k, p]) => [k, normalizePolicy(p)])),
|
|
65
|
-
|
|
67
|
+
...(input.seo === true ? { seo: true } : {}),
|
|
66
68
|
fields,
|
|
67
69
|
};
|
|
68
70
|
}
|
package/dist/pairing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentType, ReactElement } from "react";
|
|
2
|
-
import type { BuilderMap, ElementSchema,
|
|
2
|
+
import type { BuilderMap, ElementSchema, SectionSchema, Values } from "./schema.ts";
|
|
3
3
|
import type { PageContext, ZoneContent } from "./render.tsx";
|
|
4
4
|
import type { StyleProps } from "./styles.ts";
|
|
5
5
|
/** What the view receives. A page view gets its zones and where it sits
|
|
@@ -14,28 +14,28 @@ export type Props<S> = S extends {
|
|
|
14
14
|
__fields?: infer F extends BuilderMap;
|
|
15
15
|
} ? {
|
|
16
16
|
zones: Record<keyof Z & string, ZoneContent>;
|
|
17
|
-
page: PageContext<
|
|
17
|
+
page: PageContext<Values<F>>;
|
|
18
18
|
} : S extends {
|
|
19
19
|
__f?: infer F extends BuilderMap;
|
|
20
20
|
__s?: infer St extends BuilderMap;
|
|
21
|
-
} ?
|
|
22
|
-
settings:
|
|
21
|
+
} ? Values<F> & {
|
|
22
|
+
settings: Values<St>;
|
|
23
23
|
styles: StyleProps;
|
|
24
24
|
} : never;
|
|
25
25
|
/** What hard-coded JSX may pass: fields flat, settings optional and partial, styles optional. */
|
|
26
|
-
type ExternalProps<F extends BuilderMap, St extends BuilderMap> =
|
|
27
|
-
settings?: Partial<
|
|
26
|
+
type ExternalProps<F extends BuilderMap, St extends BuilderMap> = Values<F> & {
|
|
27
|
+
settings?: Partial<Values<St>>;
|
|
28
28
|
styles?: StyleProps;
|
|
29
29
|
};
|
|
30
30
|
export type PairedComponent<Schema, F extends BuilderMap, St extends BuilderMap> = ((props: ExternalProps<F, St>) => ReactElement) & {
|
|
31
31
|
schema: Schema;
|
|
32
32
|
};
|
|
33
|
-
export declare function section<F extends BuilderMap, St extends BuilderMap>(schema: SectionSchema<F, St>, View: ComponentType<
|
|
34
|
-
settings:
|
|
33
|
+
export declare function section<F extends BuilderMap, St extends BuilderMap>(schema: SectionSchema<F, St>, View: ComponentType<Values<F> & {
|
|
34
|
+
settings: Values<St>;
|
|
35
35
|
styles: StyleProps;
|
|
36
36
|
}>): PairedComponent<SectionSchema<F, St>, F, St>;
|
|
37
|
-
export declare function element<F extends BuilderMap, St extends BuilderMap>(schema: ElementSchema<F, St>, View: ComponentType<
|
|
38
|
-
settings:
|
|
37
|
+
export declare function element<F extends BuilderMap, St extends BuilderMap>(schema: ElementSchema<F, St>, View: ComponentType<Values<F> & {
|
|
38
|
+
settings: Values<St>;
|
|
39
39
|
styles: StyleProps;
|
|
40
40
|
}>): PairedComponent<ElementSchema<F, St>, F, St>;
|
|
41
41
|
export {};
|
package/dist/path-index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface PathIndex {
|
|
|
16
16
|
resolve(locale: string, path: string): Promise<PathHit | null>;
|
|
17
17
|
/** locale → the target's LIVE path. */
|
|
18
18
|
pathsOf(target: PathTarget): Promise<Record<string, string>>;
|
|
19
|
+
/** id → the LIVE full path in one locale, for many targets at once (the resolver's EntryRef paths). */
|
|
20
|
+
livePathsIn(kind: "page" | "entry", ids: string[], locale: string): Promise<Record<string, string>>;
|
|
19
21
|
/** The target's redirect rows. */
|
|
20
22
|
redirectsOf(target: PathTarget): Promise<PathRow[]>;
|
|
21
23
|
listLive(locale: string): Promise<PathRow[]>;
|
|
@@ -40,6 +42,7 @@ export declare class MemoryPathIndex implements PathIndex {
|
|
|
40
42
|
private hit;
|
|
41
43
|
resolve(locale: string, path: string): Promise<PathHit | null>;
|
|
42
44
|
pathsOf(target: PathTarget): Promise<Record<string, string>>;
|
|
45
|
+
livePathsIn(kind: "page" | "entry", ids: string[], locale: string): Promise<Record<string, string>>;
|
|
43
46
|
redirectsOf(target: PathTarget): Promise<PathRow[]>;
|
|
44
47
|
/** Every live URL in the locale; an editor's row under a code address is left out, since visitors never reach it (spec §4). */
|
|
45
48
|
listLive(locale: string): Promise<PathRow[]>;
|
package/dist/path-index.js
CHANGED
|
@@ -61,6 +61,13 @@ export class MemoryPathIndex {
|
|
|
61
61
|
out[r.locale] = r.path;
|
|
62
62
|
return out;
|
|
63
63
|
}
|
|
64
|
+
async livePathsIn(kind, ids, locale) {
|
|
65
|
+
const wanted = new Set(ids);
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const r of this.shownAll(this.rows.filter((r) => !r.redirect && r.kind === kind && r.locale === locale && wanted.has(r.id))))
|
|
68
|
+
out[r.id] = r.path;
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
64
71
|
async redirectsOf(target) {
|
|
65
72
|
return this.shownAll(this.rows.filter((r) => r.redirect && sameTarget(r, target)));
|
|
66
73
|
}
|