@mepuka/skygent 0.2.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/README.md +59 -0
- package/index.ts +146 -0
- package/package.json +56 -0
- package/src/cli/app.ts +75 -0
- package/src/cli/config-command.ts +140 -0
- package/src/cli/config.ts +91 -0
- package/src/cli/derive.ts +205 -0
- package/src/cli/doc/annotation.ts +36 -0
- package/src/cli/doc/filter.ts +69 -0
- package/src/cli/doc/index.ts +9 -0
- package/src/cli/doc/post.ts +155 -0
- package/src/cli/doc/primitives.ts +25 -0
- package/src/cli/doc/render.ts +18 -0
- package/src/cli/doc/table.ts +114 -0
- package/src/cli/doc/thread.ts +46 -0
- package/src/cli/doc/tree.ts +126 -0
- package/src/cli/errors.ts +59 -0
- package/src/cli/exit-codes.ts +52 -0
- package/src/cli/feed.ts +177 -0
- package/src/cli/filter-dsl.ts +1411 -0
- package/src/cli/filter-errors.ts +208 -0
- package/src/cli/filter-help.ts +70 -0
- package/src/cli/filter-input.ts +54 -0
- package/src/cli/filter.ts +435 -0
- package/src/cli/graph.ts +472 -0
- package/src/cli/help.ts +14 -0
- package/src/cli/interval.ts +35 -0
- package/src/cli/jetstream.ts +173 -0
- package/src/cli/layers.ts +180 -0
- package/src/cli/logging.ts +136 -0
- package/src/cli/output-format.ts +26 -0
- package/src/cli/output.ts +82 -0
- package/src/cli/parse.ts +80 -0
- package/src/cli/post.ts +193 -0
- package/src/cli/preferences.ts +11 -0
- package/src/cli/query-fields.ts +247 -0
- package/src/cli/query.ts +415 -0
- package/src/cli/range.ts +44 -0
- package/src/cli/search.ts +465 -0
- package/src/cli/shared-options.ts +169 -0
- package/src/cli/shared.ts +20 -0
- package/src/cli/store-errors.ts +80 -0
- package/src/cli/store-tree.ts +392 -0
- package/src/cli/store.ts +395 -0
- package/src/cli/sync-factory.ts +107 -0
- package/src/cli/sync.ts +366 -0
- package/src/cli/view-thread.ts +196 -0
- package/src/cli/view.ts +47 -0
- package/src/cli/watch.ts +344 -0
- package/src/db/migrations/store-catalog/001_init.ts +14 -0
- package/src/db/migrations/store-index/001_init.ts +34 -0
- package/src/db/migrations/store-index/002_event_log.ts +24 -0
- package/src/db/migrations/store-index/003_fts_and_derived.ts +52 -0
- package/src/db/migrations/store-index/004_query_indexes.ts +9 -0
- package/src/db/migrations/store-index/005_post_lang.ts +15 -0
- package/src/db/migrations/store-index/006_has_embed.ts +10 -0
- package/src/db/migrations/store-index/007_event_seq_and_checkpoints.ts +68 -0
- package/src/domain/bsky.ts +467 -0
- package/src/domain/config.ts +11 -0
- package/src/domain/credentials.ts +6 -0
- package/src/domain/defaults.ts +8 -0
- package/src/domain/derivation.ts +55 -0
- package/src/domain/errors.ts +71 -0
- package/src/domain/events.ts +55 -0
- package/src/domain/extract.ts +64 -0
- package/src/domain/filter-describe.ts +551 -0
- package/src/domain/filter-explain.ts +9 -0
- package/src/domain/filter.ts +797 -0
- package/src/domain/format.ts +91 -0
- package/src/domain/index.ts +13 -0
- package/src/domain/indexes.ts +17 -0
- package/src/domain/policies.ts +16 -0
- package/src/domain/post.ts +88 -0
- package/src/domain/primitives.ts +50 -0
- package/src/domain/raw.ts +140 -0
- package/src/domain/store.ts +103 -0
- package/src/domain/sync.ts +211 -0
- package/src/domain/text-width.ts +56 -0
- package/src/services/app-config.ts +278 -0
- package/src/services/bsky-client.ts +2113 -0
- package/src/services/credential-store.ts +408 -0
- package/src/services/derivation-engine.ts +502 -0
- package/src/services/derivation-settings.ts +61 -0
- package/src/services/derivation-validator.ts +68 -0
- package/src/services/filter-compiler.ts +269 -0
- package/src/services/filter-library.ts +371 -0
- package/src/services/filter-runtime.ts +821 -0
- package/src/services/filter-settings.ts +30 -0
- package/src/services/identity-resolver.ts +563 -0
- package/src/services/jetstream-sync.ts +636 -0
- package/src/services/lineage-store.ts +89 -0
- package/src/services/link-validator.ts +244 -0
- package/src/services/output-manager.ts +274 -0
- package/src/services/post-parser.ts +62 -0
- package/src/services/profile-resolver.ts +223 -0
- package/src/services/resource-monitor.ts +106 -0
- package/src/services/shared.ts +69 -0
- package/src/services/store-cleaner.ts +43 -0
- package/src/services/store-commit.ts +168 -0
- package/src/services/store-db.ts +248 -0
- package/src/services/store-event-log.ts +285 -0
- package/src/services/store-index-sql.ts +289 -0
- package/src/services/store-index.ts +1152 -0
- package/src/services/store-keys.ts +4 -0
- package/src/services/store-manager.ts +358 -0
- package/src/services/store-stats.ts +522 -0
- package/src/services/store-writer.ts +200 -0
- package/src/services/sync-checkpoint-store.ts +169 -0
- package/src/services/sync-engine.ts +547 -0
- package/src/services/sync-reporter.ts +16 -0
- package/src/services/sync-settings.ts +72 -0
- package/src/services/trending-topics.ts +226 -0
- package/src/services/view-checkpoint-store.ts +238 -0
- package/src/typeclass/chunk.ts +84 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Manager Service
|
|
3
|
+
*
|
|
4
|
+
* Manages the lifecycle of content stores for Bluesky data.
|
|
5
|
+
* Stores are SQLite databases that persist filtered posts and metadata.
|
|
6
|
+
*
|
|
7
|
+
* **Responsibilities:**
|
|
8
|
+
* - Create new stores with configuration
|
|
9
|
+
* - List all existing stores
|
|
10
|
+
* - Retrieve store references and metadata
|
|
11
|
+
* - Delete stores and clean up resources
|
|
12
|
+
* - Manage store catalog database (SQLite)
|
|
13
|
+
*
|
|
14
|
+
* **Store Catalog:**
|
|
15
|
+
* Each store is tracked in a central catalog database (catalog.sqlite)
|
|
16
|
+
* with metadata: name, root path, creation date, config JSON.
|
|
17
|
+
*
|
|
18
|
+
* **Store Root:**
|
|
19
|
+
* Stores are organized under `{storeRoot}/stores/{storeName}/` with:
|
|
20
|
+
* - posts.sqlite - Main content database
|
|
21
|
+
* - Additional store-specific files
|
|
22
|
+
*
|
|
23
|
+
* **Database Schema:**
|
|
24
|
+
* The catalog database runs migrations from `../db/migrations/store-catalog`
|
|
25
|
+
* to maintain the stores table schema.
|
|
26
|
+
*
|
|
27
|
+
* @module services/store-manager
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { StoreManager } from "./services/store-manager.js";
|
|
32
|
+
* import { Effect } from "effect";
|
|
33
|
+
*
|
|
34
|
+
* const program = Effect.gen(function* () {
|
|
35
|
+
* const manager = yield* StoreManager;
|
|
36
|
+
*
|
|
37
|
+
* // Create a new store
|
|
38
|
+
* const storeRef = yield* manager.createStore("my-feed", {
|
|
39
|
+
* filter: { _tag: "Contains", text: "tech" },
|
|
40
|
+
* errorPolicy: { _tag: "Exclude" }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // List all stores
|
|
44
|
+
* const stores = yield* manager.listStores();
|
|
45
|
+
* for (const store of stores) {
|
|
46
|
+
* console.log(`${store.name}: ${store.root}`);
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // Get store reference
|
|
50
|
+
* const ref = yield* manager.getStore("my-feed");
|
|
51
|
+
* if (Option.isSome(ref)) {
|
|
52
|
+
* console.log(`Store at: ${ref.value.root}`);
|
|
53
|
+
* }
|
|
54
|
+
* }).pipe(Effect.provide(StoreManager.layer));
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
59
|
+
import { Chunk, Clock, Context, Effect, Exit, Layer, Option, Schema, Scope } from "effect";
|
|
60
|
+
import * as Reactivity from "@effect/experimental/Reactivity";
|
|
61
|
+
import * as Migrator from "@effect/sql/Migrator";
|
|
62
|
+
import * as MigratorFileSystem from "@effect/sql/Migrator/FileSystem";
|
|
63
|
+
import * as SqlClient from "@effect/sql/SqlClient";
|
|
64
|
+
import * as SqlSchema from "@effect/sql/SqlSchema";
|
|
65
|
+
import { SqliteClient } from "@effect/sql-sqlite-bun";
|
|
66
|
+
import { StoreIoError } from "../domain/errors.js";
|
|
67
|
+
import { StoreConfig, StoreMetadata, StoreRef } from "../domain/store.js";
|
|
68
|
+
import { StoreName, StorePath } from "../domain/primitives.js";
|
|
69
|
+
import { AppConfigService } from "./app-config.js";
|
|
70
|
+
|
|
71
|
+
const migrationsDir = decodeURIComponent(
|
|
72
|
+
new URL("../db/migrations/store-catalog", import.meta.url).pathname
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const storeRootKey = (name: StoreName) => `stores/${name}`;
|
|
76
|
+
const manifestPath = Schema.decodeUnknownSync(StorePath)("stores");
|
|
77
|
+
|
|
78
|
+
const storeRow = Schema.Struct({
|
|
79
|
+
name: StoreName,
|
|
80
|
+
root: StorePath,
|
|
81
|
+
created_at: Schema.String,
|
|
82
|
+
updated_at: Schema.String,
|
|
83
|
+
config_json: Schema.String
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const toStoreIoError = (path: StorePath) => (cause: unknown) =>
|
|
87
|
+
StoreIoError.make({ path, cause });
|
|
88
|
+
|
|
89
|
+
const decodeStorePath = (path: string) =>
|
|
90
|
+
Schema.decodeUnknown(StorePath)(path).pipe(
|
|
91
|
+
Effect.mapError(toStoreIoError(manifestPath))
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const storeRefFromMetadata = (metadata: StoreMetadata) =>
|
|
95
|
+
StoreRef.make({ name: metadata.name, root: metadata.root });
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Context tag and Layer implementation for the store manager service.
|
|
99
|
+
* Provides CRUD operations for content stores with SQLite persistence.
|
|
100
|
+
*
|
|
101
|
+
* **Methods:**
|
|
102
|
+
* - createStore: Creates a new store or returns existing if name exists
|
|
103
|
+
* - getStore: Retrieves store reference by name
|
|
104
|
+
* - listStores: Returns all stores sorted by name
|
|
105
|
+
* - getConfig: Gets configuration for a specific store
|
|
106
|
+
* - deleteStore: Removes a store from the catalog
|
|
107
|
+
*
|
|
108
|
+
* **Idempotency:**
|
|
109
|
+
* createStore is idempotent - if a store with the given name already exists,
|
|
110
|
+
* it returns the existing store reference instead of failing.
|
|
111
|
+
*
|
|
112
|
+
* **Error Handling:**
|
|
113
|
+
* All methods return StoreIoError for filesystem or database issues.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* // Create and use a store
|
|
118
|
+
* const storeRef = yield* manager.createStore("tech-posts", {
|
|
119
|
+
* filter: { _tag: "Hashtag", tag: "tech" },
|
|
120
|
+
* errorPolicy: { _tag: "Retry", maxRetries: 3, baseDelay: Duration.seconds(1) }
|
|
121
|
+
* });
|
|
122
|
+
*
|
|
123
|
+
* // Check if store exists before creating
|
|
124
|
+
* const existing = yield* manager.getStore("tech-posts");
|
|
125
|
+
* if (Option.isNone(existing)) {
|
|
126
|
+
* yield* manager.createStore("tech-posts", config);
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* // Cleanup
|
|
130
|
+
* yield* manager.deleteStore("old-store");
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export class StoreManager extends Context.Tag("@skygent/StoreManager")<
|
|
134
|
+
StoreManager,
|
|
135
|
+
{
|
|
136
|
+
/**
|
|
137
|
+
* Creates a new store or returns existing if name already exists.
|
|
138
|
+
* Stores creation timestamp and configuration in the catalog.
|
|
139
|
+
*
|
|
140
|
+
* @param name - Unique name for the store
|
|
141
|
+
* @param config - Store configuration including filter and error policy
|
|
142
|
+
* @returns Effect resolving to StoreRef (existing or newly created)
|
|
143
|
+
* @throws {StoreIoError} When database operations fail
|
|
144
|
+
*/
|
|
145
|
+
readonly createStore: (
|
|
146
|
+
name: StoreName,
|
|
147
|
+
config: StoreConfig
|
|
148
|
+
) => Effect.Effect<StoreRef, StoreIoError>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Retrieves a store reference by name.
|
|
152
|
+
*
|
|
153
|
+
* @param name - Store name to look up
|
|
154
|
+
* @returns Effect resolving to Some(StoreRef) if found, None otherwise
|
|
155
|
+
* @throws {StoreIoError} When database operations fail
|
|
156
|
+
*/
|
|
157
|
+
readonly getStore: (
|
|
158
|
+
name: StoreName
|
|
159
|
+
) => Effect.Effect<Option.Option<StoreRef>, StoreIoError>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Lists all stores sorted alphabetically by name.
|
|
163
|
+
*
|
|
164
|
+
* @returns Effect resolving to chunk of StoreMetadata
|
|
165
|
+
* @throws {StoreIoError} When database operations fail
|
|
166
|
+
*/
|
|
167
|
+
readonly listStores: () => Effect.Effect<Chunk.Chunk<StoreMetadata>, StoreIoError>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Retrieves configuration for a specific store.
|
|
171
|
+
*
|
|
172
|
+
* @param name - Store name to look up
|
|
173
|
+
* @returns Effect resolving to Some(StoreConfig) if found, None otherwise
|
|
174
|
+
* @throws {StoreIoError} When database operations fail or config parsing fails
|
|
175
|
+
*/
|
|
176
|
+
readonly getConfig: (
|
|
177
|
+
name: StoreName
|
|
178
|
+
) => Effect.Effect<Option.Option<StoreConfig>, StoreIoError>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Deletes a store from the catalog.
|
|
182
|
+
* Note: This only removes the catalog entry, not the store files.
|
|
183
|
+
*
|
|
184
|
+
* @param name - Store name to delete
|
|
185
|
+
* @returns Effect resolving to void
|
|
186
|
+
* @throws {StoreIoError} When database operations fail
|
|
187
|
+
*/
|
|
188
|
+
readonly deleteStore: (
|
|
189
|
+
name: StoreName
|
|
190
|
+
) => Effect.Effect<void, StoreIoError>;
|
|
191
|
+
}
|
|
192
|
+
>() {
|
|
193
|
+
/**
|
|
194
|
+
* Scoped layer that creates the store manager service.
|
|
195
|
+
* Manages SQLite client lifecycle with automatic cleanup.
|
|
196
|
+
* Requires: AppConfigService, FileSystem, Path, Reactivity
|
|
197
|
+
*/
|
|
198
|
+
static readonly layer = Layer.scoped(
|
|
199
|
+
StoreManager,
|
|
200
|
+
Effect.gen(function* () {
|
|
201
|
+
const appConfig = yield* AppConfigService;
|
|
202
|
+
const fs = yield* FileSystem.FileSystem;
|
|
203
|
+
const path = yield* Path.Path;
|
|
204
|
+
const reactivity = yield* Reactivity.Reactivity;
|
|
205
|
+
|
|
206
|
+
const scope = yield* Scope.make();
|
|
207
|
+
yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
|
|
208
|
+
|
|
209
|
+
const dbPath = path.join(appConfig.storeRoot, "catalog.sqlite");
|
|
210
|
+
const dbDir = path.dirname(dbPath);
|
|
211
|
+
yield* fs.makeDirectory(dbDir, { recursive: true });
|
|
212
|
+
|
|
213
|
+
const client = yield* SqliteClient.make({ filename: dbPath }).pipe(
|
|
214
|
+
Effect.provideService(Scope.Scope, scope),
|
|
215
|
+
Effect.provideService(Reactivity.Reactivity, reactivity)
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const migrate = Migrator.make({})({
|
|
219
|
+
loader: MigratorFileSystem.fromFileSystem(migrationsDir)
|
|
220
|
+
});
|
|
221
|
+
yield* migrate.pipe(
|
|
222
|
+
Effect.provideService(SqlClient.SqlClient, client),
|
|
223
|
+
Effect.provideService(FileSystem.FileSystem, fs)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const decodeMetadataRow = (row: typeof storeRow.Type) =>
|
|
227
|
+
Schema.decodeUnknown(StoreMetadata)({
|
|
228
|
+
name: row.name,
|
|
229
|
+
root: row.root,
|
|
230
|
+
createdAt: row.created_at,
|
|
231
|
+
updatedAt: row.updated_at
|
|
232
|
+
}).pipe(Effect.mapError(toStoreIoError(manifestPath)));
|
|
233
|
+
|
|
234
|
+
const decodeConfigRow = (row: typeof storeRow.Type) =>
|
|
235
|
+
Schema.decodeUnknown(Schema.parseJson(StoreConfig))(row.config_json).pipe(
|
|
236
|
+
Effect.mapError(toStoreIoError(manifestPath))
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const encodeConfigJson = (config: StoreConfig) =>
|
|
240
|
+
Schema.encode(Schema.parseJson(StoreConfig))(config).pipe(
|
|
241
|
+
Effect.mapError(toStoreIoError(manifestPath))
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const findStore = SqlSchema.findAll({
|
|
245
|
+
Request: StoreName,
|
|
246
|
+
Result: storeRow,
|
|
247
|
+
execute: (name) =>
|
|
248
|
+
client`SELECT name, root, created_at, updated_at, config_json FROM stores WHERE name = ${name}`
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const listStoresSql = SqlSchema.findAll({
|
|
252
|
+
Request: Schema.Void,
|
|
253
|
+
Result: storeRow,
|
|
254
|
+
execute: () =>
|
|
255
|
+
client`SELECT name, root, created_at, updated_at, config_json FROM stores ORDER BY name ASC`
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const insertStore = SqlSchema.void({
|
|
259
|
+
Request: storeRow,
|
|
260
|
+
execute: (row) =>
|
|
261
|
+
client`INSERT INTO stores ${client.insert(row)}`
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const deleteStoreSql = SqlSchema.void({
|
|
265
|
+
Request: StoreName,
|
|
266
|
+
execute: (name) =>
|
|
267
|
+
client`DELETE FROM stores WHERE name = ${name}`
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const createStore = Effect.fn("StoreManager.createStore")(
|
|
271
|
+
(name: StoreName, config: StoreConfig) =>
|
|
272
|
+
decodeStorePath(storeRootKey(name)).pipe(
|
|
273
|
+
Effect.flatMap((root) =>
|
|
274
|
+
Effect.gen(function* () {
|
|
275
|
+
const existingRows = yield* findStore(name);
|
|
276
|
+
if (existingRows.length > 0) {
|
|
277
|
+
const existing = yield* decodeMetadataRow(existingRows[0]!);
|
|
278
|
+
return storeRefFromMetadata(existing);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const nowMillis = yield* Clock.currentTimeMillis;
|
|
282
|
+
const now = new Date(nowMillis).toISOString();
|
|
283
|
+
const configJson = yield* encodeConfigJson(config);
|
|
284
|
+
yield* insertStore({
|
|
285
|
+
name,
|
|
286
|
+
root,
|
|
287
|
+
created_at: now,
|
|
288
|
+
updated_at: now,
|
|
289
|
+
config_json: configJson
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return StoreRef.make({ name, root });
|
|
293
|
+
}).pipe(Effect.mapError(toStoreIoError(root)))
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const getStore = Effect.fn("StoreManager.getStore")((name: StoreName) => {
|
|
299
|
+
return decodeStorePath(storeRootKey(name)).pipe(
|
|
300
|
+
Effect.flatMap((root) =>
|
|
301
|
+
findStore(name).pipe(
|
|
302
|
+
Effect.flatMap((rows) =>
|
|
303
|
+
rows.length === 0
|
|
304
|
+
? Effect.succeed(Option.none())
|
|
305
|
+
: decodeMetadataRow(rows[0]!).pipe(
|
|
306
|
+
Effect.map((metadata) =>
|
|
307
|
+
Option.some(storeRefFromMetadata(metadata))
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
),
|
|
311
|
+
Effect.mapError(toStoreIoError(root))
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const getConfig = Effect.fn("StoreManager.getConfig")((name: StoreName) => {
|
|
318
|
+
return decodeStorePath(storeRootKey(name)).pipe(
|
|
319
|
+
Effect.flatMap((root) =>
|
|
320
|
+
findStore(name).pipe(
|
|
321
|
+
Effect.flatMap((rows) =>
|
|
322
|
+
rows.length === 0
|
|
323
|
+
? Effect.succeed(Option.none())
|
|
324
|
+
: decodeConfigRow(rows[0]!).pipe(Effect.map(Option.some))
|
|
325
|
+
),
|
|
326
|
+
Effect.mapError(toStoreIoError(root))
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const deleteStore = Effect.fn("StoreManager.deleteStore")((name: StoreName) => {
|
|
333
|
+
return decodeStorePath(storeRootKey(name)).pipe(
|
|
334
|
+
Effect.flatMap((root) =>
|
|
335
|
+
deleteStoreSql(name).pipe(Effect.mapError(toStoreIoError(root)))
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const listStores = Effect.fn("StoreManager.listStores")(() =>
|
|
341
|
+
Effect.gen(function* () {
|
|
342
|
+
const rows = yield* listStoresSql(undefined);
|
|
343
|
+
if (rows.length === 0) {
|
|
344
|
+
return Chunk.empty<StoreMetadata>();
|
|
345
|
+
}
|
|
346
|
+
const decoded = yield* Effect.forEach(
|
|
347
|
+
rows,
|
|
348
|
+
(row) => decodeMetadataRow(row),
|
|
349
|
+
{ discard: false }
|
|
350
|
+
);
|
|
351
|
+
return Chunk.fromIterable(decoded);
|
|
352
|
+
}).pipe(Effect.mapError(toStoreIoError(manifestPath)))
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
return StoreManager.of({ createStore, getStore, listStores, getConfig, deleteStore });
|
|
356
|
+
})
|
|
357
|
+
).pipe(Layer.provide(Reactivity.layer));
|
|
358
|
+
}
|