@gettersethya/mira 0.1.0-alpha.2
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 +21 -0
- package/dist/src/app/app.d.ts +96 -0
- package/dist/src/app/app.d.ts.map +1 -0
- package/dist/src/app/app.js +139 -0
- package/dist/src/app/app.js.map +1 -0
- package/dist/src/app/builder.d.ts +106 -0
- package/dist/src/app/builder.d.ts.map +1 -0
- package/dist/src/app/builder.js +122 -0
- package/dist/src/app/builder.js.map +1 -0
- package/dist/src/app/index.d.ts +31 -0
- package/dist/src/app/index.d.ts.map +1 -0
- package/dist/src/app/index.js +29 -0
- package/dist/src/app/index.js.map +1 -0
- package/dist/src/app/types.d.ts +55 -0
- package/dist/src/app/types.d.ts.map +1 -0
- package/dist/src/app/types.js +2 -0
- package/dist/src/app/types.js.map +1 -0
- package/dist/src/cache/cached-collection.d.ts +30 -0
- package/dist/src/cache/cached-collection.d.ts.map +1 -0
- package/dist/src/cache/cached-collection.js +107 -0
- package/dist/src/cache/cached-collection.js.map +1 -0
- package/dist/src/cache/collection-cache.d.ts +17 -0
- package/dist/src/cache/collection-cache.d.ts.map +1 -0
- package/dist/src/cache/collection-cache.js +37 -0
- package/dist/src/cache/collection-cache.js.map +1 -0
- package/dist/src/cache/index.d.ts +4 -0
- package/dist/src/cache/index.d.ts.map +1 -0
- package/dist/src/cache/index.js +3 -0
- package/dist/src/cache/index.js.map +1 -0
- package/dist/src/cache/types.d.ts +12 -0
- package/dist/src/cache/types.d.ts.map +1 -0
- package/dist/src/cache/types.js +2 -0
- package/dist/src/cache/types.js.map +1 -0
- package/dist/src/collection-service/collection-service.d.ts +62 -0
- package/dist/src/collection-service/collection-service.d.ts.map +1 -0
- package/dist/src/collection-service/collection-service.js +267 -0
- package/dist/src/collection-service/collection-service.js.map +1 -0
- package/dist/src/collection-service/context.d.ts +17 -0
- package/dist/src/collection-service/context.d.ts.map +1 -0
- package/dist/src/collection-service/context.js +2 -0
- package/dist/src/collection-service/context.js.map +1 -0
- package/dist/src/collection-service/decode.d.ts +6 -0
- package/dist/src/collection-service/decode.d.ts.map +1 -0
- package/dist/src/collection-service/decode.js +26 -0
- package/dist/src/collection-service/decode.js.map +1 -0
- package/dist/src/collection-service/errors.d.ts +67 -0
- package/dist/src/collection-service/errors.d.ts.map +1 -0
- package/dist/src/collection-service/errors.js +43 -0
- package/dist/src/collection-service/errors.js.map +1 -0
- package/dist/src/collection-service/index.d.ts +5 -0
- package/dist/src/collection-service/index.d.ts.map +1 -0
- package/dist/src/collection-service/index.js +3 -0
- package/dist/src/collection-service/index.js.map +1 -0
- package/dist/src/collection-service/input-schema.d.ts +10 -0
- package/dist/src/collection-service/input-schema.d.ts.map +1 -0
- package/dist/src/collection-service/input-schema.js +109 -0
- package/dist/src/collection-service/input-schema.js.map +1 -0
- package/dist/src/collection-service/where.d.ts +35 -0
- package/dist/src/collection-service/where.d.ts.map +1 -0
- package/dist/src/collection-service/where.js +88 -0
- package/dist/src/collection-service/where.js.map +1 -0
- package/dist/src/config/config.d.ts +44 -0
- package/dist/src/config/config.d.ts.map +1 -0
- package/dist/src/config/config.js +89 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +2 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/types.d.ts +29 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +2 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/crypto/crypto.d.ts +11 -0
- package/dist/src/crypto/crypto.d.ts.map +1 -0
- package/dist/src/crypto/crypto.js +4 -0
- package/dist/src/crypto/crypto.js.map +1 -0
- package/dist/src/crypto/index.d.ts +3 -0
- package/dist/src/crypto/index.d.ts.map +1 -0
- package/dist/src/crypto/index.js +3 -0
- package/dist/src/crypto/index.js.map +1 -0
- package/dist/src/crypto/node.d.ts +4 -0
- package/dist/src/crypto/node.d.ts.map +1 -0
- package/dist/src/crypto/node.js +9 -0
- package/dist/src/crypto/node.js.map +1 -0
- package/dist/src/databases/index.d.ts +2 -0
- package/dist/src/databases/index.d.ts.map +1 -0
- package/dist/src/databases/index.js +2 -0
- package/dist/src/databases/index.js.map +1 -0
- package/dist/src/databases/sqlite.d.ts +24 -0
- package/dist/src/databases/sqlite.d.ts.map +1 -0
- package/dist/src/databases/sqlite.js +27 -0
- package/dist/src/databases/sqlite.js.map +1 -0
- package/dist/src/http/auth-node.d.ts +4 -0
- package/dist/src/http/auth-node.d.ts.map +1 -0
- package/dist/src/http/auth-node.js +34 -0
- package/dist/src/http/auth-node.js.map +1 -0
- package/dist/src/http/auth.d.ts +38 -0
- package/dist/src/http/auth.d.ts.map +1 -0
- package/dist/src/http/auth.js +66 -0
- package/dist/src/http/auth.js.map +1 -0
- package/dist/src/http/errors.d.ts +31 -0
- package/dist/src/http/errors.d.ts.map +1 -0
- package/dist/src/http/errors.js +36 -0
- package/dist/src/http/errors.js.map +1 -0
- package/dist/src/http/file-serve.d.ts +10 -0
- package/dist/src/http/file-serve.d.ts.map +1 -0
- package/dist/src/http/file-serve.js +159 -0
- package/dist/src/http/file-serve.js.map +1 -0
- package/dist/src/http/file-token.d.ts +7 -0
- package/dist/src/http/file-token.d.ts.map +1 -0
- package/dist/src/http/file-token.js +55 -0
- package/dist/src/http/file-token.js.map +1 -0
- package/dist/src/http/files.d.ts +11 -0
- package/dist/src/http/files.d.ts.map +1 -0
- package/dist/src/http/files.js +85 -0
- package/dist/src/http/files.js.map +1 -0
- package/dist/src/http/index.d.ts +10 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +8 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/ip-middleware.d.ts +4 -0
- package/dist/src/http/ip-middleware.d.ts.map +1 -0
- package/dist/src/http/ip-middleware.js +21 -0
- package/dist/src/http/ip-middleware.js.map +1 -0
- package/dist/src/http/params.d.ts +15 -0
- package/dist/src/http/params.d.ts.map +1 -0
- package/dist/src/http/params.js +48 -0
- package/dist/src/http/params.js.map +1 -0
- package/dist/src/http/router.d.ts +11 -0
- package/dist/src/http/router.d.ts.map +1 -0
- package/dist/src/http/router.js +216 -0
- package/dist/src/http/router.js.map +1 -0
- package/dist/src/http/server-factory-node.d.ts +4 -0
- package/dist/src/http/server-factory-node.d.ts.map +1 -0
- package/dist/src/http/server-factory-node.js +8 -0
- package/dist/src/http/server-factory-node.js.map +1 -0
- package/dist/src/http/server-factory.d.ts +11 -0
- package/dist/src/http/server-factory.d.ts.map +1 -0
- package/dist/src/http/server-factory.js +4 -0
- package/dist/src/http/server-factory.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migrator/dialect-sqlite.d.ts +9 -0
- package/dist/src/migrator/dialect-sqlite.d.ts.map +1 -0
- package/dist/src/migrator/dialect-sqlite.js +102 -0
- package/dist/src/migrator/dialect-sqlite.js.map +1 -0
- package/dist/src/migrator/dialect.d.ts +24 -0
- package/dist/src/migrator/dialect.d.ts.map +1 -0
- package/dist/src/migrator/dialect.js +5 -0
- package/dist/src/migrator/dialect.js.map +1 -0
- package/dist/src/migrator/index.d.ts +8 -0
- package/dist/src/migrator/index.d.ts.map +1 -0
- package/dist/src/migrator/index.js +6 -0
- package/dist/src/migrator/index.js.map +1 -0
- package/dist/src/migrator/migrator.d.ts +36 -0
- package/dist/src/migrator/migrator.d.ts.map +1 -0
- package/dist/src/migrator/migrator.js +200 -0
- package/dist/src/migrator/migrator.js.map +1 -0
- package/dist/src/migrator/schema-diff.d.ts +25 -0
- package/dist/src/migrator/schema-diff.d.ts.map +1 -0
- package/dist/src/migrator/schema-diff.js +157 -0
- package/dist/src/migrator/schema-diff.js.map +1 -0
- package/dist/src/migrator/types.d.ts +91 -0
- package/dist/src/migrator/types.d.ts.map +1 -0
- package/dist/src/migrator/types.js +13 -0
- package/dist/src/migrator/types.js.map +1 -0
- package/dist/src/platforms/node.d.ts +40 -0
- package/dist/src/platforms/node.d.ts.map +1 -0
- package/dist/src/platforms/node.js +45 -0
- package/dist/src/platforms/node.js.map +1 -0
- package/dist/src/repository/id.d.ts +5 -0
- package/dist/src/repository/id.d.ts.map +1 -0
- package/dist/src/repository/id.js +9 -0
- package/dist/src/repository/id.js.map +1 -0
- package/dist/src/repository/index.d.ts +3 -0
- package/dist/src/repository/index.d.ts.map +1 -0
- package/dist/src/repository/index.js +2 -0
- package/dist/src/repository/index.js.map +1 -0
- package/dist/src/repository/repository.d.ts +53 -0
- package/dist/src/repository/repository.d.ts.map +1 -0
- package/dist/src/repository/repository.js +150 -0
- package/dist/src/repository/repository.js.map +1 -0
- package/dist/src/repository/types.d.ts +42 -0
- package/dist/src/repository/types.d.ts.map +1 -0
- package/dist/src/repository/types.js +2 -0
- package/dist/src/repository/types.js.map +1 -0
- package/dist/src/rule/compiler.d.ts +52 -0
- package/dist/src/rule/compiler.d.ts.map +1 -0
- package/dist/src/rule/compiler.js +113 -0
- package/dist/src/rule/compiler.js.map +1 -0
- package/dist/src/rule/enforcer.d.ts +47 -0
- package/dist/src/rule/enforcer.d.ts.map +1 -0
- package/dist/src/rule/enforcer.js +60 -0
- package/dist/src/rule/enforcer.js.map +1 -0
- package/dist/src/rule/index.d.ts +3 -0
- package/dist/src/rule/index.d.ts.map +1 -0
- package/dist/src/rule/index.js +3 -0
- package/dist/src/rule/index.js.map +1 -0
- package/dist/src/storage/index.d.ts +3 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +3 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/local-preset.d.ts +21 -0
- package/dist/src/storage/local-preset.d.ts.map +1 -0
- package/dist/src/storage/local-preset.js +21 -0
- package/dist/src/storage/local-preset.js.map +1 -0
- package/dist/src/storage/storage.d.ts +83 -0
- package/dist/src/storage/storage.d.ts.map +1 -0
- package/dist/src/storage/storage.js +120 -0
- package/dist/src/storage/storage.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +6 -0
- package/dist/src/telemetry/index.d.ts.map +1 -0
- package/dist/src/telemetry/index.js +7 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/telemetry/logger.d.ts +4 -0
- package/dist/src/telemetry/logger.d.ts.map +1 -0
- package/dist/src/telemetry/logger.js +27 -0
- package/dist/src/telemetry/logger.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts +16 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -0
- package/dist/src/telemetry/tracer.js +106 -0
- package/dist/src/telemetry/tracer.js.map +1 -0
- package/dist/src/thumbnail/index.d.ts +4 -0
- package/dist/src/thumbnail/index.d.ts.map +1 -0
- package/dist/src/thumbnail/index.js +3 -0
- package/dist/src/thumbnail/index.js.map +1 -0
- package/dist/src/thumbnail/photon.d.ts +9 -0
- package/dist/src/thumbnail/photon.d.ts.map +1 -0
- package/dist/src/thumbnail/photon.js +73 -0
- package/dist/src/thumbnail/photon.js.map +1 -0
- package/dist/src/thumbnail/types.d.ts +23 -0
- package/dist/src/thumbnail/types.d.ts.map +1 -0
- package/dist/src/thumbnail/types.js +22 -0
- package/dist/src/thumbnail/types.js.map +1 -0
- package/dist/tests/app/app.test.d.ts +2 -0
- package/dist/tests/app/app.test.d.ts.map +1 -0
- package/dist/tests/app/app.test.js +78 -0
- package/dist/tests/app/app.test.js.map +1 -0
- package/dist/tests/app/builder.test.d.ts +2 -0
- package/dist/tests/app/builder.test.d.ts.map +1 -0
- package/dist/tests/app/builder.test.js +69 -0
- package/dist/tests/app/builder.test.js.map +1 -0
- package/dist/tests/cache/cached-collection.test.d.ts +2 -0
- package/dist/tests/cache/cached-collection.test.d.ts.map +1 -0
- package/dist/tests/cache/cached-collection.test.js +244 -0
- package/dist/tests/cache/cached-collection.test.js.map +1 -0
- package/dist/tests/cache/collection-cache.test.d.ts +2 -0
- package/dist/tests/cache/collection-cache.test.d.ts.map +1 -0
- package/dist/tests/cache/collection-cache.test.js +73 -0
- package/dist/tests/cache/collection-cache.test.js.map +1 -0
- package/dist/tests/collection-service/collection-service.test.d.ts +2 -0
- package/dist/tests/collection-service/collection-service.test.d.ts.map +1 -0
- package/dist/tests/collection-service/collection-service.test.js +805 -0
- package/dist/tests/collection-service/collection-service.test.js.map +1 -0
- package/dist/tests/collection-service/input-schema.test.d.ts +2 -0
- package/dist/tests/collection-service/input-schema.test.d.ts.map +1 -0
- package/dist/tests/collection-service/input-schema.test.js +260 -0
- package/dist/tests/collection-service/input-schema.test.js.map +1 -0
- package/dist/tests/config/config.test.d.ts +2 -0
- package/dist/tests/config/config.test.d.ts.map +1 -0
- package/dist/tests/config/config.test.js +84 -0
- package/dist/tests/config/config.test.js.map +1 -0
- package/dist/tests/crypto/crypto.test.d.ts +2 -0
- package/dist/tests/crypto/crypto.test.d.ts.map +1 -0
- package/dist/tests/crypto/crypto.test.js +56 -0
- package/dist/tests/crypto/crypto.test.js.map +1 -0
- package/dist/tests/http/file-serve.test.d.ts +2 -0
- package/dist/tests/http/file-serve.test.d.ts.map +1 -0
- package/dist/tests/http/file-serve.test.js +210 -0
- package/dist/tests/http/file-serve.test.js.map +1 -0
- package/dist/tests/http/file-token.test.d.ts +2 -0
- package/dist/tests/http/file-token.test.d.ts.map +1 -0
- package/dist/tests/http/file-token.test.js +155 -0
- package/dist/tests/http/file-token.test.js.map +1 -0
- package/dist/tests/http/ip-middleware.test.d.ts +2 -0
- package/dist/tests/http/ip-middleware.test.d.ts.map +1 -0
- package/dist/tests/http/ip-middleware.test.js +58 -0
- package/dist/tests/http/ip-middleware.test.js.map +1 -0
- package/dist/tests/http/router.test.d.ts +2 -0
- package/dist/tests/http/router.test.d.ts.map +1 -0
- package/dist/tests/http/router.test.js +252 -0
- package/dist/tests/http/router.test.js.map +1 -0
- package/dist/tests/migrator/dialect-sqlite.test.d.ts +2 -0
- package/dist/tests/migrator/dialect-sqlite.test.d.ts.map +1 -0
- package/dist/tests/migrator/dialect-sqlite.test.js +90 -0
- package/dist/tests/migrator/dialect-sqlite.test.js.map +1 -0
- package/dist/tests/migrator/integration.test.d.ts +2 -0
- package/dist/tests/migrator/integration.test.d.ts.map +1 -0
- package/dist/tests/migrator/integration.test.js +180 -0
- package/dist/tests/migrator/integration.test.js.map +1 -0
- package/dist/tests/migrator/migrator.test.d.ts +2 -0
- package/dist/tests/migrator/migrator.test.d.ts.map +1 -0
- package/dist/tests/migrator/migrator.test.js +225 -0
- package/dist/tests/migrator/migrator.test.js.map +1 -0
- package/dist/tests/migrator/schema-diff.test.d.ts +2 -0
- package/dist/tests/migrator/schema-diff.test.d.ts.map +1 -0
- package/dist/tests/migrator/schema-diff.test.js +323 -0
- package/dist/tests/migrator/schema-diff.test.js.map +1 -0
- package/dist/tests/repository/repository.test.d.ts +2 -0
- package/dist/tests/repository/repository.test.d.ts.map +1 -0
- package/dist/tests/repository/repository.test.js +255 -0
- package/dist/tests/repository/repository.test.js.map +1 -0
- package/dist/tests/rule/compiler.test.d.ts +2 -0
- package/dist/tests/rule/compiler.test.d.ts.map +1 -0
- package/dist/tests/rule/compiler.test.js +226 -0
- package/dist/tests/rule/compiler.test.js.map +1 -0
- package/dist/tests/rule/enforcer.test.d.ts +2 -0
- package/dist/tests/rule/enforcer.test.d.ts.map +1 -0
- package/dist/tests/rule/enforcer.test.js +190 -0
- package/dist/tests/rule/enforcer.test.js.map +1 -0
- package/dist/tests/storage/storage.test.d.ts +2 -0
- package/dist/tests/storage/storage.test.d.ts.map +1 -0
- package/dist/tests/storage/storage.test.js +76 -0
- package/dist/tests/storage/storage.test.js.map +1 -0
- package/dist/tests/telemetry/instrumentation.test.d.ts +2 -0
- package/dist/tests/telemetry/instrumentation.test.d.ts.map +1 -0
- package/dist/tests/telemetry/instrumentation.test.js +226 -0
- package/dist/tests/telemetry/instrumentation.test.js.map +1 -0
- package/dist/tests/telemetry/logger.test.d.ts +2 -0
- package/dist/tests/telemetry/logger.test.d.ts.map +1 -0
- package/dist/tests/telemetry/logger.test.js +56 -0
- package/dist/tests/telemetry/logger.test.js.map +1 -0
- package/dist/tests/telemetry/tracer.test.d.ts +2 -0
- package/dist/tests/telemetry/tracer.test.d.ts.map +1 -0
- package/dist/tests/telemetry/tracer.test.js +63 -0
- package/dist/tests/telemetry/tracer.test.js.map +1 -0
- package/dist/tests/thumbnail/photon.test.d.ts +2 -0
- package/dist/tests/thumbnail/photon.test.d.ts.map +1 -0
- package/dist/tests/thumbnail/photon.test.js +478 -0
- package/dist/tests/thumbnail/photon.test.js.map +1 -0
- package/dist/tests/thumbnail/thumbnail.test.d.ts +2 -0
- package/dist/tests/thumbnail/thumbnail.test.d.ts.map +1 -0
- package/dist/tests/thumbnail/thumbnail.test.js +35 -0
- package/dist/tests/thumbnail/thumbnail.test.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SqlClient } from "@effect/sql";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import type { AnyCollectionDef } from "@gettersethya/mira-client";
|
|
4
|
+
import { Repository } from "../repository/repository.js";
|
|
5
|
+
import { CollectionService } from "../collection-service/collection-service.js";
|
|
6
|
+
import { FileStorage } from "../storage/storage.js";
|
|
7
|
+
import type { CollectionCacheConfigValues } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Create a cached `CollectionService` layer that wraps the standard collection service.
|
|
10
|
+
*
|
|
11
|
+
* The cache is transparent — it implements the same `CollectionService` tag.
|
|
12
|
+
* List results and individual records are cached with a configurable TTL.
|
|
13
|
+
* Cache is invalidated (nuked) on any write operation (create/update/delete).
|
|
14
|
+
*
|
|
15
|
+
* This is the default service layer used by MiraApp — the standard
|
|
16
|
+
* `makeCollectionServiceLayer` is wrapped by this function internally.
|
|
17
|
+
*
|
|
18
|
+
* @param allCollections - Array of collection definitions
|
|
19
|
+
* @param config - Optional cache configuration (record TTL, list TTL, max entries)
|
|
20
|
+
* @returns A Layer providing CollectionService with caching
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* makeCachedCollectionServiceLayer([Posts, Users])
|
|
24
|
+
* makeCachedCollectionServiceLayer([Posts], { recordTtlMs: 60_000 })
|
|
25
|
+
*
|
|
26
|
+
* @see CollectionService — the service tag
|
|
27
|
+
* @see makeCollectionServiceLayer — the underlying uncached service
|
|
28
|
+
*/
|
|
29
|
+
export declare function makeCachedCollectionServiceLayer(allCollections: ReadonlyArray<AnyCollectionDef>, config?: CollectionCacheConfigValues): Layer.Layer<CollectionService, never, Repository | SqlClient.SqlClient | FileStorage>;
|
|
30
|
+
//# sourceMappingURL=cached-collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-collection.d.ts","sourceRoot":"","sources":["../../../src/cache/cached-collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAU,KAAK,EAAkB,MAAM,QAAQ,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAEjE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAEvD,OAAO,EAAE,iBAAiB,EAA8B,MAAM,4CAA4C,CAAA;AAC1G,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AA2D7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,aAAa,CAAC,gBAAgB,CAAC,EAC/C,MAAM,GAAE,2BAA2C,GAClD,KAAK,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC,SAAS,GAAG,WAAW,CAAC,CAmFvF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Effect, Layer, Option } from "effect";
|
|
2
|
+
import { CollectionService, makeCollectionServiceLayer } from "../collection-service/collection-service.js";
|
|
3
|
+
import { CollectionCache, makeCollectionCacheLayer } from "./collection-cache.js";
|
|
4
|
+
function serializeFilterNode(node) {
|
|
5
|
+
switch (node.op) {
|
|
6
|
+
case "eq":
|
|
7
|
+
case "neq":
|
|
8
|
+
case "gt":
|
|
9
|
+
case "gte":
|
|
10
|
+
case "lt":
|
|
11
|
+
case "lte":
|
|
12
|
+
return `${node.op}(${node.field},${String(node.value)})`;
|
|
13
|
+
case "in":
|
|
14
|
+
return `in(${node.field},[${node.values.map(String).join(",")}])`;
|
|
15
|
+
case "like":
|
|
16
|
+
return `like(${node.field},${node.value})`;
|
|
17
|
+
case "null":
|
|
18
|
+
case "not_null":
|
|
19
|
+
return `${node.op}(${node.field})`;
|
|
20
|
+
case "and":
|
|
21
|
+
case "or":
|
|
22
|
+
return `${node.op}(${serializeFilterNode(node.left)},${serializeFilterNode(node.right)})`;
|
|
23
|
+
case "not":
|
|
24
|
+
return `not(${serializeFilterNode(node.node)})`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function stableViewKey(col, id, select, expand) {
|
|
28
|
+
const selectPart = select != null ? [...select].sort().join(",") : "";
|
|
29
|
+
const expandPart = expand != null ? [...expand].sort().join(",") : "";
|
|
30
|
+
return `${col}:${id}:${selectPart}|${expandPart}`;
|
|
31
|
+
}
|
|
32
|
+
function stableListKey(cursor, perPage, filter, sort, select, expand) {
|
|
33
|
+
const filterPart = filter !== undefined ? serializeFilterNode(filter) : "";
|
|
34
|
+
const sortPart = sort !== undefined ? `${sort.field}:${sort.direction}` : "";
|
|
35
|
+
const selectPart = select != null ? [...select].sort().join(",") : "";
|
|
36
|
+
const expandPart = expand != null ? [...expand].sort().join(",") : "";
|
|
37
|
+
return `${cursor ?? ""}|${perPage}|${filterPart}|${sortPart}|${selectPart}|${expandPart}`;
|
|
38
|
+
}
|
|
39
|
+
const defaultConfig = {
|
|
40
|
+
recordTtlMs: 30_000,
|
|
41
|
+
listTtlMs: 10_000,
|
|
42
|
+
maxRecords: 50_000,
|
|
43
|
+
maxLists: 5_000,
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Create a cached `CollectionService` layer that wraps the standard collection service.
|
|
47
|
+
*
|
|
48
|
+
* The cache is transparent — it implements the same `CollectionService` tag.
|
|
49
|
+
* List results and individual records are cached with a configurable TTL.
|
|
50
|
+
* Cache is invalidated (nuked) on any write operation (create/update/delete).
|
|
51
|
+
*
|
|
52
|
+
* This is the default service layer used by MiraApp — the standard
|
|
53
|
+
* `makeCollectionServiceLayer` is wrapped by this function internally.
|
|
54
|
+
*
|
|
55
|
+
* @param allCollections - Array of collection definitions
|
|
56
|
+
* @param config - Optional cache configuration (record TTL, list TTL, max entries)
|
|
57
|
+
* @returns A Layer providing CollectionService with caching
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* makeCachedCollectionServiceLayer([Posts, Users])
|
|
61
|
+
* makeCachedCollectionServiceLayer([Posts], { recordTtlMs: 60_000 })
|
|
62
|
+
*
|
|
63
|
+
* @see CollectionService — the service tag
|
|
64
|
+
* @see makeCollectionServiceLayer — the underlying uncached service
|
|
65
|
+
*/
|
|
66
|
+
export function makeCachedCollectionServiceLayer(allCollections, config = defaultConfig) {
|
|
67
|
+
return Layer.effect(CollectionService, Effect.gen(function* () {
|
|
68
|
+
const svc = yield* CollectionService;
|
|
69
|
+
const cache = yield* CollectionCache;
|
|
70
|
+
return CollectionService.of({
|
|
71
|
+
list: (collection, cursor, perPage, ctx, filter, sort, select, expand) => {
|
|
72
|
+
const key = `${collection.name}:${stableListKey(cursor, perPage, filter, sort, select, expand)}`;
|
|
73
|
+
return Effect.gen(function* () {
|
|
74
|
+
const cached = yield* cache.getList(key);
|
|
75
|
+
yield* Effect.currentSpan.pipe(Effect.tap((span) => Effect.sync(() => span.attribute("cache.hit", Option.isSome(cached)))), Effect.ignore);
|
|
76
|
+
if (Option.isSome(cached))
|
|
77
|
+
return cached.value;
|
|
78
|
+
const page = yield* svc.list(collection, cursor, perPage, ctx, filter, sort, select, expand);
|
|
79
|
+
yield* cache.putList(key, page);
|
|
80
|
+
return page;
|
|
81
|
+
}).pipe(Effect.withSpan("cache.list", {
|
|
82
|
+
kind: "internal",
|
|
83
|
+
attributes: { collection: collection.name },
|
|
84
|
+
}));
|
|
85
|
+
},
|
|
86
|
+
view: (collection, id, ctx, select, expand) => {
|
|
87
|
+
const key = stableViewKey(collection.name, id, select, expand);
|
|
88
|
+
return Effect.gen(function* () {
|
|
89
|
+
const cached = yield* cache.getRecord(key);
|
|
90
|
+
yield* Effect.currentSpan.pipe(Effect.tap((span) => Effect.sync(() => span.attribute("cache.hit", Option.isSome(cached)))), Effect.ignore);
|
|
91
|
+
if (Option.isSome(cached))
|
|
92
|
+
return cached.value;
|
|
93
|
+
const record = yield* svc.view(collection, id, ctx, select, expand);
|
|
94
|
+
yield* cache.putRecord(key, record);
|
|
95
|
+
return record;
|
|
96
|
+
}).pipe(Effect.withSpan("cache.view", {
|
|
97
|
+
kind: "internal",
|
|
98
|
+
attributes: { collection: collection.name },
|
|
99
|
+
}));
|
|
100
|
+
},
|
|
101
|
+
create: (collection, data, ctx) => svc.create(collection, data, ctx).pipe(Effect.tap(() => cache.nukeListsFor(collection.name))),
|
|
102
|
+
update: (collection, id, data, ctx) => svc.update(collection, id, data, ctx).pipe(Effect.tap(() => Effect.all([cache.invalidateRecord(collection.name, id), cache.nukeListsFor(collection.name)], { concurrency: "unbounded" }))),
|
|
103
|
+
delete: (collection, id, ctx) => svc.delete(collection, id, ctx).pipe(Effect.tap(() => Effect.all([cache.invalidateRecord(collection.name, id), cache.nukeListsFor(collection.name)], { concurrency: "unbounded" }))),
|
|
104
|
+
});
|
|
105
|
+
})).pipe(Layer.provide(Layer.merge(makeCollectionServiceLayer(allCollections), makeCollectionCacheLayer(config))));
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=cached-collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-collection.js","sourceRoot":"","sources":["../../../src/cache/cached-collection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
2
|
+
import type { CursorPage } from "../collection-service/context.js";
|
|
3
|
+
import type { RepoRecord } from "../repository/types.js";
|
|
4
|
+
import type { CollectionCacheConfigValues } from "./types.js";
|
|
5
|
+
declare const CollectionCache_base: Context.TagClass<CollectionCache, "CollectionCache", {
|
|
6
|
+
getRecord(key: string): Effect.Effect<Option.Option<RepoRecord>>;
|
|
7
|
+
putRecord(key: string, v: RepoRecord): Effect.Effect<void>;
|
|
8
|
+
invalidateRecord(col: string, id: string): Effect.Effect<void>;
|
|
9
|
+
getList(key: string): Effect.Effect<Option.Option<CursorPage>>;
|
|
10
|
+
putList(key: string, v: CursorPage): Effect.Effect<void>;
|
|
11
|
+
nukeListsFor(col: string): Effect.Effect<void>;
|
|
12
|
+
}>;
|
|
13
|
+
export declare class CollectionCache extends CollectionCache_base {
|
|
14
|
+
}
|
|
15
|
+
export declare function makeCollectionCacheLayer(config: CollectionCacheConfigValues): Layer.Layer<CollectionCache>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=collection-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-cache.d.ts","sourceRoot":"","sources":["../../../src/cache/collection-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAW,KAAK,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAA;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EAAc,2BAA2B,EAAE,MAAM,YAAY,CAAA;;mBAKtD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;mBACjD,MAAM,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;0BACpC,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;iBACjD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;iBACjD,MAAM,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;sBACtC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AARlD,qBAAa,eAAgB,SAAQ,oBAUlC;CAAG;AAoCN,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,2BAA2B,GAClC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,CA4B9B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Context, Effect, HashMap, Layer, Option, Ref } from "effect";
|
|
2
|
+
export class CollectionCache extends Context.Tag("CollectionCache")() {
|
|
3
|
+
}
|
|
4
|
+
const get = (ref, key) => Ref.get(ref).pipe(Effect.map(HashMap.get(key)), Effect.map(Option.filter((e) => e.expiresAt > Date.now())), Effect.map(Option.map((e) => e.value)));
|
|
5
|
+
const put = (ref, key, value, ttlMs, maxSize) => {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const entry = { value, insertedAt: now, expiresAt: now + ttlMs };
|
|
8
|
+
return Ref.update(ref, (map) => {
|
|
9
|
+
const inserted = HashMap.set(map, key, entry);
|
|
10
|
+
if (HashMap.size(inserted) <= maxSize)
|
|
11
|
+
return inserted;
|
|
12
|
+
let oldestKey;
|
|
13
|
+
let oldestTime = Infinity;
|
|
14
|
+
for (const [k, v] of inserted) {
|
|
15
|
+
if (v.insertedAt < oldestTime) {
|
|
16
|
+
oldestTime = v.insertedAt;
|
|
17
|
+
oldestKey = k;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return oldestKey !== undefined ? HashMap.remove(inserted, oldestKey) : inserted;
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export function makeCollectionCacheLayer(config) {
|
|
24
|
+
return Layer.effect(CollectionCache, Effect.gen(function* () {
|
|
25
|
+
const recordRef = yield* Ref.make(HashMap.empty());
|
|
26
|
+
const listRef = yield* Ref.make(HashMap.empty());
|
|
27
|
+
return CollectionCache.of({
|
|
28
|
+
getRecord: (key) => get(recordRef, key),
|
|
29
|
+
putRecord: (key, v) => put(recordRef, key, v, config.recordTtlMs, config.maxRecords),
|
|
30
|
+
invalidateRecord: (col, id) => Ref.update(recordRef, (map) => HashMap.filter(map, (_, key) => !key.startsWith(`${col}:${id}:`))),
|
|
31
|
+
getList: (key) => get(listRef, key),
|
|
32
|
+
putList: (key, v) => put(listRef, key, v, config.listTtlMs, config.maxLists),
|
|
33
|
+
nukeListsFor: (col) => Ref.update(listRef, (map) => HashMap.filter(map, (_, key) => !key.startsWith(`${col}:`))),
|
|
34
|
+
});
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=collection-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-cache.js","sourceRoot":"","sources":["../../../src/cache/collection-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { CollectionCache, makeCollectionCacheLayer } from "./collection-cache.js";
|
|
2
|
+
export { makeCachedCollectionServiceLayer } from "./cached-collection.js";
|
|
3
|
+
export type { CacheEntry, CollectionCacheConfigValues } from "./types.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAA;AACzE,YAAY,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CacheEntry<A> {
|
|
2
|
+
value: A;
|
|
3
|
+
insertedAt: number;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CollectionCacheConfigValues {
|
|
7
|
+
recordTtlMs: number;
|
|
8
|
+
listTtlMs: number;
|
|
9
|
+
maxRecords: number;
|
|
10
|
+
maxLists: number;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cache/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAA;IACR,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cache/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SqlClient } from "@effect/sql";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import type { AnyCollectionDef } from "@gettersethya/mira-client";
|
|
4
|
+
import type { FilterNode } from "@gettersethya/mira-client";
|
|
5
|
+
import { Repository } from "../repository/repository.js";
|
|
6
|
+
import type { RepoRecord, SortOrder } from "../repository/types.js";
|
|
7
|
+
import { FileStorage } from "../storage/storage.js";
|
|
8
|
+
import type { CursorPage, RequestCtx } from "./context.js";
|
|
9
|
+
import type { CollectionError } from "./errors.js";
|
|
10
|
+
declare const CollectionService_base: Context.TagClass<CollectionService, "CollectionService", {
|
|
11
|
+
list(collection: AnyCollectionDef, cursor: number | null, perPage: number, ctx: RequestCtx, filter?: FilterNode, sort?: SortOrder, select?: ReadonlyArray<string> | null, expand?: ReadonlyArray<string> | null): Effect.Effect<CursorPage, CollectionError>;
|
|
12
|
+
view(collection: AnyCollectionDef, id: string, ctx: RequestCtx, select?: ReadonlyArray<string> | null, expand?: ReadonlyArray<string> | null): Effect.Effect<RepoRecord, CollectionError>;
|
|
13
|
+
create(collection: AnyCollectionDef, data: RepoRecord, ctx: RequestCtx): Effect.Effect<RepoRecord, CollectionError>;
|
|
14
|
+
update(collection: AnyCollectionDef, id: string, data: RepoRecord, ctx: RequestCtx): Effect.Effect<RepoRecord, CollectionError>;
|
|
15
|
+
delete(collection: AnyCollectionDef, id: string, ctx: RequestCtx): Effect.Effect<void, CollectionError>;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Collection Service tag — the core business-logic layer sitting between
|
|
19
|
+
* the HTTP router and the repository.
|
|
20
|
+
*
|
|
21
|
+
* Each method performs:
|
|
22
|
+
* - Schema validation (field types, required fields, constraints via Effect Schema)
|
|
23
|
+
* - Access rule enforcement (compiles and applies x-rules)
|
|
24
|
+
* - System field management (seqId, created, updated timestamps)
|
|
25
|
+
* - Hidden/read-only field stripping for responses
|
|
26
|
+
* - Cursor-based pagination for list operations
|
|
27
|
+
* - File cleanup on update/delete (best-effort)
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* import { CollectionService } from "@gettersethya/mira"
|
|
31
|
+
*
|
|
32
|
+
* Effect.gen(function* () {
|
|
33
|
+
* const svc = yield* CollectionService
|
|
34
|
+
* const page = yield* svc.list(postsDef, null, 20, ctx, undefined, { field: "seqId", direction: "asc" })
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* @see makeCollectionServiceLayer — factory function
|
|
38
|
+
* @see Repository — the underlying data access layer
|
|
39
|
+
*/
|
|
40
|
+
export declare class CollectionService extends CollectionService_base {
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a `CollectionService` layer for a given set of collection definitions.
|
|
44
|
+
* Each collection gets its own validated service instance with per-collection
|
|
45
|
+
* rules, field schemas, and system field handling.
|
|
46
|
+
*
|
|
47
|
+
* The layer depends on `Repository`, `SqlClient`, and `FileStorage`.
|
|
48
|
+
*
|
|
49
|
+
* @param allCollections - Array of collection definitions
|
|
50
|
+
* @returns A Layer providing CollectionService
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* Layer.provideMerge(makeCollectionServiceLayer([Posts, Users]))
|
|
54
|
+
*
|
|
55
|
+
* @see CollectionService — the service tag
|
|
56
|
+
* @see makeCachedCollectionServiceLayer — wrapping layer that adds caching
|
|
57
|
+
*/
|
|
58
|
+
export declare function makeCollectionServiceLayer(allCollections: ReadonlyArray<AnyCollectionDef>): Layer.Layer<CollectionService, never, Repository | SqlClient.SqlClient | FileStorage>;
|
|
59
|
+
/** @deprecated Use makeCollectionServiceLayer(allCollections) */
|
|
60
|
+
export declare const CollectionServiceLive: Layer.Layer<CollectionService, never, FileStorage | SqlClient.SqlClient | Repository>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=collection-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-service.d.ts","sourceRoot":"","sources":["../../../src/collection-service/collection-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAkB,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,2BAA2B,CAAA;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAE3D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,KAAK,EAAa,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;;qBAgChC,gBAAgB,UACpB,MAAM,GAAG,IAAI,WACZ,MAAM,OACV,UAAU,WACN,UAAU,SACZ,SAAS,WACP,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,WAC5B,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GACpC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC;qBAG/B,gBAAgB,MACxB,MAAM,OACL,UAAU,WACN,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,WAC5B,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GACpC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC;uBAE1B,gBAAgB,QAAQ,UAAU,OAAO,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC;uBAGrG,gBAAgB,MACxB,MAAM,QACJ,UAAU,OACX,UAAU,GACd,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC;uBAE1B,gBAAgB,MAAM,MAAM,OAAO,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAAC;;AAtD3G;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iBAAkB,SAAQ,sBAiCpC;CAAG;AAqGN;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,cAAc,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAC9C,KAAK,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC,SAAS,GAAG,WAAW,CAAC,CAyOvF;AAED,iEAAiE;AACjE,eAAO,MAAM,qBAAqB,uFAAiC,CAAA"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { SqlClient } from "@effect/sql";
|
|
2
|
+
import { unsafeFragment } from "@effect/sql/Statement";
|
|
3
|
+
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
4
|
+
import { filterNodeToWhereClause } from "@gettersethya/mira-client";
|
|
5
|
+
import { enforcerForAction } from "../rule/enforcer.js";
|
|
6
|
+
import { Repository } from "../repository/repository.js";
|
|
7
|
+
import { FileStorage } from "../storage/storage.js";
|
|
8
|
+
import { makeRowDecoder } from "./decode.js";
|
|
9
|
+
import { ForbiddenError, NotFoundError, ReadOnlyError, ValidationError } from "./errors.js";
|
|
10
|
+
import { buildInputSchemas, parseErrToValidationError } from "./input-schema.js";
|
|
11
|
+
import { andWhere, cursorClause, idClause, resolveCtxPlaceholders, resolveFieldRefs } from "./where.js";
|
|
12
|
+
/**
|
|
13
|
+
* Collection Service tag — the core business-logic layer sitting between
|
|
14
|
+
* the HTTP router and the repository.
|
|
15
|
+
*
|
|
16
|
+
* Each method performs:
|
|
17
|
+
* - Schema validation (field types, required fields, constraints via Effect Schema)
|
|
18
|
+
* - Access rule enforcement (compiles and applies x-rules)
|
|
19
|
+
* - System field management (seqId, created, updated timestamps)
|
|
20
|
+
* - Hidden/read-only field stripping for responses
|
|
21
|
+
* - Cursor-based pagination for list operations
|
|
22
|
+
* - File cleanup on update/delete (best-effort)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* import { CollectionService } from "@gettersethya/mira"
|
|
26
|
+
*
|
|
27
|
+
* Effect.gen(function* () {
|
|
28
|
+
* const svc = yield* CollectionService
|
|
29
|
+
* const page = yield* svc.list(postsDef, null, 20, ctx, undefined, { field: "seqId", direction: "asc" })
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* @see makeCollectionServiceLayer — factory function
|
|
33
|
+
* @see Repository — the underlying data access layer
|
|
34
|
+
*/
|
|
35
|
+
export class CollectionService extends Context.Tag("CollectionService")() {
|
|
36
|
+
}
|
|
37
|
+
function resolveFields(schema, userSelect) {
|
|
38
|
+
const nonHidden = Object.entries(schema.properties)
|
|
39
|
+
.filter(([, p]) => !p["x-hidden"])
|
|
40
|
+
.map(([k]) => k);
|
|
41
|
+
if (userSelect === null)
|
|
42
|
+
return nonHidden;
|
|
43
|
+
// Intersect with non-hidden fields
|
|
44
|
+
const nonHiddenSet = new Set(nonHidden);
|
|
45
|
+
const selected = userSelect.filter((f) => nonHiddenSet.has(f));
|
|
46
|
+
// Always include core system fields needed for the response
|
|
47
|
+
const always = new Set(["id", "created", "updated"]);
|
|
48
|
+
for (const a of always) {
|
|
49
|
+
if (!selected.includes(a)) {
|
|
50
|
+
selected.push(a);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return selected;
|
|
54
|
+
}
|
|
55
|
+
function resolveExpand(schema, expandFieldNames, allCollections) {
|
|
56
|
+
if (expandFieldNames === null)
|
|
57
|
+
return [];
|
|
58
|
+
const collectionMap = new Map(allCollections.map((c) => [c.name, c]));
|
|
59
|
+
const result = [];
|
|
60
|
+
for (const name of expandFieldNames) {
|
|
61
|
+
const prop = schema.properties[name];
|
|
62
|
+
if (!prop || prop["x-kind"] !== "relation")
|
|
63
|
+
continue;
|
|
64
|
+
if (!prop["x-collection"])
|
|
65
|
+
continue;
|
|
66
|
+
const target = collectionMap.get(prop["x-collection"]);
|
|
67
|
+
if (!target)
|
|
68
|
+
continue;
|
|
69
|
+
const targetColumns = resolveFields(target.schema, null);
|
|
70
|
+
result.push({ localField: name, targetTable: prop["x-collection"], targetColumns });
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function rejectSystemFields(data, schema, collectionName) {
|
|
75
|
+
const sysKeys = Object.keys(data).filter((k) => schema.properties[k]?.["x-system"] === true);
|
|
76
|
+
if (sysKeys.length === 0)
|
|
77
|
+
return Effect.void;
|
|
78
|
+
return Effect.fail(new ValidationError({
|
|
79
|
+
collection: collectionName,
|
|
80
|
+
issues: [`Fields are read-only: ${sysKeys.join(", ")}`]
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
function extractFileKeys(record, schema) {
|
|
84
|
+
return Object.entries(schema.properties)
|
|
85
|
+
.filter(([, prop]) => prop["x-kind"] === "file")
|
|
86
|
+
.map(([field]) => record[field])
|
|
87
|
+
.filter((v) => typeof v === "string" && v.length > 0);
|
|
88
|
+
}
|
|
89
|
+
function extractReplacedFileKeys(existing, patch, schema) {
|
|
90
|
+
return Object.entries(schema.properties)
|
|
91
|
+
.filter(([, prop]) => prop["x-kind"] === "file")
|
|
92
|
+
.flatMap(([field]) => {
|
|
93
|
+
const oldKey = existing[field];
|
|
94
|
+
const newKey = patch[field];
|
|
95
|
+
if (typeof oldKey === "string" && oldKey.length > 0 && field in patch && newKey !== oldKey) {
|
|
96
|
+
return [oldKey];
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function compileRule(collection, action, ctx) {
|
|
102
|
+
const result = enforcerForAction(collection.schema, action);
|
|
103
|
+
if (result === null) {
|
|
104
|
+
return Effect.fail(new ForbiddenError({ collection: collection.name, action }));
|
|
105
|
+
}
|
|
106
|
+
return resolveCtxPlaceholders(result, ctx, collection.name, action);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create a `CollectionService` layer for a given set of collection definitions.
|
|
110
|
+
* Each collection gets its own validated service instance with per-collection
|
|
111
|
+
* rules, field schemas, and system field handling.
|
|
112
|
+
*
|
|
113
|
+
* The layer depends on `Repository`, `SqlClient`, and `FileStorage`.
|
|
114
|
+
*
|
|
115
|
+
* @param allCollections - Array of collection definitions
|
|
116
|
+
* @returns A Layer providing CollectionService
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* Layer.provideMerge(makeCollectionServiceLayer([Posts, Users]))
|
|
120
|
+
*
|
|
121
|
+
* @see CollectionService — the service tag
|
|
122
|
+
* @see makeCachedCollectionServiceLayer — wrapping layer that adds caching
|
|
123
|
+
*/
|
|
124
|
+
export function makeCollectionServiceLayer(allCollections) {
|
|
125
|
+
return Layer.effect(CollectionService, Effect.gen(function* () {
|
|
126
|
+
const repo = yield* Repository;
|
|
127
|
+
const sql = yield* SqlClient.SqlClient;
|
|
128
|
+
const fileStorage = yield* FileStorage;
|
|
129
|
+
const decoderMap = new Map(allCollections.map((c) => [c.name, makeRowDecoder(c.schema)]));
|
|
130
|
+
const inputSchemaMap = new Map(allCollections.map((c) => [c.name, buildInputSchemas(c)]));
|
|
131
|
+
const getInputSchemas = (col) => inputSchemaMap.get(col.name) ?? buildInputSchemas(col);
|
|
132
|
+
const decodeRow = (collection, record) => (decoderMap.get(collection.name) ?? Effect.succeed)(record);
|
|
133
|
+
const list = (collection, cursor, perPage, ctx, filter, sort, select, expand) => Effect.gen(function* () {
|
|
134
|
+
const ruleWhere = yield* compileRule(collection, "list", ctx);
|
|
135
|
+
const userFilter = filter !== undefined ? yield* filterNodeToWhereClause(filter, collection.schema, collection.name) : null;
|
|
136
|
+
const where = userFilter !== null ? andWhere(ruleWhere, userFilter) : ruleWhere;
|
|
137
|
+
const combined = cursor !== null ? andWhere(where, cursorClause(cursor)) : where;
|
|
138
|
+
const fields = resolveFields(collection.schema, select ?? null);
|
|
139
|
+
const expandDefs = resolveExpand(collection.schema, expand ?? null, allCollections);
|
|
140
|
+
const fieldsWithExpand = [...new Set([...fields, ...expandDefs.map((e) => e.localField)])];
|
|
141
|
+
// seqId is x-hidden but must be fetched for cursor-based pagination
|
|
142
|
+
const fieldsForQuery = fieldsWithExpand.includes("seqId") ? fieldsWithExpand : [...fieldsWithExpand, "seqId"];
|
|
143
|
+
const rows = yield* repo.list(collection.name, perPage, {
|
|
144
|
+
where: combined,
|
|
145
|
+
sort: sort ?? { field: "seqId", direction: "asc" },
|
|
146
|
+
fields: fieldsForQuery,
|
|
147
|
+
expand: expandDefs
|
|
148
|
+
});
|
|
149
|
+
const lastRow = rows.items.at(-1);
|
|
150
|
+
const rawSeqId = lastRow?.["seqId"];
|
|
151
|
+
const lastSeqId = typeof rawSeqId === "number" ? rawSeqId : null;
|
|
152
|
+
const nextCursor = rows.items.length === perPage ? lastSeqId : null;
|
|
153
|
+
// Strip seqId — it's internal and must not appear in API responses
|
|
154
|
+
const items = yield* Effect.forEach(rows.items.map((r) => {
|
|
155
|
+
const copy = { ...r };
|
|
156
|
+
delete copy["seqId"];
|
|
157
|
+
return copy;
|
|
158
|
+
}), (r) => decodeRow(collection, r));
|
|
159
|
+
return { items, nextCursor };
|
|
160
|
+
}).pipe(Effect.withSpan("collection.list", {
|
|
161
|
+
kind: "internal",
|
|
162
|
+
attributes: { collection: collection.name }
|
|
163
|
+
}));
|
|
164
|
+
const view = (collection, id, ctx, select, expand) => Effect.gen(function* () {
|
|
165
|
+
const ruleWhere = yield* compileRule(collection, "view", ctx);
|
|
166
|
+
const combined = andWhere(ruleWhere, idClause(id));
|
|
167
|
+
const fields = resolveFields(collection.schema, select ?? null);
|
|
168
|
+
const expandDefs = resolveExpand(collection.schema, expand ?? null, allCollections);
|
|
169
|
+
const fieldsWithExpand = [...new Set([...fields, ...expandDefs.map((e) => e.localField)])];
|
|
170
|
+
const rows = yield* repo.viewFilter(collection.name, {
|
|
171
|
+
where: combined,
|
|
172
|
+
fields: fieldsWithExpand,
|
|
173
|
+
expand: expandDefs
|
|
174
|
+
});
|
|
175
|
+
if (rows.length === 0) {
|
|
176
|
+
return yield* new NotFoundError({ collection: collection.name, id });
|
|
177
|
+
}
|
|
178
|
+
return yield* decodeRow(collection, rows[0]);
|
|
179
|
+
}).pipe(Effect.withSpan("collection.view", {
|
|
180
|
+
kind: "internal",
|
|
181
|
+
attributes: { collection: collection.name }
|
|
182
|
+
}));
|
|
183
|
+
const create = (collection, data, ctx) => Effect.gen(function* () {
|
|
184
|
+
if (collection.schema["x-collection-kind"] === "view") {
|
|
185
|
+
return yield* new ReadOnlyError({ collection: collection.name });
|
|
186
|
+
}
|
|
187
|
+
yield* rejectSystemFields(data, collection.schema, collection.name);
|
|
188
|
+
const schemas = getInputSchemas(collection);
|
|
189
|
+
const cleaned = yield* Schema.decodeUnknown(schemas.create)(data).pipe(Effect.mapError(parseErrToValidationError(collection.name)));
|
|
190
|
+
const ruleWhere = yield* compileRule(collection, "create", ctx);
|
|
191
|
+
// Pre-check: evaluate the create rule against the payload (no table needed).
|
|
192
|
+
const payloadBound = resolveFieldRefs(ruleWhere, cleaned);
|
|
193
|
+
const where = unsafeFragment(payloadBound.sql, payloadBound.params);
|
|
194
|
+
const check = yield* sql `SELECT 1 AS ok WHERE ${where}`;
|
|
195
|
+
if (check.length === 0) {
|
|
196
|
+
return yield* new ForbiddenError({ collection: collection.name, action: "create" });
|
|
197
|
+
}
|
|
198
|
+
const row = yield* repo.create(collection.name, cleaned);
|
|
199
|
+
return yield* decodeRow(collection, row);
|
|
200
|
+
}).pipe(Effect.withSpan("collection.create", {
|
|
201
|
+
kind: "internal",
|
|
202
|
+
attributes: { collection: collection.name }
|
|
203
|
+
}));
|
|
204
|
+
const update = (collection, id, data, ctx) => Effect.gen(function* () {
|
|
205
|
+
if (collection.schema["x-collection-kind"] === "view") {
|
|
206
|
+
return yield* new ReadOnlyError({ collection: collection.name });
|
|
207
|
+
}
|
|
208
|
+
yield* rejectSystemFields(data, collection.schema, collection.name);
|
|
209
|
+
const schemas = getInputSchemas(collection);
|
|
210
|
+
const cleaned = yield* Schema.decodeUnknown(schemas.update)(data).pipe(Effect.mapError(parseErrToValidationError(collection.name)));
|
|
211
|
+
const ruleWhere = yield* compileRule(collection, "update", ctx);
|
|
212
|
+
// Existence check first — distinguish NotFound from Forbidden
|
|
213
|
+
const existing = yield* repo.view(collection.name, id);
|
|
214
|
+
if (Option.isNone(existing)) {
|
|
215
|
+
return yield* new NotFoundError({ collection: collection.name, id });
|
|
216
|
+
}
|
|
217
|
+
// Rule check against this specific row
|
|
218
|
+
const combined = andWhere(ruleWhere, idClause(id));
|
|
219
|
+
const allowed = yield* repo.viewFilter(collection.name, { where: combined });
|
|
220
|
+
if (allowed.length === 0) {
|
|
221
|
+
return yield* new ForbiddenError({ collection: collection.name, action: "update" });
|
|
222
|
+
}
|
|
223
|
+
const updated = yield* repo.update(collection.name, id, cleaned);
|
|
224
|
+
// Best-effort: delete old files and their thumbnails for any replaced file fields.
|
|
225
|
+
const oldKeys = extractReplacedFileKeys(existing.value, cleaned, collection.schema);
|
|
226
|
+
yield* Effect.forEach(oldKeys, (key) => Effect.gen(function* () {
|
|
227
|
+
yield* fileStorage.delete(key).pipe(Effect.orElse(() => Effect.void));
|
|
228
|
+
const thumbKeys = yield* fileStorage.list(`_thumbs/${key}/`).pipe(Effect.orElseSucceed(() => []));
|
|
229
|
+
yield* Effect.forEach(thumbKeys, (tk) => fileStorage.delete(tk).pipe(Effect.orElse(() => Effect.void)), { concurrency: "unbounded" });
|
|
230
|
+
}), { concurrency: "unbounded" });
|
|
231
|
+
return yield* decodeRow(collection, Option.getOrElse(updated, () => existing.value));
|
|
232
|
+
}).pipe(Effect.withSpan("collection.update", {
|
|
233
|
+
kind: "internal",
|
|
234
|
+
attributes: { collection: collection.name }
|
|
235
|
+
}));
|
|
236
|
+
const deleteImpl = (collection, id, ctx) => Effect.gen(function* () {
|
|
237
|
+
if (collection.schema["x-collection-kind"] === "view") {
|
|
238
|
+
return yield* new ReadOnlyError({ collection: collection.name });
|
|
239
|
+
}
|
|
240
|
+
const ruleWhere = yield* compileRule(collection, "delete", ctx);
|
|
241
|
+
const existing = yield* repo.view(collection.name, id);
|
|
242
|
+
if (Option.isNone(existing)) {
|
|
243
|
+
return yield* new NotFoundError({ collection: collection.name, id });
|
|
244
|
+
}
|
|
245
|
+
const combined = andWhere(ruleWhere, idClause(id));
|
|
246
|
+
const allowed = yield* repo.viewFilter(collection.name, { where: combined });
|
|
247
|
+
if (allowed.length === 0) {
|
|
248
|
+
return yield* new ForbiddenError({ collection: collection.name, action: "delete" });
|
|
249
|
+
}
|
|
250
|
+
yield* repo.delete(collection.name, id);
|
|
251
|
+
// Best-effort: delete files and their thumbnails after the record is gone.
|
|
252
|
+
const keys = extractFileKeys(existing.value, collection.schema);
|
|
253
|
+
yield* Effect.forEach(keys, (key) => Effect.gen(function* () {
|
|
254
|
+
yield* fileStorage.delete(key).pipe(Effect.orElse(() => Effect.void));
|
|
255
|
+
const thumbKeys = yield* fileStorage.list(`_thumbs/${key}/`).pipe(Effect.orElseSucceed(() => []));
|
|
256
|
+
yield* Effect.forEach(thumbKeys, (tk) => fileStorage.delete(tk).pipe(Effect.orElse(() => Effect.void)), { concurrency: "unbounded" });
|
|
257
|
+
}), { concurrency: "unbounded" });
|
|
258
|
+
}).pipe(Effect.withSpan("collection.delete", {
|
|
259
|
+
kind: "internal",
|
|
260
|
+
attributes: { collection: collection.name }
|
|
261
|
+
}));
|
|
262
|
+
return { list, view, create, update, delete: deleteImpl };
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
/** @deprecated Use makeCollectionServiceLayer(allCollections) */
|
|
266
|
+
export const CollectionServiceLive = makeCollectionServiceLayer([]);
|
|
267
|
+
//# sourceMappingURL=collection-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-service.js","sourceRoot":"","sources":["../../../src/collection-service/collection-service.ts"],"names":[],"mappings":"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"}
|