@topik/core 0.1.0-alpha.4 → 0.1.0-alpha.6

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.
@@ -0,0 +1,268 @@
1
+ import { TopikGeneratedAssetName } from "@topik/content";
2
+ import { Person } from "@topik/schema/person/v1";
3
+ import { Asset } from "@topik/schema/asset/v1";
4
+ import { Course } from "@topik/schema/course/v1";
5
+ import { CourseModule } from "@topik/schema/course-module/v1";
6
+ import { CoursePage } from "@topik/schema/course-page/v1";
7
+ import { Guide } from "@topik/schema/guide/v1";
8
+ import { Wiki } from "@topik/schema/wiki/v1";
9
+ import { WikiPage } from "@topik/schema/wiki-page/v1";
10
+ //#region src/resource.d.ts
11
+ type Resource = Asset | Course | CourseModule | CoursePage | Guide | Person | Wiki | WikiPage;
12
+ /** Resources accepted as compiler input. `Asset` exists only in compiler output. */
13
+ type SourceResource = Exclude<Resource, Asset>;
14
+ type ResourceType = Resource["type"];
15
+ //#endregion
16
+ //#region src/assets/constants.d.ts
17
+ declare const ASSET_API_VERSION: "v1";
18
+ declare const ASSET_TYPE: "Asset";
19
+ declare const ASSET_SCHEMA_ID: "https://topik.dev/schemas/asset/v1.json";
20
+ declare const TOPIK_JSON_VERSION: "topik-json-v1";
21
+ declare const TOPIK_PATH_VERSION: "topik-path-v1";
22
+ declare const TOPIK_ASSET_REFERENCE_VERSION: "topik-asset-reference-v1";
23
+ declare const TOPIK_MATERIALIZATION_VERSION: "topik-materialization-v1";
24
+ declare const TOPIK_ASSET_NAME_VERSION: "topik-asset-name-v1";
25
+ declare const TOPIK_BLOB_OUTPUT_PREFIX: "blobs";
26
+ declare const TOPIK_PATH_V1_DESCRIPTOR: {
27
+ readonly collisionNormalization: "toNFKC_Casefold";
28
+ readonly id: "topik-path-v1";
29
+ readonly localReferenceEncoding: "rfc3986-utf8-upper-v1";
30
+ readonly maxComponentUtf8Bytes: 255;
31
+ readonly maxComponents: 64;
32
+ readonly maxRepositoryPathUtf8Bytes: 768;
33
+ readonly normalizationSensitiveExclusion: "unicode-17-new-marks";
34
+ readonly storageNormalization: "NFC";
35
+ readonly unicodeVersion: "17.0.0";
36
+ };
37
+ declare const TOPIK_ASSET_LIMITS: {
38
+ readonly maxDescriptorBytes: 1048576;
39
+ readonly maxAssetBytes: 268435456;
40
+ readonly maxAssets: 10000;
41
+ readonly maxJsonDepth: 8;
42
+ readonly maxComponentUtf8Bytes: 255;
43
+ readonly maxComponents: 64;
44
+ readonly maxRepositoryPathUtf8Bytes: 768;
45
+ };
46
+ //#endregion
47
+ //#region src/assets/diagnostics.d.ts
48
+ declare const TOPIK_ASSET_DIAGNOSTIC_IDS: readonly ["TOPIK_ASSET_UNSUPPORTED_VERSION", "TOPIK_ASSET_DUPLICATE_MEMBER", "TOPIK_ASSET_SCHEMA_INVALID", "TOPIK_ASSET_NON_CANONICAL", "TOPIK_ASSET_NAME_INVALID", "TOPIK_ASSET_NAME_COLLISION", "TOPIK_ASSET_NAME_GENERATOR_REQUIRED", "TOPIK_ASSET_PROJECT_NAMESPACE_INVALID", "TOPIK_ASSET_PATH_INVALID", "TOPIK_ASSET_PATH_COLLISION", "TOPIK_ASSET_REFERENCE_AMBIGUOUS", "TOPIK_ASSET_REFERENCE_MALFORMED", "TOPIK_ASSET_REFERENCE_MISSING", "TOPIK_ASSET_FILE_MISSING", "TOPIK_ASSET_DIGEST_MISMATCH", "TOPIK_ASSET_SIZE_MISMATCH", "TOPIK_ASSET_MEDIA_TYPE_MISMATCH", "TOPIK_ASSET_FILE_TYPE_UNSUPPORTED", "TOPIK_ASSET_ACTIVE_CONTENT_UNSUPPORTED", "TOPIK_ASSET_REFERENCE_ACCESSIBILITY_INVALID", "TOPIK_EXTERNAL_REFERENCE_UNSAFE", "TOPIK_ASSET_INVENTORY_INCOMPLETE", "TOPIK_ASSET_VERSION_INCOMPARABLE"];
49
+ declare const TOPIK_ASSET_DEFAULT_CORRELATION_ID: "cor_00000000000000000000000000";
50
+ declare const TOPIK_ASSET_CORRELATION_ID_PATTERN: RegExp;
51
+ type TopikAssetCorrelationId = `cor_${string}`;
52
+ type TopikAssetDiagnosticId = (typeof TOPIK_ASSET_DIAGNOSTIC_IDS)[number];
53
+ type TopikAssetPathDiagnosticReason = "invalid_utf8" | "capability_invalid" | "unicode_version_unsupported" | "not_nfc" | "absolute" | "separator_alias" | "dot_segment" | "percent_noncanonical" | "forbidden_character" | "reserved_name" | "too_long" | "casefold_collision";
54
+ type TopikAssetRecoveryCategory = "upgrade-reader" | "repair-source" | "restore-file" | "verify-bytes" | "preserve-read-only";
55
+ interface TopikAssetDiagnosticLocation {
56
+ jsonPointer?: string;
57
+ contentPosition?: string;
58
+ path?: string;
59
+ key?: string;
60
+ commit?: string;
61
+ }
62
+ interface TopikAssetDiagnostic {
63
+ id: TopikAssetDiagnosticId;
64
+ /** Safe opaque operation correlation; callers may replace the deterministic default. */
65
+ correlationId: TopikAssetCorrelationId;
66
+ severity: "error";
67
+ consequence: "block-resource" | "block-identity-and-writes" | "block-mutation";
68
+ descriptorVersion: string;
69
+ location: TopikAssetDiagnosticLocation;
70
+ recovery: TopikAssetRecoveryCategory;
71
+ reason?: TopikAssetPathDiagnosticReason;
72
+ /** Human wording is intentionally not a stable compatibility surface. */
73
+ message: string;
74
+ }
75
+ type TopikAssetResult<T> = {
76
+ ok: true;
77
+ value: T;
78
+ diagnostics: readonly [];
79
+ source?: Uint8Array;
80
+ } | {
81
+ ok: false;
82
+ value?: T;
83
+ diagnostics: readonly TopikAssetDiagnostic[];
84
+ /** Exact caller bytes are non-loggable evidence; never copy them into diagnostics/telemetry. */
85
+ source?: Uint8Array;
86
+ };
87
+ declare function correlateTopikAssetResult<T>(result: TopikAssetResult<T>, correlationId: TopikAssetCorrelationId): TopikAssetResult<T>;
88
+ //#endregion
89
+ //#region src/assets/asset.d.ts
90
+ interface ParsedAsset {
91
+ asset: Asset;
92
+ canonicalBytes: Uint8Array;
93
+ }
94
+ /** Compiler-generated name used by automatic Asset compilation. */
95
+ type GeneratedAssetName = TopikGeneratedAssetName;
96
+ declare const assetBlobUriBrand: unique symbol;
97
+ /** Opaque compiler output path for one SHA-256-addressed Asset blob. */
98
+ type AssetBlobUri = string & {
99
+ readonly [assetBlobUriBrand]: "AssetBlobUri";
100
+ };
101
+ declare function isAssetBlobUri(value: unknown): value is AssetBlobUri;
102
+ declare function parseAssetBlobUri(value: string): AssetBlobUri;
103
+ declare function parseGeneratedAssetName(value: string): GeneratedAssetName;
104
+ /** Asset descriptor emitted by the compiler with a materialized payload. */
105
+ interface CompiledAsset extends Asset {
106
+ name: GeneratedAssetName;
107
+ spec: {
108
+ uri: AssetBlobUri;
109
+ integrity: `sha256:${string}`;
110
+ size: number;
111
+ mediaType: string;
112
+ };
113
+ }
114
+ declare function parseAsset(input: string | Uint8Array): TopikAssetResult<ParsedAsset>;
115
+ declare function validateAssetValue(value: unknown): TopikAssetResult<Asset>;
116
+ /** Serialize one Asset to deterministic UTF-8 canonical JSON and prove its round trip. */
117
+ declare function serializeAsset(value: unknown): TopikAssetResult<Uint8Array>;
118
+ /** Admit a compiler-materialized blob path; Asset resources may use other URI forms. */
119
+ declare function validateAssetBlobUri(value: string): TopikAssetResult<{
120
+ uri: AssetBlobUri;
121
+ }>;
122
+ declare function isGeneratedAssetName(value: unknown): value is GeneratedAssetName;
123
+ declare function validateProjectNamespace(value: string): TopikAssetResult<string>;
124
+ interface GenerateAutomaticAssetNameOptions {
125
+ projectNamespace: string;
126
+ manifestRelativePath: string;
127
+ }
128
+ declare function generateAutomaticAssetName(options: GenerateAutomaticAssetNameOptions): TopikAssetResult<GeneratedAssetName>;
129
+ declare function topikAssetNameDescriptor(): {
130
+ id: typeof TOPIK_ASSET_NAME_VERSION;
131
+ };
132
+ //#endregion
133
+ //#region src/assets/identity.d.ts
134
+ interface TopikAssetReferenceMappingV1 {
135
+ resource: string;
136
+ position: string;
137
+ slot: string;
138
+ name: string;
139
+ }
140
+ interface TopikAssetSemanticRecordV1 {
141
+ descriptor: "topik-asset-semantic-v1";
142
+ assetNames: readonly string[];
143
+ references: readonly TopikAssetReferenceMappingV1[];
144
+ }
145
+ interface TopikMaterializationResourceV1 {
146
+ resource: string;
147
+ path: string;
148
+ size: number;
149
+ sha256: string;
150
+ }
151
+ interface TopikMaterializationPayloadV1 {
152
+ path: string;
153
+ size: number;
154
+ sha256: string;
155
+ assetNames: readonly string[];
156
+ }
157
+ interface TopikMaterializationRecordV1 {
158
+ descriptor: "topik-materialization-v1";
159
+ resources: readonly TopikMaterializationResourceV1[];
160
+ payloads: readonly TopikMaterializationPayloadV1[];
161
+ }
162
+ interface TopikMaterializationResourceInput {
163
+ resource: {
164
+ type: string;
165
+ name: string;
166
+ };
167
+ bytes: Uint8Array;
168
+ }
169
+ interface TopikMaterializationPayloadInput {
170
+ path: string;
171
+ bytes: Uint8Array;
172
+ assetNames: readonly string[];
173
+ }
174
+ declare function createTopikAssetSemanticRecord(assets: readonly Asset[], references: readonly TopikAssetReferenceMappingV1[]): TopikAssetSemanticRecordV1;
175
+ declare function createTopikMaterializationRecord(resources: readonly TopikMaterializationResourceInput[], payloads: readonly TopikMaterializationPayloadInput[]): TopikMaterializationRecordV1;
176
+ declare function validateTopikMaterializationRecord(record: unknown, resources: readonly Resource[], semantic: unknown): TopikAssetResult<TopikMaterializationRecordV1>;
177
+ declare function digestTopikAssetSemanticRecord(record: TopikAssetSemanticRecordV1): string;
178
+ declare function digestTopikMaterializationRecord(record: TopikMaterializationRecordV1): string;
179
+ declare function compareTopikAssetIdentities(left: {
180
+ semantic: TopikAssetSemanticRecordV1;
181
+ materialization: TopikMaterializationRecordV1;
182
+ }, right: {
183
+ semantic: TopikAssetSemanticRecordV1;
184
+ materialization: TopikMaterializationRecordV1;
185
+ }): TopikAssetResult<{
186
+ semanticEqual: boolean;
187
+ exactEqual: boolean;
188
+ }>;
189
+ //#endregion
190
+ //#region src/compile/assets.d.ts
191
+ type ContentBearingResource = CoursePage | Guide | WikiPage;
192
+ interface AssetNameInput {
193
+ /** Absolute native path after resolving the authored reference within its allowed root.
194
+ * This is a file location, not a portable identity or a delivery URL. */
195
+ resolvedAssetPath: string;
196
+ }
197
+ /** Return a stable, canonical auto-v1 name. Called once per distinct asset path per compilation. */
198
+ type AssetNameGenerator = (input: AssetNameInput) => string | Promise<string>;
199
+ interface AssetCompilationOptions {
200
+ /** Required only when supported local references are discovered automatically. */
201
+ generateName?: AssetNameGenerator;
202
+ }
203
+ interface CompileAssetResourcesInput extends AssetCompilationOptions {
204
+ rootDir: string;
205
+ resources: readonly SourceResource[];
206
+ /** Compilation-root-relative source paths keyed by `Type/name`. */
207
+ sourcePathsByResource: Readonly<Record<string, string>>;
208
+ /** Optional per-document containment directories relative to rootDir (empty for root).
209
+ * Asset identities still use paths relative to rootDir. */
210
+ sourceDirectoriesByResource?: Readonly<Record<string, string>>;
211
+ /** Other consumed compiler inputs that cannot be owned as Asset bytes. */
212
+ protectedSourcePaths?: readonly string[];
213
+ }
214
+ interface AssetPayload {
215
+ path: AssetBlobUri;
216
+ integrity: `sha256:${string}`;
217
+ mediaType: string;
218
+ size: number;
219
+ bytes: Uint8Array;
220
+ assetNames: readonly string[];
221
+ }
222
+ interface AssetCompilationResult {
223
+ resources: CompiledResource[];
224
+ payloads: AssetPayload[];
225
+ semantic: TopikAssetSemanticRecordV1;
226
+ materialization: TopikMaterializationRecordV1;
227
+ }
228
+ type CompiledResource = SourceResource | CompiledAsset;
229
+ declare class AssetCompilationError extends Error {
230
+ readonly diagnostics: readonly TopikAssetDiagnostic[];
231
+ constructor(message: string, diagnostics?: readonly TopikAssetDiagnostic[]);
232
+ }
233
+ /** Discover and resolve one compilation-wide Asset set and deduplicated payload inventory. */
234
+ declare function compileAssetResources(input: CompileAssetResourcesInput): Promise<AssetCompilationResult>;
235
+ //#endregion
236
+ //#region src/preview.d.ts
237
+ /** Browser-safe capability and wire types. No filesystem imports at runtime. */
238
+ declare const TOPIK_PREVIEW_PROTOCOL: "topik-preview/1";
239
+ interface CourseNavigationModule {
240
+ name: string;
241
+ declarationIndex: number;
242
+ pages: {
243
+ name: string;
244
+ declarationIndex: number;
245
+ }[];
246
+ }
247
+ type CourseNavigationIndex = Record<string, {
248
+ modules: CourseNavigationModule[];
249
+ }>;
250
+ type PreviewPayloadDescriptor = Omit<AssetPayload, "bytes">;
251
+ interface PreviewDiagnostic {
252
+ id: string;
253
+ message: string;
254
+ }
255
+ interface PreviewState {
256
+ protocol: typeof TOPIK_PREVIEW_PROTOCOL;
257
+ sessionId: string;
258
+ sequence: number;
259
+ /** Last successful generation, retained during compiling/error; null before first success. */
260
+ generation: number | null;
261
+ status: "compiling" | "ready" | "error";
262
+ diagnostics: PreviewDiagnostic[];
263
+ resources: Resource[];
264
+ payloads: PreviewPayloadDescriptor[];
265
+ courseNavigation: CourseNavigationIndex;
266
+ }
267
+ //#endregion
268
+ export { TopikAssetPathDiagnosticReason as $, AssetBlobUri as A, parseGeneratedAssetName as B, TopikMaterializationResourceV1 as C, digestTopikAssetSemanticRecord as D, createTopikMaterializationRecord as E, generateAutomaticAssetName as F, validateProjectNamespace as G, topikAssetNameDescriptor as H, isAssetBlobUri as I, TOPIK_ASSET_DIAGNOSTIC_IDS as J, TOPIK_ASSET_CORRELATION_ID_PATTERN as K, isGeneratedAssetName as L, GenerateAutomaticAssetNameOptions as M, GeneratedAssetName as N, digestTopikMaterializationRecord as O, ParsedAsset as P, TopikAssetDiagnosticLocation as Q, parseAsset as R, TopikMaterializationResourceInput as S, createTopikAssetSemanticRecord as T, validateAssetBlobUri as U, serializeAsset as V, validateAssetValue as W, TopikAssetDiagnostic as X, TopikAssetCorrelationId as Y, TopikAssetDiagnosticId as Z, TopikAssetReferenceMappingV1 as _, PreviewState as a, ASSET_TYPE as at, TopikMaterializationPayloadV1 as b, AssetCompilationOptions as c, TOPIK_ASSET_REFERENCE_VERSION as ct, AssetNameInput as d, TOPIK_MATERIALIZATION_VERSION as dt, TopikAssetRecoveryCategory as et, AssetPayload as f, TOPIK_PATH_V1_DESCRIPTOR as ft, compileAssetResources as g, SourceResource as gt, ContentBearingResource as h, ResourceType as ht, PreviewPayloadDescriptor as i, ASSET_SCHEMA_ID as it, CompiledAsset as j, validateTopikMaterializationRecord as k, AssetCompilationResult as l, TOPIK_BLOB_OUTPUT_PREFIX as lt, CompiledResource as m, Resource as mt, CourseNavigationModule as n, correlateTopikAssetResult as nt, TOPIK_PREVIEW_PROTOCOL as o, TOPIK_ASSET_LIMITS as ot, CompileAssetResourcesInput as p, TOPIK_PATH_VERSION as pt, TOPIK_ASSET_DEFAULT_CORRELATION_ID as q, PreviewDiagnostic as r, ASSET_API_VERSION as rt, AssetCompilationError as s, TOPIK_ASSET_NAME_VERSION as st, CourseNavigationIndex as t, TopikAssetResult as tt, AssetNameGenerator as u, TOPIK_JSON_VERSION as ut, TopikAssetSemanticRecordV1 as v, compareTopikAssetIdentities as w, TopikMaterializationRecordV1 as x, TopikMaterializationPayloadInput as y, parseAssetBlobUri as z };
@@ -0,0 +1,2 @@
1
+ import { a as PreviewState, i as PreviewPayloadDescriptor, n as CourseNavigationModule, o as TOPIK_PREVIEW_PROTOCOL, r as PreviewDiagnostic, t as CourseNavigationIndex } from "./preview-C_3OnNUi.mjs";
2
+ export { CourseNavigationIndex, CourseNavigationModule, PreviewDiagnostic, PreviewPayloadDescriptor, PreviewState, TOPIK_PREVIEW_PROTOCOL };
@@ -0,0 +1,5 @@
1
+ //#region src/preview.ts
2
+ /** Browser-safe capability and wire types. No filesystem imports at runtime. */
3
+ const TOPIK_PREVIEW_PROTOCOL = "topik-preview/1";
4
+ //#endregion
5
+ export { TOPIK_PREVIEW_PROTOCOL };
@@ -0,0 +1,65 @@
1
+ import { WikiDropdownNavNode, WikiExternalDropdownNavNode, WikiExternalTabNavNode, WikiInternalDropdownNavNode, WikiInternalTabNavNode, WikiNavContainerNode, WikiNavNode, WikiNavigation, WikiPageNavNode, WikiTabNavNode } from "@topik/schema/wiki/v1";
2
+ //#region src/wiki-navigation.d.ts
3
+ export type ResolvedWikiPage = {
4
+ /** The compiled WikiPage resource name. */
5
+ page: string;
6
+ /** The page navigation node from the Wiki resource. */
7
+ node: WikiPageNavNode;
8
+ /** Public path without a leading slash. */
9
+ route: string;
10
+ /** Logical Markdown source path without an extension. */
11
+ sourcePath: string;
12
+ /** Internal navigation containers from outermost to innermost. */
13
+ ancestors: readonly WikiNavContainerNode[];
14
+ };
15
+ export type ResolvedWikiNavigation = {
16
+ pages: readonly ResolvedWikiPage[];
17
+ pageByName: ReadonlyMap<string, ResolvedWikiPage>;
18
+ pageByRoute: ReadonlyMap<string, ResolvedWikiPage>;
19
+ pageBySourcePath: ReadonlyMap<string, ResolvedWikiPage>;
20
+ sourceVersion?: 1;
21
+ };
22
+ export type ResolvedWikiContentLink = {
23
+ page: ResolvedWikiPage;
24
+ route: string;
25
+ hash: string;
26
+ search: string;
27
+ };
28
+ export type ResolvedWikiContentReference = {
29
+ kind: "page";
30
+ target: ResolvedWikiContentLink;
31
+ } | {
32
+ kind: "asset";
33
+ } | {
34
+ kind: "external";
35
+ } | {
36
+ kind: "unresolved";
37
+ };
38
+ /** Classify an already-admitted compiled href without treating missing page context as external.
39
+ * Local files/downloads must first be compiled to Asset references using their source inventory;
40
+ * this browser-safe helper never guesses whether an unresolved local path names a file.
41
+ */
42
+ export declare function resolveWikiContentReference(href: string, currentPageName: string, resolved: ResolvedWikiNavigation): ResolvedWikiContentReference;
43
+ /** Returns true for tab, dropdown, and group nodes containing navigation children. */
44
+ export declare function hasWikiNavChildren(node: WikiNavNode): node is WikiNavContainerNode;
45
+ export declare function isInternalWikiTab(node: WikiNavNode): node is WikiInternalTabNavNode;
46
+ export declare function isExternalWikiTab(node: WikiNavNode): node is WikiExternalTabNavNode;
47
+ export declare function isInternalWikiDropdown(node: WikiNavNode): node is WikiInternalDropdownNavNode;
48
+ export declare function isExternalWikiDropdown(node: WikiNavNode): node is WikiExternalDropdownNavNode;
49
+ /** Resolves page routes, source paths, and navigation ancestry from a compiled Wiki tree. */
50
+ export declare function resolveWikiNavigation(navigation: WikiNavigation | readonly WikiNavNode[], options?: {
51
+ sourceVersion?: 1;
52
+ }): ResolvedWikiNavigation;
53
+ /** Finds the first page node in navigation order, ignoring external destinations. */
54
+ export declare function findFirstWikiPage(navigation: WikiNavigation | readonly WikiNavNode[]): WikiPageNavNode | null;
55
+ /** Returns the internal containers owning a page, or an empty array for an unknown page. */
56
+ export declare function findWikiPageAncestors(resolved: ResolvedWikiNavigation, pageName: string): readonly WikiNavContainerNode[];
57
+ /** Resolves compiled WikiPage references by name, or legacy links against their source context. */
58
+ export declare function resolveWikiContentHref(href: string, currentPageName: string, resolved: ResolvedWikiNavigation): ResolvedWikiContentLink | null;
59
+ export declare function joinWikiPath(prefix: string, slug?: string): string;
60
+ export type InternalWikiTab = WikiInternalTabNavNode;
61
+ export type ExternalWikiTab = WikiExternalTabNavNode;
62
+ export type InternalWikiDropdown = WikiInternalDropdownNavNode;
63
+ export type ExternalWikiDropdown = WikiExternalDropdownNavNode;
64
+ export type WikiSwitcherNode = WikiTabNavNode | WikiDropdownNavNode;
65
+ //#endregion
@@ -0,0 +1,133 @@
1
+ import { a as resolveDeclaredSourcePage, i as resolveDeclaredPublicRoute, n as hasDeclaredFolder, o as resolveNavigationPath, r as isFolderHref } from "./folder-links-DI4xfipU.mjs";
2
+ import { parseTopikResourceReference } from "@topik/content";
3
+ //#region src/wiki-navigation.ts
4
+ const INTERNAL_WIKI_ORIGIN = "https://topik.invalid";
5
+ /** Classify an already-admitted compiled href without treating missing page context as external.
6
+ * Local files/downloads must first be compiled to Asset references using their source inventory;
7
+ * this browser-safe helper never guesses whether an unresolved local path names a file.
8
+ */
9
+ function resolveWikiContentReference(href, currentPageName, resolved) {
10
+ if (/^asset:/i.test(href)) return { kind: "asset" };
11
+ if (/^(?:https?|mailto|tel):/i.test(href)) return { kind: "external" };
12
+ const target = resolveWikiContentHref(href, currentPageName, resolved);
13
+ return target ? {
14
+ kind: "page",
15
+ target
16
+ } : { kind: "unresolved" };
17
+ }
18
+ /** Returns true for tab, dropdown, and group nodes containing navigation children. */
19
+ function hasWikiNavChildren(node) {
20
+ return "children" in node;
21
+ }
22
+ function isInternalWikiTab(node) {
23
+ return node.type === "tab" && "children" in node;
24
+ }
25
+ function isExternalWikiTab(node) {
26
+ return node.type === "tab" && "href" in node;
27
+ }
28
+ function isInternalWikiDropdown(node) {
29
+ return node.type === "dropdown" && "children" in node;
30
+ }
31
+ function isExternalWikiDropdown(node) {
32
+ return node.type === "dropdown" && "href" in node;
33
+ }
34
+ /** Resolves page routes, source paths, and navigation ancestry from a compiled Wiki tree. */
35
+ function resolveWikiNavigation(navigation, options = {}) {
36
+ const pages = [];
37
+ const pageByName = /* @__PURE__ */ new Map();
38
+ const pageByRoute = /* @__PURE__ */ new Map();
39
+ const pageBySourcePath = /* @__PURE__ */ new Map();
40
+ const visit = (nodes, prefix, ancestors) => {
41
+ for (const node of nodes) if (node.type === "page") {
42
+ const route = joinWikiPath(prefix, node.slug);
43
+ const resolved = {
44
+ page: node.page,
45
+ node,
46
+ route,
47
+ sourcePath: node.sourcePath ?? joinWikiPath(prefix, node.slug || "index"),
48
+ ancestors
49
+ };
50
+ if (pageByName.has(node.page)) throw new Error(`Wiki navigation contains page ${node.page} more than once`);
51
+ if (pageByRoute.has(route)) throw new Error(`Wiki navigation contains duplicate route /${route}`);
52
+ if (pageBySourcePath.has(resolved.sourcePath)) throw new Error("Wiki navigation contains duplicate source paths");
53
+ pages.push(resolved);
54
+ pageByName.set(node.page, resolved);
55
+ pageByRoute.set(route, resolved);
56
+ pageBySourcePath.set(resolved.sourcePath, resolved);
57
+ } else if (hasWikiNavChildren(node)) visit(node.children, joinWikiPath(prefix, node.slug), [...ancestors, node]);
58
+ };
59
+ visit(navigation, "", []);
60
+ return {
61
+ pages,
62
+ pageByName,
63
+ pageByRoute,
64
+ pageBySourcePath,
65
+ ...options.sourceVersion !== void 0 ? { sourceVersion: options.sourceVersion } : {}
66
+ };
67
+ }
68
+ /** Finds the first page node in navigation order, ignoring external destinations. */
69
+ function findFirstWikiPage(navigation) {
70
+ for (const node of navigation) {
71
+ if (node.type === "page") return node;
72
+ if (hasWikiNavChildren(node)) {
73
+ const page = findFirstWikiPage(node.children);
74
+ if (page) return page;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ /** Returns the internal containers owning a page, or an empty array for an unknown page. */
80
+ function findWikiPageAncestors(resolved, pageName) {
81
+ return resolved.pageByName.get(pageName)?.ancestors ?? [];
82
+ }
83
+ /** Resolves compiled WikiPage references by name, or legacy links against their source context. */
84
+ function resolveWikiContentHref(href, currentPageName, resolved) {
85
+ if (href.startsWith("asset:")) return null;
86
+ const reference = parseTopikResourceReference(href);
87
+ if (reference) {
88
+ if (reference.type !== "WikiPage") return null;
89
+ const page = resolved.pageByName.get(reference.name);
90
+ return page ? {
91
+ page,
92
+ route: page.route,
93
+ hash: reference.hash,
94
+ search: reference.search
95
+ } : null;
96
+ }
97
+ const currentPage = resolved.pageByName.get(currentPageName);
98
+ if (!currentPage) return null;
99
+ if (href.startsWith("#")) return {
100
+ page: currentPage,
101
+ route: currentPage.route,
102
+ hash: href.slice(1),
103
+ search: ""
104
+ };
105
+ let url;
106
+ try {
107
+ const sourcePath = currentPage.sourcePath.split("/").map(encodeURIComponent).join("/");
108
+ url = new URL(href, `${INTERNAL_WIKI_ORIGIN}/${sourcePath}`);
109
+ } catch {
110
+ return null;
111
+ }
112
+ if (url.origin !== INTERNAL_WIKI_ORIGIN) return null;
113
+ const pathname = resolveNavigationPath(href, currentPage.sourcePath);
114
+ if (pathname === void 0) return null;
115
+ const markdown = /\.(?:mdx?|markdown)$/i.test(pathname) && !hasDeclaredFolder(pathname, resolved.pageBySourcePath);
116
+ const normalizedPath = markdown ? pathname.replace(/\.(?:mdx?|markdown)$/i, "") : pathname;
117
+ const route = normalizedPath === "index" ? "" : normalizedPath.replace(/\/index$/, "");
118
+ const folder = isFolderHref(href);
119
+ const sourceLink = !href.startsWith("/") || markdown || folder;
120
+ const page = sourceLink && (resolved.sourceVersion === 1 || folder || hasDeclaredFolder(normalizedPath, resolved.pageBySourcePath)) ? markdown ? resolved.pageBySourcePath.get(normalizedPath) : resolveDeclaredSourcePage(normalizedPath, resolved.pageBySourcePath, folder) : (!sourceLink && href.startsWith("/") ? resolveDeclaredPublicRoute(href, resolved.pageByRoute, true) : resolved.pageByRoute.get(route)) ?? resolveDeclaredSourcePage(normalizedPath, resolved.pageBySourcePath, true);
121
+ return page ? {
122
+ page,
123
+ route: page.route,
124
+ hash: url.hash.replace(/^#/, ""),
125
+ search: url.search
126
+ } : null;
127
+ }
128
+ function joinWikiPath(prefix, slug) {
129
+ if (!slug) return prefix;
130
+ return prefix ? `${prefix}/${slug}` : slug;
131
+ }
132
+ //#endregion
133
+ export { findFirstWikiPage, findWikiPageAncestors, hasWikiNavChildren, isExternalWikiDropdown, isExternalWikiTab, isInternalWikiDropdown, isInternalWikiTab, joinWikiPath, resolveWikiContentHref, resolveWikiContentReference, resolveWikiNavigation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topik/core",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Topik core library",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,6 +14,9 @@
14
14
  "type": "module",
15
15
  "exports": {
16
16
  ".": "./dist/index.mjs",
17
+ "./course-navigation": "./dist/course-navigation.mjs",
18
+ "./preview": "./dist/preview.mjs",
19
+ "./wiki-navigation": "./dist/wiki-navigation.mjs",
17
20
  "./package.json": "./package.json"
18
21
  },
19
22
  "publishConfig": {
@@ -23,16 +26,16 @@
23
26
  "ajv": "^8.20.0",
24
27
  "ajv-formats": "^3.0.1",
25
28
  "chokidar": "^5.0.0",
26
- "yaml": "^2.9.0",
27
- "zod": "^4.4.3",
28
- "@topik/content-schema": "0.1.0-alpha.4",
29
- "@topik/schema": "0.1.0-alpha.4"
29
+ "yaml": "^2.9.1",
30
+ "zod": "^4.6.7",
31
+ "@topik/content": "0.1.0-alpha.6",
32
+ "@topik/schema": "0.1.0-alpha.6"
30
33
  },
31
34
  "devDependencies": {
32
- "@types/node": "22.20.1",
33
- "@typescript/native-preview": "7.0.0-dev.20260707.2",
34
- "typescript": "^6.0.3",
35
- "vite-plus": "0.2.4"
35
+ "@types/node": "22.20.4",
36
+ "typescript": "^7.0.2",
37
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
38
+ "vite-plus": "1.2.0"
36
39
  },
37
40
  "scripts": {
38
41
  "build": "vp pack",