@entifix/mongo 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/adapters/filter-translator.d.ts +1 -1
- package/dist/adapters/filter-translator.d.ts.map +1 -1
- package/dist/adapters/filter-translator.js +6 -5
- package/dist/adapters/filter-translator.js.map +1 -1
- package/dist/adapters/make-mongo-facet-counter.d.ts +18 -0
- package/dist/adapters/make-mongo-facet-counter.d.ts.map +1 -0
- package/dist/adapters/make-mongo-facet-counter.js +77 -0
- package/dist/adapters/make-mongo-facet-counter.js.map +1 -0
- package/dist/adapters/make-mongo-repository.d.ts +2 -0
- package/dist/adapters/make-mongo-repository.d.ts.map +1 -1
- package/dist/adapters/make-mongo-repository.js +2 -2
- package/dist/adapters/make-mongo-repository.js.map +1 -1
- package/dist/adapters/mongo-facet-counter-factory.d.ts +18 -0
- package/dist/adapters/mongo-facet-counter-factory.d.ts.map +1 -0
- package/dist/adapters/mongo-facet-counter-factory.js +31 -0
- package/dist/adapters/mongo-facet-counter-factory.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@ pnpm add @entifix/mongo effect
|
|
|
12
12
|
|
|
13
13
|
## Entry points
|
|
14
14
|
|
|
15
|
-
| Import | What it holds
|
|
16
|
-
| ----------------------------- |
|
|
17
|
-
| `@entifix/mongo` | Repository (optionally inside a session), repository factories (`MongoRepositoryFactoryLayer`, `MongoTenantRepositoryFactoryLayer`), link resolver, query translation, client and database Layers, tenant resolver and tenant directory, health probe
|
|
18
|
-
| `@entifix/mongo/transactions` | The unit of work (`makeMongoUnitOfWork`, `MongoUnitOfWorkLayer`) and its command inbox, the transactional outbox and inbox, the relays that drain the outbox (one database, or every organization's) and the inline `drainOutboxNow`, and the saga store (`makeMongoSagaStore`, `MongoSagaStoreLayer`, `mongoSagaStoreLayer({ database })`)
|
|
15
|
+
| Import | What it holds |
|
|
16
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `@entifix/mongo` | Repository (optionally inside a session), repository factories (`MongoRepositoryFactoryLayer`, `MongoTenantRepositoryFactoryLayer`), facet counter (`makeMongoFacetCounter`, one `$facet` aggregation) and its factories (`MongoFacetCounterFactoryLayer`, `MongoTenantFacetCounterFactoryLayer`), link resolver, query translation, client and database Layers, tenant resolver and tenant directory, health probe |
|
|
18
|
+
| `@entifix/mongo/transactions` | The unit of work (`makeMongoUnitOfWork`, `MongoUnitOfWorkLayer`) and its command inbox, the transactional outbox and inbox, the relays that drain the outbox (one database, or every organization's) and the inline `drainOutboxNow`, and the saga store (`makeMongoSagaStore`, `MongoSagaStoreLayer`, `mongoSagaStoreLayer({ database })`) |
|
|
19
19
|
|
|
20
20
|
## Optional peers
|
|
21
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Entity, type EntityFiltering, type EntitySorting } from '@entifix/core';
|
|
2
2
|
/** A Mongo query document (`{ field: condition, $and: [...], ... }`). */
|
|
3
3
|
export type MongoFilter = Record<string, unknown>;
|
|
4
4
|
/** A Mongo sort document (`{ field: 1 | -1 }`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-translator.d.ts","sourceRoot":"","sources":["../../src/adapters/filter-translator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"filter-translator.d.ts","sourceRoot":"","sources":["../../src/adapters/filter-translator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAEX,KAAK,eAAe,EACpB,KAAK,aAAa,EAGnB,MAAM,eAAe,CAAC;AAEvB,yEAAyE;AACzE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClD,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAsE/C;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,MAAM,EACvD,SAAS,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,GACrC,WAAW,CAiBb;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,GACjC,SAAS,CAgBX"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { foldedPattern } from '@entifix/core';
|
|
1
2
|
const BINARY_OPERATORS = {
|
|
2
3
|
eq: '$eq',
|
|
3
4
|
ne: '$ne',
|
|
@@ -6,9 +7,6 @@ const BINARY_OPERATORS = {
|
|
|
6
7
|
lt: '$lt',
|
|
7
8
|
lte: '$lte'
|
|
8
9
|
};
|
|
9
|
-
/** Escapes a string so it is matched literally inside a Mongo `$regex`. */ function escapeRegex(value) {
|
|
10
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
|
-
}
|
|
12
10
|
function isFilterGroup(node) {
|
|
13
11
|
// A group carries `values` + a logic operator and, unlike the array filter,
|
|
14
12
|
// has no `property`.
|
|
@@ -63,9 +61,12 @@ function translateNode(node) {
|
|
|
63
61
|
}
|
|
64
62
|
};
|
|
65
63
|
case 'like':
|
|
64
|
+
// A stored value cannot be folded in a `find`, and a collation does not
|
|
65
|
+
// reach `$regex` — so the query is widened instead: each letter becomes
|
|
66
|
+
// the class of its accented forms, both cases listed (`fold.ts`).
|
|
66
67
|
return {
|
|
67
68
|
[field]: {
|
|
68
|
-
$regex:
|
|
69
|
+
$regex: foldedPattern(node.value),
|
|
69
70
|
$options: 'i'
|
|
70
71
|
}
|
|
71
72
|
};
|
|
@@ -73,7 +74,7 @@ function translateNode(node) {
|
|
|
73
74
|
return {
|
|
74
75
|
[field]: {
|
|
75
76
|
$not: {
|
|
76
|
-
$regex:
|
|
77
|
+
$regex: foldedPattern(node.value),
|
|
77
78
|
$options: 'i'
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/filter-translator.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/filter-translator.ts"],"sourcesContent":["import {\n type Entity,\n type EntityFilter,\n type EntityFiltering,\n type EntitySorting,\n type FilterGroup,\n foldedPattern,\n} from '@entifix/core';\n\n/** A Mongo query document (`{ field: condition, $and: [...], ... }`). */\nexport type MongoFilter = Record<string, unknown>;\n/** A Mongo sort document (`{ field: 1 | -1 }`). */\nexport type MongoSort = Record<string, 1 | -1>;\n\nconst BINARY_OPERATORS: Record<string, string> = {\n eq: '$eq',\n ne: '$ne',\n gt: '$gt',\n gte: '$gte',\n lt: '$lt',\n lte: '$lte',\n};\n\nfunction isFilterGroup<TEntity extends Entity>(\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): node is FilterGroup<TEntity> {\n // A group carries `values` + a logic operator and, unlike the array filter,\n // has no `property`.\n return (\n !('property' in node) && (node.operator === 'and' || node.operator === 'or')\n );\n}\n\nfunction translateNode<TEntity extends Entity>(\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): MongoFilter {\n if (isFilterGroup(node)) {\n const clause = node.operator === 'or' ? '$or' : '$and';\n return { [clause]: node.values.map(translateNode) };\n }\n\n const field = String(node.property);\n\n switch (node.operator) {\n case 'eq':\n case 'ne':\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte':\n return { [field]: { [BINARY_OPERATORS[node.operator]]: node.value } };\n case 'in':\n return { [field]: { $in: node.values } };\n case 'nin':\n return { [field]: { $nin: node.values } };\n case 'between':\n return { [field]: { $gte: node.start, $lte: node.end } };\n case 'nbetween':\n return { [field]: { $not: { $gte: node.start, $lte: node.end } } };\n case 'like':\n // A stored value cannot be folded in a `find`, and a collation does not\n // reach `$regex` — so the query is widened instead: each letter becomes\n // the class of its accented forms, both cases listed (`fold.ts`).\n return { [field]: { $regex: foldedPattern(node.value), $options: 'i' } };\n case 'nlike':\n return {\n [field]: {\n $not: { $regex: foldedPattern(node.value), $options: 'i' },\n },\n };\n case 'isNull':\n return { [field]: { $eq: null } };\n case 'isNotNull':\n return { [field]: { $ne: null } };\n default: {\n // Exhaustiveness guard — a new operator must be handled explicitly.\n const _never: never = node;\n return _never;\n }\n }\n}\n\n/**\n * Translates the request's `filtering` (an array of {@link EntityFiltering},\n * each a single filter, an array of filters, or a {@link FilterGroup}) into a\n * single Mongo query document. Every top-level entry is combined with `$and`;\n * an empty/absent filtering yields the match-all `{}`.\n */\nexport function translateFiltering<TEntity extends Entity>(\n filtering?: EntityFiltering<TEntity>[],\n): MongoFilter {\n if (!filtering || filtering.length === 0) {\n return {};\n }\n\n const clauses: MongoFilter[] = [];\n for (const entry of filtering) {\n if (Array.isArray(entry)) {\n clauses.push(...entry.map(translateNode));\n } else {\n clauses.push(translateNode(entry));\n }\n }\n\n if (clauses.length === 0) return {};\n if (clauses.length === 1) return clauses[0];\n return { $and: clauses };\n}\n\n/**\n * Translates the request's `sorting` (an array of {@link EntitySorting} records,\n * each keyed by numeric priority) into a Mongo sort document, preserving the\n * numeric ordering within each record.\n */\nexport function translateSorting<TEntity extends Entity>(\n sorting?: EntitySorting<TEntity>[],\n): MongoSort {\n const sort: MongoSort = {};\n if (!sorting) return sort;\n\n for (const record of sorting) {\n for (const priority of Object.keys(record)\n .map(Number)\n .sort((a, b) => a - b)) {\n const entry = record[priority];\n if (entry) {\n sort[String(entry.property)] = entry.type === 'desc' ? -1 : 1;\n }\n }\n }\n\n return sort;\n}\n"],"names":["foldedPattern","BINARY_OPERATORS","eq","ne","gt","gte","lt","lte","isFilterGroup","node","operator","translateNode","clause","values","map","field","String","property","value","$in","$nin","$gte","start","$lte","end","$not","$regex","$options","$eq","$ne","_never","translateFiltering","filtering","length","clauses","entry","Array","isArray","push","$and","translateSorting","sorting","sort","record","priority","Object","keys","Number","a","b","type"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EntityFacetCounter } from '@entifix/business';
|
|
2
|
+
import { type Entity, type EntityConstructor } from '@entifix/core';
|
|
3
|
+
import type { ClientSession, Db } from 'mongodb';
|
|
4
|
+
/**
|
|
5
|
+
* Builds an {@link EntityFacetCounter} for one entity over a live mongodb
|
|
6
|
+
* {@link Db}: every facet in **one** aggregation, a `$facet` whose branches
|
|
7
|
+
* each `$match` their own filtering, so a disjunctive panel costs one round
|
|
8
|
+
* trip however many facets it shows.
|
|
9
|
+
*
|
|
10
|
+
* Filtering goes through the same translator the repository's `load` uses, so a
|
|
11
|
+
* count and the page beside it can never disagree about what a filter means.
|
|
12
|
+
* A value that is not a string, number or boolean is dropped, as in core's
|
|
13
|
+
* in-memory counter.
|
|
14
|
+
*/
|
|
15
|
+
export declare function makeMongoFacetCounter<TEntity extends Entity>(db: Db, entityConstructor: EntityConstructor<TEntity>, options?: {
|
|
16
|
+
readonly session?: ClientSession;
|
|
17
|
+
}): EntityFacetCounter;
|
|
18
|
+
//# sourceMappingURL=make-mongo-facet-counter.d.ts.map
|
|
@@ -0,0 +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,EAGL,KAAK,MAAM,EACX,KAAK,iBAAiB,EAIvB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAY,MAAM,SAAS,CAAC;AA8B3D;;;;;;;;;;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,CA8CpB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { compareStoredValues, EntifixConnError } from '@entifix/core';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { translateFiltering } from './filter-translator.js';
|
|
4
|
+
import { mongoCollectionName } from './make-mongo-repository.js';
|
|
5
|
+
const isFacetValue = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
6
|
+
/** Count first, highest first; a tie by value — the order core's counter keeps. */ const byCountThenValue = (left, right)=>right.count - left.count || compareStoredValues(left.value, right.value);
|
|
7
|
+
/**
|
|
8
|
+
* One `$facet` branch: the member's own filtering, then one row per value.
|
|
9
|
+
*
|
|
10
|
+
* `$unwind` is in every branch, not only an array member's: on a scalar it
|
|
11
|
+
* passes the document through, and on a missing or `null` value it drops it —
|
|
12
|
+
* which is the "a missing value is not counted" rule, for free.
|
|
13
|
+
*/ const branchFor = ({ member, filtering })=>[
|
|
14
|
+
{
|
|
15
|
+
$match: translateFiltering(filtering)
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
$unwind: `$${member}`
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
$group: {
|
|
22
|
+
_id: `$${member}`,
|
|
23
|
+
count: {
|
|
24
|
+
$sum: 1
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Builds an {@link EntityFacetCounter} for one entity over a live mongodb
|
|
31
|
+
* {@link Db}: every facet in **one** aggregation, a `$facet` whose branches
|
|
32
|
+
* each `$match` their own filtering, so a disjunctive panel costs one round
|
|
33
|
+
* trip however many facets it shows.
|
|
34
|
+
*
|
|
35
|
+
* Filtering goes through the same translator the repository's `load` uses, so a
|
|
36
|
+
* count and the page beside it can never disagree about what a filter means.
|
|
37
|
+
* A value that is not a string, number or boolean is dropped, as in core's
|
|
38
|
+
* in-memory counter.
|
|
39
|
+
*/ export function makeMongoFacetCounter(db, entityConstructor, options) {
|
|
40
|
+
const collection = db.collection(mongoCollectionName(entityConstructor));
|
|
41
|
+
const inSession = (options == null ? void 0 : options.session) ? {
|
|
42
|
+
session: options.session
|
|
43
|
+
} : {};
|
|
44
|
+
const count = (queries)=>Effect.gen(function*() {
|
|
45
|
+
// A `$facet` with no branch is a server error; asking for nothing is
|
|
46
|
+
// not.
|
|
47
|
+
if (queries.length === 0) return {};
|
|
48
|
+
const [result] = yield* Effect.tryPromise({
|
|
49
|
+
try: ()=>collection.aggregate([
|
|
50
|
+
{
|
|
51
|
+
$facet: Object.fromEntries(queries.map((query)=>[
|
|
52
|
+
query.member,
|
|
53
|
+
branchFor(query)
|
|
54
|
+
]))
|
|
55
|
+
}
|
|
56
|
+
], inSession).toArray(),
|
|
57
|
+
catch: (error)=>new EntifixConnError('Failed to count facets in MongoDB', error, {
|
|
58
|
+
entity: entityConstructor.name
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
const counts = {};
|
|
62
|
+
for (const { member } of queries){
|
|
63
|
+
var _ref;
|
|
64
|
+
const rows = (_ref = result == null ? void 0 : result[member]) != null ? _ref : [];
|
|
65
|
+
counts[member] = rows.filter((row)=>isFacetValue(row['_id'])).map((row)=>({
|
|
66
|
+
value: row['_id'],
|
|
67
|
+
count: row['count']
|
|
68
|
+
})).sort(byCountThenValue);
|
|
69
|
+
}
|
|
70
|
+
return counts;
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
count
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=make-mongo-facet-counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/make-mongo-facet-counter.ts"],"sourcesContent":["import type { EntityFacetCounter } from '@entifix/business';\nimport {\n compareStoredValues,\n EntifixConnError,\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 { 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 =>\n new EntifixConnError('Failed to count facets in MongoDB', error, {\n entity: entityConstructor.name,\n }),\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","EntifixConnError","Effect","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","entity","name","counts","rows","filter","row","sort"],"mappings":"AACA,SACEA,mBAAmB,EACnBC,gBAAgB,QAMX,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAGhC,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/BxB,OAAOyB,GAAG,CAAC;YACT,qEAAqE;YACrE,OAAO;YACP,IAAID,QAAQE,MAAM,KAAK,GAAG,OAAO,CAAC;YAElC,MAAM,CAACC,OAAO,GAAG,OAAO3B,OAAO4B,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,QACL,IAAIvC,iBAAiB,qCAAqCuC,OAAO;wBAC/DC,QAAQpB,kBAAkBqB,IAAI;oBAChC;YACJ;YAEA,MAAMC,SAAsD,CAAC;YAC7D,KAAK,MAAM,EAAE/B,MAAM,EAAE,IAAIc,QAAS;;gBAChC,MAAMkB,eAASf,0BAAD,AAACA,MAAiC,CAACjB,OAAO,mBACtD,EAAE;gBACJ+B,MAAM,CAAC/B,OAAO,GAAGgC,KACdC,MAAM,CAACC,CAAAA,MAAOzC,aAAayC,GAAG,CAAC,MAAM,GACrCV,GAAG,CAACU,CAAAA,MAAQ,CAAA;wBACXxC,OAAOwC,GAAG,CAAC,MAAM;wBACjBpC,OAAOoC,GAAG,CAAC,QAAQ;oBACrB,CAAA,GACCC,IAAI,CAACxC;YACV;YACA,OAAOoC;QACT;IAEF,OAAO;QAAEjC;IAAM;AACjB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { EntityRepository } from '@entifix/business';
|
|
2
2
|
import { EntifixError, Entity, EntityConstructor } from '@entifix/core';
|
|
3
3
|
import type { ClientSession, Db } from 'mongodb';
|
|
4
|
+
/** The collection an entity's records live in: its `key`, else its class name. */
|
|
5
|
+
export declare function mongoCollectionName<TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>): string;
|
|
4
6
|
/**
|
|
5
7
|
* Builds an {@link EntityRepository} for a single entity type, backed by a live
|
|
6
8
|
* mongodb {@link Db}. The `db` and `entityConstructor` are captured in closures,
|
|
@@ -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,EAIL,YAAY,EACZ,MAAM,EACN,iBAAiB,EAMlB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;
|
|
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,EAIL,YAAY,EACZ,MAAM,EACN,iBAAiB,EAMlB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAOjD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,EACxD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAC5C,MAAM,CAGR;AAQD;;;;;;;;;;;;;;;;GAgBG;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,CA0GlB;AAED,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -8,7 +8,7 @@ import { translateFiltering, translateSorting } from './filter-translator.js';
|
|
|
8
8
|
_id: 0
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
|
-
function
|
|
11
|
+
/** The collection an entity's records live in: its `key`, else its class name. */ export function mongoCollectionName(entityConstructor) {
|
|
12
12
|
var _metaEntity_key;
|
|
13
13
|
const metaEntity = extractMetaEntity(entityConstructor);
|
|
14
14
|
return (_metaEntity_key = metaEntity.key) != null ? _metaEntity_key : metaEntity.name;
|
|
@@ -33,7 +33,7 @@ function toEntityId(entityOrId) {
|
|
|
33
33
|
* work's handle — so its writes commit with the unit and its reads see what the
|
|
34
34
|
* unit has already written.
|
|
35
35
|
*/ export function makeMongoRepository(db, entityConstructor, options) {
|
|
36
|
-
const collection = db.collection(
|
|
36
|
+
const collection = db.collection(mongoCollectionName(entityConstructor));
|
|
37
37
|
// Spread into every call, so a repository with no session passes none.
|
|
38
38
|
const inSession = (options == null ? void 0 : options.session) ? {
|
|
39
39
|
session: options.session
|
|
@@ -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 EntifixConnError,\n EntifixError,\n Entity,\n EntityConstructor,\n EntityId,\n EntityLoadRequest,\n EntityPage,\n extractMetaEntity,\n serializeEntity,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport type { ClientSession, Db } from 'mongodb';\n\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\
|
|
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 EntifixConnError,\n EntifixError,\n Entity,\n EntityConstructor,\n EntityId,\n EntityLoadRequest,\n EntityPage,\n extractMetaEntity,\n serializeEntity,\n} from '@entifix/core';\nimport { Effect } from 'effect';\nimport type { ClientSession, Db } from 'mongodb';\n\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 * {@link EntifixConnError}.\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 fail = (\n message: string,\n error: unknown,\n details?: Record<string, unknown>,\n ) =>\n new EntifixConnError(message, error, {\n entity: entityConstructor.name,\n ...details,\n });\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 => fail('Failed to load entities from MongoDB', error),\n });\n const total = yield* Effect.tryPromise({\n try: () => collection.countDocuments(filter, inSession),\n catch: error => fail('Failed to count entities in MongoDB', 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 =>\n fail('Failed to read entity from MongoDB', error, { id }),\n });\n const entity = yield* deserializeSingleEntity(entityConstructor, doc);\n if (entity === undefined) {\n return yield* Effect.fail(fail(`Entity not found`, undefined, { id }));\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 => fail('Failed to save entity to MongoDB', error, { id }),\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 =>\n fail('Failed to delete entity from MongoDB', error, { id }),\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\nexport type { EntifixError };\n"],"names":["randomUUID","deserializeEntityCollection","deserializeSingleEntity","EntifixConnError","extractMetaEntity","serializeEntity","Effect","translateFiltering","translateSorting","WITHOUT_MONGO_ID","projection","_id","mongoCollectionName","entityConstructor","metaEntity","key","name","toEntityId","entityOrId","id","makeMongoRepository","db","options","collection","inSession","session","fail","message","error","details","entity","load","request","gen","filter","filtering","sort","sorting","page","pageSize","skip","docs","tryPromise","try","find","limit","toArray","catch","total","countDocuments","items","get","doc","findOne","undefined","save","replaceOne","upsert","del","deleteOne","delete"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EntityFacetCounterFactoryTag, TenantDatabaseResolverTag } from '@entifix/business';
|
|
2
|
+
import { Layer } from 'effect';
|
|
3
|
+
import { MongoDatabaseTag } from '../mongo-database/mongo-database.js';
|
|
4
|
+
/**
|
|
5
|
+
* Facet counters over the service's one database, whoever the request is for —
|
|
6
|
+
* `MongoRepositoryFactoryLayer`'s counterpart, bound beside it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MongoFacetCounterFactoryLayer: Layer.Layer<EntityFacetCounterFactoryTag, never, MongoDatabaseTag>;
|
|
9
|
+
/**
|
|
10
|
+
* Facet counters over the requesting organization's own database, resolved
|
|
11
|
+
* per request through the host's {@link TenantDatabaseResolverTag}.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ A request that names no organization is **refused**, never counted from a
|
|
14
|
+
* default: on a per-organization store a default database is some other
|
|
15
|
+
* organization's, and its counts would say how much stock a competitor holds.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MongoTenantFacetCounterFactoryLayer: Layer.Layer<EntityFacetCounterFactoryTag, never, TenantDatabaseResolverTag>;
|
|
18
|
+
//# sourceMappingURL=mongo-facet-counter-factory.d.ts.map
|
|
@@ -0,0 +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,6EAoB/C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EntityFacetCounterFactoryTag, TenantDatabaseResolverTag } from '@entifix/business';
|
|
2
|
+
import { EntifixConnError } from '@entifix/core';
|
|
3
|
+
import { Effect, Layer } from 'effect';
|
|
4
|
+
import { MongoDatabaseTag } from '../mongo-database/mongo-database.js';
|
|
5
|
+
import { makeMongoFacetCounter } from './make-mongo-facet-counter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Facet counters over the service's one database, whoever the request is for —
|
|
8
|
+
* `MongoRepositoryFactoryLayer`'s counterpart, bound beside it.
|
|
9
|
+
*/ export const MongoFacetCounterFactoryLayer = Layer.effect(EntityFacetCounterFactoryTag, Effect.gen(function*() {
|
|
10
|
+
const db = yield* MongoDatabaseTag;
|
|
11
|
+
return {
|
|
12
|
+
for: (entityConstructor)=>Effect.succeed(makeMongoFacetCounter(db, entityConstructor))
|
|
13
|
+
};
|
|
14
|
+
}));
|
|
15
|
+
/**
|
|
16
|
+
* Facet counters over the requesting organization's own database, resolved
|
|
17
|
+
* per request through the host's {@link TenantDatabaseResolverTag}.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ A request that names no organization is **refused**, never counted from a
|
|
20
|
+
* default: on a per-organization store a default database is some other
|
|
21
|
+
* organization's, and its counts would say how much stock a competitor holds.
|
|
22
|
+
*/ export const MongoTenantFacetCounterFactoryLayer = Layer.effect(EntityFacetCounterFactoryTag, Effect.gen(function*() {
|
|
23
|
+
const resolver = yield* TenantDatabaseResolverTag;
|
|
24
|
+
return {
|
|
25
|
+
for: (entityConstructor, scope)=>scope.organizationId === undefined ? Effect.fail(new EntifixConnError('A per-organization store was asked for a facet counter with no organization', undefined, {
|
|
26
|
+
entity: entityConstructor.name
|
|
27
|
+
})) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoFacetCounter(db, entityConstructor))
|
|
28
|
+
};
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=mongo-facet-counter-factory.js.map
|
|
@@ -0,0 +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 { EntifixConnError } 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(\n new EntifixConnError(\n 'A per-organization store was asked for a facet counter with no organization',\n undefined,\n { entity: entityConstructor.name },\n ),\n )\n : Effect.map(resolver.forOrganization(scope.organizationId), db =>\n makeMongoFacetCounter(db, entityConstructor),\n ),\n };\n }),\n);\n"],"names":["EntityFacetCounterFactoryTag","TenantDatabaseResolverTag","EntifixConnError","Effect","Layer","MongoDatabaseTag","makeMongoFacetCounter","MongoFacetCounterFactoryLayer","effect","gen","db","for","entityConstructor","succeed","MongoTenantFacetCounterFactoryLayer","resolver","scope","organizationId","undefined","fail","entity","name","map","forOrganization"],"mappings":"AAAA,SACEA,4BAA4B,EAE5BC,yBAAyB,QACpB,oBAAoB;AAC3B,SAASC,gBAAgB,QAAQ,gBAAgB;AACjD,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,CACT,IAAIjB,iBACF,+EACAgB,WACA;gBAAEE,QAAQR,kBAAkBS,IAAI;YAAC,MAGrClB,OAAOmB,GAAG,CAACP,SAASQ,eAAe,CAACP,MAAMC,cAAc,GAAGP,CAAAA,KACzDJ,sBAAsBI,IAAIE;IAEpC;AACF,IACA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './adapters/filter-translator.js';
|
|
2
|
+
export * from './adapters/make-mongo-facet-counter.js';
|
|
2
3
|
export * from './adapters/make-mongo-link-resolver.js';
|
|
3
4
|
export * from './adapters/make-mongo-repository.js';
|
|
5
|
+
export * from './adapters/mongo-facet-counter-factory.js';
|
|
4
6
|
export * from './adapters/mongo-repository-factory.js';
|
|
5
7
|
export * from './mongo-database/mongo-database.js';
|
|
6
8
|
export * from './mongo-database/mongo-health-probe.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,wCAAwC,CAAC;AACvD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2CAA2C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './adapters/filter-translator.js';
|
|
2
|
+
export * from './adapters/make-mongo-facet-counter.js';
|
|
2
3
|
export * from './adapters/make-mongo-link-resolver.js';
|
|
3
4
|
export * from './adapters/make-mongo-repository.js';
|
|
5
|
+
export * from './adapters/mongo-facet-counter-factory.js';
|
|
4
6
|
export * from './adapters/mongo-repository-factory.js';
|
|
5
7
|
export * from './mongo-database/mongo-database.js';
|
|
6
8
|
export * from './mongo-database/mongo-health-probe.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/filter-translator.js';\nexport * from './adapters/make-mongo-link-resolver.js';\nexport * from './adapters/make-mongo-repository.js';\nexport * from './adapters/mongo-repository-factory.js';\nexport * from './mongo-database/mongo-database.js';\nexport * from './mongo-database/mongo-health-probe.js';\nexport * from './mongo-database/mongo-tenant-directory.js';\nexport * from './mongo-database/mongo-tenant-resolver.js';\n"],"names":[],"mappings":"AAAA,cAAc,kCAAkC;AAChD,cAAc,yCAAyC;AACvD,cAAc,sCAAsC;AACpD,cAAc,yCAAyC;AACvD,cAAc,qCAAqC;AACnD,cAAc,yCAAyC;AACvD,cAAc,6CAA6C;AAC3D,cAAc,4CAA4C"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/filter-translator.js';\nexport * from './adapters/make-mongo-facet-counter.js';\nexport * from './adapters/make-mongo-link-resolver.js';\nexport * from './adapters/make-mongo-repository.js';\nexport * from './adapters/mongo-facet-counter-factory.js';\nexport * from './adapters/mongo-repository-factory.js';\nexport * from './mongo-database/mongo-database.js';\nexport * from './mongo-database/mongo-health-probe.js';\nexport * from './mongo-database/mongo-tenant-directory.js';\nexport * from './mongo-database/mongo-tenant-resolver.js';\n"],"names":[],"mappings":"AAAA,cAAc,kCAAkC;AAChD,cAAc,yCAAyC;AACvD,cAAc,yCAAyC;AACvD,cAAc,sCAAsC;AACpD,cAAc,4CAA4C;AAC1D,cAAc,yCAAyC;AACvD,cAAc,qCAAqC;AACnD,cAAc,yCAAyC;AACvD,cAAc,6CAA6C;AAC3D,cAAc,4CAA4C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entifix/mongo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "MongoDB adapter: an entity repository, RSQL-to-Mongo query translation, per-tenant databases, and an optional transactional outbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@swc/helpers": "0.5.23",
|
|
59
59
|
"mongodb": "7.7.0",
|
|
60
|
-
"@entifix/
|
|
61
|
-
"@entifix/
|
|
60
|
+
"@entifix/business": "0.9.0",
|
|
61
|
+
"@entifix/core": "0.9.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"effect": "4.0.5",
|
|
65
|
-
"@entifix/testing-unit": "0.
|
|
66
|
-
"@entifix/transactions": "0.
|
|
65
|
+
"@entifix/testing-unit": "0.9.0",
|
|
66
|
+
"@entifix/transactions": "0.9.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"effect": "^4.0.5",
|
|
70
|
-
"@entifix/transactions": "0.
|
|
70
|
+
"@entifix/transactions": "0.9.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {
|
|
73
73
|
"@entifix/transactions": {
|