@tsdoctor/snapshot 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C. Spencer Beggs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @tsdoctor/snapshot
2
+
3
+ [![npm](https://img.shields.io/npm/v/@tsdoctor%2Fsnapshot?label=npm&color=cb3837)](https://www.npmjs.com/package/@tsdoctor/snapshot)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/)
6
+ [![TypeScript 6.0](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg)](https://www.typescriptlang.org/)
7
+
8
+ Incremental-build snapshot tracking for static documentation pipelines. The package stores per-file content hashes and timestamps in a schema-versioned SQLite database (built on `@effected/store`'s `Store`), so a build can skip writes for unchanged files, preserve SEO-critical publication timestamps, and clean up files that fell out of the source model.
9
+
10
+ ## What you get
11
+
12
+ - **`SnapshotService`** — an Effect service tag with typed operations: single and bulk lookup, transactional batch upsert (with a conditional `ON CONFLICT ... DO UPDATE ... WHERE` that avoids rewriting unchanged rows), deletion and stale-entry cleanup.
13
+ - **`SnapshotServiceLive(dbPath)`** — the live layer over a SQLite file. Layer construction applies migrations through `@effected/store`'s ledger; a WAL checkpoint runs as a scope finalizer on clean shutdown.
14
+ - **`hashContent` / `hashFrontmatter` / `normalizeContent`** — pure SHA-256 helpers that normalize markdown bodies and frontmatter (excluding timestamp fields) into stable change-detection hashes.
15
+ - **`FileSnapshot`** / **`SnapshotDbError`** — the tracked-file record and the typed error every operation can fail with.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @tsdoctor/snapshot
21
+ # or
22
+ pnpm add @tsdoctor/snapshot
23
+ ```
24
+
25
+ This is an ESM-only package. `effect` (v4) and `@effected/store` are peer dependencies.
26
+
27
+ ## Quick start
28
+
29
+ ```ts
30
+ import { SnapshotService, SnapshotServiceLive, hashContent } from "@tsdoctor/snapshot";
31
+ import { Effect } from "effect";
32
+
33
+ const layer = SnapshotServiceLive(".api-docs/snapshot/api-docs.db");
34
+
35
+ const program = Effect.gen(function* () {
36
+ const snapshots = yield* SnapshotService;
37
+ const existing = yield* snapshots.getAllForDirectory("docs/api");
38
+ // ... compare hashContent(body) against existing entries, then:
39
+ yield* snapshots.batchUpsert(changed);
40
+ yield* snapshots.cleanupStale("docs/api", generatedFiles);
41
+ });
42
+
43
+ await Effect.runPromise(Effect.scoped(program.pipe(Effect.provide(layer))));
44
+ ```
45
+
46
+ The parent directory of the database path must exist before the layer is built; path policy (where the database lives) is the caller's concern.
47
+
48
+ ## Provenance
49
+
50
+ Extracted in phase 2 of the tsdoctor consolidation from the snapshot tracking system inside `rspress-plugin-api-extractor`, rebuilt on `@effected/store` with the same SQL schema and query semantics.
51
+
52
+ ## License
53
+
54
+ [MIT](LICENSE)
@@ -0,0 +1,27 @@
1
+ import { Context, Data } from "effect";
2
+
3
+ //#region src/SnapshotService.ts
4
+ /**
5
+ * Raised when a snapshot database operation fails.
6
+ *
7
+ * @public
8
+ */
9
+ var SnapshotDbError = class extends Data.TaggedError("SnapshotDbError") {
10
+ /** Formatted failure message combining operation, path and reason. */
11
+ get message() {
12
+ return `Snapshot DB error during '${this.operation}' at '${this.dbPath}': ${this.reason}`;
13
+ }
14
+ };
15
+ /**
16
+ * Effect service tag for the snapshot tracking store.
17
+ *
18
+ * @remarks
19
+ * Provided by {@link SnapshotServiceLive}, which backs it with a
20
+ * schema-versioned SQLite database via `@effected/store`.
21
+ *
22
+ * @public
23
+ */
24
+ var SnapshotService = class extends Context.Service()("@tsdoctor/snapshot/SnapshotService") {};
25
+
26
+ //#endregion
27
+ export { SnapshotDbError, SnapshotService };
@@ -0,0 +1,131 @@
1
+ import { hashContent } from "./content-hash.js";
2
+ import { SnapshotDbError, SnapshotService } from "./SnapshotService.js";
3
+ import { Effect, Layer, Option } from "effect";
4
+ import { Store } from "@effected/store";
5
+
6
+ //#region src/SnapshotServiceLive.ts
7
+ function toFileSnapshot(row) {
8
+ return {
9
+ outputDir: row.output_dir,
10
+ filePath: row.file_path,
11
+ publishedTime: row.published_time,
12
+ modifiedTime: row.modified_time,
13
+ contentHash: row.content_hash,
14
+ frontmatterHash: row.frontmatter_hash,
15
+ buildTime: row.build_time
16
+ };
17
+ }
18
+ function toSnapshotDbError(error) {
19
+ return new SnapshotDbError({
20
+ operation: "query",
21
+ dbPath: "snapshot-db",
22
+ reason: error instanceof Error ? error.message : String(error)
23
+ });
24
+ }
25
+ const migrations = [{
26
+ id: 1,
27
+ name: "001_create_snapshots",
28
+ up: (sql) => Effect.gen(function* () {
29
+ yield* sql`
30
+ CREATE TABLE IF NOT EXISTS file_snapshots (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ output_dir TEXT NOT NULL,
33
+ file_path TEXT NOT NULL,
34
+ published_time TEXT NOT NULL,
35
+ modified_time TEXT NOT NULL,
36
+ content_hash TEXT NOT NULL,
37
+ frontmatter_hash TEXT NOT NULL,
38
+ build_time TEXT NOT NULL,
39
+ UNIQUE(output_dir, file_path)
40
+ )
41
+ `;
42
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_output_dir ON file_snapshots(output_dir)`;
43
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_file_path ON file_snapshots(file_path)`;
44
+ })
45
+ }];
46
+ /**
47
+ * Builds the live {@link SnapshotService} layer over a SQLite database file.
48
+ *
49
+ * @remarks
50
+ * Backed by `@effected/store`'s `Store.layerSqlite`: layer construction opens
51
+ * the database (WAL mode), ensures the migration ledger and applies pending
52
+ * migrations. A WAL checkpoint (`PRAGMA wal_checkpoint(TRUNCATE)`) is
53
+ * registered as a scope finalizer so the sidecar files are folded back into
54
+ * the main database on clean shutdown.
55
+ *
56
+ * This is a parameterized layer factory: call it once per database path and
57
+ * bind the result to a `const` — layers memoize by reference, and a fresh
58
+ * call at each provide site would open the database more than once. The
59
+ * parent directory of `dbPath` must already exist.
60
+ *
61
+ * @param dbPath - Path to the SQLite database file
62
+ * @returns A layer providing {@link SnapshotService}
63
+ * @public
64
+ */
65
+ const SnapshotServiceLive = (dbPath) => {
66
+ const StoreLive = Store.layerSqlite({
67
+ filename: dbPath,
68
+ migrations
69
+ });
70
+ const ServiceImpl = Layer.effect(SnapshotService, Effect.gen(function* () {
71
+ const sql = (yield* Store).client;
72
+ yield* Effect.addFinalizer(() => sql`PRAGMA wal_checkpoint(TRUNCATE)`.pipe(Effect.ignore));
73
+ return {
74
+ hashContent,
75
+ getSnapshot: (outputDir, filePath) => sql`SELECT * FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${filePath}`.pipe(Effect.map((rows) => rows.length > 0 ? Option.some(toFileSnapshot(rows[0])) : Option.none()), Effect.mapError(toSnapshotDbError)),
76
+ getAllForDirectory: (outputDir) => sql`SELECT * FROM file_snapshots WHERE output_dir = ${outputDir}`.pipe(Effect.map((rows) => rows.map(toFileSnapshot)), Effect.mapError(toSnapshotDbError)),
77
+ getFilePaths: (outputDir) => sql`SELECT file_path FROM file_snapshots WHERE output_dir = ${outputDir}`.pipe(Effect.map((rows) => rows.map((r) => r.file_path)), Effect.mapError(toSnapshotDbError)),
78
+ upsert: (snapshot) => sql`INSERT INTO file_snapshots
79
+ (output_dir, file_path, published_time, modified_time,
80
+ content_hash, frontmatter_hash, build_time)
81
+ VALUES (${snapshot.outputDir}, ${snapshot.filePath},
82
+ ${snapshot.publishedTime}, ${snapshot.modifiedTime},
83
+ ${snapshot.contentHash}, ${snapshot.frontmatterHash},
84
+ ${snapshot.buildTime})
85
+ ON CONFLICT(output_dir, file_path) DO UPDATE SET
86
+ published_time = ${snapshot.publishedTime},
87
+ modified_time = ${snapshot.modifiedTime},
88
+ content_hash = ${snapshot.contentHash},
89
+ frontmatter_hash = ${snapshot.frontmatterHash},
90
+ build_time = ${snapshot.buildTime}
91
+ WHERE published_time != ${snapshot.publishedTime}
92
+ OR modified_time != ${snapshot.modifiedTime}
93
+ OR content_hash != ${snapshot.contentHash}
94
+ OR frontmatter_hash != ${snapshot.frontmatterHash}`.pipe(Effect.as(true), Effect.mapError(toSnapshotDbError)),
95
+ batchUpsert: (snapshots) => (snapshots.length === 0 ? Effect.succeed(0) : sql.withTransaction(Effect.forEach(snapshots, (s) => sql`INSERT INTO file_snapshots
96
+ (output_dir, file_path, published_time, modified_time,
97
+ content_hash, frontmatter_hash, build_time)
98
+ VALUES (${s.outputDir}, ${s.filePath},
99
+ ${s.publishedTime}, ${s.modifiedTime},
100
+ ${s.contentHash}, ${s.frontmatterHash},
101
+ ${s.buildTime})
102
+ ON CONFLICT(output_dir, file_path) DO UPDATE SET
103
+ published_time = ${s.publishedTime},
104
+ modified_time = ${s.modifiedTime},
105
+ content_hash = ${s.contentHash},
106
+ frontmatter_hash = ${s.frontmatterHash},
107
+ build_time = ${s.buildTime}
108
+ WHERE published_time != ${s.publishedTime}
109
+ OR modified_time != ${s.modifiedTime}
110
+ OR content_hash != ${s.contentHash}
111
+ OR frontmatter_hash != ${s.frontmatterHash}`, { concurrency: 1 })).pipe(Effect.map(() => snapshots.length))).pipe(Effect.mapError(toSnapshotDbError)),
112
+ deleteSnapshot: (outputDir, filePath) => sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${filePath}`.pipe(Effect.asVoid, Effect.mapError(toSnapshotDbError)),
113
+ cleanupStale: (outputDir, currentFiles) => Effect.gen(function* () {
114
+ const rows = yield* sql`SELECT file_path FROM file_snapshots WHERE output_dir = ${outputDir}`;
115
+ const staleFiles = [];
116
+ for (const row of rows) {
117
+ const fp = row.file_path;
118
+ if (!currentFiles.has(fp)) {
119
+ yield* sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${fp}`;
120
+ staleFiles.push(fp);
121
+ }
122
+ }
123
+ return staleFiles;
124
+ }).pipe(Effect.mapError(toSnapshotDbError))
125
+ };
126
+ }));
127
+ return Layer.provide(ServiceImpl, StoreLive);
128
+ };
129
+
130
+ //#endregion
131
+ export { SnapshotServiceLive };
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ //#region src/content-hash.ts
4
+ /**
5
+ * Normalizes content string for consistent hashing.
6
+ *
7
+ * @remarks
8
+ * Applies the following transformations:
9
+ * - Converts all line endings to Unix-style (`\n`)
10
+ * - Trims leading and trailing whitespace
11
+ * - Collapses multiple consecutive blank lines to a single blank line
12
+ *
13
+ * @param content - The content string to normalize
14
+ * @returns Normalized content string
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { normalizeContent } from "@tsdoctor/snapshot";
19
+ *
20
+ * const normalized = normalizeContent("line1\r\n\r\n\r\nline2 ");
21
+ * // => "line1\n\nline2"
22
+ * ```
23
+ *
24
+ * @public
25
+ */
26
+ function normalizeContent(content) {
27
+ return content.replaceAll("\r\n", "\n").replaceAll("\r", "\n").trim().replaceAll(/\n{3,}/g, "\n\n");
28
+ }
29
+ /**
30
+ * Generates a SHA-256 hash of normalized markdown content.
31
+ *
32
+ * @remarks
33
+ * The content is normalized before hashing to ensure consistent results
34
+ * regardless of line ending differences or trailing whitespace.
35
+ *
36
+ * @param content - The markdown content to hash (excluding frontmatter)
37
+ * @returns Hexadecimal SHA-256 hash string
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { hashContent } from "@tsdoctor/snapshot";
42
+ *
43
+ * const hash = hashContent("# My Title\n\nContent here");
44
+ * ```
45
+ *
46
+ * @public
47
+ */
48
+ function hashContent(content) {
49
+ const normalized = normalizeContent(content);
50
+ return createHash("sha256").update(normalized).digest("hex");
51
+ }
52
+ /**
53
+ * Generates a SHA-256 hash of frontmatter fields.
54
+ *
55
+ * @remarks
56
+ * Excludes timestamp-related fields (`publishedTime`, `modifiedTime`, `head`,
57
+ * `article:published_time`, `article:modified_time`) to prevent circular
58
+ * dependencies in change detection. Keys are sorted alphabetically before
59
+ * hashing to ensure consistent results regardless of object key order.
60
+ *
61
+ * @param frontmatter - The frontmatter object to hash
62
+ * @returns Hexadecimal SHA-256 hash string
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { hashFrontmatter } from "@tsdoctor/snapshot";
67
+ *
68
+ * const hash = hashFrontmatter({
69
+ * title: "My Page",
70
+ * description: "Page description"
71
+ * });
72
+ * ```
73
+ *
74
+ * @public
75
+ */
76
+ function hashFrontmatter(frontmatter) {
77
+ const filtered = {};
78
+ for (const [key, value] of Object.entries(frontmatter)) {
79
+ if (key === "publishedTime" || key === "modifiedTime" || key === "head" || key === "article:published_time" || key === "article:modified_time") continue;
80
+ filtered[key] = value;
81
+ }
82
+ const sorted = Object.keys(filtered).sort().reduce((acc, key) => {
83
+ acc[key] = filtered[key];
84
+ return acc;
85
+ }, {});
86
+ const json = JSON.stringify(sorted);
87
+ return createHash("sha256").update(json).digest("hex");
88
+ }
89
+
90
+ //#endregion
91
+ export { hashContent, hashFrontmatter, normalizeContent };
package/index.d.ts ADDED
@@ -0,0 +1,176 @@
1
+ import { Context, Effect, Layer, Option } from "effect";
2
+ import { StoreError, StoreMigrationError } from "@effected/store";
3
+ //#region src/content-hash.d.ts
4
+ /**
5
+ * Normalizes content string for consistent hashing.
6
+ *
7
+ * @remarks
8
+ * Applies the following transformations:
9
+ * - Converts all line endings to Unix-style (`\n`)
10
+ * - Trims leading and trailing whitespace
11
+ * - Collapses multiple consecutive blank lines to a single blank line
12
+ *
13
+ * @param content - The content string to normalize
14
+ * @returns Normalized content string
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { normalizeContent } from "@tsdoctor/snapshot";
19
+ *
20
+ * const normalized = normalizeContent("line1\r\n\r\n\r\nline2 ");
21
+ * // => "line1\n\nline2"
22
+ * ```
23
+ *
24
+ * @public
25
+ */
26
+ declare function normalizeContent(content: string): string;
27
+ /**
28
+ * Generates a SHA-256 hash of normalized markdown content.
29
+ *
30
+ * @remarks
31
+ * The content is normalized before hashing to ensure consistent results
32
+ * regardless of line ending differences or trailing whitespace.
33
+ *
34
+ * @param content - The markdown content to hash (excluding frontmatter)
35
+ * @returns Hexadecimal SHA-256 hash string
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { hashContent } from "@tsdoctor/snapshot";
40
+ *
41
+ * const hash = hashContent("# My Title\n\nContent here");
42
+ * ```
43
+ *
44
+ * @public
45
+ */
46
+ declare function hashContent(content: string): string;
47
+ /**
48
+ * Generates a SHA-256 hash of frontmatter fields.
49
+ *
50
+ * @remarks
51
+ * Excludes timestamp-related fields (`publishedTime`, `modifiedTime`, `head`,
52
+ * `article:published_time`, `article:modified_time`) to prevent circular
53
+ * dependencies in change detection. Keys are sorted alphabetically before
54
+ * hashing to ensure consistent results regardless of object key order.
55
+ *
56
+ * @param frontmatter - The frontmatter object to hash
57
+ * @returns Hexadecimal SHA-256 hash string
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * import { hashFrontmatter } from "@tsdoctor/snapshot";
62
+ *
63
+ * const hash = hashFrontmatter({
64
+ * title: "My Page",
65
+ * description: "Page description"
66
+ * });
67
+ * ```
68
+ *
69
+ * @public
70
+ */
71
+ declare function hashFrontmatter(frontmatter: Record<string, unknown>): string;
72
+ //#endregion
73
+ //#region src/SnapshotService.d.ts
74
+ /**
75
+ * A tracked snapshot of one generated file: its content/frontmatter hashes
76
+ * and the timestamps preserved across incremental builds.
77
+ *
78
+ * @public
79
+ */
80
+ interface FileSnapshot {
81
+ /** Output directory the file was generated into. */
82
+ readonly outputDir: string;
83
+ /** File path relative to `outputDir`. */
84
+ readonly filePath: string;
85
+ /** ISO timestamp of first publication, preserved for unchanged files. */
86
+ readonly publishedTime: string;
87
+ /** ISO timestamp of last content change. */
88
+ readonly modifiedTime: string;
89
+ /** SHA-256 hash of the normalized file body. */
90
+ readonly contentHash: string;
91
+ /** SHA-256 hash of the frontmatter (timestamp fields excluded). */
92
+ readonly frontmatterHash: string;
93
+ /** ISO timestamp of the build that last wrote this snapshot. */
94
+ readonly buildTime: string;
95
+ }
96
+ declare const SnapshotDbError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
97
+ readonly _tag: "SnapshotDbError";
98
+ } & Readonly<A>;
99
+ /**
100
+ * Raised when a snapshot database operation fails.
101
+ *
102
+ * @public
103
+ */
104
+ declare class SnapshotDbError extends SnapshotDbError_base<{
105
+ /** The database operation that failed (e.g. `"query"`). */
106
+ readonly operation: string;
107
+ /** Path or label of the database the operation ran against. */
108
+ readonly dbPath: string;
109
+ /** Human-readable failure reason. */
110
+ readonly reason: string;
111
+ }> {
112
+ /** Formatted failure message combining operation, path and reason. */
113
+ get message(): string;
114
+ }
115
+ /**
116
+ * The operations {@link SnapshotService} provides for tracking generated
117
+ * files across incremental builds.
118
+ *
119
+ * @public
120
+ */
121
+ interface SnapshotServiceShape {
122
+ /** Hash normalized content with SHA-256 (pure, synchronous). */
123
+ readonly hashContent: (content: string) => string;
124
+ /** Look up a single snapshot by output directory and file path. */
125
+ readonly getSnapshot: (outputDir: string, filePath: string) => Effect.Effect<Option.Option<FileSnapshot>, SnapshotDbError>;
126
+ /** Load every snapshot recorded for an output directory. */
127
+ readonly getAllForDirectory: (outputDir: string) => Effect.Effect<ReadonlyArray<FileSnapshot>, SnapshotDbError>;
128
+ /** List the tracked file paths for an output directory. */
129
+ readonly getFilePaths: (outputDir: string) => Effect.Effect<ReadonlyArray<string>, SnapshotDbError>;
130
+ /** Insert or update a single snapshot. */
131
+ readonly upsert: (snapshot: FileSnapshot) => Effect.Effect<boolean, SnapshotDbError>;
132
+ /** Insert or update many snapshots in one transaction; returns the count. */
133
+ readonly batchUpsert: (snapshots: ReadonlyArray<FileSnapshot>) => Effect.Effect<number, SnapshotDbError>;
134
+ /** Remove a single snapshot row. */
135
+ readonly deleteSnapshot: (outputDir: string, filePath: string) => Effect.Effect<void, SnapshotDbError>;
136
+ /**
137
+ * Delete rows for files no longer generated; returns the stale file paths.
138
+ */
139
+ readonly cleanupStale: (outputDir: string, currentFiles: ReadonlySet<string>) => Effect.Effect<ReadonlyArray<string>, SnapshotDbError>;
140
+ }
141
+ declare const SnapshotService_base: Context.ServiceClass<SnapshotService, "@tsdoctor/snapshot/SnapshotService", SnapshotServiceShape>;
142
+ /**
143
+ * Effect service tag for the snapshot tracking store.
144
+ *
145
+ * @remarks
146
+ * Provided by {@link SnapshotServiceLive}, which backs it with a
147
+ * schema-versioned SQLite database via `@effected/store`.
148
+ *
149
+ * @public
150
+ */
151
+ declare class SnapshotService extends SnapshotService_base {}
152
+ //#endregion
153
+ //#region src/SnapshotServiceLive.d.ts
154
+ /**
155
+ * Builds the live {@link SnapshotService} layer over a SQLite database file.
156
+ *
157
+ * @remarks
158
+ * Backed by `@effected/store`'s `Store.layerSqlite`: layer construction opens
159
+ * the database (WAL mode), ensures the migration ledger and applies pending
160
+ * migrations. A WAL checkpoint (`PRAGMA wal_checkpoint(TRUNCATE)`) is
161
+ * registered as a scope finalizer so the sidecar files are folded back into
162
+ * the main database on clean shutdown.
163
+ *
164
+ * This is a parameterized layer factory: call it once per database path and
165
+ * bind the result to a `const` — layers memoize by reference, and a fresh
166
+ * call at each provide site would open the database more than once. The
167
+ * parent directory of `dbPath` must already exist.
168
+ *
169
+ * @param dbPath - Path to the SQLite database file
170
+ * @returns A layer providing {@link SnapshotService}
171
+ * @public
172
+ */
173
+ declare const SnapshotServiceLive: (dbPath: string) => Layer.Layer<SnapshotService, StoreError | StoreMigrationError>;
174
+ //#endregion
175
+ export { type FileSnapshot, SnapshotDbError, SnapshotService, SnapshotServiceLive, type SnapshotServiceShape, hashContent, hashFrontmatter, normalizeContent };
176
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { hashContent, hashFrontmatter, normalizeContent } from "./content-hash.js";
2
+ import { SnapshotDbError, SnapshotService } from "./SnapshotService.js";
3
+ import { SnapshotServiceLive } from "./SnapshotServiceLive.js";
4
+
5
+ export { SnapshotDbError, SnapshotService, SnapshotServiceLive, hashContent, hashFrontmatter, normalizeContent };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@tsdoctor/snapshot",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Incremental-build snapshot tracking for static documentation pipelines: a schema-versioned SQLite store of per-file content hashes and timestamps, built on @effected/store, plus the pure SHA-256 content-hashing helpers that feed it.",
6
+ "keywords": [
7
+ "snapshot",
8
+ "incremental-build",
9
+ "sqlite",
10
+ "content-hash",
11
+ "effect",
12
+ "documentation"
13
+ ],
14
+ "homepage": "https://github.com/spencerbeggs/tsdoctor#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/spencerbeggs/tsdoctor/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/spencerbeggs/tsdoctor.git",
21
+ "directory": "packages/snapshot"
22
+ },
23
+ "license": "MIT",
24
+ "author": {
25
+ "name": "C. Spencer Beggs",
26
+ "email": "spencer@beggs.codes",
27
+ "url": "https://spencerbeg.gs"
28
+ },
29
+ "sideEffects": false,
30
+ "type": "module",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./index.d.ts",
34
+ "import": "./index.js",
35
+ "default": "./index.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "peerDependencies": {
40
+ "@effected/store": "^0.4.0",
41
+ "effect": "4.0.0-rc.109"
42
+ },
43
+ "engines": {
44
+ "node": ">=24.11.0"
45
+ }
46
+ }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.59.0"
9
+ }
10
+ ]
11
+ }