@mzebley/mark-down 1.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ SnippetClient: () => SnippetClient,
34
+ normalizeSlug: () => normalizeSlug
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/snippet-client.ts
39
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
40
+ var import_marked = require("marked");
41
+ var OPTIONAL_FIELDS = [
42
+ "title",
43
+ "order",
44
+ "type",
45
+ "tags",
46
+ "draft"
47
+ ];
48
+ var SnippetClient = class {
49
+ constructor(options) {
50
+ this.snippetCache = /* @__PURE__ */ new Map();
51
+ this.manifestSource = options.manifest;
52
+ this.fetcher = options.fetcher ?? defaultFetch;
53
+ this.markdownRenderer = options.markdownRenderer ?? import_marked.marked.parse;
54
+ this.resolveSnippetPath = options.resolveSnippetPath ?? ((meta) => meta.path);
55
+ }
56
+ async get(slug) {
57
+ const manifest = await this.loadManifest();
58
+ const entry = manifest.find((snippet) => snippet.slug === slug);
59
+ if (!entry) {
60
+ return void 0;
61
+ }
62
+ return this.loadSnippet(entry);
63
+ }
64
+ async list(filter, options) {
65
+ const manifest = await this.loadManifest();
66
+ let predicate;
67
+ let finalOptions = {};
68
+ if (typeof filter === "function") {
69
+ predicate = filter;
70
+ finalOptions = options ?? {};
71
+ } else if (filter) {
72
+ finalOptions = filter;
73
+ } else if (options) {
74
+ finalOptions = options;
75
+ }
76
+ let items = manifest;
77
+ const filters = [];
78
+ if (predicate) {
79
+ filters.push(predicate);
80
+ }
81
+ if (finalOptions.filter) {
82
+ filters.push(finalOptions.filter);
83
+ }
84
+ if (filters.length) {
85
+ items = items.filter((entry) => filters.every((fn) => fn(entry)));
86
+ }
87
+ const offset = finalOptions.offset ?? 0;
88
+ const limit = finalOptions.limit;
89
+ if (offset > 0) {
90
+ items = items.slice(offset);
91
+ }
92
+ if (typeof limit === "number") {
93
+ items = items.slice(0, limit);
94
+ }
95
+ return [...items];
96
+ }
97
+ listByType(type, options) {
98
+ return this.list((entry) => entry.type === type, options);
99
+ }
100
+ listByGroup(group, options) {
101
+ return this.list((entry) => entry.group === group, options);
102
+ }
103
+ loadSnippet(meta) {
104
+ const cached = this.snippetCache.get(meta.slug);
105
+ if (cached) {
106
+ return cached;
107
+ }
108
+ const promise = this.fetchSnippet(meta);
109
+ this.snippetCache.set(meta.slug, promise);
110
+ return promise;
111
+ }
112
+ async fetchSnippet(meta) {
113
+ const snippetPath = this.resolveSnippetPath(meta);
114
+ const response = await this.fetcher(snippetPath);
115
+ if (!response.ok) {
116
+ throw new Error(`Failed to fetch snippet '${meta.slug}' (status ${response.status})`);
117
+ }
118
+ const rawContent = await response.text();
119
+ const parsed = (0, import_gray_matter.default)(rawContent);
120
+ const frontMatter = sanitizeFrontMatter(parsed.data ?? {});
121
+ const mergedMeta = mergeFrontMatter(meta, frontMatter.meta);
122
+ const html = await this.markdownRenderer(parsed.content);
123
+ const baseExtra = mergedMeta.extra ?? {};
124
+ return {
125
+ ...mergedMeta,
126
+ extra: {
127
+ ...baseExtra,
128
+ ...frontMatter.extra
129
+ },
130
+ markdown: parsed.content,
131
+ html
132
+ };
133
+ }
134
+ loadManifest() {
135
+ if (!this.manifestPromise) {
136
+ this.manifestPromise = this.resolveManifest();
137
+ }
138
+ return this.manifestPromise;
139
+ }
140
+ async resolveManifest() {
141
+ if (Array.isArray(this.manifestSource)) {
142
+ return this.manifestSource;
143
+ }
144
+ if (typeof this.manifestSource === "function") {
145
+ const result = await this.manifestSource();
146
+ return Array.isArray(result) ? result : Promise.reject(new Error("Manifest function must return an array"));
147
+ }
148
+ const raw = await this.fetchText(this.manifestSource);
149
+ const manifest = JSON.parse(raw);
150
+ if (!Array.isArray(manifest)) {
151
+ throw new Error("Manifest must be an array");
152
+ }
153
+ return manifest;
154
+ }
155
+ async fetchText(path) {
156
+ const response = await this.fetcher(path);
157
+ if (!response.ok) {
158
+ throw new Error(`Failed to fetch manifest from ${path} (status ${response.status})`);
159
+ }
160
+ return response.text();
161
+ }
162
+ };
163
+ function sanitizeFrontMatter(data) {
164
+ const meta = {};
165
+ const extra = {};
166
+ for (const [key, value] of Object.entries(data)) {
167
+ if (OPTIONAL_FIELDS.includes(key)) {
168
+ if (key === "tags") {
169
+ meta.tags = normalizeTags(value);
170
+ } else {
171
+ meta[key] = value;
172
+ }
173
+ } else {
174
+ extra[key] = value;
175
+ }
176
+ }
177
+ return { meta, extra };
178
+ }
179
+ function normalizeTags(value) {
180
+ if (!value) {
181
+ return void 0;
182
+ }
183
+ if (Array.isArray(value)) {
184
+ return value.map((tag) => String(tag));
185
+ }
186
+ if (typeof value === "string") {
187
+ return value.split(",").map((tag) => tag.trim()).filter(Boolean);
188
+ }
189
+ return void 0;
190
+ }
191
+ function mergeFrontMatter(base, overrides) {
192
+ const merged = { ...base };
193
+ for (const field of OPTIONAL_FIELDS) {
194
+ const overrideValue = overrides[field];
195
+ if (overrideValue !== void 0 && merged[field] === void 0) {
196
+ merged[field] = overrideValue;
197
+ }
198
+ }
199
+ return merged;
200
+ }
201
+ async function defaultFetch(input) {
202
+ const runtimeFetch = globalThis.fetch;
203
+ if (!runtimeFetch) {
204
+ throw new Error("No global fetch implementation found. Provide a custom fetcher.");
205
+ }
206
+ return runtimeFetch(input);
207
+ }
208
+
209
+ // src/slug.ts
210
+ var NON_ALPHANUMERIC = /[^a-z0-9]+/gi;
211
+ var LEADING_TRAILING_DASH = /^-+|-+$/g;
212
+ function normalizeSlug(input) {
213
+ const value = input?.trim();
214
+ if (!value) {
215
+ throw new Error("Cannot normalize an empty slug");
216
+ }
217
+ const normalized = value.toLowerCase().replace(NON_ALPHANUMERIC, "-").replace(/-{2,}/g, "-").replace(LEADING_TRAILING_DASH, "");
218
+ if (!normalized) {
219
+ throw new Error(`Slug '${input}' does not contain any alphanumeric characters`);
220
+ }
221
+ return normalized;
222
+ }
223
+ // Annotate the CommonJS export names for ESM import in node:
224
+ 0 && (module.exports = {
225
+ SnippetClient,
226
+ normalizeSlug
227
+ });
228
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/snippet-client.ts","../src/slug.ts"],"sourcesContent":["export * from \"./types\";\nexport * from \"./snippet-client\";\nexport { normalizeSlug } from \"./slug\";\n","import matter from \"gray-matter\";\nimport { marked } from \"marked\";\nimport type {\n ListOptions,\n ManifestSource,\n ResponseLike,\n Snippet,\n SnippetClientOptions,\n SnippetFilter,\n SnippetMeta\n} from \"./types\";\n\nconst OPTIONAL_FIELDS: Array<keyof SnippetMeta> = [\n \"title\",\n \"order\",\n \"type\",\n \"tags\",\n \"draft\"\n];\n\nexport class SnippetClient {\n private readonly manifestSource: ManifestSource;\n private readonly fetcher: (input: string) => Promise<ResponseLike>;\n private readonly markdownRenderer: (markdown: string) => Promise<string> | string;\n private readonly resolveSnippetPath: (meta: SnippetMeta) => string;\n private manifestPromise?: Promise<SnippetMeta[]>;\n private snippetCache = new Map<string, Promise<Snippet>>();\n\n constructor(options: SnippetClientOptions) {\n this.manifestSource = options.manifest;\n this.fetcher = options.fetcher ?? defaultFetch;\n this.markdownRenderer = options.markdownRenderer ?? marked.parse;\n this.resolveSnippetPath = options.resolveSnippetPath ?? ((meta) => meta.path);\n }\n\n async get(slug: string): Promise<Snippet | undefined> {\n const manifest = await this.loadManifest();\n const entry = manifest.find((snippet) => snippet.slug === slug);\n if (!entry) {\n return undefined;\n }\n\n return this.loadSnippet(entry);\n }\n\n async list(filter?: SnippetFilter | ListOptions, options?: ListOptions): Promise<SnippetMeta[]> {\n const manifest = await this.loadManifest();\n let predicate: SnippetFilter | undefined;\n let finalOptions: ListOptions = {};\n\n if (typeof filter === \"function\") {\n predicate = filter;\n finalOptions = options ?? {};\n } else if (filter) {\n finalOptions = filter;\n } else if (options) {\n finalOptions = options;\n }\n\n let items = manifest;\n const filters: SnippetFilter[] = [];\n if (predicate) {\n filters.push(predicate);\n }\n if (finalOptions.filter) {\n filters.push(finalOptions.filter);\n }\n if (filters.length) {\n items = items.filter((entry) => filters.every((fn) => fn(entry)));\n }\n\n const offset = finalOptions.offset ?? 0;\n const limit = finalOptions.limit;\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (typeof limit === \"number\") {\n items = items.slice(0, limit);\n }\n\n return [...items];\n }\n\n listByType(type: string, options?: ListOptions): Promise<SnippetMeta[]> {\n return this.list((entry) => entry.type === type, options);\n }\n\n listByGroup(group: string, options?: ListOptions): Promise<SnippetMeta[]> {\n return this.list((entry) => entry.group === group, options);\n }\n\n private loadSnippet(meta: SnippetMeta): Promise<Snippet> {\n const cached = this.snippetCache.get(meta.slug);\n if (cached) {\n return cached;\n }\n\n const promise = this.fetchSnippet(meta);\n this.snippetCache.set(meta.slug, promise);\n return promise;\n }\n\n private async fetchSnippet(meta: SnippetMeta): Promise<Snippet> {\n const snippetPath = this.resolveSnippetPath(meta);\n const response = await this.fetcher(snippetPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch snippet '${meta.slug}' (status ${response.status})`);\n }\n\n const rawContent = await response.text();\n const parsed = matter(rawContent);\n const frontMatter = sanitizeFrontMatter(parsed.data ?? {});\n const mergedMeta = mergeFrontMatter(meta, frontMatter.meta);\n\n const html = await this.markdownRenderer(parsed.content);\n\n const baseExtra = mergedMeta.extra ?? {};\n return {\n ...mergedMeta,\n extra: {\n ...baseExtra,\n ...frontMatter.extra\n },\n markdown: parsed.content,\n html\n };\n }\n\n private loadManifest(): Promise<SnippetMeta[]> {\n if (!this.manifestPromise) {\n this.manifestPromise = this.resolveManifest();\n }\n return this.manifestPromise;\n }\n\n private async resolveManifest(): Promise<SnippetMeta[]> {\n if (Array.isArray(this.manifestSource)) {\n return this.manifestSource;\n }\n\n if (typeof this.manifestSource === \"function\") {\n const result = await this.manifestSource();\n return Array.isArray(result) ? result : Promise.reject(new Error(\"Manifest function must return an array\"));\n }\n\n const raw = await this.fetchText(this.manifestSource);\n const manifest = JSON.parse(raw);\n if (!Array.isArray(manifest)) {\n throw new Error(\"Manifest must be an array\");\n }\n return manifest;\n }\n\n private async fetchText(path: string): Promise<string> {\n const response = await this.fetcher(path);\n if (!response.ok) {\n throw new Error(`Failed to fetch manifest from ${path} (status ${response.status})`);\n }\n return response.text();\n }\n}\n\nfunction sanitizeFrontMatter(data: Record<string, unknown>) {\n const meta: Partial<SnippetMeta> = {};\n const extra: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (OPTIONAL_FIELDS.includes(key as keyof SnippetMeta)) {\n if (key === \"tags\") {\n meta.tags = normalizeTags(value);\n } else {\n (meta as Record<string, unknown>)[key] = value;\n }\n } else {\n extra[key] = value;\n }\n }\n\n return { meta, extra };\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((tag) => String(tag));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((tag) => tag.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction mergeFrontMatter(base: SnippetMeta, overrides: Partial<SnippetMeta>): SnippetMeta {\n const merged: SnippetMeta = { ...base };\n for (const field of OPTIONAL_FIELDS) {\n const overrideValue = overrides[field];\n if (overrideValue !== undefined && merged[field] === undefined) {\n merged[field] = overrideValue as never;\n }\n }\n return merged;\n}\n\nasync function defaultFetch(input: string): Promise<ResponseLike> {\n const runtimeFetch = (globalThis as typeof globalThis & { fetch?: typeof fetch }).fetch;\n if (!runtimeFetch) {\n throw new Error(\"No global fetch implementation found. Provide a custom fetcher.\");\n }\n return runtimeFetch(input);\n}\n","const NON_ALPHANUMERIC = /[^a-z0-9]+/gi;\nconst LEADING_TRAILING_DASH = /^-+|-+$/g;\n\nexport function normalizeSlug(input: string): string {\n const value = input?.trim();\n if (!value) {\n throw new Error(\"Cannot normalize an empty slug\");\n }\n\n const normalized = value\n .toLowerCase()\n .replace(NON_ALPHANUMERIC, \"-\")\n .replace(/-{2,}/g, \"-\")\n .replace(LEADING_TRAILING_DASH, \"\");\n\n if (!normalized) {\n throw new Error(`Slug '${input}' does not contain any alphanumeric characters`);\n }\n\n return normalized;\n}\n"],"mappings":"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","names":["matter"]}
@@ -0,0 +1,60 @@
1
+ export { normalizeSlug } from './slug.cjs';
2
+
3
+ interface SnippetMeta {
4
+ slug: string;
5
+ title?: string;
6
+ order?: number | null;
7
+ type?: string;
8
+ tags?: string[];
9
+ draft?: boolean;
10
+ path: string;
11
+ group: string;
12
+ extra?: Record<string, unknown>;
13
+ }
14
+ interface Snippet extends SnippetMeta {
15
+ markdown: string;
16
+ html: string;
17
+ }
18
+ type SnippetFilter = (snippet: SnippetMeta) => boolean;
19
+ interface ListOptions {
20
+ filter?: SnippetFilter;
21
+ limit?: number;
22
+ offset?: number;
23
+ }
24
+ type ManifestSource = string | SnippetMeta[] | (() => Promise<SnippetMeta[]> | SnippetMeta[]);
25
+ interface SnippetClientOptions {
26
+ /** Where to load the manifest; defaults to `/snippets-index.json`. */
27
+ manifest: ManifestSource;
28
+ /** Custom fetch to use in Node environments. Falls back to global `fetch`. */
29
+ fetcher?: (input: string) => Promise<ResponseLike>;
30
+ /** Allows overriding markdown → HTML rendering. Defaults to `marked`. */
31
+ markdownRenderer?: (markdown: string) => Promise<string> | string;
32
+ /** Customize how snippet URLs are resolved; defaults to the manifest `path`. */
33
+ resolveSnippetPath?: (meta: SnippetMeta) => string;
34
+ }
35
+ interface ResponseLike {
36
+ ok: boolean;
37
+ status: number;
38
+ text(): Promise<string>;
39
+ }
40
+
41
+ declare class SnippetClient {
42
+ private readonly manifestSource;
43
+ private readonly fetcher;
44
+ private readonly markdownRenderer;
45
+ private readonly resolveSnippetPath;
46
+ private manifestPromise?;
47
+ private snippetCache;
48
+ constructor(options: SnippetClientOptions);
49
+ get(slug: string): Promise<Snippet | undefined>;
50
+ list(filter?: SnippetFilter | ListOptions, options?: ListOptions): Promise<SnippetMeta[]>;
51
+ listByType(type: string, options?: ListOptions): Promise<SnippetMeta[]>;
52
+ listByGroup(group: string, options?: ListOptions): Promise<SnippetMeta[]>;
53
+ private loadSnippet;
54
+ private fetchSnippet;
55
+ private loadManifest;
56
+ private resolveManifest;
57
+ private fetchText;
58
+ }
59
+
60
+ export { type ListOptions, type ManifestSource, type ResponseLike, type Snippet, SnippetClient, type SnippetClientOptions, type SnippetFilter, type SnippetMeta };
@@ -0,0 +1,60 @@
1
+ export { normalizeSlug } from './slug.js';
2
+
3
+ interface SnippetMeta {
4
+ slug: string;
5
+ title?: string;
6
+ order?: number | null;
7
+ type?: string;
8
+ tags?: string[];
9
+ draft?: boolean;
10
+ path: string;
11
+ group: string;
12
+ extra?: Record<string, unknown>;
13
+ }
14
+ interface Snippet extends SnippetMeta {
15
+ markdown: string;
16
+ html: string;
17
+ }
18
+ type SnippetFilter = (snippet: SnippetMeta) => boolean;
19
+ interface ListOptions {
20
+ filter?: SnippetFilter;
21
+ limit?: number;
22
+ offset?: number;
23
+ }
24
+ type ManifestSource = string | SnippetMeta[] | (() => Promise<SnippetMeta[]> | SnippetMeta[]);
25
+ interface SnippetClientOptions {
26
+ /** Where to load the manifest; defaults to `/snippets-index.json`. */
27
+ manifest: ManifestSource;
28
+ /** Custom fetch to use in Node environments. Falls back to global `fetch`. */
29
+ fetcher?: (input: string) => Promise<ResponseLike>;
30
+ /** Allows overriding markdown → HTML rendering. Defaults to `marked`. */
31
+ markdownRenderer?: (markdown: string) => Promise<string> | string;
32
+ /** Customize how snippet URLs are resolved; defaults to the manifest `path`. */
33
+ resolveSnippetPath?: (meta: SnippetMeta) => string;
34
+ }
35
+ interface ResponseLike {
36
+ ok: boolean;
37
+ status: number;
38
+ text(): Promise<string>;
39
+ }
40
+
41
+ declare class SnippetClient {
42
+ private readonly manifestSource;
43
+ private readonly fetcher;
44
+ private readonly markdownRenderer;
45
+ private readonly resolveSnippetPath;
46
+ private manifestPromise?;
47
+ private snippetCache;
48
+ constructor(options: SnippetClientOptions);
49
+ get(slug: string): Promise<Snippet | undefined>;
50
+ list(filter?: SnippetFilter | ListOptions, options?: ListOptions): Promise<SnippetMeta[]>;
51
+ listByType(type: string, options?: ListOptions): Promise<SnippetMeta[]>;
52
+ listByGroup(group: string, options?: ListOptions): Promise<SnippetMeta[]>;
53
+ private loadSnippet;
54
+ private fetchSnippet;
55
+ private loadManifest;
56
+ private resolveManifest;
57
+ private fetchText;
58
+ }
59
+
60
+ export { type ListOptions, type ManifestSource, type ResponseLike, type Snippet, SnippetClient, type SnippetClientOptions, type SnippetFilter, type SnippetMeta };
package/dist/index.mjs ADDED
@@ -0,0 +1,179 @@
1
+ import {
2
+ normalizeSlug
3
+ } from "./chunk-TQ5Y4RZJ.mjs";
4
+
5
+ // src/snippet-client.ts
6
+ import matter from "gray-matter";
7
+ import { marked } from "marked";
8
+ var OPTIONAL_FIELDS = [
9
+ "title",
10
+ "order",
11
+ "type",
12
+ "tags",
13
+ "draft"
14
+ ];
15
+ var SnippetClient = class {
16
+ constructor(options) {
17
+ this.snippetCache = /* @__PURE__ */ new Map();
18
+ this.manifestSource = options.manifest;
19
+ this.fetcher = options.fetcher ?? defaultFetch;
20
+ this.markdownRenderer = options.markdownRenderer ?? marked.parse;
21
+ this.resolveSnippetPath = options.resolveSnippetPath ?? ((meta) => meta.path);
22
+ }
23
+ async get(slug) {
24
+ const manifest = await this.loadManifest();
25
+ const entry = manifest.find((snippet) => snippet.slug === slug);
26
+ if (!entry) {
27
+ return void 0;
28
+ }
29
+ return this.loadSnippet(entry);
30
+ }
31
+ async list(filter, options) {
32
+ const manifest = await this.loadManifest();
33
+ let predicate;
34
+ let finalOptions = {};
35
+ if (typeof filter === "function") {
36
+ predicate = filter;
37
+ finalOptions = options ?? {};
38
+ } else if (filter) {
39
+ finalOptions = filter;
40
+ } else if (options) {
41
+ finalOptions = options;
42
+ }
43
+ let items = manifest;
44
+ const filters = [];
45
+ if (predicate) {
46
+ filters.push(predicate);
47
+ }
48
+ if (finalOptions.filter) {
49
+ filters.push(finalOptions.filter);
50
+ }
51
+ if (filters.length) {
52
+ items = items.filter((entry) => filters.every((fn) => fn(entry)));
53
+ }
54
+ const offset = finalOptions.offset ?? 0;
55
+ const limit = finalOptions.limit;
56
+ if (offset > 0) {
57
+ items = items.slice(offset);
58
+ }
59
+ if (typeof limit === "number") {
60
+ items = items.slice(0, limit);
61
+ }
62
+ return [...items];
63
+ }
64
+ listByType(type, options) {
65
+ return this.list((entry) => entry.type === type, options);
66
+ }
67
+ listByGroup(group, options) {
68
+ return this.list((entry) => entry.group === group, options);
69
+ }
70
+ loadSnippet(meta) {
71
+ const cached = this.snippetCache.get(meta.slug);
72
+ if (cached) {
73
+ return cached;
74
+ }
75
+ const promise = this.fetchSnippet(meta);
76
+ this.snippetCache.set(meta.slug, promise);
77
+ return promise;
78
+ }
79
+ async fetchSnippet(meta) {
80
+ const snippetPath = this.resolveSnippetPath(meta);
81
+ const response = await this.fetcher(snippetPath);
82
+ if (!response.ok) {
83
+ throw new Error(`Failed to fetch snippet '${meta.slug}' (status ${response.status})`);
84
+ }
85
+ const rawContent = await response.text();
86
+ const parsed = matter(rawContent);
87
+ const frontMatter = sanitizeFrontMatter(parsed.data ?? {});
88
+ const mergedMeta = mergeFrontMatter(meta, frontMatter.meta);
89
+ const html = await this.markdownRenderer(parsed.content);
90
+ const baseExtra = mergedMeta.extra ?? {};
91
+ return {
92
+ ...mergedMeta,
93
+ extra: {
94
+ ...baseExtra,
95
+ ...frontMatter.extra
96
+ },
97
+ markdown: parsed.content,
98
+ html
99
+ };
100
+ }
101
+ loadManifest() {
102
+ if (!this.manifestPromise) {
103
+ this.manifestPromise = this.resolveManifest();
104
+ }
105
+ return this.manifestPromise;
106
+ }
107
+ async resolveManifest() {
108
+ if (Array.isArray(this.manifestSource)) {
109
+ return this.manifestSource;
110
+ }
111
+ if (typeof this.manifestSource === "function") {
112
+ const result = await this.manifestSource();
113
+ return Array.isArray(result) ? result : Promise.reject(new Error("Manifest function must return an array"));
114
+ }
115
+ const raw = await this.fetchText(this.manifestSource);
116
+ const manifest = JSON.parse(raw);
117
+ if (!Array.isArray(manifest)) {
118
+ throw new Error("Manifest must be an array");
119
+ }
120
+ return manifest;
121
+ }
122
+ async fetchText(path) {
123
+ const response = await this.fetcher(path);
124
+ if (!response.ok) {
125
+ throw new Error(`Failed to fetch manifest from ${path} (status ${response.status})`);
126
+ }
127
+ return response.text();
128
+ }
129
+ };
130
+ function sanitizeFrontMatter(data) {
131
+ const meta = {};
132
+ const extra = {};
133
+ for (const [key, value] of Object.entries(data)) {
134
+ if (OPTIONAL_FIELDS.includes(key)) {
135
+ if (key === "tags") {
136
+ meta.tags = normalizeTags(value);
137
+ } else {
138
+ meta[key] = value;
139
+ }
140
+ } else {
141
+ extra[key] = value;
142
+ }
143
+ }
144
+ return { meta, extra };
145
+ }
146
+ function normalizeTags(value) {
147
+ if (!value) {
148
+ return void 0;
149
+ }
150
+ if (Array.isArray(value)) {
151
+ return value.map((tag) => String(tag));
152
+ }
153
+ if (typeof value === "string") {
154
+ return value.split(",").map((tag) => tag.trim()).filter(Boolean);
155
+ }
156
+ return void 0;
157
+ }
158
+ function mergeFrontMatter(base, overrides) {
159
+ const merged = { ...base };
160
+ for (const field of OPTIONAL_FIELDS) {
161
+ const overrideValue = overrides[field];
162
+ if (overrideValue !== void 0 && merged[field] === void 0) {
163
+ merged[field] = overrideValue;
164
+ }
165
+ }
166
+ return merged;
167
+ }
168
+ async function defaultFetch(input) {
169
+ const runtimeFetch = globalThis.fetch;
170
+ if (!runtimeFetch) {
171
+ throw new Error("No global fetch implementation found. Provide a custom fetcher.");
172
+ }
173
+ return runtimeFetch(input);
174
+ }
175
+ export {
176
+ SnippetClient,
177
+ normalizeSlug
178
+ };
179
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/snippet-client.ts"],"sourcesContent":["import matter from \"gray-matter\";\nimport { marked } from \"marked\";\nimport type {\n ListOptions,\n ManifestSource,\n ResponseLike,\n Snippet,\n SnippetClientOptions,\n SnippetFilter,\n SnippetMeta\n} from \"./types\";\n\nconst OPTIONAL_FIELDS: Array<keyof SnippetMeta> = [\n \"title\",\n \"order\",\n \"type\",\n \"tags\",\n \"draft\"\n];\n\nexport class SnippetClient {\n private readonly manifestSource: ManifestSource;\n private readonly fetcher: (input: string) => Promise<ResponseLike>;\n private readonly markdownRenderer: (markdown: string) => Promise<string> | string;\n private readonly resolveSnippetPath: (meta: SnippetMeta) => string;\n private manifestPromise?: Promise<SnippetMeta[]>;\n private snippetCache = new Map<string, Promise<Snippet>>();\n\n constructor(options: SnippetClientOptions) {\n this.manifestSource = options.manifest;\n this.fetcher = options.fetcher ?? defaultFetch;\n this.markdownRenderer = options.markdownRenderer ?? marked.parse;\n this.resolveSnippetPath = options.resolveSnippetPath ?? ((meta) => meta.path);\n }\n\n async get(slug: string): Promise<Snippet | undefined> {\n const manifest = await this.loadManifest();\n const entry = manifest.find((snippet) => snippet.slug === slug);\n if (!entry) {\n return undefined;\n }\n\n return this.loadSnippet(entry);\n }\n\n async list(filter?: SnippetFilter | ListOptions, options?: ListOptions): Promise<SnippetMeta[]> {\n const manifest = await this.loadManifest();\n let predicate: SnippetFilter | undefined;\n let finalOptions: ListOptions = {};\n\n if (typeof filter === \"function\") {\n predicate = filter;\n finalOptions = options ?? {};\n } else if (filter) {\n finalOptions = filter;\n } else if (options) {\n finalOptions = options;\n }\n\n let items = manifest;\n const filters: SnippetFilter[] = [];\n if (predicate) {\n filters.push(predicate);\n }\n if (finalOptions.filter) {\n filters.push(finalOptions.filter);\n }\n if (filters.length) {\n items = items.filter((entry) => filters.every((fn) => fn(entry)));\n }\n\n const offset = finalOptions.offset ?? 0;\n const limit = finalOptions.limit;\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (typeof limit === \"number\") {\n items = items.slice(0, limit);\n }\n\n return [...items];\n }\n\n listByType(type: string, options?: ListOptions): Promise<SnippetMeta[]> {\n return this.list((entry) => entry.type === type, options);\n }\n\n listByGroup(group: string, options?: ListOptions): Promise<SnippetMeta[]> {\n return this.list((entry) => entry.group === group, options);\n }\n\n private loadSnippet(meta: SnippetMeta): Promise<Snippet> {\n const cached = this.snippetCache.get(meta.slug);\n if (cached) {\n return cached;\n }\n\n const promise = this.fetchSnippet(meta);\n this.snippetCache.set(meta.slug, promise);\n return promise;\n }\n\n private async fetchSnippet(meta: SnippetMeta): Promise<Snippet> {\n const snippetPath = this.resolveSnippetPath(meta);\n const response = await this.fetcher(snippetPath);\n if (!response.ok) {\n throw new Error(`Failed to fetch snippet '${meta.slug}' (status ${response.status})`);\n }\n\n const rawContent = await response.text();\n const parsed = matter(rawContent);\n const frontMatter = sanitizeFrontMatter(parsed.data ?? {});\n const mergedMeta = mergeFrontMatter(meta, frontMatter.meta);\n\n const html = await this.markdownRenderer(parsed.content);\n\n const baseExtra = mergedMeta.extra ?? {};\n return {\n ...mergedMeta,\n extra: {\n ...baseExtra,\n ...frontMatter.extra\n },\n markdown: parsed.content,\n html\n };\n }\n\n private loadManifest(): Promise<SnippetMeta[]> {\n if (!this.manifestPromise) {\n this.manifestPromise = this.resolveManifest();\n }\n return this.manifestPromise;\n }\n\n private async resolveManifest(): Promise<SnippetMeta[]> {\n if (Array.isArray(this.manifestSource)) {\n return this.manifestSource;\n }\n\n if (typeof this.manifestSource === \"function\") {\n const result = await this.manifestSource();\n return Array.isArray(result) ? result : Promise.reject(new Error(\"Manifest function must return an array\"));\n }\n\n const raw = await this.fetchText(this.manifestSource);\n const manifest = JSON.parse(raw);\n if (!Array.isArray(manifest)) {\n throw new Error(\"Manifest must be an array\");\n }\n return manifest;\n }\n\n private async fetchText(path: string): Promise<string> {\n const response = await this.fetcher(path);\n if (!response.ok) {\n throw new Error(`Failed to fetch manifest from ${path} (status ${response.status})`);\n }\n return response.text();\n }\n}\n\nfunction sanitizeFrontMatter(data: Record<string, unknown>) {\n const meta: Partial<SnippetMeta> = {};\n const extra: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (OPTIONAL_FIELDS.includes(key as keyof SnippetMeta)) {\n if (key === \"tags\") {\n meta.tags = normalizeTags(value);\n } else {\n (meta as Record<string, unknown>)[key] = value;\n }\n } else {\n extra[key] = value;\n }\n }\n\n return { meta, extra };\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((tag) => String(tag));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((tag) => tag.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction mergeFrontMatter(base: SnippetMeta, overrides: Partial<SnippetMeta>): SnippetMeta {\n const merged: SnippetMeta = { ...base };\n for (const field of OPTIONAL_FIELDS) {\n const overrideValue = overrides[field];\n if (overrideValue !== undefined && merged[field] === undefined) {\n merged[field] = overrideValue as never;\n }\n }\n return merged;\n}\n\nasync function defaultFetch(input: string): Promise<ResponseLike> {\n const runtimeFetch = (globalThis as typeof globalThis & { fetch?: typeof fetch }).fetch;\n if (!runtimeFetch) {\n throw new Error(\"No global fetch implementation found. Provide a custom fetcher.\");\n }\n return runtimeFetch(input);\n}\n"],"mappings":"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","names":[]}
package/dist/slug.cjs ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/slug.ts
21
+ var slug_exports = {};
22
+ __export(slug_exports, {
23
+ normalizeSlug: () => normalizeSlug
24
+ });
25
+ module.exports = __toCommonJS(slug_exports);
26
+ var NON_ALPHANUMERIC = /[^a-z0-9]+/gi;
27
+ var LEADING_TRAILING_DASH = /^-+|-+$/g;
28
+ function normalizeSlug(input) {
29
+ const value = input?.trim();
30
+ if (!value) {
31
+ throw new Error("Cannot normalize an empty slug");
32
+ }
33
+ const normalized = value.toLowerCase().replace(NON_ALPHANUMERIC, "-").replace(/-{2,}/g, "-").replace(LEADING_TRAILING_DASH, "");
34
+ if (!normalized) {
35
+ throw new Error(`Slug '${input}' does not contain any alphanumeric characters`);
36
+ }
37
+ return normalized;
38
+ }
39
+ // Annotate the CommonJS export names for ESM import in node:
40
+ 0 && (module.exports = {
41
+ normalizeSlug
42
+ });
43
+ //# sourceMappingURL=slug.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/slug.ts"],"sourcesContent":["const NON_ALPHANUMERIC = /[^a-z0-9]+/gi;\nconst LEADING_TRAILING_DASH = /^-+|-+$/g;\n\nexport function normalizeSlug(input: string): string {\n const value = input?.trim();\n if (!value) {\n throw new Error(\"Cannot normalize an empty slug\");\n }\n\n const normalized = value\n .toLowerCase()\n .replace(NON_ALPHANUMERIC, \"-\")\n .replace(/-{2,}/g, \"-\")\n .replace(LEADING_TRAILING_DASH, \"\");\n\n if (!normalized) {\n throw new Error(`Slug '${input}' does not contain any alphanumeric characters`);\n }\n\n return normalized;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAEvB,SAAS,cAAc,OAAuB;AACnD,QAAM,QAAQ,OAAO,KAAK;AAC1B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,gCAAgC;AAAA,EAClD;AAEA,QAAM,aAAa,MAChB,YAAY,EACZ,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,UAAU,GAAG,EACrB,QAAQ,uBAAuB,EAAE;AAEpC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,SAAS,KAAK,gDAAgD;AAAA,EAChF;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,3 @@
1
+ declare function normalizeSlug(input: string): string;
2
+
3
+ export { normalizeSlug };
package/dist/slug.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare function normalizeSlug(input: string): string;
2
+
3
+ export { normalizeSlug };
package/dist/slug.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ normalizeSlug
3
+ } from "./chunk-TQ5Y4RZJ.mjs";
4
+ export {
5
+ normalizeSlug
6
+ };
7
+ //# sourceMappingURL=slug.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}