@tsdoctor/snapshot 0.4.1 → 0.5.1

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.
@@ -44,6 +44,26 @@ var SnapshotService = class SnapshotService extends Context.Service()("@tsdoctor
44
44
  */
45
45
  static layer = (dbPath) => make(dbPath);
46
46
  /**
47
+ * The live {@link SnapshotService} implementation over an in-memory
48
+ * (`:memory:`) SQLite database.
49
+ *
50
+ * @remarks
51
+ * The same SQL, migrations and upsert semantics as
52
+ * {@link SnapshotService.layer}, backed by `@effected/store`'s
53
+ * `Store.layerTest` instead of a file — so a test exercises the real queries
54
+ * without a temp directory. What it cannot cover is file-backed behaviour:
55
+ * WAL mode, `checkpointOnClose`, and persistence across a reopen. Unlike
56
+ * {@link SnapshotService.layerTest}, nothing is stubbed.
57
+ *
58
+ * This is a layer FACTORY: each call mints a fresh layer and so a fresh,
59
+ * empty database. Bind the result to a `const` and provide it once when the
60
+ * steps of one test must see each other's rows.
61
+ *
62
+ * @returns A layer providing {@link SnapshotService}
63
+ * @public
64
+ */
65
+ static layerMemory = () => makeMemory();
66
+ /**
47
67
  * An in-memory double: no SQLite file, no migrations, no WAL.
48
68
  *
49
69
  * @remarks
@@ -113,20 +133,21 @@ const migrations = [{
113
133
  yield* sql`CREATE INDEX IF NOT EXISTS idx_file_path ON file_snapshots(file_path)`;
114
134
  })
115
135
  }];
116
- const make = (dbPath) => {
117
- const StoreLive = Store.layerSqlite({
118
- filename: dbPath,
119
- migrations,
120
- checkpointOnClose: true
121
- });
122
- const ServiceImpl = Layer.effect(SnapshotService, Effect.gen(function* () {
123
- const sql = (yield* Store).client;
124
- return {
125
- hashContent,
126
- 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)),
127
- getAllForDirectory: (outputDir) => sql`SELECT * FROM file_snapshots WHERE output_dir = ${outputDir}`.pipe(Effect.map((rows) => rows.map(toFileSnapshot)), Effect.mapError(toSnapshotDbError)),
128
- 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)),
129
- upsert: (snapshot) => sql`INSERT INTO file_snapshots
136
+ const make = (dbPath) => Layer.provide(serviceImpl(), Store.layerSqlite({
137
+ filename: dbPath,
138
+ migrations,
139
+ checkpointOnClose: true
140
+ }));
141
+ const makeMemory = () => Layer.provide(serviceImpl(), Store.layerTest({ migrations }));
142
+ /** The SQL implementation, over whichever `Store` the caller provides. */
143
+ const serviceImpl = () => Layer.effect(SnapshotService, Effect.gen(function* () {
144
+ const sql = (yield* Store).client;
145
+ return {
146
+ hashContent,
147
+ 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)),
148
+ getAllForDirectory: (outputDir) => sql`SELECT * FROM file_snapshots WHERE output_dir = ${outputDir}`.pipe(Effect.map((rows) => rows.map(toFileSnapshot)), Effect.mapError(toSnapshotDbError)),
149
+ 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)),
150
+ upsert: (snapshot) => sql`INSERT INTO file_snapshots
130
151
  (output_dir, file_path, published_time, modified_time,
131
152
  content_hash, frontmatter_hash, build_time)
132
153
  VALUES (${snapshot.outputDir}, ${snapshot.filePath},
@@ -143,7 +164,7 @@ const make = (dbPath) => {
143
164
  OR modified_time != ${snapshot.modifiedTime}
144
165
  OR content_hash != ${snapshot.contentHash}
145
166
  OR frontmatter_hash != ${snapshot.frontmatterHash}`.pipe(Effect.as(true), Effect.mapError(toSnapshotDbError)),
146
- batchUpsert: (snapshots) => (snapshots.length === 0 ? Effect.succeed(0) : sql.withTransaction(Effect.forEach(snapshots, (s) => sql`INSERT INTO file_snapshots
167
+ batchUpsert: (snapshots) => (snapshots.length === 0 ? Effect.succeed(0) : sql.withTransaction(Effect.forEach(snapshots, (s) => sql`INSERT INTO file_snapshots
147
168
  (output_dir, file_path, published_time, modified_time,
148
169
  content_hash, frontmatter_hash, build_time)
149
170
  VALUES (${s.outputDir}, ${s.filePath},
@@ -160,23 +181,21 @@ const make = (dbPath) => {
160
181
  OR modified_time != ${s.modifiedTime}
161
182
  OR content_hash != ${s.contentHash}
162
183
  OR frontmatter_hash != ${s.frontmatterHash}`, { concurrency: 1 })).pipe(Effect.map(() => snapshots.length))).pipe(Effect.mapError(toSnapshotDbError)),
163
- deleteSnapshot: (outputDir, filePath) => sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${filePath}`.pipe(Effect.asVoid, Effect.mapError(toSnapshotDbError)),
164
- cleanupStale: (outputDir, currentFiles) => Effect.gen(function* () {
165
- const rows = yield* sql`SELECT file_path FROM file_snapshots WHERE output_dir = ${outputDir}`;
166
- const staleFiles = [];
167
- for (const row of rows) {
168
- const fp = row.file_path;
169
- if (!currentFiles.has(fp)) {
170
- yield* sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${fp}`;
171
- staleFiles.push(fp);
172
- }
184
+ deleteSnapshot: (outputDir, filePath) => sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${filePath}`.pipe(Effect.asVoid, Effect.mapError(toSnapshotDbError)),
185
+ cleanupStale: (outputDir, currentFiles) => Effect.gen(function* () {
186
+ const rows = yield* sql`SELECT file_path FROM file_snapshots WHERE output_dir = ${outputDir}`;
187
+ const staleFiles = [];
188
+ for (const row of rows) {
189
+ const fp = row.file_path;
190
+ if (!currentFiles.has(fp)) {
191
+ yield* sql`DELETE FROM file_snapshots WHERE output_dir = ${outputDir} AND file_path = ${fp}`;
192
+ staleFiles.push(fp);
173
193
  }
174
- return staleFiles;
175
- }).pipe(Effect.mapError(toSnapshotDbError))
176
- };
177
- }));
178
- return Layer.provide(ServiceImpl, StoreLive);
179
- };
194
+ }
195
+ return staleFiles;
196
+ }).pipe(Effect.mapError(toSnapshotDbError))
197
+ };
198
+ }));
180
199
 
181
200
  //#endregion
182
201
  export { SnapshotDbError, SnapshotService };
package/index.d.ts CHANGED
@@ -171,6 +171,26 @@ export declare class SnapshotService extends SnapshotService_base {
171
171
  * @public
172
172
  */
173
173
  static readonly layer: (dbPath: string) => Layer.Layer<SnapshotService, StoreError | StoreMigrationError>;
174
+ /**
175
+ * The live {@link SnapshotService} implementation over an in-memory
176
+ * (`:memory:`) SQLite database.
177
+ *
178
+ * @remarks
179
+ * The same SQL, migrations and upsert semantics as
180
+ * {@link SnapshotService.layer}, backed by `@effected/store`'s
181
+ * `Store.layerTest` instead of a file — so a test exercises the real queries
182
+ * without a temp directory. What it cannot cover is file-backed behaviour:
183
+ * WAL mode, `checkpointOnClose`, and persistence across a reopen. Unlike
184
+ * {@link SnapshotService.layerTest}, nothing is stubbed.
185
+ *
186
+ * This is a layer FACTORY: each call mints a fresh layer and so a fresh,
187
+ * empty database. Bind the result to a `const` and provide it once when the
188
+ * steps of one test must see each other's rows.
189
+ *
190
+ * @returns A layer providing {@link SnapshotService}
191
+ * @public
192
+ */
193
+ static readonly layerMemory: () => Layer.Layer<SnapshotService, StoreError | StoreMigrationError>;
174
194
  /**
175
195
  * An in-memory double: no SQLite file, no migrations, no WAL.
176
196
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsdoctor/snapshot",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
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
6
  "keywords": [
@@ -37,11 +37,11 @@
37
37
  "./package.json": "./package.json"
38
38
  },
39
39
  "dependencies": {
40
- "@effected/jsonc": "^0.14.0"
40
+ "@effected/jsonc": "^0.15.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@effected/store": "^0.11.0",
44
- "effect": "4.0.0-rc.118"
43
+ "@effected/store": "^0.12.0",
44
+ "effect": "^4.0.0"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.11.0"