@entifix/mongo 0.8.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.
Files changed (65) hide show
  1. package/README.md +4 -4
  2. package/dist/adapters/filter-translator.d.ts +1 -1
  3. package/dist/adapters/filter-translator.d.ts.map +1 -1
  4. package/dist/adapters/filter-translator.js +6 -5
  5. package/dist/adapters/filter-translator.js.map +1 -1
  6. package/dist/adapters/make-mongo-facet-counter.d.ts +18 -0
  7. package/dist/adapters/make-mongo-facet-counter.d.ts.map +1 -0
  8. package/dist/adapters/make-mongo-facet-counter.js +76 -0
  9. package/dist/adapters/make-mongo-facet-counter.js.map +1 -0
  10. package/dist/adapters/make-mongo-link-resolver.d.ts.map +1 -1
  11. package/dist/adapters/make-mongo-link-resolver.js +2 -4
  12. package/dist/adapters/make-mongo-link-resolver.js.map +1 -1
  13. package/dist/adapters/make-mongo-repository.d.ts +5 -3
  14. package/dist/adapters/make-mongo-repository.d.ts.map +1 -1
  15. package/dist/adapters/make-mongo-repository.js +13 -20
  16. package/dist/adapters/make-mongo-repository.js.map +1 -1
  17. package/dist/adapters/mongo-facet-counter-factory.d.ts +18 -0
  18. package/dist/adapters/mongo-facet-counter-factory.d.ts.map +1 -0
  19. package/dist/adapters/mongo-facet-counter-factory.js +29 -0
  20. package/dist/adapters/mongo-facet-counter-factory.js.map +1 -0
  21. package/dist/adapters/mongo-repository-factory.d.ts.map +1 -1
  22. package/dist/adapters/mongo-repository-factory.js +2 -4
  23. package/dist/adapters/mongo-repository-factory.js.map +1 -1
  24. package/dist/errors/translate-mongo-error.d.ts +30 -0
  25. package/dist/errors/translate-mongo-error.d.ts.map +1 -0
  26. package/dist/errors/translate-mongo-error.js +74 -0
  27. package/dist/errors/translate-mongo-error.js.map +1 -0
  28. package/dist/inbox/store.d.ts +1 -2
  29. package/dist/inbox/store.d.ts.map +1 -1
  30. package/dist/inbox/store.js +3 -8
  31. package/dist/inbox/store.js.map +1 -1
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/mongo-database/mongo-database.d.ts +3 -3
  37. package/dist/mongo-database/mongo-database.d.ts.map +1 -1
  38. package/dist/mongo-database/mongo-database.js +2 -2
  39. package/dist/mongo-database/mongo-database.js.map +1 -1
  40. package/dist/mongo-database/mongo-tenant-directory.d.ts.map +1 -1
  41. package/dist/mongo-database/mongo-tenant-directory.js +2 -4
  42. package/dist/mongo-database/mongo-tenant-directory.js.map +1 -1
  43. package/dist/mongo-database/mongo-tenant-resolver.d.ts.map +1 -1
  44. package/dist/mongo-database/mongo-tenant-resolver.js +18 -7
  45. package/dist/mongo-database/mongo-tenant-resolver.js.map +1 -1
  46. package/dist/outbox/relay.d.ts +50 -9
  47. package/dist/outbox/relay.d.ts.map +1 -1
  48. package/dist/outbox/relay.js +67 -26
  49. package/dist/outbox/relay.js.map +1 -1
  50. package/dist/outbox/store.d.ts +3 -3
  51. package/dist/outbox/store.d.ts.map +1 -1
  52. package/dist/outbox/store.js +8 -22
  53. package/dist/outbox/store.js.map +1 -1
  54. package/dist/saga/store.d.ts +3 -4
  55. package/dist/saga/store.d.ts.map +1 -1
  56. package/dist/saga/store.js +10 -15
  57. package/dist/saga/store.js.map +1 -1
  58. package/dist/unit-of-work/command-inbox.d.ts +2 -3
  59. package/dist/unit-of-work/command-inbox.d.ts.map +1 -1
  60. package/dist/unit-of-work/command-inbox.js +3 -7
  61. package/dist/unit-of-work/command-inbox.js.map +1 -1
  62. package/dist/unit-of-work/mongo-unit-of-work.d.ts.map +1 -1
  63. package/dist/unit-of-work/mongo-unit-of-work.js +30 -26
  64. package/dist/unit-of-work/mongo-unit-of-work.js.map +1 -1
  65. package/package.json +8 -8
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 { Entity, EntityFiltering, EntitySorting } from '@entifix/core';
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,EACV,MAAM,EAEN,eAAe,EACf,aAAa,EAEd,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
+ {"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: escapeRegex(node.value),
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: escapeRegex(node.value),
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 type {\n Entity,\n EntityFilter,\n EntityFiltering,\n EntitySorting,\n FilterGroup,\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\n/** Escapes a string so it is matched literally inside a Mongo `$regex`. */\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\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 return { [field]: { $regex: escapeRegex(node.value), $options: 'i' } };\n case 'nlike':\n return {\n [field]: { $not: { $regex: escapeRegex(node.value), $options: 'i' } },\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":["BINARY_OPERATORS","eq","ne","gt","gte","lt","lte","escapeRegex","value","replace","isFilterGroup","node","operator","translateNode","clause","values","map","field","String","property","$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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"}
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,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"}
@@ -0,0 +1,76 @@
1
+ import { compareStoredValues } from '@entifix/core';
2
+ import { Effect } from 'effect';
3
+ import { translateMongoError } from '../errors/translate-mongo-error.js';
4
+ import { translateFiltering } from './filter-translator.js';
5
+ import { mongoCollectionName } from './make-mongo-repository.js';
6
+ const isFacetValue = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
7
+ /** 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);
8
+ /**
9
+ * One `$facet` branch: the member's own filtering, then one row per value.
10
+ *
11
+ * `$unwind` is in every branch, not only an array member's: on a scalar it
12
+ * passes the document through, and on a missing or `null` value it drops it —
13
+ * which is the "a missing value is not counted" rule, for free.
14
+ */ const branchFor = ({ member, filtering })=>[
15
+ {
16
+ $match: translateFiltering(filtering)
17
+ },
18
+ {
19
+ $unwind: `$${member}`
20
+ },
21
+ {
22
+ $group: {
23
+ _id: `$${member}`,
24
+ count: {
25
+ $sum: 1
26
+ }
27
+ }
28
+ }
29
+ ];
30
+ /**
31
+ * Builds an {@link EntityFacetCounter} for one entity over a live mongodb
32
+ * {@link Db}: every facet in **one** aggregation, a `$facet` whose branches
33
+ * each `$match` their own filtering, so a disjunctive panel costs one round
34
+ * trip however many facets it shows.
35
+ *
36
+ * Filtering goes through the same translator the repository's `load` uses, so a
37
+ * count and the page beside it can never disagree about what a filter means.
38
+ * A value that is not a string, number or boolean is dropped, as in core's
39
+ * in-memory counter.
40
+ */ export function makeMongoFacetCounter(db, entityConstructor, options) {
41
+ const collection = db.collection(mongoCollectionName(entityConstructor));
42
+ const inSession = (options == null ? void 0 : options.session) ? {
43
+ session: options.session
44
+ } : {};
45
+ const count = (queries)=>Effect.gen(function*() {
46
+ // A `$facet` with no branch is a server error; asking for nothing is
47
+ // not.
48
+ if (queries.length === 0) return {};
49
+ const [result] = yield* Effect.tryPromise({
50
+ try: ()=>collection.aggregate([
51
+ {
52
+ $facet: Object.fromEntries(queries.map((query)=>[
53
+ query.member,
54
+ branchFor(query)
55
+ ]))
56
+ }
57
+ ], inSession).toArray(),
58
+ catch: (error)=>translateMongoError(error)
59
+ });
60
+ const counts = {};
61
+ for (const { member } of queries){
62
+ var _ref;
63
+ const rows = (_ref = result == null ? void 0 : result[member]) != null ? _ref : [];
64
+ counts[member] = rows.filter((row)=>isFacetValue(row['_id'])).map((row)=>({
65
+ value: row['_id'],
66
+ count: row['count']
67
+ })).sort(byCountThenValue);
68
+ }
69
+ return counts;
70
+ });
71
+ return {
72
+ count
73
+ };
74
+ }
75
+
76
+ //# 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 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,EAGL,MAAM,EACN,iBAAiB,EAElB,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,CAiCxC"}
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 { EntifixLogicError } from '@entifix/core';
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 EntifixLogicError(`No Mongo link resolver registered for entity "${entityConstructor.name}"`, undefined, {
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 EntifixError,\n EntifixLogicError,\n Entity,\n EntityConstructor,\n EntityLinkResolver,\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(\n new EntifixLogicError(\n `No Mongo link resolver registered for entity \"${entityConstructor.name}\"`,\n undefined,\n { entity: entityConstructor.name },\n ),\n );\n }\n return repository.get(id) as unknown as Effect.Effect<\n never,\n EntifixError\n >;\n },\n };\n\n return Context.make(EntityLinkResolverTag, resolver);\n}\n"],"names":["EntityLinkResolverTag","EntifixLogicError","Context","Effect","makeMongoRepository","makeMongoLinkResolver","db","registrations","repositoryByConstructor","Map","map","entityConstructor","resolver","resolve","id","repository","get","fail","name","undefined","entity","make"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,oBAAoB;AAC1D,SAEEC,iBAAiB,QAIZ,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,CAChB,IAAIhB,kBACF,CAAC,8CAA8C,EAAEU,kBAAkBO,IAAI,CAAC,CAAC,CAAC,EAC1EC,WACA;oBAAEC,QAAQT,kBAAkBO,IAAI;gBAAC;YAGvC;YACA,OAAOH,WAAWC,GAAG,CAACF;QAIxB;IACF;IAEA,OAAOZ,QAAQmB,IAAI,CAACrB,uBAAuBY;AAC7C"}
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,6 +1,8 @@
1
1
  import type { EntityRepository } from '@entifix/business';
2
- import { EntifixError, Entity, EntityConstructor } from '@entifix/core';
2
+ import { 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,
@@ -10,7 +12,8 @@ import type { ClientSession, Db } from 'mongodb';
10
12
  * Collection name = the entity's `key` (falling back to its class name). Reads
11
13
  * reuse the shared entifix deserializer (rebuilding links from embedded objects
12
14
  * or scalar FKs); writes reuse the shared serializer. Mongo failures surface as
13
- * {@link EntifixConnError}.
15
+ * categorised failures — `NotFound`, `DuplicateKey`, `Unavailable` and the
16
+ * rest — through `translateMongoError`.
14
17
  *
15
18
  * This is the Mongo mirror of the REST `buildEntityRestAdapter*` builders.
16
19
  *
@@ -21,5 +24,4 @@ import type { ClientSession, Db } from 'mongodb';
21
24
  export declare function makeMongoRepository<TEntity extends Entity>(db: Db, entityConstructor: EntityConstructor<TEntity>, options?: {
22
25
  readonly session?: ClientSession;
23
26
  }): EntityRepository;
24
- export type { EntifixError };
25
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,EAIL,YAAY,EACZ,MAAM,EACN,iBAAiB,EAMlB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAoBjD;;;;;;;;;;;;;;;;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"}
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,14 +1,15 @@
1
1
  import { _ as _extends } from "@swc/helpers/_/_extends";
2
2
  import { randomUUID } from 'node:crypto';
3
- import { deserializeEntityCollection, deserializeSingleEntity, EntifixConnError, extractMetaEntity, serializeEntity } from '@entifix/core';
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: {
8
9
  _id: 0
9
10
  }
10
11
  };
11
- function collectionName(entityConstructor) {
12
+ /** The collection an entity's records live in: its `key`, else its class name. */ export function mongoCollectionName(entityConstructor) {
12
13
  var _metaEntity_key;
13
14
  const metaEntity = extractMetaEntity(entityConstructor);
14
15
  return (_metaEntity_key = metaEntity.key) != null ? _metaEntity_key : metaEntity.name;
@@ -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
- * {@link EntifixConnError}.
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
  *
@@ -33,14 +35,11 @@ function toEntityId(entityOrId) {
33
35
  * work's handle — so its writes commit with the unit and its reads see what the
34
36
  * unit has already written.
35
37
  */ export function makeMongoRepository(db, entityConstructor, options) {
36
- const collection = db.collection(collectionName(entityConstructor));
38
+ const collection = db.collection(mongoCollectionName(entityConstructor));
37
39
  // Spread into every call, so a repository with no session passes none.
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)=>fail('Failed to load entities from MongoDB', 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)=>fail('Failed to count entities in MongoDB', 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)=>fail('Failed to read entity from MongoDB', 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(fail(`Entity not found`, undefined, {
78
- id
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)=>fail('Failed to save entity to MongoDB', 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)=>fail('Failed to delete entity from MongoDB', 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 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\nfunction collectionName<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(collectionName(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","collectionName","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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"}
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":"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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,6EAc/C,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { EntityFacetCounterFactoryTag, TenantDatabaseResolverTag } from '@entifix/business';
2
+ import { Internal } 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 Internal()) : Effect.map(resolver.forOrganization(scope.organizationId), (db)=>makeMongoFacetCounter(db, entityConstructor))
26
+ };
27
+ }));
28
+
29
+ //# 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 { 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,2EAoB7C,CAAC"}
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 { EntifixConnError } from '@entifix/core';
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 EntifixConnError('A per-organization store was asked for a repository with no organization', undefined, {
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 { EntifixConnError } 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(\n new EntifixConnError(\n 'A per-organization store was asked for a repository with no organization',\n undefined,\n { entity: entityConstructor.name },\n ),\n )\n : Effect.map(resolver.forOrganization(scope.organizationId), db =>\n makeMongoRepository(db, entityConstructor),\n ),\n };\n }),\n);\n"],"names":["EntityRepositoryFactoryTag","TenantDatabaseResolverTag","EntifixConnError","Effect","Layer","MongoDatabaseTag","makeMongoRepository","MongoRepositoryFactoryLayer","effect","gen","db","for","entityConstructor","succeed","MongoTenantRepositoryFactoryLayer","resolver","scope","organizationId","undefined","fail","entity","name","map","forOrganization"],"mappings":"AAAA,SACEA,0BAA0B,EAE1BC,yBAAyB,QACpB,oBAAoB;AAC3B,SAASC,gBAAgB,QAAQ,gBAAgB;AACjD,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,CACT,IAAIjB,iBACF,4EACAgB,WACA;gBAAEE,QAAQR,kBAAkBS,IAAI;YAAC,MAGrClB,OAAOmB,GAAG,CAACP,SAASQ,eAAe,CAACP,MAAMC,cAAc,GAAGP,CAAAA,KACzDJ,oBAAoBI,IAAIE;IAElC;AACF,IACA"}
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"}