@riebeckite/core 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export { ContentManager } from "./src/content/content_manager.js";
19
19
  export { extractFrontmatterAliases } from "./src/content/content_metadata.js";
20
20
  export type { ContentSource, ContentSourceContent, ContentSourceEntry, ContentSourceMetadata, } from "./src/content/content_source.js";
21
21
  export { getContentSourceEntry, readContentSourceEntry, } from "./src/content/content_source.js";
22
+ export { resolveContentStableId } from "./src/content/content_stable_id.js";
22
23
  export { FileSystemContentSource } from "./src/content/file_system_content_source.js";
23
24
  export { IMAGE_EXTENSIONS } from "./src/content/image_extensions.js";
24
25
  export { readOnlyContentGraph } from "./src/content/read_only_content_graph.js";
@@ -30,19 +31,21 @@ export type { LogContext, Logger, LogLevel, Observability, ObservabilityValue, T
30
31
  export { CompositeTraceSink, ConsoleLogger, NoopLogger, NoopTracer, SinkTracer, } from "./src/observability.js";
31
32
  export type { PipelineOptions } from "./src/pipeline.js";
32
33
  export { Pipeline } from "./src/pipeline.js";
33
- export type { JsonValue, PluginCache } from "./src/plugin/plugin_cache.js";
34
+ export type { JsonValue } from "./src/types/json_value.js";
35
+ export type { PluginCache } from "./src/plugin/plugin_cache.js";
34
36
  export type { CreatePluginMemoOptions, PluginMemo, } from "./src/plugin/plugin_memo.js";
35
37
  export { createPluginMemo, stableStringify, } from "./src/plugin/plugin_memo.js";
36
38
  export { PluginDependencyError, type PluginDependencyErrorKind, } from "./src/plugin/plugin_dependency_error.js";
37
39
  export type { ConfigValidationIssue, PluginOptionsValidator, } from "./src/types/config_validation.js";
38
- export type { ContentAsset, ContentLink, ContentLinkKind, ContentLocationInput, ContentManifest, ContentManifestEntry, ContentPublicLocation, ContentRedirect, } from "./src/types/content_manifest.js";
40
+ export type { ContentAsset, ContentBodySlot, ContentLink, ContentLinkKind, ContentLocationInput, ContentManifest, ContentManifestEntry, ContentManifestPluginClientEntry, ContentPublicLocation, ContentRedirect, } from "./src/types/content_manifest.js";
41
+ export { appendContentBodySlot } from "./src/types/content_manifest.js";
39
42
  export type { Diagnostic, DiagnosticCode, DiagnosticSeverity, } from "./src/types/diagnostic.js";
40
43
  export type { GeneratedOutput, GeneratedOutputContent, GeneratedOutputInput, GeneratedOutputSink, } from "./src/types/generated_output.js";
41
44
  export { createUnavailableGeneratedOutputSink, normalizeGeneratedOutputPath, } from "./src/types/generated_output.js";
42
45
  export type { PluginInput, ResolvedPluginMetadata, RiebeckitePlugin, } from "./src/types/plugin.js";
43
46
  export { definePlugin, getResolvedPluginMetadata, resolvePlugins, } from "./src/types/plugin.js";
44
47
  export type { PluginAsset, PluginAssetKind, PluginClientEntry, } from "./src/types/plugin_asset.js";
45
- export { createClientEntry, createStyleAsset, } from "./src/types/plugin_asset.js";
48
+ export { createClientEntry, createStyleAsset, serializePublicClientConfig, } from "./src/types/plugin_asset.js";
46
49
  export type { PluginContentContext, PluginContentLocationAugmentContext, PluginContentLocationContext, PluginContentLocationResolver, PluginContentRenderer, PluginContext, PluginGeneratedHtml, PluginGeneratedHtmlInspector, PluginGraphContext, PluginLifecycleContext, PluginManifestContext, PluginPostContext, PluginRenderContext, PluginRenderInput, PluginRenderTarget, } from "./src/types/plugin_context.js";
47
50
  export type { PluginDiagnostic, PluginDiagnosticLevel, } from "./src/types/plugin_diagnostic.js";
48
51
  export type { PluginHeadTag } from "./src/types/plugin_head.js";
package/dist/index.js CHANGED
@@ -1749,6 +1749,55 @@ function escapeRegExp(value) {
1749
1749
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1750
1750
  }
1751
1751
 
1752
+ // src/types/plugin_asset.ts
1753
+ function createStyleAsset(pluginName) {
1754
+ return {
1755
+ pluginName,
1756
+ kind: "style",
1757
+ moduleSpecifier: `@riebeckite/plugin-${pluginName}/style.css`
1758
+ };
1759
+ }
1760
+ function createClientEntry(pluginName, exportName, publicConfig) {
1761
+ return {
1762
+ pluginName,
1763
+ moduleSpecifier: `@riebeckite/plugin-${pluginName}/client`,
1764
+ ...exportName === void 0 ? {} : { exportName },
1765
+ ...publicConfig === void 0 ? {} : { publicConfig }
1766
+ };
1767
+ }
1768
+ function serializePublicClientConfig(value) {
1769
+ assertJsonValue(value, /* @__PURE__ */ new Set());
1770
+ return JSON.stringify(value);
1771
+ }
1772
+ function assertJsonValue(value, ancestors) {
1773
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
1774
+ return;
1775
+ }
1776
+ if (typeof value === "number") {
1777
+ if (Number.isFinite(value)) return;
1778
+ throw new TypeError("Public client configuration numbers must be finite.");
1779
+ }
1780
+ if (typeof value !== "object") {
1781
+ throw new TypeError(
1782
+ "Public client configuration must be JSON-serializable."
1783
+ );
1784
+ }
1785
+ if (ancestors.has(value)) {
1786
+ throw new TypeError("Public client configuration must not contain cycles.");
1787
+ }
1788
+ const prototype = Object.getPrototypeOf(value);
1789
+ if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value)) {
1790
+ throw new TypeError(
1791
+ "Public client configuration must contain only plain objects."
1792
+ );
1793
+ }
1794
+ ancestors.add(value);
1795
+ for (const item of Array.isArray(value) ? value : Object.values(value)) {
1796
+ assertJsonValue(item, ancestors);
1797
+ }
1798
+ ancestors.delete(value);
1799
+ }
1800
+
1752
1801
  // src/plugin/plugin_lifecycle.ts
1753
1802
  var PluginLifecycleError = class extends Error {
1754
1803
  constructor(pluginName, hookName, cause) {
@@ -1921,6 +1970,21 @@ var PluginRuntime = class {
1921
1970
  }))
1922
1971
  );
1923
1972
  }
1973
+ /**
1974
+ * Returns browser entries with only explicitly registered public
1975
+ * configuration. Validation occurs here as well as in integrations so a
1976
+ * manifest can safely be serialized by a future static HTML host.
1977
+ */
1978
+ collectClientEntries() {
1979
+ return this.plugins().flatMap(
1980
+ (plugin) => (plugin.clientEntries ?? []).map((entry) => {
1981
+ if (entry.publicConfig !== void 0) {
1982
+ serializePublicClientConfig(entry.publicConfig);
1983
+ }
1984
+ return entry;
1985
+ })
1986
+ );
1987
+ }
1924
1988
  /** Returns registered outputs sorted by path for a deterministic build. */
1925
1989
  collectGeneratedOutputs() {
1926
1990
  return [...this.generatedOutputs].sort(
@@ -2520,6 +2584,34 @@ function linkKind(value) {
2520
2584
  return "note";
2521
2585
  }
2522
2586
 
2587
+ // src/content/content_stable_id.ts
2588
+ var MAX_STABLE_CONTENT_ID_LENGTH = 160;
2589
+ function resolveContentStableId(frontmatter) {
2590
+ const id = readOptionalStableId(frontmatter.id, "id");
2591
+ const uid = readOptionalStableId(frontmatter.uid, "uid");
2592
+ if (id !== void 0 && uid !== void 0 && id !== uid) {
2593
+ throw new Error(
2594
+ 'Content frontmatter fields "id" and "uid" must have the same value.'
2595
+ );
2596
+ }
2597
+ return id ?? uid;
2598
+ }
2599
+ function readOptionalStableId(value, field) {
2600
+ if (value === void 0) return void 0;
2601
+ if (typeof value !== "string") {
2602
+ throw new Error(`Content frontmatter field "${field}" must be a string.`);
2603
+ }
2604
+ if (value.length === 0 || value.length > MAX_STABLE_CONTENT_ID_LENGTH || value.trim() !== value || [...value].some((character) => {
2605
+ const code = character.codePointAt(0) ?? 0;
2606
+ return code <= 31 || code === 127;
2607
+ })) {
2608
+ throw new Error(
2609
+ `Content frontmatter field "${field}" must be a non-empty, trimmed string of at most ${MAX_STABLE_CONTENT_ID_LENGTH} characters without control characters.`
2610
+ );
2611
+ }
2612
+ return value;
2613
+ }
2614
+
2523
2615
  // src/content/manifest_builder.ts
2524
2616
  var ManifestBuilder = class {
2525
2617
  createEntry(slug, markdown, processed, contentIndex, location) {
@@ -2527,8 +2619,10 @@ var ManifestBuilder = class {
2527
2619
  const assets = links.filter(
2528
2620
  (link) => (link.kind === "image" || link.kind === "attachment") && link.slug !== null
2529
2621
  ).map((link) => ({ path: link.slug }));
2622
+ const contentId = resolveContentStableId(processed.frontmatter);
2530
2623
  return {
2531
2624
  slug,
2625
+ ...contentId === void 0 ? {} : { contentId },
2532
2626
  permalink: location.permalink,
2533
2627
  publicLocation: location,
2534
2628
  title: getManifestTitle(slug, processed.frontmatter.title),
@@ -2545,6 +2639,7 @@ var ManifestBuilder = class {
2545
2639
  }
2546
2640
  build(entries, contentIndex) {
2547
2641
  const bySlug = new Map(entries.map((entry) => [entry.slug, entry]));
2642
+ const byContentId = createContentIdIndex(entries);
2548
2643
  const byPermalink = new Map(
2549
2644
  entries.map((entry) => [entry.permalink, entry])
2550
2645
  );
@@ -2570,6 +2665,7 @@ var ManifestBuilder = class {
2570
2665
  entries,
2571
2666
  publicEntries: [...entries],
2572
2667
  bySlug,
2668
+ byContentId,
2573
2669
  byPermalink,
2574
2670
  redirects: /* @__PURE__ */ new Map(),
2575
2671
  publicRedirects: /* @__PURE__ */ new Map(),
@@ -2579,12 +2675,27 @@ var ManifestBuilder = class {
2579
2675
  incomingLinks,
2580
2676
  contentIndex,
2581
2677
  assets: [],
2678
+ clientEntries: [],
2582
2679
  diagnostics: [],
2583
2680
  generatedOutputs: []
2584
2681
  };
2585
2682
  return { ...manifest, graph: createContentGraph(manifest) };
2586
2683
  }
2587
2684
  };
2685
+ function createContentIdIndex(entries) {
2686
+ const byContentId = /* @__PURE__ */ new Map();
2687
+ for (const entry of entries) {
2688
+ if (!entry.contentId) continue;
2689
+ const existing = byContentId.get(entry.contentId);
2690
+ if (existing) {
2691
+ throw new Error(
2692
+ `Duplicate content ID "${entry.contentId}" for "${existing.slug}" and "${entry.slug}".`
2693
+ );
2694
+ }
2695
+ byContentId.set(entry.contentId, entry);
2696
+ }
2697
+ return byContentId;
2698
+ }
2588
2699
  function getManifestTitle(slug, title) {
2589
2700
  return typeof title === "string" && title.trim() ? title : slug;
2590
2701
  }
@@ -2767,6 +2878,7 @@ var ContentManager = class {
2767
2878
  await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
2768
2879
  this.applyPublicView(manifest);
2769
2880
  manifest.assets = this.pluginRuntime.collectAssets();
2881
+ manifest.clientEntries = this.pluginRuntime.collectClientEntries();
2770
2882
  manifest.diagnostics = [
2771
2883
  ...this.pluginRuntime.getDiagnostics(),
2772
2884
  ...await this.pluginRuntime.collectDiagnostics(contentIndex)
@@ -2800,7 +2912,11 @@ var ContentManager = class {
2800
2912
  async inspect() {
2801
2913
  const [entries, contentIndex] = await Promise.all([
2802
2914
  this.entryReader.getEntries(),
2803
- this.getContentIndex()
2915
+ this.getContentIndex(),
2916
+ // Location resolution is the generic phase where plugins can inspect all
2917
+ // content entries (including their frontmatter) and register diagnostics.
2918
+ // Run it for inspect/doctor as well as for a full manifest build.
2919
+ this.getContentLocations()
2804
2920
  ]);
2805
2921
  const diagnostics = await this.pluginRuntime.collectDiagnostics(contentIndex);
2806
2922
  return { entries, contentIndex, diagnostics };
@@ -2943,19 +3059,14 @@ function warn(logger, message, key, error) {
2943
3059
  });
2944
3060
  }
2945
3061
 
2946
- // src/types/plugin_asset.ts
2947
- function createStyleAsset(pluginName) {
2948
- return {
2949
- pluginName,
2950
- kind: "style",
2951
- moduleSpecifier: `@riebeckite/plugin-${pluginName}/style.css`
2952
- };
2953
- }
2954
- function createClientEntry(pluginName, exportName) {
2955
- return {
2956
- pluginName,
2957
- moduleSpecifier: `@riebeckite/plugin-${pluginName}/client`,
2958
- ...exportName === void 0 ? {} : { exportName }
3062
+ // src/types/content_manifest.ts
3063
+ function appendContentBodySlot(entry, slot, html) {
3064
+ if (!html.trim()) return;
3065
+ const previous = entry.bodySlots?.[slot];
3066
+ entry.bodySlots = {
3067
+ ...entry.bodySlots,
3068
+ [slot]: previous ? `${previous}
3069
+ ${html}` : html
2959
3070
  };
2960
3071
  }
2961
3072
 
@@ -2991,6 +3102,7 @@ export {
2991
3102
  Pipeline,
2992
3103
  PluginDependencyError,
2993
3104
  SinkTracer,
3105
+ appendContentBodySlot,
2994
3106
  attachmentUrl,
2995
3107
  buildContentCollections,
2996
3108
  buildGraphEdges,
@@ -3032,8 +3144,10 @@ export {
3032
3144
  resolveConfigModule,
3033
3145
  resolveContentBuildStatePath,
3034
3146
  resolveContentQueryPagination,
3147
+ resolveContentStableId,
3035
3148
  resolveDefaultContentLocation,
3036
3149
  resolvePlugins,
3150
+ serializePublicClientConfig,
3037
3151
  stableStringify,
3038
3152
  stripHtml,
3039
3153
  uniqueStrings
@@ -0,0 +1,8 @@
1
+ import type { PostFrontmatter } from "../types/post_content.js";
2
+ /**
3
+ * Reads the optional, source-authored stable content ID.
4
+ *
5
+ * `id` is the canonical frontmatter field. `uid` is accepted only for
6
+ * compatibility with existing content that used it before `id` was standard.
7
+ */
8
+ export declare function resolveContentStableId(frontmatter: PostFrontmatter): string | undefined;
@@ -1,8 +1,7 @@
1
1
  import type { Logger, Tracer } from "../observability.js";
2
+ import type { JsonValue } from "../types/json_value.js";
2
3
  import type { ResolvedRiebeckiteConfig } from "../types/resolved_riebeckite_config.js";
3
- export type JsonValue = null | boolean | number | string | JsonValue[] | {
4
- [key: string]: JsonValue;
5
- };
4
+ export type { JsonValue } from "../types/json_value.js";
6
5
  export type PluginCache = {
7
6
  get<T extends JsonValue>(key: string): Promise<T | undefined>;
8
7
  set<T extends JsonValue>(key: string, value: T): Promise<void>;
@@ -19,4 +18,3 @@ type CreatePluginCacheOptions = {
19
18
  };
20
19
  export declare function createPluginCache(options: CreatePluginCacheOptions): PluginCache;
21
20
  export declare function resolvePluginCacheDirectory(config: ResolvedRiebeckiteConfig | undefined): string;
22
- export {};
@@ -2,6 +2,7 @@ import type { PipelineOptions } from "../pipeline.js";
2
2
  import type { ContentLocationInput, ContentManifest, ContentManifestEntry, ContentPublicLocation } from "../types/content_manifest.js";
3
3
  import type { Diagnostic } from "../types/diagnostic.js";
4
4
  import type { GeneratedOutput } from "../types/generated_output.js";
5
+ import { type PluginClientEntry } from "../types/plugin_asset.js";
5
6
  import type { PostContent } from "../types/post_content.js";
6
7
  /**
7
8
  * Explicit build lifecycle stages. `idle` means no build lifecycle has been
@@ -45,6 +46,12 @@ export declare class PluginRuntime {
45
46
  kind: import("../types/plugin_asset.js").PluginAssetKind;
46
47
  moduleSpecifier: string;
47
48
  }[];
49
+ /**
50
+ * Returns browser entries with only explicitly registered public
51
+ * configuration. Validation occurs here as well as in integrations so a
52
+ * manifest can safely be serialized by a future static HTML host.
53
+ */
54
+ collectClientEntries(): PluginClientEntry[];
48
55
  /** Returns registered outputs sorted by path for a deterministic build. */
49
56
  collectGeneratedOutputs(): GeneratedOutput[];
50
57
  collectDiagnostics(contentIndex: Map<string, string>): Promise<Diagnostic[]>;
@@ -1,7 +1,7 @@
1
1
  import type { ContentGraph } from "../content/content_graph.js";
2
2
  import type { Diagnostic } from "./diagnostic.js";
3
3
  import type { GeneratedOutput } from "./generated_output.js";
4
- import type { PluginAsset } from "./plugin_asset.js";
4
+ import type { PluginAsset, PluginClientEntry } from "./plugin_asset.js";
5
5
  import type { PluginHeadTag } from "./plugin_head.js";
6
6
  import type { PostFrontmatter } from "./post_content.js";
7
7
  export type ContentLinkKind = "note" | "image" | "attachment" | "unresolved";
@@ -17,8 +17,22 @@ export type ContentAsset = {
17
17
  export type ContentManifestPluginAsset = PluginAsset & {
18
18
  path: string;
19
19
  };
20
+ /**
21
+ * A semantic position in a Site-owned article layout.
22
+ *
23
+ * The standard article consumer recognizes the `article.*` positions below.
24
+ * Other names remain valid so a custom Site can define its own layout slots.
25
+ */
26
+ export type ContentBodySlot = "article.after-header" | "article.after-meta" | "article.before-content" | "article.after-content" | "article.footer" | (string & {});
27
+ /** Client entries and their explicitly public configuration. */
28
+ export type ContentManifestPluginClientEntry = PluginClientEntry;
20
29
  export type ContentManifestEntry = {
21
30
  slug: string;
31
+ /**
32
+ * Optional source-authored stable content identity. It is independent of the
33
+ * slug, permalink, aliases, and redirects.
34
+ */
35
+ contentId?: string;
22
36
  permalink: string;
23
37
  publicLocation: ContentPublicLocation;
24
38
  title: string;
@@ -40,6 +54,13 @@ export type ContentManifestEntry = {
40
54
  backlinks: string[];
41
55
  assets: ContentAsset[];
42
56
  };
57
+ /**
58
+ * Appends a Plugin-provided HTML fragment to a named Site layout slot.
59
+ *
60
+ * Plugins publish fragments; the Site chooses which slots to render and where.
61
+ * Appending preserves contributions from earlier plugins in resolved order.
62
+ */
63
+ export declare function appendContentBodySlot(entry: ContentManifestEntry, slot: ContentBodySlot, html: string): void;
43
64
  export type ContentRedirect = {
44
65
  path: string;
45
66
  status: 301 | 302 | 307 | 308;
@@ -64,6 +85,8 @@ export type ContentManifest = {
64
85
  */
65
86
  publicEntries: ContentManifestEntry[];
66
87
  bySlug: Map<string, ContentManifestEntry>;
88
+ /** Contains only entries with an explicit source-authored content ID. */
89
+ byContentId: Map<string, ContentManifestEntry>;
67
90
  byPermalink: Map<string, ContentManifestEntry>;
68
91
  redirects: Map<string, ContentRedirect & {
69
92
  slug: string;
@@ -82,6 +105,11 @@ export type ContentManifest = {
82
105
  contentIndex: Map<string, string>;
83
106
  graph: ContentGraph;
84
107
  assets: ContentManifestPluginAsset[];
108
+ /**
109
+ * Browser entries registered by plugins. This contains only each entry's
110
+ * explicit `publicConfig`, never the plugin's complete `options` object.
111
+ */
112
+ clientEntries: ContentManifestPluginClientEntry[];
85
113
  diagnostics: Diagnostic[];
86
114
  generatedOutputs: GeneratedOutput[];
87
115
  };
@@ -0,0 +1,4 @@
1
+ /** A value that can be represented without loss in JSON. */
2
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
@@ -1,13 +1,28 @@
1
+ import type { JsonValue } from "./json_value.js";
1
2
  export type PluginAssetKind = "style" | "script";
2
3
  export type PluginAsset = {
3
4
  pluginName: string;
4
5
  kind: PluginAssetKind;
5
6
  moduleSpecifier: string;
6
7
  };
7
- export type PluginClientEntry = {
8
+ export type PluginClientEntry<TPublicConfig extends JsonValue = JsonValue> = {
8
9
  pluginName: string;
9
10
  moduleSpecifier: string;
10
11
  exportName?: string;
12
+ /**
13
+ * Configuration intentionally made available to this browser entry.
14
+ *
15
+ * Plugin `options` are never inferred or copied here. Only this value is
16
+ * included in client-facing output, so it must not contain secrets or other
17
+ * private build-time options.
18
+ */
19
+ publicConfig?: TPublicConfig;
11
20
  };
12
21
  export declare function createStyleAsset(pluginName: string): PluginAsset;
13
- export declare function createClientEntry(pluginName: string, exportName?: string): PluginClientEntry;
22
+ export declare function createClientEntry<TPublicConfig extends JsonValue>(pluginName: string, exportName?: string, publicConfig?: TPublicConfig): PluginClientEntry<TPublicConfig>;
23
+ /**
24
+ * Serializes explicitly registered client configuration for code generation.
25
+ * Rejecting non-JSON values prevents generated client modules from silently
26
+ * dropping or changing configuration supplied through an untyped plugin.
27
+ */
28
+ export declare function serializePublicClientConfig(value: unknown): string;
@@ -3,6 +3,13 @@ export type PostContent = {
3
3
  html: string;
4
4
  };
5
5
  export type PostFrontmatter = Record<string, unknown> & {
6
+ /**
7
+ * Optional source-authored stable content identity. Unlike a slug or URL,
8
+ * this value survives a rename or public-location change.
9
+ */
10
+ id?: string;
11
+ /** @deprecated Use `id`. Read only as a compatibility fallback. */
12
+ uid?: string;
6
13
  title?: string;
7
14
  description?: string;
8
15
  date?: string | Date;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",