@entifix/mongo 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/make-mongo-facet-counter.d.ts.map +1 -1
- package/dist/adapters/make-mongo-facet-counter.js +3 -4
- package/dist/adapters/make-mongo-facet-counter.js.map +1 -1
- package/dist/adapters/make-mongo-link-resolver.d.ts.map +1 -1
- package/dist/adapters/make-mongo-link-resolver.js +2 -4
- package/dist/adapters/make-mongo-link-resolver.js.map +1 -1
- package/dist/adapters/make-mongo-repository.d.ts +3 -3
- package/dist/adapters/make-mongo-repository.d.ts.map +1 -1
- package/dist/adapters/make-mongo-repository.js +11 -18
- package/dist/adapters/make-mongo-repository.js.map +1 -1
- package/dist/adapters/mongo-facet-counter-factory.d.ts.map +1 -1
- package/dist/adapters/mongo-facet-counter-factory.js +2 -4
- package/dist/adapters/mongo-facet-counter-factory.js.map +1 -1
- package/dist/adapters/mongo-repository-factory.d.ts.map +1 -1
- package/dist/adapters/mongo-repository-factory.js +2 -4
- package/dist/adapters/mongo-repository-factory.js.map +1 -1
- package/dist/errors/translate-mongo-error.d.ts +30 -0
- package/dist/errors/translate-mongo-error.d.ts.map +1 -0
- package/dist/errors/translate-mongo-error.js +74 -0
- package/dist/errors/translate-mongo-error.js.map +1 -0
- package/dist/inbox/store.d.ts +1 -2
- package/dist/inbox/store.d.ts.map +1 -1
- package/dist/inbox/store.js +3 -8
- package/dist/inbox/store.js.map +1 -1
- package/dist/mongo-database/mongo-database.d.ts +3 -3
- package/dist/mongo-database/mongo-database.d.ts.map +1 -1
- package/dist/mongo-database/mongo-database.js +2 -2
- package/dist/mongo-database/mongo-database.js.map +1 -1
- package/dist/mongo-database/mongo-tenant-directory.d.ts.map +1 -1
- package/dist/mongo-database/mongo-tenant-directory.js +2 -4
- package/dist/mongo-database/mongo-tenant-directory.js.map +1 -1
- package/dist/mongo-database/mongo-tenant-resolver.d.ts.map +1 -1
- package/dist/mongo-database/mongo-tenant-resolver.js +18 -7
- package/dist/mongo-database/mongo-tenant-resolver.js.map +1 -1
- package/dist/outbox/relay.d.ts +50 -9
- package/dist/outbox/relay.d.ts.map +1 -1
- package/dist/outbox/relay.js +67 -26
- package/dist/outbox/relay.js.map +1 -1
- package/dist/outbox/store.d.ts +3 -3
- package/dist/outbox/store.d.ts.map +1 -1
- package/dist/outbox/store.js +8 -22
- package/dist/outbox/store.js.map +1 -1
- package/dist/saga/store.d.ts +3 -4
- package/dist/saga/store.d.ts.map +1 -1
- package/dist/saga/store.js +10 -15
- package/dist/saga/store.js.map +1 -1
- package/dist/unit-of-work/command-inbox.d.ts +2 -3
- package/dist/unit-of-work/command-inbox.d.ts.map +1 -1
- package/dist/unit-of-work/command-inbox.js +3 -7
- package/dist/unit-of-work/command-inbox.js.map +1 -1
- package/dist/unit-of-work/mongo-unit-of-work.d.ts.map +1 -1
- package/dist/unit-of-work/mongo-unit-of-work.js +30 -26
- package/dist/unit-of-work/mongo-unit-of-work.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-mongo-facet-counter.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-facet-counter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,
|
|
1
|
+
{"version":3,"file":"make-mongo-facet-counter.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-facet-counter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,iBAAiB,EAIvB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAY,MAAM,SAAS,CAAC;AA+B3D;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,MAAM,EAC1D,EAAE,EAAE,EAAE,EACN,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,GAC7C,kBAAkB,CA2CpB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { compareStoredValues
|
|
1
|
+
import { compareStoredValues } from '@entifix/core';
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
|
+
import { translateMongoError } from '../errors/translate-mongo-error.js';
|
|
3
4
|
import { translateFiltering } from './filter-translator.js';
|
|
4
5
|
import { mongoCollectionName } from './make-mongo-repository.js';
|
|
5
6
|
const isFacetValue = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
@@ -54,9 +55,7 @@ const isFacetValue = (value)=>typeof value === 'string' || typeof value === 'num
|
|
|
54
55
|
]))
|
|
55
56
|
}
|
|
56
57
|
], inSession).toArray(),
|
|
57
|
-
catch: (error)=>
|
|
58
|
-
entity: entityConstructor.name
|
|
59
|
-
})
|
|
58
|
+
catch: (error)=>translateMongoError(error)
|
|
60
59
|
});
|
|
61
60
|
const counts = {};
|
|
62
61
|
for (const { member } of queries){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/make-mongo-facet-counter.ts"],"sourcesContent":["import type { EntityFacetCounter } from '@entifix/business';\nimport {\n compareStoredValues,\n
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/make-mongo-facet-counter.ts"],"sourcesContent":["import type { EntityFacetCounter } from '@entifix/business';\nimport {\n compareStoredValues,\n type Entity,\n type EntityConstructor,\n type EntityFacetCounts,\n type EntityFacetQuery,\n type EntityFacetValue,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport type { ClientSession, Db, Document } from 'mongodb';\n\nimport { translateMongoError } from '../errors/translate-mongo-error.js';\nimport { translateFiltering } from './filter-translator.js';\nimport { mongoCollectionName } from './make-mongo-repository.js';\n\nconst isFacetValue = (value: unknown): value is EntityFacetValue['value'] =>\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean';\n\n/** Count first, highest first; a tie by value — the order core's counter keeps. */\nconst byCountThenValue = (left: EntityFacetValue, right: EntityFacetValue) =>\n right.count - left.count || compareStoredValues(left.value, right.value);\n\n/**\n * One `$facet` branch: the member's own filtering, then one row per value.\n *\n * `$unwind` is in every branch, not only an array member's: on a scalar it\n * passes the document through, and on a missing or `null` value it drops it —\n * which is the \"a missing value is not counted\" rule, for free.\n */\nconst branchFor = <TEntity extends Entity>({\n member,\n filtering,\n}: EntityFacetQuery<TEntity>): Document[] => [\n { $match: translateFiltering(filtering) },\n { $unwind: `$${member}` },\n { $group: { _id: `$${member}`, count: { $sum: 1 } } },\n];\n\n/**\n * Builds an {@link EntityFacetCounter} for one entity over a live mongodb\n * {@link Db}: every facet in **one** aggregation, a `$facet` whose branches\n * each `$match` their own filtering, so a disjunctive panel costs one round\n * trip however many facets it shows.\n *\n * Filtering goes through the same translator the repository's `load` uses, so a\n * count and the page beside it can never disagree about what a filter means.\n * A value that is not a string, number or boolean is dropped, as in core's\n * in-memory counter.\n */\nexport function makeMongoFacetCounter<TEntity extends Entity>(\n db: Db,\n entityConstructor: EntityConstructor<TEntity>,\n options?: { readonly session?: ClientSession },\n): EntityFacetCounter {\n const collection = db.collection(mongoCollectionName(entityConstructor));\n const inSession = options?.session ? { session: options.session } : {};\n\n const count = <T extends Entity>(queries: readonly EntityFacetQuery<T>[]) =>\n Effect.gen(function* () {\n // A `$facet` with no branch is a server error; asking for nothing is\n // not.\n if (queries.length === 0) return {} satisfies EntityFacetCounts;\n\n const [result] = yield* Effect.tryPromise({\n try: () =>\n collection\n .aggregate(\n [\n {\n $facet: Object.fromEntries(\n queries.map(query => [query.member, branchFor(query)]),\n ),\n },\n ],\n inSession,\n )\n .toArray(),\n catch: error => translateMongoError(error),\n });\n\n const counts: Record<string, readonly EntityFacetValue[]> = {};\n for (const { member } of queries) {\n const rows = ((result as Document | undefined)?.[member] ??\n []) as Document[];\n counts[member] = rows\n .filter(row => isFacetValue(row['_id']))\n .map(row => ({\n value: row['_id'] as EntityFacetValue['value'],\n count: row['count'] as number,\n }))\n .sort(byCountThenValue);\n }\n return counts satisfies EntityFacetCounts;\n });\n\n return { count };\n}\n"],"names":["compareStoredValues","Effect","translateMongoError","translateFiltering","mongoCollectionName","isFacetValue","value","byCountThenValue","left","right","count","branchFor","member","filtering","$match","$unwind","$group","_id","$sum","makeMongoFacetCounter","db","entityConstructor","options","collection","inSession","session","queries","gen","length","result","tryPromise","try","aggregate","$facet","Object","fromEntries","map","query","toArray","catch","error","counts","rows","filter","row","sort"],"mappings":"AACA,SACEA,mBAAmB,QAMd,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAGhC,SAASC,mBAAmB,QAAQ,qCAAqC;AACzE,SAASC,kBAAkB,QAAQ,yBAAyB;AAC5D,SAASC,mBAAmB,QAAQ,6BAA6B;AAEjE,MAAMC,eAAe,CAACC,QACpB,OAAOA,UAAU,YACjB,OAAOA,UAAU,YACjB,OAAOA,UAAU;AAEnB,iFAAiF,GACjF,MAAMC,mBAAmB,CAACC,MAAwBC,QAChDA,MAAMC,KAAK,GAAGF,KAAKE,KAAK,IAAIV,oBAAoBQ,KAAKF,KAAK,EAAEG,MAAMH,KAAK;AAEzE;;;;;;CAMC,GACD,MAAMK,YAAY,CAAyB,EACzCC,MAAM,EACNC,SAAS,EACiB,GAAiB;QAC3C;YAAEC,QAAQX,mBAAmBU;QAAW;QACxC;YAAEE,SAAS,CAAC,CAAC,EAAEH,QAAQ;QAAC;QACxB;YAAEI,QAAQ;gBAAEC,KAAK,CAAC,CAAC,EAAEL,QAAQ;gBAAEF,OAAO;oBAAEQ,MAAM;gBAAE;YAAE;QAAE;KACrD;AAED;;;;;;;;;;CAUC,GACD,OAAO,SAASC,sBACdC,EAAM,EACNC,iBAA6C,EAC7CC,OAA8C;IAE9C,MAAMC,aAAaH,GAAGG,UAAU,CAACnB,oBAAoBiB;IACrD,MAAMG,YAAYF,CAAAA,2BAAAA,QAASG,OAAO,IAAG;QAAEA,SAASH,QAAQG,OAAO;IAAC,IAAI,CAAC;IAErE,MAAMf,QAAQ,CAAmBgB,UAC/BzB,OAAO0B,GAAG,CAAC;YACT,qEAAqE;YACrE,OAAO;YACP,IAAID,QAAQE,MAAM,KAAK,GAAG,OAAO,CAAC;YAElC,MAAM,CAACC,OAAO,GAAG,OAAO5B,OAAO6B,UAAU,CAAC;gBACxCC,KAAK,IACHR,WACGS,SAAS,CACR;wBACE;4BACEC,QAAQC,OAAOC,WAAW,CACxBT,QAAQU,GAAG,CAACC,CAAAA,QAAS;oCAACA,MAAMzB,MAAM;oCAAED,UAAU0B;iCAAO;wBAEzD;qBACD,EACDb,WAEDc,OAAO;gBACZC,OAAOC,CAAAA,QAAStC,oBAAoBsC;YACtC;YAEA,MAAMC,SAAsD,CAAC;YAC7D,KAAK,MAAM,EAAE7B,MAAM,EAAE,IAAIc,QAAS;;gBAChC,MAAMgB,eAASb,0BAAD,AAACA,MAAiC,CAACjB,OAAO,mBACtD,EAAE;gBACJ6B,MAAM,CAAC7B,OAAO,GAAG8B,KACdC,MAAM,CAACC,CAAAA,MAAOvC,aAAauC,GAAG,CAAC,MAAM,GACrCR,GAAG,CAACQ,CAAAA,MAAQ,CAAA;wBACXtC,OAAOsC,GAAG,CAAC,MAAM;wBACjBlC,OAAOkC,GAAG,CAAC,QAAQ;oBACrB,CAAA,GACCC,IAAI,CAACtC;YACV;YACA,OAAOkC;QACT;IAEF,OAAO;QAAE/B;IAAM;AACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-mongo-link-resolver.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-link-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"make-mongo-link-resolver.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-link-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EACL,MAAM,EACN,iBAAiB,EAIlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAU,MAAM,QAAQ,CAAC;AACzC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAIlC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,EAAE,EACN,aAAa,EAAE,aAAa,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,GACtD,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CA2BxC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityLinkResolverTag } from '@entifix/business';
|
|
2
|
-
import {
|
|
2
|
+
import { Internal } from '@entifix/core';
|
|
3
3
|
import { Context, Effect } from 'effect';
|
|
4
4
|
import { makeMongoRepository } from './make-mongo-repository.js';
|
|
5
5
|
/**
|
|
@@ -18,9 +18,7 @@ import { makeMongoRepository } from './make-mongo-repository.js';
|
|
|
18
18
|
resolve (entityConstructor, id) {
|
|
19
19
|
const repository = repositoryByConstructor.get(entityConstructor);
|
|
20
20
|
if (!repository) {
|
|
21
|
-
return Effect.fail(new
|
|
22
|
-
entity: entityConstructor.name
|
|
23
|
-
}));
|
|
21
|
+
return Effect.fail(new Internal());
|
|
24
22
|
}
|
|
25
23
|
return repository.get(id);
|
|
26
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/make-mongo-link-resolver.ts"],"sourcesContent":["import { EntityLinkResolverTag } from '@entifix/business';\nimport {\n
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/make-mongo-link-resolver.ts"],"sourcesContent":["import { EntityLinkResolverTag } from '@entifix/business';\nimport {\n Entity,\n EntityConstructor,\n EntityLinkResolver,\n Internal,\n type TransitionalFailure,\n} from '@entifix/core';\nimport { Context, Effect } from 'effect';\nimport type { Db } from 'mongodb';\n\nimport { makeMongoRepository } from './make-mongo-repository.js';\n\n/**\n * Builds an {@link EntityLinkResolverTag} context backed by Mongo repositories,\n * so a load use-case can materialize not-yet-loaded {@link EntityLink}s on the\n * backend. Mirrors the REST `createEntityLinkResolver`, but each Mongo `get`\n * already has no outstanding requirements, so nothing needs to be provided.\n *\n * `registrations` lists the link-target constructors this resolver can fetch.\n */\nexport function makeMongoLinkResolver(\n db: Db,\n registrations: ReadonlyArray<EntityConstructor<Entity>>,\n): Context.Context<EntityLinkResolverTag> {\n const repositoryByConstructor = new Map(\n registrations.map(\n entityConstructor =>\n [\n entityConstructor,\n makeMongoRepository(db, entityConstructor),\n ] as const,\n ),\n );\n\n const resolver: EntityLinkResolver = {\n resolve(entityConstructor, id) {\n const repository = repositoryByConstructor.get(\n entityConstructor as EntityConstructor<Entity>,\n );\n if (!repository) {\n return Effect.fail(new Internal());\n }\n return repository.get(id) as unknown as Effect.Effect<\n never,\n TransitionalFailure\n >;\n },\n };\n\n return Context.make(EntityLinkResolverTag, resolver);\n}\n"],"names":["EntityLinkResolverTag","Internal","Context","Effect","makeMongoRepository","makeMongoLinkResolver","db","registrations","repositoryByConstructor","Map","map","entityConstructor","resolver","resolve","id","repository","get","fail","make"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,oBAAoB;AAC1D,SAIEC,QAAQ,QAEH,gBAAgB;AACvB,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AAGzC,SAASC,mBAAmB,QAAQ,6BAA6B;AAEjE;;;;;;;CAOC,GACD,OAAO,SAASC,sBACdC,EAAM,EACNC,aAAuD;IAEvD,MAAMC,0BAA0B,IAAIC,IAClCF,cAAcG,GAAG,CACfC,CAAAA,oBACE;YACEA;YACAP,oBAAoBE,IAAIK;SACzB;IAIP,MAAMC,WAA+B;QACnCC,SAAQF,iBAAiB,EAAEG,EAAE;YAC3B,MAAMC,aAAaP,wBAAwBQ,GAAG,CAC5CL;YAEF,IAAI,CAACI,YAAY;gBACf,OAAOZ,OAAOc,IAAI,CAAC,IAAIhB;YACzB;YACA,OAAOc,WAAWC,GAAG,CAACF;QAIxB;IACF;IAEA,OAAOZ,QAAQgB,IAAI,CAAClB,uBAAuBY;AAC7C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityRepository } from '@entifix/business';
|
|
2
|
-
import {
|
|
2
|
+
import { Entity, EntityConstructor } from '@entifix/core';
|
|
3
3
|
import type { ClientSession, Db } from 'mongodb';
|
|
4
4
|
/** The collection an entity's records live in: its `key`, else its class name. */
|
|
5
5
|
export declare function mongoCollectionName<TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>): string;
|
|
@@ -12,7 +12,8 @@ export declare function mongoCollectionName<TEntity extends Entity>(entityConstr
|
|
|
12
12
|
* Collection name = the entity's `key` (falling back to its class name). Reads
|
|
13
13
|
* reuse the shared entifix deserializer (rebuilding links from embedded objects
|
|
14
14
|
* or scalar FKs); writes reuse the shared serializer. Mongo failures surface as
|
|
15
|
-
*
|
|
15
|
+
* categorised failures — `NotFound`, `DuplicateKey`, `Unavailable` and the
|
|
16
|
+
* rest — through `translateMongoError`.
|
|
16
17
|
*
|
|
17
18
|
* This is the Mongo mirror of the REST `buildEntityRestAdapter*` builders.
|
|
18
19
|
*
|
|
@@ -23,5 +24,4 @@ export declare function mongoCollectionName<TEntity extends Entity>(entityConstr
|
|
|
23
24
|
export declare function makeMongoRepository<TEntity extends Entity>(db: Db, entityConstructor: EntityConstructor<TEntity>, options?: {
|
|
24
25
|
readonly session?: ClientSession;
|
|
25
26
|
}): EntityRepository;
|
|
26
|
-
export type { EntifixError };
|
|
27
27
|
//# sourceMappingURL=make-mongo-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-mongo-repository.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"make-mongo-repository.d.ts","sourceRoot":"","sources":["../../src/adapters/make-mongo-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAGL,MAAM,EACN,iBAAiB,EAOlB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAQjD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,EACxD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAC5C,MAAM,CAGR;AAQD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,EACxD,EAAE,EAAE,EAAE,EACN,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,GAC7C,gBAAgB,CAgGlB"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { deserializeEntityCollection, deserializeSingleEntity,
|
|
3
|
+
import { deserializeEntityCollection, deserializeSingleEntity, extractMetaEntity, NotFound, serializeEntity } from '@entifix/core';
|
|
4
4
|
import { Effect } from 'effect';
|
|
5
|
+
import { translateMongoError } from '../errors/translate-mongo-error.js';
|
|
5
6
|
import { translateFiltering, translateSorting } from './filter-translator.js';
|
|
6
7
|
/** Projection that drops Mongo's internal `_id` from read results. */ const WITHOUT_MONGO_ID = {
|
|
7
8
|
projection: {
|
|
@@ -25,7 +26,8 @@ function toEntityId(entityOrId) {
|
|
|
25
26
|
* Collection name = the entity's `key` (falling back to its class name). Reads
|
|
26
27
|
* reuse the shared entifix deserializer (rebuilding links from embedded objects
|
|
27
28
|
* or scalar FKs); writes reuse the shared serializer. Mongo failures surface as
|
|
28
|
-
*
|
|
29
|
+
* categorised failures — `NotFound`, `DuplicateKey`, `Unavailable` and the
|
|
30
|
+
* rest — through `translateMongoError`.
|
|
29
31
|
*
|
|
30
32
|
* This is the Mongo mirror of the REST `buildEntityRestAdapter*` builders.
|
|
31
33
|
*
|
|
@@ -38,9 +40,6 @@ function toEntityId(entityOrId) {
|
|
|
38
40
|
const inSession = (options == null ? void 0 : options.session) ? {
|
|
39
41
|
session: options.session
|
|
40
42
|
} : {};
|
|
41
|
-
const fail = (message, error, details)=>new EntifixConnError(message, error, _extends({
|
|
42
|
-
entity: entityConstructor.name
|
|
43
|
-
}, details));
|
|
44
43
|
const load = (request)=>Effect.gen(function*() {
|
|
45
44
|
var _request_page, _request_pageSize;
|
|
46
45
|
const filter = translateFiltering(request.filtering);
|
|
@@ -50,11 +49,11 @@ function toEntityId(entityOrId) {
|
|
|
50
49
|
const skip = (page - 1) * pageSize;
|
|
51
50
|
const docs = yield* Effect.tryPromise({
|
|
52
51
|
try: ()=>collection.find(filter, _extends({}, WITHOUT_MONGO_ID, inSession)).sort(sort).skip(skip).limit(pageSize).toArray(),
|
|
53
|
-
catch: (error)=>
|
|
52
|
+
catch: (error)=>translateMongoError(error)
|
|
54
53
|
});
|
|
55
54
|
const total = yield* Effect.tryPromise({
|
|
56
55
|
try: ()=>collection.countDocuments(filter, inSession),
|
|
57
|
-
catch: (error)=>
|
|
56
|
+
catch: (error)=>translateMongoError(error)
|
|
58
57
|
});
|
|
59
58
|
const items = yield* deserializeEntityCollection(entityConstructor, docs);
|
|
60
59
|
return {
|
|
@@ -68,14 +67,12 @@ function toEntityId(entityOrId) {
|
|
|
68
67
|
try: ()=>collection.findOne({
|
|
69
68
|
id
|
|
70
69
|
}, _extends({}, WITHOUT_MONGO_ID, inSession)),
|
|
71
|
-
catch: (error)=>
|
|
72
|
-
id
|
|
73
|
-
})
|
|
70
|
+
catch: (error)=>translateMongoError(error)
|
|
74
71
|
});
|
|
75
72
|
const entity = yield* deserializeSingleEntity(entityConstructor, doc);
|
|
76
73
|
if (entity === undefined) {
|
|
77
|
-
return yield* Effect.fail(
|
|
78
|
-
|
|
74
|
+
return yield* Effect.fail(new NotFound({
|
|
75
|
+
entity: extractMetaEntity(entityConstructor).key
|
|
79
76
|
}));
|
|
80
77
|
}
|
|
81
78
|
return entity;
|
|
@@ -98,9 +95,7 @@ function toEntityId(entityOrId) {
|
|
|
98
95
|
}), _extends({
|
|
99
96
|
upsert: true
|
|
100
97
|
}, inSession)),
|
|
101
|
-
catch: (error)=>
|
|
102
|
-
id
|
|
103
|
-
})
|
|
98
|
+
catch: (error)=>translateMongoError(error)
|
|
104
99
|
});
|
|
105
100
|
entity.id = id;
|
|
106
101
|
return entity;
|
|
@@ -111,9 +106,7 @@ function toEntityId(entityOrId) {
|
|
|
111
106
|
try: ()=>collection.deleteOne({
|
|
112
107
|
id
|
|
113
108
|
}, inSession),
|
|
114
|
-
catch: (error)=>
|
|
115
|
-
id
|
|
116
|
-
})
|
|
109
|
+
catch: (error)=>translateMongoError(error)
|
|
117
110
|
});
|
|
118
111
|
});
|
|
119
112
|
// Cast through the interface to keep its generic signatures: each method here
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/make-mongo-repository.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { EntityRepository } from '@entifix/business';\nimport {\n deserializeEntityCollection,\n deserializeSingleEntity,\n
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/make-mongo-repository.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { EntityRepository } from '@entifix/business';\nimport {\n deserializeEntityCollection,\n deserializeSingleEntity,\n Entity,\n EntityConstructor,\n EntityId,\n EntityLoadRequest,\n EntityPage,\n extractMetaEntity,\n NotFound,\n serializeEntity,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport type { ClientSession, Db } from 'mongodb';\n\nimport { translateMongoError } from '../errors/translate-mongo-error.js';\nimport { translateFiltering, translateSorting } from './filter-translator.js';\n\n/** Projection that drops Mongo's internal `_id` from read results. */\nconst WITHOUT_MONGO_ID = { projection: { _id: 0 } } as const;\n\n/** The collection an entity's records live in: its `key`, else its class name. */\nexport function mongoCollectionName<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n): string {\n const metaEntity = extractMetaEntity(entityConstructor);\n return metaEntity.key ?? metaEntity.name;\n}\n\nfunction toEntityId(entityOrId: EntityId | Entity): EntityId {\n return entityOrId != null && typeof entityOrId === 'object'\n ? (entityOrId as Entity).id\n : (entityOrId as EntityId);\n}\n\n/**\n * Builds an {@link EntityRepository} for a single entity type, backed by a live\n * mongodb {@link Db}. The `db` and `entityConstructor` are captured in closures,\n * so every method's Effect has no outstanding requirements (`R = never`), which\n * is exactly what the interface asks.\n *\n * Collection name = the entity's `key` (falling back to its class name). Reads\n * reuse the shared entifix deserializer (rebuilding links from embedded objects\n * or scalar FKs); writes reuse the shared serializer. Mongo failures surface as\n * categorised failures — `NotFound`, `DuplicateKey`, `Unavailable` and the\n * rest — through `translateMongoError`.\n *\n * This is the Mongo mirror of the REST `buildEntityRestAdapter*` builders.\n *\n * Pass `session` to make the repository part of a transaction — a unit of\n * work's handle — so its writes commit with the unit and its reads see what the\n * unit has already written.\n */\nexport function makeMongoRepository<TEntity extends Entity>(\n db: Db,\n entityConstructor: EntityConstructor<TEntity>,\n options?: { readonly session?: ClientSession },\n): EntityRepository {\n const collection = db.collection(mongoCollectionName(entityConstructor));\n // Spread into every call, so a repository with no session passes none.\n const inSession = options?.session ? { session: options.session } : {};\n\n const load = <T extends Entity>(request: EntityLoadRequest<T>) =>\n Effect.gen(function* () {\n const filter = translateFiltering(request.filtering);\n const sort = translateSorting(request.sorting);\n const page = request.page ?? 1;\n const pageSize = request.pageSize ?? 10;\n const skip = (page - 1) * pageSize;\n\n const docs = yield* Effect.tryPromise({\n try: () =>\n collection\n .find(filter, { ...WITHOUT_MONGO_ID, ...inSession })\n .sort(sort)\n .skip(skip)\n .limit(pageSize)\n .toArray(),\n catch: error => translateMongoError(error),\n });\n const total = yield* Effect.tryPromise({\n try: () => collection.countDocuments(filter, inSession),\n catch: error => translateMongoError(error),\n });\n\n const items = yield* deserializeEntityCollection(entityConstructor, docs);\n\n return {\n items: items as unknown as T[],\n total,\n request,\n } satisfies EntityPage<T>;\n });\n\n const get = <T extends Entity>(id: EntityId) =>\n Effect.gen(function* () {\n const doc = yield* Effect.tryPromise({\n try: () =>\n collection.findOne({ id }, { ...WITHOUT_MONGO_ID, ...inSession }),\n catch: error => translateMongoError(error),\n });\n const entity = yield* deserializeSingleEntity(entityConstructor, doc);\n if (entity === undefined) {\n return yield* Effect.fail(\n new NotFound({ entity: extractMetaEntity(entityConstructor).key }),\n );\n }\n return entity as unknown as T;\n });\n\n const save = <T extends Entity>(entity: T) =>\n Effect.gen(function* () {\n const doc = serializeEntity(\n entityConstructor,\n entity as unknown as TEntity,\n );\n // A create arrives without an id, so the store mints one. Entities are\n // addressed by their own `id` field (never Mongo's `_id`, which reads\n // project away), so an inserted document without one would be\n // unreachable by `get` and by any later `replaceOne`.\n const id = entity.id ?? randomUUID();\n yield* Effect.tryPromise({\n // `doc` omits `id` when it was undefined (the serializer drops\n // undefined), hence spreading the resolved id back in.\n try: () =>\n collection.replaceOne(\n { id },\n { ...doc, id },\n { upsert: true, ...inSession },\n ),\n catch: error => translateMongoError(error),\n });\n entity.id = id;\n return entity;\n });\n\n const del = <T extends Entity>(entityOrId: EntityId | T) =>\n Effect.gen(function* () {\n const id = toEntityId(entityOrId);\n yield* Effect.tryPromise({\n try: () => collection.deleteOne({ id }, inSession),\n catch: error => translateMongoError(error),\n });\n });\n\n // Cast through the interface to keep its generic signatures: each method here\n // is written against this entity's type, the interface against any entity's.\n return {\n load,\n get,\n save,\n delete: del,\n } as unknown as EntityRepository;\n}\n"],"names":["randomUUID","deserializeEntityCollection","deserializeSingleEntity","extractMetaEntity","NotFound","serializeEntity","Effect","translateMongoError","translateFiltering","translateSorting","WITHOUT_MONGO_ID","projection","_id","mongoCollectionName","entityConstructor","metaEntity","key","name","toEntityId","entityOrId","id","makeMongoRepository","db","options","collection","inSession","session","load","request","gen","filter","filtering","sort","sorting","page","pageSize","skip","docs","tryPromise","try","find","limit","toArray","catch","error","total","countDocuments","items","get","doc","findOne","entity","undefined","fail","save","replaceOne","upsert","del","deleteOne","delete"],"mappings":";AAAA,SAASA,UAAU,QAAQ,cAAc;AAGzC,SACEC,2BAA2B,EAC3BC,uBAAuB,EAMvBC,iBAAiB,EACjBC,QAAQ,EACRC,eAAe,QACV,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAGhC,SAASC,mBAAmB,QAAQ,qCAAqC;AACzE,SAASC,kBAAkB,EAAEC,gBAAgB,QAAQ,yBAAyB;AAE9E,oEAAoE,GACpE,MAAMC,mBAAmB;IAAEC,YAAY;QAAEC,KAAK;IAAE;AAAE;AAElD,gFAAgF,GAChF,OAAO,SAASC,oBACdC,iBAA6C;QAGtCC;IADP,MAAMA,aAAaZ,kBAAkBW;IACrC,QAAOC,kBAAAA,WAAWC,GAAG,YAAdD,kBAAkBA,WAAWE,IAAI;AAC1C;AAEA,SAASC,WAAWC,UAA6B;IAC/C,OAAOA,cAAc,QAAQ,OAAOA,eAAe,WAC/C,AAACA,WAAsBC,EAAE,GACxBD;AACP;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASE,oBACdC,EAAM,EACNR,iBAA6C,EAC7CS,OAA8C;IAE9C,MAAMC,aAAaF,GAAGE,UAAU,CAACX,oBAAoBC;IACrD,uEAAuE;IACvE,MAAMW,YAAYF,CAAAA,2BAAAA,QAASG,OAAO,IAAG;QAAEA,SAASH,QAAQG,OAAO;IAAC,IAAI,CAAC;IAErE,MAAMC,OAAO,CAAmBC,UAC9BtB,OAAOuB,GAAG,CAAC;gBAGID,eACIA;YAHjB,MAAME,SAAStB,mBAAmBoB,QAAQG,SAAS;YACnD,MAAMC,OAAOvB,iBAAiBmB,QAAQK,OAAO;YAC7C,MAAMC,QAAON,gBAAAA,QAAQM,IAAI,YAAZN,gBAAgB;YAC7B,MAAMO,YAAWP,oBAAAA,QAAQO,QAAQ,YAAhBP,oBAAoB;YACrC,MAAMQ,OAAO,AAACF,CAAAA,OAAO,CAAA,IAAKC;YAE1B,MAAME,OAAO,OAAO/B,OAAOgC,UAAU,CAAC;gBACpCC,KAAK,IACHf,WACGgB,IAAI,CAACV,QAAQ,aAAKpB,kBAAqBe,YACvCO,IAAI,CAACA,MACLI,IAAI,CAACA,MACLK,KAAK,CAACN,UACNO,OAAO;gBACZC,OAAOC,CAAAA,QAASrC,oBAAoBqC;YACtC;YACA,MAAMC,QAAQ,OAAOvC,OAAOgC,UAAU,CAAC;gBACrCC,KAAK,IAAMf,WAAWsB,cAAc,CAAChB,QAAQL;gBAC7CkB,OAAOC,CAAAA,QAASrC,oBAAoBqC;YACtC;YAEA,MAAMG,QAAQ,OAAO9C,4BAA4Ba,mBAAmBuB;YAEpE,OAAO;gBACLU,OAAOA;gBACPF;gBACAjB;YACF;QACF;IAEF,MAAMoB,MAAM,CAAmB5B,KAC7Bd,OAAOuB,GAAG,CAAC;YACT,MAAMoB,MAAM,OAAO3C,OAAOgC,UAAU,CAAC;gBACnCC,KAAK,IACHf,WAAW0B,OAAO,CAAC;wBAAE9B;oBAAG,GAAG,aAAKV,kBAAqBe;gBACvDkB,OAAOC,CAAAA,QAASrC,oBAAoBqC;YACtC;YACA,MAAMO,SAAS,OAAOjD,wBAAwBY,mBAAmBmC;YACjE,IAAIE,WAAWC,WAAW;gBACxB,OAAO,OAAO9C,OAAO+C,IAAI,CACvB,IAAIjD,SAAS;oBAAE+C,QAAQhD,kBAAkBW,mBAAmBE,GAAG;gBAAC;YAEpE;YACA,OAAOmC;QACT;IAEF,MAAMG,OAAO,CAAmBH,SAC9B7C,OAAOuB,GAAG,CAAC;gBASEsB;YARX,MAAMF,MAAM5C,gBACVS,mBACAqC;YAEF,uEAAuE;YACvE,sEAAsE;YACtE,8DAA8D;YAC9D,sDAAsD;YACtD,MAAM/B,MAAK+B,aAAAA,OAAO/B,EAAE,YAAT+B,aAAanD;YACxB,OAAOM,OAAOgC,UAAU,CAAC;gBACvB,+DAA+D;gBAC/D,uDAAuD;gBACvDC,KAAK,IACHf,WAAW+B,UAAU,CACnB;wBAAEnC;oBAAG,GACL,aAAK6B;wBAAK7B;wBACV;wBAAEoC,QAAQ;uBAAS/B;gBAEvBkB,OAAOC,CAAAA,QAASrC,oBAAoBqC;YACtC;YACAO,OAAO/B,EAAE,GAAGA;YACZ,OAAO+B;QACT;IAEF,MAAMM,MAAM,CAAmBtC,aAC7Bb,OAAOuB,GAAG,CAAC;YACT,MAAMT,KAAKF,WAAWC;YACtB,OAAOb,OAAOgC,UAAU,CAAC;gBACvBC,KAAK,IAAMf,WAAWkC,SAAS,CAAC;wBAAEtC;oBAAG,GAAGK;gBACxCkB,OAAOC,CAAAA,QAASrC,oBAAoBqC;YACtC;QACF;IAEF,8EAA8E;IAC9E,6EAA6E;IAC7E,OAAO;QACLjB;QACAqB;QACAM;QACAK,QAAQF;IACV;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo-facet-counter-factory.d.ts","sourceRoot":"","sources":["../../src/adapters/mongo-facet-counter-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAE5B,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE;;;GAGG;AACH,eAAO,MAAM,6BAA6B,oEASzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,
|
|
1
|
+
{"version":3,"file":"mongo-facet-counter-factory.d.ts","sourceRoot":"","sources":["../../src/adapters/mongo-facet-counter-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAE5B,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE;;;GAGG;AACH,eAAO,MAAM,6BAA6B,oEASzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,6EAc/C,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityFacetCounterFactoryTag, TenantDatabaseResolverTag } from '@entifix/business';
|
|
2
|
-
import {
|
|
2
|
+
import { Internal } from '@entifix/core';
|
|
3
3
|
import { Effect, Layer } from 'effect';
|
|
4
4
|
import { MongoDatabaseTag } from '../mongo-database/mongo-database.js';
|
|
5
5
|
import { makeMongoFacetCounter } from './make-mongo-facet-counter.js';
|
|
@@ -22,9 +22,7 @@ import { makeMongoFacetCounter } from './make-mongo-facet-counter.js';
|
|
|
22
22
|
*/ export const MongoTenantFacetCounterFactoryLayer = Layer.effect(EntityFacetCounterFactoryTag, Effect.gen(function*() {
|
|
23
23
|
const resolver = yield* TenantDatabaseResolverTag;
|
|
24
24
|
return {
|
|
25
|
-
for: (entityConstructor, scope)=>scope.organizationId === undefined ? Effect.fail(new
|
|
26
|
-
entity: entityConstructor.name
|
|
27
|
-
})) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoFacetCounter(db, entityConstructor))
|
|
25
|
+
for: (entityConstructor, scope)=>scope.organizationId === undefined ? Effect.fail(new Internal()) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoFacetCounter(db, entityConstructor))
|
|
28
26
|
};
|
|
29
27
|
}));
|
|
30
28
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/mongo-facet-counter-factory.ts"],"sourcesContent":["import {\n EntityFacetCounterFactoryTag,\n type TenantDatabaseResolver,\n TenantDatabaseResolverTag,\n} from '@entifix/business';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/mongo-facet-counter-factory.ts"],"sourcesContent":["import {\n EntityFacetCounterFactoryTag,\n type TenantDatabaseResolver,\n TenantDatabaseResolverTag,\n} from '@entifix/business';\nimport { Internal } from '@entifix/core';\nimport { Effect, Layer } from 'effect';\nimport type { Db } from 'mongodb';\n\nimport { MongoDatabaseTag } from '../mongo-database/mongo-database.js';\nimport { makeMongoFacetCounter } from './make-mongo-facet-counter.js';\n\n/**\n * Facet counters over the service's one database, whoever the request is for —\n * `MongoRepositoryFactoryLayer`'s counterpart, bound beside it.\n */\nexport const MongoFacetCounterFactoryLayer = Layer.effect(\n EntityFacetCounterFactoryTag,\n Effect.gen(function* () {\n const db = yield* MongoDatabaseTag;\n return {\n for: entityConstructor =>\n Effect.succeed(makeMongoFacetCounter(db, entityConstructor)),\n };\n }),\n);\n\n/**\n * Facet counters over the requesting organization's own database, resolved\n * per request through the host's {@link TenantDatabaseResolverTag}.\n *\n * ⚠️ A request that names no organization is **refused**, never counted from a\n * default: on a per-organization store a default database is some other\n * organization's, and its counts would say how much stock a competitor holds.\n */\nexport const MongoTenantFacetCounterFactoryLayer = Layer.effect(\n EntityFacetCounterFactoryTag,\n Effect.gen(function* () {\n const resolver =\n (yield* TenantDatabaseResolverTag) as TenantDatabaseResolver<Db>;\n return {\n for: (entityConstructor, scope) =>\n scope.organizationId === undefined\n ? Effect.fail(new Internal())\n : Effect.map(resolver.forOrganization(scope.organizationId), db =>\n makeMongoFacetCounter(db, entityConstructor),\n ),\n };\n }),\n);\n"],"names":["EntityFacetCounterFactoryTag","TenantDatabaseResolverTag","Internal","Effect","Layer","MongoDatabaseTag","makeMongoFacetCounter","MongoFacetCounterFactoryLayer","effect","gen","db","for","entityConstructor","succeed","MongoTenantFacetCounterFactoryLayer","resolver","scope","organizationId","undefined","fail","map","forOrganization"],"mappings":"AAAA,SACEA,4BAA4B,EAE5BC,yBAAyB,QACpB,oBAAoB;AAC3B,SAASC,QAAQ,QAAQ,gBAAgB;AACzC,SAASC,MAAM,EAAEC,KAAK,QAAQ,SAAS;AAGvC,SAASC,gBAAgB,QAAQ,sCAAsC;AACvE,SAASC,qBAAqB,QAAQ,gCAAgC;AAEtE;;;CAGC,GACD,OAAO,MAAMC,gCAAgCH,MAAMI,MAAM,CACvDR,8BACAG,OAAOM,GAAG,CAAC;IACT,MAAMC,KAAK,OAAOL;IAClB,OAAO;QACLM,KAAKC,CAAAA,oBACHT,OAAOU,OAAO,CAACP,sBAAsBI,IAAIE;IAC7C;AACF,IACA;AAEF;;;;;;;CAOC,GACD,OAAO,MAAME,sCAAsCV,MAAMI,MAAM,CAC7DR,8BACAG,OAAOM,GAAG,CAAC;IACT,MAAMM,WACH,OAAOd;IACV,OAAO;QACLU,KAAK,CAACC,mBAAmBI,QACvBA,MAAMC,cAAc,KAAKC,YACrBf,OAAOgB,IAAI,CAAC,IAAIjB,cAChBC,OAAOiB,GAAG,CAACL,SAASM,eAAe,CAACL,MAAMC,cAAc,GAAGP,CAAAA,KACzDJ,sBAAsBI,IAAIE;IAEpC;AACF,IACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo-repository-factory.d.ts","sourceRoot":"","sources":["../../src/adapters/mongo-repository-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAE1B,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE;;GAEG;AACH,eAAO,MAAM,2BAA2B,kEASvC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,
|
|
1
|
+
{"version":3,"file":"mongo-repository-factory.d.ts","sourceRoot":"","sources":["../../src/adapters/mongo-repository-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAE1B,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE;;GAEG;AACH,eAAO,MAAM,2BAA2B,kEASvC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,2EAc7C,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityRepositoryFactoryTag, TenantDatabaseResolverTag } from '@entifix/business';
|
|
2
|
-
import {
|
|
2
|
+
import { Internal } from '@entifix/core';
|
|
3
3
|
import { Effect, Layer } from 'effect';
|
|
4
4
|
import { MongoDatabaseTag } from '../mongo-database/mongo-database.js';
|
|
5
5
|
import { makeMongoRepository } from './make-mongo-repository.js';
|
|
@@ -21,9 +21,7 @@ import { makeMongoRepository } from './make-mongo-repository.js';
|
|
|
21
21
|
*/ export const MongoTenantRepositoryFactoryLayer = Layer.effect(EntityRepositoryFactoryTag, Effect.gen(function*() {
|
|
22
22
|
const resolver = yield* TenantDatabaseResolverTag;
|
|
23
23
|
return {
|
|
24
|
-
for: (entityConstructor, scope)=>scope.organizationId === undefined ? Effect.fail(new
|
|
25
|
-
entity: entityConstructor.name
|
|
26
|
-
})) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoRepository(db, entityConstructor))
|
|
24
|
+
for: (entityConstructor, scope)=>scope.organizationId === undefined ? Effect.fail(new Internal()) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoRepository(db, entityConstructor))
|
|
27
25
|
};
|
|
28
26
|
}));
|
|
29
27
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/mongo-repository-factory.ts"],"sourcesContent":["import {\n EntityRepositoryFactoryTag,\n type TenantDatabaseResolver,\n TenantDatabaseResolverTag,\n} from '@entifix/business';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/mongo-repository-factory.ts"],"sourcesContent":["import {\n EntityRepositoryFactoryTag,\n type TenantDatabaseResolver,\n TenantDatabaseResolverTag,\n} from '@entifix/business';\nimport { Internal } from '@entifix/core';\nimport { Effect, Layer } from 'effect';\nimport type { Db } from 'mongodb';\n\nimport { MongoDatabaseTag } from '../mongo-database/mongo-database.js';\nimport { makeMongoRepository } from './make-mongo-repository.js';\n\n/**\n * Repositories over the service's one database, whoever the request is for.\n */\nexport const MongoRepositoryFactoryLayer = Layer.effect(\n EntityRepositoryFactoryTag,\n Effect.gen(function* () {\n const db = yield* MongoDatabaseTag;\n return {\n for: entityConstructor =>\n Effect.succeed(makeMongoRepository(db, entityConstructor)),\n };\n }),\n);\n\n/**\n * Repositories over the requesting organization's own database, resolved per\n * request through the host's {@link TenantDatabaseResolverTag}.\n *\n * ⚠️ A request that names no organization is **refused**, never served from a\n * default: on a per-organization store a default database is some other\n * organization's.\n */\nexport const MongoTenantRepositoryFactoryLayer = Layer.effect(\n EntityRepositoryFactoryTag,\n Effect.gen(function* () {\n const resolver =\n (yield* TenantDatabaseResolverTag) as TenantDatabaseResolver<Db>;\n return {\n for: (entityConstructor, scope) =>\n scope.organizationId === undefined\n ? Effect.fail(new Internal())\n : Effect.map(resolver.forOrganization(scope.organizationId), db =>\n makeMongoRepository(db, entityConstructor),\n ),\n };\n }),\n);\n"],"names":["EntityRepositoryFactoryTag","TenantDatabaseResolverTag","Internal","Effect","Layer","MongoDatabaseTag","makeMongoRepository","MongoRepositoryFactoryLayer","effect","gen","db","for","entityConstructor","succeed","MongoTenantRepositoryFactoryLayer","resolver","scope","organizationId","undefined","fail","map","forOrganization"],"mappings":"AAAA,SACEA,0BAA0B,EAE1BC,yBAAyB,QACpB,oBAAoB;AAC3B,SAASC,QAAQ,QAAQ,gBAAgB;AACzC,SAASC,MAAM,EAAEC,KAAK,QAAQ,SAAS;AAGvC,SAASC,gBAAgB,QAAQ,sCAAsC;AACvE,SAASC,mBAAmB,QAAQ,6BAA6B;AAEjE;;CAEC,GACD,OAAO,MAAMC,8BAA8BH,MAAMI,MAAM,CACrDR,4BACAG,OAAOM,GAAG,CAAC;IACT,MAAMC,KAAK,OAAOL;IAClB,OAAO;QACLM,KAAKC,CAAAA,oBACHT,OAAOU,OAAO,CAACP,oBAAoBI,IAAIE;IAC3C;AACF,IACA;AAEF;;;;;;;CAOC,GACD,OAAO,MAAME,oCAAoCV,MAAMI,MAAM,CAC3DR,4BACAG,OAAOM,GAAG,CAAC;IACT,MAAMM,WACH,OAAOd;IACV,OAAO;QACLU,KAAK,CAACC,mBAAmBI,QACvBA,MAAMC,cAAc,KAAKC,YACrBf,OAAOgB,IAAI,CAAC,IAAIjB,cAChBC,OAAOiB,GAAG,CAACL,SAASM,eAAe,CAACL,MAAMC,cAAc,GAAGP,CAAAA,KACzDJ,oBAAoBI,IAAIE;IAElC;AACF,IACA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type StoreFailure, type StoreFault } from '@entifix/core';
|
|
2
|
+
/**
|
|
3
|
+
* What a Mongo driver error means, as a categorised failure
|
|
4
|
+
* ([ADR 0013](../../../../../docs/adr/0013-failures-are-typed-values-decided-at-the-shell.md)).
|
|
5
|
+
*
|
|
6
|
+
* Read by **name, code and label**, never by class: the driver's error classes
|
|
7
|
+
* are not what a consumer can match on, and a second copy of the driver in one
|
|
8
|
+
* process would answer `instanceof` with `false`.
|
|
9
|
+
*/
|
|
10
|
+
export interface MongoErrorContext {
|
|
11
|
+
/** What the failure names as its dependency; `mongo` when absent. */
|
|
12
|
+
readonly dependency?: string;
|
|
13
|
+
/**
|
|
14
|
+
* What a duplicate key means here. On a declared uniqueness it is a
|
|
15
|
+
* `conflict` — the caller asked for something that already exists. On a
|
|
16
|
+
* claim or a race (an inbox, a command claim, a saga claim) it is
|
|
17
|
+
* `contention` — someone else got there first, and retrying wins or replays.
|
|
18
|
+
*/
|
|
19
|
+
readonly duplicate?: 'conflict' | 'contention';
|
|
20
|
+
}
|
|
21
|
+
/** Whether a server label is on `error` or in its cause chain. */
|
|
22
|
+
export declare const mongoErrorLabelled: (error: unknown, label: string) => boolean;
|
|
23
|
+
export declare const translateMongoError: (error: unknown, context?: MongoErrorContext) => StoreFailure;
|
|
24
|
+
/**
|
|
25
|
+
* A driver error as the store's own fault, for the bookkeeping a mechanism
|
|
26
|
+
* does around a caller's work — a claim, a commit, a remembered answer. A
|
|
27
|
+
* duplicate key there means a race lost, never an answer for the caller.
|
|
28
|
+
*/
|
|
29
|
+
export declare const translateMongoFault: (error: unknown, context?: Omit<MongoErrorContext, "duplicate">) => StoreFault;
|
|
30
|
+
//# sourceMappingURL=translate-mongo-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate-mongo-error.d.ts","sourceRoot":"","sources":["../../src/errors/translate-mongo-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,UAAU,EAGhB,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;CAChD;AAqBD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,EAAE,OAAO,MAAM,KAAG,OAOlE,CAAC;AAwBF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,OAAO,EACd,UAAS,iBAAsB,KAC9B,YA+BF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,OAAO,EACd,UAAS,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAM,KACjD,UAMF,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { Contention, DuplicateKey, isFailure, isStoreFault, ProtocolFailure, Timeout, Unavailable } from '@entifix/core';
|
|
3
|
+
const shape = (error)=>typeof error === 'object' && error !== null ? error : {};
|
|
4
|
+
const labelled = (error, label)=>{
|
|
5
|
+
var _ref, _ref1;
|
|
6
|
+
var _error_errorLabels;
|
|
7
|
+
return (_ref = (_ref1 = error.hasErrorLabel == null ? void 0 : error.hasErrorLabel.call(error, label)) != null ? _ref1 : (_error_errorLabels = error.errorLabels) == null ? void 0 : _error_errorLabels.includes(label)) != null ? _ref : false;
|
|
8
|
+
};
|
|
9
|
+
/** Whether a server label is on `error` or in its cause chain. */ export const mongoErrorLabelled = (error, label)=>{
|
|
10
|
+
let current = error;
|
|
11
|
+
for(let depth = 0; current != null && depth < 5; depth += 1){
|
|
12
|
+
if (labelled(shape(current), label)) return true;
|
|
13
|
+
current = shape(current).cause;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
const DUPLICATE_KEY = 11000;
|
|
18
|
+
const WRITE_CONFLICT = 112;
|
|
19
|
+
const MAX_TIME_EXPIRED = 50;
|
|
20
|
+
const UNREACHABLE = new Set([
|
|
21
|
+
'MongoNetworkError',
|
|
22
|
+
'MongoServerSelectionError',
|
|
23
|
+
'MongoNotConnectedError',
|
|
24
|
+
'MongoTopologyClosedError',
|
|
25
|
+
'MongoServerClosedError',
|
|
26
|
+
'MongoPoolClosedError'
|
|
27
|
+
]);
|
|
28
|
+
const isDuplicate = (error)=>{
|
|
29
|
+
var _ref;
|
|
30
|
+
var _error_writeErrors;
|
|
31
|
+
return error.code === DUPLICATE_KEY || ((_ref = (_error_writeErrors = error.writeErrors) == null ? void 0 : _error_writeErrors.some((write)=>write.code === DUPLICATE_KEY)) != null ? _ref : false);
|
|
32
|
+
};
|
|
33
|
+
const duplicatedKey = (error)=>typeof error.keyPattern === 'object' && error.keyPattern !== null ? Object.keys(error.keyPattern).join(',') : undefined;
|
|
34
|
+
export const translateMongoError = (error, context = {})=>{
|
|
35
|
+
var _context_dependency;
|
|
36
|
+
// Already translated, by an inner call: pass it through as it is.
|
|
37
|
+
if (isFailure(error)) return error;
|
|
38
|
+
const dependency = (_context_dependency = context.dependency) != null ? _context_dependency : 'mongo';
|
|
39
|
+
const driver = shape(error);
|
|
40
|
+
if (isDuplicate(driver)) {
|
|
41
|
+
if (context.duplicate === 'contention') return new Contention();
|
|
42
|
+
const key = duplicatedKey(driver);
|
|
43
|
+
return new DuplicateKey(key === undefined ? {} : {
|
|
44
|
+
key
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (driver.code === WRITE_CONFLICT || mongoErrorLabelled(error, 'TransientTransactionError')) return new Contention();
|
|
48
|
+
if (driver.code === MAX_TIME_EXPIRED || driver.name === 'MongoNetworkTimeoutError' || driver.name === 'MongoOperationTimeoutError') return new Timeout({
|
|
49
|
+
dependency
|
|
50
|
+
});
|
|
51
|
+
if (driver.name === 'BSONError') return new ProtocolFailure({
|
|
52
|
+
reason: `${dependency} answered a document that does not decode`
|
|
53
|
+
});
|
|
54
|
+
if (typeof driver.name === 'string' && UNREACHABLE.has(driver.name)) return new Unavailable({
|
|
55
|
+
dependency
|
|
56
|
+
});
|
|
57
|
+
// Anything else the driver raised is still the store failing to answer,
|
|
58
|
+
// which is what the class tree called every one of these.
|
|
59
|
+
return new Unavailable({
|
|
60
|
+
dependency
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A driver error as the store's own fault, for the bookkeeping a mechanism
|
|
65
|
+
* does around a caller's work — a claim, a commit, a remembered answer. A
|
|
66
|
+
* duplicate key there means a race lost, never an answer for the caller.
|
|
67
|
+
*/ export const translateMongoFault = (error, context = {})=>{
|
|
68
|
+
const failure = translateMongoError(error, _extends({}, context, {
|
|
69
|
+
duplicate: 'contention'
|
|
70
|
+
}));
|
|
71
|
+
return isStoreFault(failure) ? failure : new Contention();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=translate-mongo-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/translate-mongo-error.ts"],"sourcesContent":["import {\n Contention,\n DuplicateKey,\n isFailure,\n isStoreFault,\n ProtocolFailure,\n type StoreFailure,\n type StoreFault,\n Timeout,\n Unavailable,\n} from '@entifix/core';\n\n/**\n * What a Mongo driver error means, as a categorised failure\n * ([ADR 0013](../../../../../docs/adr/0013-failures-are-typed-values-decided-at-the-shell.md)).\n *\n * Read by **name, code and label**, never by class: the driver's error classes\n * are not what a consumer can match on, and a second copy of the driver in one\n * process would answer `instanceof` with `false`.\n */\nexport interface MongoErrorContext {\n /** What the failure names as its dependency; `mongo` when absent. */\n readonly dependency?: string;\n /**\n * What a duplicate key means here. On a declared uniqueness it is a\n * `conflict` — the caller asked for something that already exists. On a\n * claim or a race (an inbox, a command claim, a saga claim) it is\n * `contention` — someone else got there first, and retrying wins or replays.\n */\n readonly duplicate?: 'conflict' | 'contention';\n}\n\ninterface DriverErrorShape {\n readonly name?: unknown;\n readonly code?: unknown;\n readonly codeName?: unknown;\n readonly keyPattern?: unknown;\n readonly writeErrors?: ReadonlyArray<{ readonly code?: unknown }>;\n readonly errorLabels?: readonly string[];\n readonly hasErrorLabel?: (label: string) => boolean;\n readonly cause?: unknown;\n}\n\nconst shape = (error: unknown): DriverErrorShape =>\n typeof error === 'object' && error !== null\n ? (error as DriverErrorShape)\n : {};\n\nconst labelled = (error: DriverErrorShape, label: string): boolean =>\n error.hasErrorLabel?.(label) ?? error.errorLabels?.includes(label) ?? false;\n\n/** Whether a server label is on `error` or in its cause chain. */\nexport const mongoErrorLabelled = (error: unknown, label: string): boolean => {\n let current: unknown = error;\n for (let depth = 0; current != null && depth < 5; depth += 1) {\n if (labelled(shape(current), label)) return true;\n current = shape(current).cause;\n }\n return false;\n};\n\nconst DUPLICATE_KEY = 11000;\nconst WRITE_CONFLICT = 112;\nconst MAX_TIME_EXPIRED = 50;\n\nconst UNREACHABLE = new Set([\n 'MongoNetworkError',\n 'MongoServerSelectionError',\n 'MongoNotConnectedError',\n 'MongoTopologyClosedError',\n 'MongoServerClosedError',\n 'MongoPoolClosedError',\n]);\n\nconst isDuplicate = (error: DriverErrorShape): boolean =>\n error.code === DUPLICATE_KEY ||\n (error.writeErrors?.some(write => write.code === DUPLICATE_KEY) ?? false);\n\nconst duplicatedKey = (error: DriverErrorShape): string | undefined =>\n typeof error.keyPattern === 'object' && error.keyPattern !== null\n ? Object.keys(error.keyPattern).join(',')\n : undefined;\n\nexport const translateMongoError = (\n error: unknown,\n context: MongoErrorContext = {},\n): StoreFailure => {\n // Already translated, by an inner call: pass it through as it is.\n if (isFailure(error)) return error as StoreFailure;\n const dependency = context.dependency ?? 'mongo';\n const driver = shape(error);\n\n if (isDuplicate(driver)) {\n if (context.duplicate === 'contention') return new Contention();\n const key = duplicatedKey(driver);\n return new DuplicateKey(key === undefined ? {} : { key });\n }\n if (\n driver.code === WRITE_CONFLICT ||\n mongoErrorLabelled(error, 'TransientTransactionError')\n )\n return new Contention();\n if (\n driver.code === MAX_TIME_EXPIRED ||\n driver.name === 'MongoNetworkTimeoutError' ||\n driver.name === 'MongoOperationTimeoutError'\n )\n return new Timeout({ dependency });\n if (driver.name === 'BSONError')\n return new ProtocolFailure({\n reason: `${dependency} answered a document that does not decode`,\n });\n if (typeof driver.name === 'string' && UNREACHABLE.has(driver.name))\n return new Unavailable({ dependency });\n // Anything else the driver raised is still the store failing to answer,\n // which is what the class tree called every one of these.\n return new Unavailable({ dependency });\n};\n\n/**\n * A driver error as the store's own fault, for the bookkeeping a mechanism\n * does around a caller's work — a claim, a commit, a remembered answer. A\n * duplicate key there means a race lost, never an answer for the caller.\n */\nexport const translateMongoFault = (\n error: unknown,\n context: Omit<MongoErrorContext, 'duplicate'> = {},\n): StoreFault => {\n const failure = translateMongoError(error, {\n ...context,\n duplicate: 'contention',\n });\n return isStoreFault(failure) ? failure : new Contention();\n};\n"],"names":["Contention","DuplicateKey","isFailure","isStoreFault","ProtocolFailure","Timeout","Unavailable","shape","error","labelled","label","hasErrorLabel","errorLabels","includes","mongoErrorLabelled","current","depth","cause","DUPLICATE_KEY","WRITE_CONFLICT","MAX_TIME_EXPIRED","UNREACHABLE","Set","isDuplicate","code","writeErrors","some","write","duplicatedKey","keyPattern","Object","keys","join","undefined","translateMongoError","context","dependency","driver","duplicate","key","name","reason","has","translateMongoFault","failure"],"mappings":";AAAA,SACEA,UAAU,EACVC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,eAAe,EAGfC,OAAO,EACPC,WAAW,QACN,gBAAgB;AAiCvB,MAAMC,QAAQ,CAACC,QACb,OAAOA,UAAU,YAAYA,UAAU,OAClCA,QACD,CAAC;AAEP,MAAMC,WAAW,CAACD,OAAyBE;QACzCF;QAAgCA;YAAhCA,gBAAAA,MAAMG,aAAa,oBAAnBH,MAAMG,aAAa,MAAnBH,OAAsBE,2BAAUF,qBAAAA,MAAMI,WAAW,qBAAjBJ,mBAAmBK,QAAQ,CAACH,kBAA5DF,OAAsE;;AAExE,gEAAgE,GAChE,OAAO,MAAMM,qBAAqB,CAACN,OAAgBE;IACjD,IAAIK,UAAmBP;IACvB,IAAK,IAAIQ,QAAQ,GAAGD,WAAW,QAAQC,QAAQ,GAAGA,SAAS,EAAG;QAC5D,IAAIP,SAASF,MAAMQ,UAAUL,QAAQ,OAAO;QAC5CK,UAAUR,MAAMQ,SAASE,KAAK;IAChC;IACA,OAAO;AACT,EAAE;AAEF,MAAMC,gBAAgB;AACtB,MAAMC,iBAAiB;AACvB,MAAMC,mBAAmB;AAEzB,MAAMC,cAAc,IAAIC,IAAI;IAC1B;IACA;IACA;IACA;IACA;IACA;CACD;AAED,MAAMC,cAAc,CAACf;;QAElBA;WADDA,MAAMgB,IAAI,KAAKN,2BACdV,qBAAAA,MAAMiB,WAAW,qBAAjBjB,mBAAmBkB,IAAI,CAACC,CAAAA,QAASA,MAAMH,IAAI,KAAKN,iCAAkB;;AAErE,MAAMU,gBAAgB,CAACpB,QACrB,OAAOA,MAAMqB,UAAU,KAAK,YAAYrB,MAAMqB,UAAU,KAAK,OACzDC,OAAOC,IAAI,CAACvB,MAAMqB,UAAU,EAAEG,IAAI,CAAC,OACnCC;AAEN,OAAO,MAAMC,sBAAsB,CACjC1B,OACA2B,UAA6B,CAAC,CAAC;QAIZA;IAFnB,kEAAkE;IAClE,IAAIjC,UAAUM,QAAQ,OAAOA;IAC7B,MAAM4B,cAAaD,sBAAAA,QAAQC,UAAU,YAAlBD,sBAAsB;IACzC,MAAME,SAAS9B,MAAMC;IAErB,IAAIe,YAAYc,SAAS;QACvB,IAAIF,QAAQG,SAAS,KAAK,cAAc,OAAO,IAAItC;QACnD,MAAMuC,MAAMX,cAAcS;QAC1B,OAAO,IAAIpC,aAAasC,QAAQN,YAAY,CAAC,IAAI;YAAEM;QAAI;IACzD;IACA,IACEF,OAAOb,IAAI,KAAKL,kBAChBL,mBAAmBN,OAAO,8BAE1B,OAAO,IAAIR;IACb,IACEqC,OAAOb,IAAI,KAAKJ,oBAChBiB,OAAOG,IAAI,KAAK,8BAChBH,OAAOG,IAAI,KAAK,8BAEhB,OAAO,IAAInC,QAAQ;QAAE+B;IAAW;IAClC,IAAIC,OAAOG,IAAI,KAAK,aAClB,OAAO,IAAIpC,gBAAgB;QACzBqC,QAAQ,GAAGL,WAAW,yCAAyC,CAAC;IAClE;IACF,IAAI,OAAOC,OAAOG,IAAI,KAAK,YAAYnB,YAAYqB,GAAG,CAACL,OAAOG,IAAI,GAChE,OAAO,IAAIlC,YAAY;QAAE8B;IAAW;IACtC,wEAAwE;IACxE,0DAA0D;IAC1D,OAAO,IAAI9B,YAAY;QAAE8B;IAAW;AACtC,EAAE;AAEF;;;;CAIC,GACD,OAAO,MAAMO,sBAAsB,CACjCnC,OACA2B,UAAgD,CAAC,CAAC;IAElD,MAAMS,UAAUV,oBAAoB1B,OAAO,aACtC2B;QACHG,WAAW;;IAEb,OAAOnC,aAAayC,WAAWA,UAAU,IAAI5C;AAC/C,EAAE"}
|
package/dist/inbox/store.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EntifixConnError } from '@entifix/core';
|
|
2
1
|
import type { InboxClaim, TransactionInbox } from '@entifix/transactions';
|
|
3
2
|
import { Effect } from 'effect';
|
|
4
3
|
import type { Db } from 'mongodb';
|
|
@@ -37,7 +36,7 @@ export declare const inboxDocument: (consumer: string, eventId: string) => Inbox
|
|
|
37
36
|
* the index must exist before the first claim, not eventually, or two
|
|
38
37
|
* concurrent first deliveries both insert.
|
|
39
38
|
*/
|
|
40
|
-
export declare const ensureInboxIndexes: (db: Db) => Effect.Effect<void,
|
|
39
|
+
export declare const ensureInboxIndexes: (db: Db) => Effect.Effect<void, import("@entifix/core").StoreFailure, never>;
|
|
41
40
|
/**
|
|
42
41
|
* A {@link TransactionInbox} over one database, for one consumer.
|
|
43
42
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/inbox/store.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/inbox/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAIlC;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAKpD,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,OAGM,CAAC;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,EAChB,SAAS,MAAM,KACd,UAID,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,EAAE,qEAQrC,CAAC;AAEL;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,EAAE,EAAE,UAAU,MAAM,KAAG,gBAuBzD,CAAC"}
|
package/dist/inbox/store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EntifixConnError } from '@entifix/core';
|
|
2
1
|
import { Effect } from 'effect';
|
|
2
|
+
import { translateMongoError } from '../errors/translate-mongo-error.js';
|
|
3
3
|
/**
|
|
4
4
|
* The inbox collection, and it lives in **whichever database the side effect it
|
|
5
5
|
* guards writes to** — not in one central place.
|
|
@@ -46,9 +46,7 @@ export const isDuplicateKey = (error)=>typeof error === 'object' && error !== nu
|
|
|
46
46
|
unique: true
|
|
47
47
|
});
|
|
48
48
|
},
|
|
49
|
-
catch: (error)=>
|
|
50
|
-
database: db.databaseName
|
|
51
|
-
})
|
|
49
|
+
catch: (error)=>translateMongoError(error)
|
|
52
50
|
});
|
|
53
51
|
/**
|
|
54
52
|
* A {@link TransactionInbox} over one database, for one consumer.
|
|
@@ -75,10 +73,7 @@ export const isDuplicateKey = (error)=>typeof error === 'object' && error !== nu
|
|
|
75
73
|
throw error;
|
|
76
74
|
}
|
|
77
75
|
},
|
|
78
|
-
catch: (error)=>
|
|
79
|
-
consumer,
|
|
80
|
-
eventId
|
|
81
|
-
})
|
|
76
|
+
catch: (error)=>translateMongoError(error)
|
|
82
77
|
})
|
|
83
78
|
};
|
|
84
79
|
};
|
package/dist/inbox/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inbox/store.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/inbox/store.ts"],"sourcesContent":["import type { InboxClaim, TransactionInbox } from '@entifix/transactions';\nimport { Effect } from 'effect';\nimport type { Db } from 'mongodb';\n\nimport { translateMongoError } from '../errors/translate-mongo-error.js';\n\n/**\n * The inbox collection, and it lives in **whichever database the side effect it\n * guards writes to** — not in one central place.\n *\n * That is the whole mechanism, not a deployment preference: a claim is only\n * worth anything if it commits in the same storage transaction as the effect,\n * and two databases cannot be one transaction. So the saga tracker's inbox is in\n * the `saga` database because its fold writes there, exactly as the outbox is\n * per-tenant because the entity is.\n */\nexport const INBOX_COLLECTION = 'transaction_inbox';\n\n/** Mongo's duplicate-key error, which is how a redelivery is recognised. */\nconst DUPLICATE_KEY = 11000;\n\nexport const isDuplicateKey = (error: unknown): boolean =>\n typeof error === 'object' &&\n error !== null &&\n (error as { code?: unknown }).code === DUPLICATE_KEY;\n\n/**\n * The stored shape of a claim.\n *\n * Exported for the same reason `outboxDocument` is: the claim that matters is\n * **not** written through the port. It has to join the side effect's session to\n * be atomic with it, and a session cannot live in a framework-free contract, so\n * the consumer inserts this document itself. {@link makeMongoInbox} is for a\n * consumer that can claim standalone.\n */\nexport const inboxDocument = (\n consumer: string,\n eventId: string,\n): InboxClaim => ({\n consumer,\n eventId,\n claimedAt: new Date().toISOString(),\n});\n\n/**\n * The unique index the claim rides, on **`(consumer, eventId)`**.\n *\n * Compound, and that is the half easy to get wrong. Two consumers legitimately\n * process the same event — the tracker's fold and the SSE hub both bind\n * `transaction.*` — so a unique index on `eventId` alone would let whichever\n * claimed first starve every other consumer of every message, while the broker,\n * the queue and the handler all went on reporting success.\n *\n * Called per handle rather than once at boot, matching `ensureOutboxIndexes`:\n * the index must exist before the first claim, not eventually, or two\n * concurrent first deliveries both insert.\n */\nexport const ensureInboxIndexes = (db: Db) =>\n Effect.tryPromise({\n try: async () => {\n await db\n .collection<InboxClaim>(INBOX_COLLECTION)\n .createIndex({ consumer: 1, eventId: 1 }, { unique: true });\n },\n catch: error => translateMongoError(error),\n });\n\n/**\n * A {@link TransactionInbox} over one database, for one consumer.\n *\n * `consumer` is the subscription's durable work-queue name\n * (`<slice>.<pattern>`) — deterministic, and stable across restarts, which an\n * anonymous broadcast queue's server-generated name is not. That is also why\n * `dedupe: 'inbox'` is only legal on a `work` subscription.\n */\nexport const makeMongoInbox = (db: Db, consumer: string): TransactionInbox => {\n const collection = db.collection<InboxClaim>(INBOX_COLLECTION);\n\n return {\n claim: eventId =>\n Effect.tryPromise({\n try: async () => {\n try {\n await collection.insertOne(inboxDocument(consumer, eventId));\n return 'claimed' as const;\n } catch (error) {\n // Not a failure: the unique index rejected a second claim for this\n // consumer and message, which is precisely how a redelivery is\n // identified.\n if (isDuplicateKey(error)) {\n return 'duplicate' as const;\n }\n throw error;\n }\n },\n catch: error => translateMongoError(error),\n }),\n };\n};\n"],"names":["Effect","translateMongoError","INBOX_COLLECTION","DUPLICATE_KEY","isDuplicateKey","error","code","inboxDocument","consumer","eventId","claimedAt","Date","toISOString","ensureInboxIndexes","db","tryPromise","try","collection","createIndex","unique","catch","makeMongoInbox","claim","insertOne"],"mappings":"AACA,SAASA,MAAM,QAAQ,SAAS;AAGhC,SAASC,mBAAmB,QAAQ,qCAAqC;AAEzE;;;;;;;;;CASC,GACD,OAAO,MAAMC,mBAAmB,oBAAoB;AAEpD,0EAA0E,GAC1E,MAAMC,gBAAgB;AAEtB,OAAO,MAAMC,iBAAiB,CAACC,QAC7B,OAAOA,UAAU,YACjBA,UAAU,QACV,AAACA,MAA6BC,IAAI,KAAKH,cAAc;AAEvD;;;;;;;;CAQC,GACD,OAAO,MAAMI,gBAAgB,CAC3BC,UACAC,UACgB,CAAA;QAChBD;QACAC;QACAC,WAAW,IAAIC,OAAOC,WAAW;IACnC,CAAA,EAAG;AAEH;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,qBAAqB,CAACC,KACjCd,OAAOe,UAAU,CAAC;QAChBC,KAAK;YACH,MAAMF,GACHG,UAAU,CAAaf,kBACvBgB,WAAW,CAAC;gBAAEV,UAAU;gBAAGC,SAAS;YAAE,GAAG;gBAAEU,QAAQ;YAAK;QAC7D;QACAC,OAAOf,CAAAA,QAASJ,oBAAoBI;IACtC,GAAG;AAEL;;;;;;;CAOC,GACD,OAAO,MAAMgB,iBAAiB,CAACP,IAAQN;IACrC,MAAMS,aAAaH,GAAGG,UAAU,CAAaf;IAE7C,OAAO;QACLoB,OAAOb,CAAAA,UACLT,OAAOe,UAAU,CAAC;gBAChBC,KAAK;oBACH,IAAI;wBACF,MAAMC,WAAWM,SAAS,CAAChB,cAAcC,UAAUC;wBACnD,OAAO;oBACT,EAAE,OAAOJ,OAAO;wBACd,mEAAmE;wBACnE,+DAA+D;wBAC/D,cAAc;wBACd,IAAID,eAAeC,QAAQ;4BACzB,OAAO;wBACT;wBACA,MAAMA;oBACR;gBACF;gBACAe,OAAOf,CAAAA,QAASJ,oBAAoBI;YACtC;IACJ;AACF,EAAE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { EntifixFailure } from '@entifix/core';
|
|
2
2
|
import { Context, Layer } from 'effect';
|
|
3
3
|
import { Db, MongoClient } from 'mongodb';
|
|
4
4
|
declare const MongoDatabaseTag_base: Context.ServiceClass<MongoDatabaseTag, "MongoDatabaseTag", Db>;
|
|
@@ -38,7 +38,7 @@ export interface MongoDatabaseSettings extends MongoClientSettings {
|
|
|
38
38
|
* {@link MongoDatabaseLayer} builds on this rather than connecting again: two
|
|
39
39
|
* layers over the same URI would mean two pools.
|
|
40
40
|
*/
|
|
41
|
-
export declare const MongoClientLayer: (settings: MongoClientSettings) => Layer.Layer<MongoClientTag,
|
|
41
|
+
export declare const MongoClientLayer: (settings: MongoClientSettings) => Layer.Layer<MongoClientTag, EntifixFailure>;
|
|
42
42
|
/**
|
|
43
43
|
* {@link MongoClientLayer} plus the named {@link Db} resolved from it, exposed
|
|
44
44
|
* under {@link MongoDatabaseTag}. One acquire, one pool, both tags — which is
|
|
@@ -47,6 +47,6 @@ export declare const MongoClientLayer: (settings: MongoClientSettings) => Layer.
|
|
|
47
47
|
* Use it when the service owns exactly one named store (`auth-service`). A
|
|
48
48
|
* service whose handles are all per-request wants {@link MongoClientLayer}.
|
|
49
49
|
*/
|
|
50
|
-
export declare const MongoDatabaseLayer: (settings: MongoDatabaseSettings) => Layer.Layer<MongoDatabaseTag | MongoClientTag,
|
|
50
|
+
export declare const MongoDatabaseLayer: (settings: MongoDatabaseSettings) => Layer.Layer<MongoDatabaseTag | MongoClientTag, EntifixFailure>;
|
|
51
51
|
export {};
|
|
52
52
|
//# sourceMappingURL=mongo-database.d.ts.map
|