@entifix/business 0.1.4 → 0.3.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/health/health-registry.d.ts +1 -1
  3. package/dist/health/health-registry.d.ts.map +1 -1
  4. package/dist/health/health-registry.js +3 -4
  5. package/dist/health/health-registry.js.map +1 -1
  6. package/dist/repository/config.repository.d.ts +1 -1
  7. package/dist/repository/config.repository.d.ts.map +1 -1
  8. package/dist/repository/config.repository.js +1 -1
  9. package/dist/repository/config.repository.js.map +1 -1
  10. package/dist/repository/entity-link-resolver.repository.d.ts +1 -1
  11. package/dist/repository/entity-link-resolver.repository.d.ts.map +1 -1
  12. package/dist/repository/entity-link-resolver.repository.js +1 -1
  13. package/dist/repository/entity-link-resolver.repository.js.map +1 -1
  14. package/dist/repository/entity-mixins.repository.d.ts +3 -3
  15. package/dist/repository/entity-mixins.repository.d.ts.map +1 -1
  16. package/dist/repository/entity-mixins.repository.js +3 -3
  17. package/dist/repository/entity-mixins.repository.js.map +1 -1
  18. package/dist/repository/entity.repository.d.ts +1 -1
  19. package/dist/repository/entity.repository.d.ts.map +1 -1
  20. package/dist/repository/entity.repository.js +1 -1
  21. package/dist/repository/entity.repository.js.map +1 -1
  22. package/dist/sessions/one-time-token-store.d.ts +1 -1
  23. package/dist/sessions/one-time-token-store.d.ts.map +1 -1
  24. package/dist/sessions/one-time-token-store.js +1 -1
  25. package/dist/sessions/one-time-token-store.js.map +1 -1
  26. package/dist/sessions/session-store.d.ts +1 -1
  27. package/dist/sessions/session-store.d.ts.map +1 -1
  28. package/dist/sessions/session-store.js +1 -1
  29. package/dist/sessions/session-store.js.map +1 -1
  30. package/dist/shutdown/shutdown-registry.d.ts +1 -1
  31. package/dist/shutdown/shutdown-registry.d.ts.map +1 -1
  32. package/dist/shutdown/shutdown-registry.js +2 -2
  33. package/dist/shutdown/shutdown-registry.js.map +1 -1
  34. package/dist/tenancy/tenant-database-resolver.d.ts +2 -2
  35. package/dist/tenancy/tenant-database-resolver.d.ts.map +1 -1
  36. package/dist/tenancy/tenant-database-resolver.js +1 -1
  37. package/dist/tenancy/tenant-database-resolver.js.map +1 -1
  38. package/dist/tokens/token-service.d.ts +1 -1
  39. package/dist/tokens/token-service.d.ts.map +1 -1
  40. package/dist/tokens/token-service.js +1 -1
  41. package/dist/tokens/token-service.js.map +1 -1
  42. package/dist/use-case/save/save.uc.d.ts +1 -1
  43. package/dist/use-case/save/save.uc.d.ts.map +1 -1
  44. package/dist/wiring/wiring-registry.d.ts +1 -1
  45. package/dist/wiring/wiring-registry.d.ts.map +1 -1
  46. package/dist/wiring/wiring-registry.js +1 -1
  47. package/dist/wiring/wiring-registry.js.map +1 -1
  48. package/package.json +4 -5
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The ports an application is written against, and the generic use-cases that run over them. A use-case depends on `EntityRepositoryTag`, never on a database, so the same code runs in a browser against `@entifix/rest` and on a backend against `@entifix/mongo` or `@entifix/sql` — the adapter is chosen where the application is composed.
4
4
 
5
- Ports are Effect `Context.Tag`s: repository, link resolver, session and one-time-token stores, token service, tenant database resolver, and the health, shutdown and wiring registries.
5
+ Ports are Effect `Context.Service`s: repository, link resolver, session and one-time-token stores, token service, tenant database resolver, and the health, shutdown and wiring registries.
6
6
 
7
7
  ## Install
8
8
 
@@ -62,7 +62,7 @@ export interface HealthRegistry {
62
62
  */
63
63
  readonly probes: Effect.Effect<readonly HealthProbe[]>;
64
64
  }
65
- declare const HealthRegistryTag_base: Context.TagClass<HealthRegistryTag, "HealthRegistryTag", HealthRegistry>;
65
+ declare const HealthRegistryTag_base: Context.ServiceClass<HealthRegistryTag, "HealthRegistryTag", HealthRegistry>;
66
66
  /**
67
67
  * DI tag for the readiness registry.
68
68
  *
@@ -1 +1 @@
1
- {"version":3,"file":"health-registry.d.ts","sourceRoot":"","sources":["../../src/health/health-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAO,MAAM,QAAQ,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAaD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;CACxD;;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,sBAGpC;CAAG;AAEN;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAgC9D,CAAC"}
1
+ {"version":3,"file":"health-registry.d.ts","sourceRoot":"","sources":["../../src/health/health-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAO,MAAM,QAAQ,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAaD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7C;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;CACxD;;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,sBAGf;CAAG;AAE3B;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CA+B9D,CAAC"}
@@ -19,7 +19,7 @@ import { Context, Effect, Layer, Ref } from 'effect';
19
19
  *
20
20
  * It lives here, in the contract layer, so `entifix-ts-*-client` packages can
21
21
  * register into it without depending on a shell.
22
- */ export class HealthRegistryTag extends Context.Tag('HealthRegistryTag')() {
22
+ */ export class HealthRegistryTag extends Context.Service()('HealthRegistryTag') {
23
23
  }
24
24
  /**
25
25
  * Live registry over a `Ref`. Provide it **once** per service and share the
@@ -36,10 +36,9 @@ import { Context, Effect, Layer, Ref } from 'effect';
36
36
  report: Effect.gen(function*() {
37
37
  const current = yield* Ref.get(probes);
38
38
  const results = yield* Effect.forEach(current, (probe)=>probe.check.pipe(// A probe that throws is a failing probe, not a broken endpoint.
39
- Effect.catchAllDefect(()=>Effect.succeed(false)), Effect.timeoutTo({
39
+ Effect.catchDefect(()=>Effect.succeed(false)), Effect.timeoutOrElse({
40
40
  duration: PROBE_TIMEOUT,
41
- onTimeout: ()=>false,
42
- onSuccess: (ok)=>ok
41
+ orElse: ()=>Effect.succeed(false)
43
42
  }), Effect.map((ok)=>({
44
43
  name: probe.name,
45
44
  ok
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/health/health-registry.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from 'effect';\n\n/**\n * What a probe is talking to. Read by `GET /api/$service` to sort the wiring\n * document into stores, brokers and upstreams; readiness ignores it entirely.\n */\nexport type ProbeKind = 'datastore' | 'broker' | 'upstream';\n\n/**\n * One named readiness fact — \"can this service still reach Mongo?\".\n *\n * `check` is fully applied: whoever registers the probe has already closed over\n * the client handle, so the readiness route can run every probe without knowing\n * what any of them talk to. It must not fail — a probe that cannot answer\n * answers `false`; an unhandled defect in a probe would take down the very\n * endpoint that is supposed to report trouble.\n */\nexport interface HealthProbe {\n readonly name: string;\n /**\n * What sort of thing is on the other end. `datastore` is a Store's backing\n * engine, `broker` an exchange, `upstream` another service.\n */\n readonly kind: ProbeKind;\n /**\n * The **logical** names this probe covers — a Store's register name\n * (`catalog`, `saga`), an exchange (`entifix.events`), a service (`zitadel`).\n * Never a URI, never a database name: `catalog` is physically\n * `tenant_<organizationId>`, and a document naming those is an organization\n * enumerator (ADR 0031).\n *\n * It is a list rather than one string because a single connection can back\n * several Stores: marketplace-admin-service opens one Mongo client for the\n * `catalog` store and the co-deployed `transaction` slice's `saga` store.\n * Registering one probe per Store instead would put both names into the\n * readiness response's `failing` array, which must not change.\n *\n * The client package that opens the connection cannot know a Store's register\n * name, so the composition root passes it in — the one thing about a probe\n * that is not derivable from the client.\n */\n readonly targets: readonly string[];\n readonly check: Effect.Effect<boolean>;\n}\n\n/** The outcome of running every registered probe. */\nexport interface HealthReport {\n readonly ready: boolean;\n /** Names of the probes that answered `false`, in registration order. */\n readonly failing: readonly string[];\n}\n\n/**\n * How long a single probe may take before it counts as failing.\n *\n * This is not belt-and-braces. A driver that queues commands while its\n * connection is down (ioredis with `maxRetriesPerRequest`, for one) leaves the\n * probe pending instead of rejecting, so without a deadline `/api/health/ready`\n * *hangs* when a datastore disappears — the one moment it has to answer. A\n * probe that cannot answer in time is not ready, by definition.\n */\nconst PROBE_TIMEOUT = '2 seconds';\n\nexport interface HealthRegistry {\n /** Add a probe. Called by an infrastructure layer as it constructs a client. */\n readonly register: (probe: HealthProbe) => Effect.Effect<void>;\n /** Run every probe and summarise. Never fails. */\n readonly report: Effect.Effect<HealthReport>;\n /**\n * The registrations themselves, in registration order — what each probe is\n * for, without running any of them.\n *\n * This is what makes the wiring document and readiness generate from **one**\n * registration rather than from two lists that drift. Deliberately separate\n * from {@link report}: describing a service must never cost a round trip to\n * every datastore it has.\n */\n readonly probes: Effect.Effect<readonly HealthProbe[]>;\n}\n\n/**\n * DI tag for the readiness registry.\n *\n * The point of a registry rather than a hand-written list per service: a\n * `-service` that gains a datastore gains its readiness probe by merging that\n * client's probe layer, and nothing in the service's own code has to remember\n * to describe it. A list in `main.ts` drifts the first time someone adds a\n * dependency; this cannot, because the probe ships with the client.\n *\n * It lives here, in the contract layer, so `entifix-ts-*-client` packages can\n * register into it without depending on a shell.\n */\nexport class HealthRegistryTag extends Context.Tag('HealthRegistryTag')<\n HealthRegistryTag,\n HealthRegistry\n>() {}\n\n/**\n * Live registry over a `Ref`. Provide it **once** per service and share the\n * instance (`Layer.provideMerge`), or the probes register into a registry the\n * readiness route never reads.\n */\nexport const HealthRegistryLayer: Layer.Layer<HealthRegistryTag> = Layer.effect(\n HealthRegistryTag,\n Effect.gen(function* () {\n const probes = yield* Ref.make<readonly HealthProbe[]>([]);\n\n return {\n register: probe => Ref.update(probes, current => [...current, probe]),\n\n probes: Ref.get(probes),\n\n report: Effect.gen(function* () {\n const current = yield* Ref.get(probes);\n const results = yield* Effect.forEach(\n current,\n probe =>\n probe.check.pipe(\n // A probe that throws is a failing probe, not a broken endpoint.\n Effect.catchAllDefect(() => Effect.succeed(false)),\n Effect.timeoutTo({\n duration: PROBE_TIMEOUT,\n onTimeout: () => false,\n onSuccess: (ok: boolean) => ok,\n }),\n Effect.map(ok => ({ name: probe.name, ok })),\n ),\n { concurrency: 'unbounded' },\n );\n const failing = results.filter(r => !r.ok).map(r => r.name);\n return { ready: failing.length === 0, failing };\n }),\n };\n }),\n);\n"],"names":["Context","Effect","Layer","Ref","PROBE_TIMEOUT","HealthRegistryTag","Tag","HealthRegistryLayer","effect","gen","probes","make","register","probe","update","current","get","report","results","forEach","check","pipe","catchAllDefect","succeed","timeoutTo","duration","onTimeout","onSuccess","ok","map","name","concurrency","failing","filter","r","ready","length"],"mappings":"AAAA,SAASA,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,GAAG,QAAQ,SAAS;AAoDrD;;;;;;;;CAQC,GACD,MAAMC,gBAAgB;AAmBtB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,0BAA0BL,QAAQM,GAAG,CAAC;AAG9C;AAEL;;;;CAIC,GACD,OAAO,MAAMC,sBAAsDL,MAAMM,MAAM,CAC7EH,mBACAJ,OAAOQ,GAAG,CAAC;IACT,MAAMC,SAAS,OAAOP,IAAIQ,IAAI,CAAyB,EAAE;IAEzD,OAAO;QACLC,UAAUC,CAAAA,QAASV,IAAIW,MAAM,CAACJ,QAAQK,CAAAA,UAAW;uBAAIA;oBAASF;iBAAM;QAEpEH,QAAQP,IAAIa,GAAG,CAACN;QAEhBO,QAAQhB,OAAOQ,GAAG,CAAC;YACjB,MAAMM,UAAU,OAAOZ,IAAIa,GAAG,CAACN;YAC/B,MAAMQ,UAAU,OAAOjB,OAAOkB,OAAO,CACnCJ,SACAF,CAAAA,QACEA,MAAMO,KAAK,CAACC,IAAI,CACd,iEAAiE;gBACjEpB,OAAOqB,cAAc,CAAC,IAAMrB,OAAOsB,OAAO,CAAC,SAC3CtB,OAAOuB,SAAS,CAAC;oBACfC,UAAUrB;oBACVsB,WAAW,IAAM;oBACjBC,WAAW,CAACC,KAAgBA;gBAC9B,IACA3B,OAAO4B,GAAG,CAACD,CAAAA,KAAO,CAAA;wBAAEE,MAAMjB,MAAMiB,IAAI;wBAAEF;oBAAG,CAAA,KAE7C;gBAAEG,aAAa;YAAY;YAE7B,MAAMC,UAAUd,QAAQe,MAAM,CAACC,CAAAA,IAAK,CAACA,EAAEN,EAAE,EAAEC,GAAG,CAACK,CAAAA,IAAKA,EAAEJ,IAAI;YAC1D,OAAO;gBAAEK,OAAOH,QAAQI,MAAM,KAAK;gBAAGJ;YAAQ;QAChD;IACF;AACF,IACA"}
1
+ {"version":3,"sources":["../../src/health/health-registry.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from 'effect';\n\n/**\n * What a probe is talking to. Read by `GET /api/$service` to sort the wiring\n * document into stores, brokers and upstreams; readiness ignores it entirely.\n */\nexport type ProbeKind = 'datastore' | 'broker' | 'upstream';\n\n/**\n * One named readiness fact — \"can this service still reach Mongo?\".\n *\n * `check` is fully applied: whoever registers the probe has already closed over\n * the client handle, so the readiness route can run every probe without knowing\n * what any of them talk to. It must not fail — a probe that cannot answer\n * answers `false`; an unhandled defect in a probe would take down the very\n * endpoint that is supposed to report trouble.\n */\nexport interface HealthProbe {\n readonly name: string;\n /**\n * What sort of thing is on the other end. `datastore` is a Store's backing\n * engine, `broker` an exchange, `upstream` another service.\n */\n readonly kind: ProbeKind;\n /**\n * The **logical** names this probe covers — a Store's register name\n * (`catalog`, `saga`), an exchange (`entifix.events`), a service (`zitadel`).\n * Never a URI, never a database name: `catalog` is physically\n * `tenant_<organizationId>`, and a document naming those is an organization\n * enumerator (ADR 0031).\n *\n * It is a list rather than one string because a single connection can back\n * several Stores: marketplace-admin-service opens one Mongo client for the\n * `catalog` store and the co-deployed `transaction` slice's `saga` store.\n * Registering one probe per Store instead would put both names into the\n * readiness response's `failing` array, which must not change.\n *\n * The client package that opens the connection cannot know a Store's register\n * name, so the composition root passes it in — the one thing about a probe\n * that is not derivable from the client.\n */\n readonly targets: readonly string[];\n readonly check: Effect.Effect<boolean>;\n}\n\n/** The outcome of running every registered probe. */\nexport interface HealthReport {\n readonly ready: boolean;\n /** Names of the probes that answered `false`, in registration order. */\n readonly failing: readonly string[];\n}\n\n/**\n * How long a single probe may take before it counts as failing.\n *\n * This is not belt-and-braces. A driver that queues commands while its\n * connection is down (ioredis with `maxRetriesPerRequest`, for one) leaves the\n * probe pending instead of rejecting, so without a deadline `/api/health/ready`\n * *hangs* when a datastore disappears — the one moment it has to answer. A\n * probe that cannot answer in time is not ready, by definition.\n */\nconst PROBE_TIMEOUT = '2 seconds';\n\nexport interface HealthRegistry {\n /** Add a probe. Called by an infrastructure layer as it constructs a client. */\n readonly register: (probe: HealthProbe) => Effect.Effect<void>;\n /** Run every probe and summarise. Never fails. */\n readonly report: Effect.Effect<HealthReport>;\n /**\n * The registrations themselves, in registration order — what each probe is\n * for, without running any of them.\n *\n * This is what makes the wiring document and readiness generate from **one**\n * registration rather than from two lists that drift. Deliberately separate\n * from {@link report}: describing a service must never cost a round trip to\n * every datastore it has.\n */\n readonly probes: Effect.Effect<readonly HealthProbe[]>;\n}\n\n/**\n * DI tag for the readiness registry.\n *\n * The point of a registry rather than a hand-written list per service: a\n * `-service` that gains a datastore gains its readiness probe by merging that\n * client's probe layer, and nothing in the service's own code has to remember\n * to describe it. A list in `main.ts` drifts the first time someone adds a\n * dependency; this cannot, because the probe ships with the client.\n *\n * It lives here, in the contract layer, so `entifix-ts-*-client` packages can\n * register into it without depending on a shell.\n */\nexport class HealthRegistryTag extends Context.Service<\n HealthRegistryTag,\n HealthRegistry\n>()('HealthRegistryTag') {}\n\n/**\n * Live registry over a `Ref`. Provide it **once** per service and share the\n * instance (`Layer.provideMerge`), or the probes register into a registry the\n * readiness route never reads.\n */\nexport const HealthRegistryLayer: Layer.Layer<HealthRegistryTag> = Layer.effect(\n HealthRegistryTag,\n Effect.gen(function* () {\n const probes = yield* Ref.make<readonly HealthProbe[]>([]);\n\n return {\n register: probe => Ref.update(probes, current => [...current, probe]),\n\n probes: Ref.get(probes),\n\n report: Effect.gen(function* () {\n const current = yield* Ref.get(probes);\n const results = yield* Effect.forEach(\n current,\n probe =>\n probe.check.pipe(\n // A probe that throws is a failing probe, not a broken endpoint.\n Effect.catchDefect(() => Effect.succeed(false)),\n Effect.timeoutOrElse({\n duration: PROBE_TIMEOUT,\n orElse: () => Effect.succeed(false),\n }),\n Effect.map(ok => ({ name: probe.name, ok })),\n ),\n { concurrency: 'unbounded' },\n );\n const failing = results.filter(r => !r.ok).map(r => r.name);\n return { ready: failing.length === 0, failing };\n }),\n };\n }),\n);\n"],"names":["Context","Effect","Layer","Ref","PROBE_TIMEOUT","HealthRegistryTag","Service","HealthRegistryLayer","effect","gen","probes","make","register","probe","update","current","get","report","results","forEach","check","pipe","catchDefect","succeed","timeoutOrElse","duration","orElse","map","ok","name","concurrency","failing","filter","r","ready","length"],"mappings":"AAAA,SAASA,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,GAAG,QAAQ,SAAS;AAoDrD;;;;;;;;CAQC,GACD,MAAMC,gBAAgB;AAmBtB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,0BAA0BL,QAAQM,OAAO,GAGlD;AAAsB;AAE1B;;;;CAIC,GACD,OAAO,MAAMC,sBAAsDL,MAAMM,MAAM,CAC7EH,mBACAJ,OAAOQ,GAAG,CAAC;IACT,MAAMC,SAAS,OAAOP,IAAIQ,IAAI,CAAyB,EAAE;IAEzD,OAAO;QACLC,UAAUC,CAAAA,QAASV,IAAIW,MAAM,CAACJ,QAAQK,CAAAA,UAAW;uBAAIA;oBAASF;iBAAM;QAEpEH,QAAQP,IAAIa,GAAG,CAACN;QAEhBO,QAAQhB,OAAOQ,GAAG,CAAC;YACjB,MAAMM,UAAU,OAAOZ,IAAIa,GAAG,CAACN;YAC/B,MAAMQ,UAAU,OAAOjB,OAAOkB,OAAO,CACnCJ,SACAF,CAAAA,QACEA,MAAMO,KAAK,CAACC,IAAI,CACd,iEAAiE;gBACjEpB,OAAOqB,WAAW,CAAC,IAAMrB,OAAOsB,OAAO,CAAC,SACxCtB,OAAOuB,aAAa,CAAC;oBACnBC,UAAUrB;oBACVsB,QAAQ,IAAMzB,OAAOsB,OAAO,CAAC;gBAC/B,IACAtB,OAAO0B,GAAG,CAACC,CAAAA,KAAO,CAAA;wBAAEC,MAAMhB,MAAMgB,IAAI;wBAAED;oBAAG,CAAA,KAE7C;gBAAEE,aAAa;YAAY;YAE7B,MAAMC,UAAUb,QAAQc,MAAM,CAACC,CAAAA,IAAK,CAACA,EAAEL,EAAE,EAAED,GAAG,CAACM,CAAAA,IAAKA,EAAEJ,IAAI;YAC1D,OAAO;gBAAEK,OAAOH,QAAQI,MAAM,KAAK;gBAAGJ;YAAQ;QAChD;IACF;AACF,IACA"}
@@ -1,6 +1,6 @@
1
1
  import { ConfigurationClient } from '@entifix/core';
2
2
  import { Context } from 'effect';
3
- declare const ConfigurationRepositoryTag_base: Context.TagClass<ConfigurationRepositoryTag, "ConfigurationRepositoryTag", ConfigurationClient>;
3
+ declare const ConfigurationRepositoryTag_base: Context.ServiceClass<ConfigurationRepositoryTag, "ConfigurationRepositoryTag", ConfigurationClient>;
4
4
  export declare class ConfigurationRepositoryTag extends ConfigurationRepositoryTag_base {
5
5
  }
6
6
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"config.repository.d.ts","sourceRoot":"","sources":["../../src/repository/config.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;;AAEjC,qBAAa,0BAA2B,SAAQ,+BAEI;CAAG"}
1
+ {"version":3,"file":"config.repository.d.ts","sourceRoot":"","sources":["../../src/repository/config.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;;AAEjC,qBAAa,0BAA2B,SAAQ,+BAGf;CAAG"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'effect';
2
- export class ConfigurationRepositoryTag extends Context.Tag('ConfigurationRepositoryTag')() {
2
+ export class ConfigurationRepositoryTag extends Context.Service()('ConfigurationRepositoryTag') {
3
3
  }
4
4
 
5
5
  //# sourceMappingURL=config.repository.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/repository/config.repository.ts"],"sourcesContent":["import { ConfigurationClient } from '@entifix/core';\nimport { Context } from 'effect';\n\nexport class ConfigurationRepositoryTag extends Context.Tag(\n 'ConfigurationRepositoryTag',\n)<ConfigurationRepositoryTag, ConfigurationClient>() {}\n"],"names":["Context","ConfigurationRepositoryTag","Tag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAEjC,OAAO,MAAMC,mCAAmCD,QAAQE,GAAG,CACzD;AACoD"}
1
+ {"version":3,"sources":["../../src/repository/config.repository.ts"],"sourcesContent":["import { ConfigurationClient } from '@entifix/core';\nimport { Context } from 'effect';\n\nexport class ConfigurationRepositoryTag extends Context.Service<\n ConfigurationRepositoryTag,\n ConfigurationClient\n>()('ConfigurationRepositoryTag') {}\n"],"names":["Context","ConfigurationRepositoryTag","Service"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAEjC,OAAO,MAAMC,mCAAmCD,QAAQE,OAAO,GAG3D;AAA+B"}
@@ -2,7 +2,7 @@ import { type Entity, type EntityConstructor, type EntityLinkResolver } from '@e
2
2
  import { Context } from 'effect';
3
3
  import { ConfigurationRepositoryTag } from './config.repository.js';
4
4
  import { EntityRepositoryTag } from './entity.repository.js';
5
- declare const EntityLinkResolverTag_base: Context.TagClass<EntityLinkResolverTag, "EntityLinkResolverTag", EntityLinkResolver>;
5
+ declare const EntityLinkResolverTag_base: Context.ServiceClass<EntityLinkResolverTag, "EntityLinkResolverTag", EntityLinkResolver>;
6
6
  /**
7
7
  * DI tag for the {@link EntityLinkResolver} an environment provides so entity
8
8
  * links can materialize their targets. The composition root supplies a resolver
@@ -1 +1 @@
1
- {"version":3,"file":"entity-link-resolver.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity-link-resolver.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAU,MAAM,QAAQ,CAAC;AAEzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;;AAE7D;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,0BAGxC;CAAG;AAEN,8EAA8E;AAC9E,MAAM,MAAM,sBAAsB,GAAG,SAAS;IAC5C,iBAAiB,CAAC,MAAM,CAAC;IACzB,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,kBAAkB,EAAE,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,EAC/D,aAAa,EAAE,aAAa,CAAC,sBAAsB,CAAC,GACnD,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAiCxC"}
1
+ {"version":3,"file":"entity-link-resolver.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity-link-resolver.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAU,MAAM,QAAQ,CAAC;AAEzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;;AAE7D;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,0BAGf;CAAG;AAE/B,8EAA8E;AAC9E,MAAM,MAAM,sBAAsB,GAAG,SAAS;IAC5C,iBAAiB,CAAC,MAAM,CAAC;IACzB,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,kBAAkB,EAAE,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,EAC/D,aAAa,EAAE,aAAa,CAAC,sBAAsB,CAAC,GACnD,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAiCxC"}
@@ -7,7 +7,7 @@ import { EntityRepositoryTag } from './entity.repository.js';
7
7
  * backed by the appropriate adapters (REST on the web, Mongo on the backend),
8
8
  * and use-cases that follow links yield this tag rather than depending on any
9
9
  * concrete repository.
10
- */ export class EntityLinkResolverTag extends Context.Tag('EntityLinkResolverTag')() {
10
+ */ export class EntityLinkResolverTag extends Context.Service()('EntityLinkResolverTag') {
11
11
  }
12
12
  /**
13
13
  * Assembles an {@link EntityLinkResolverTag} context from repository adapters
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/repository/entity-link-resolver.repository.ts"],"sourcesContent":["import {\n type EntifixError,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityLinkResolver,\n} from '@entifix/core';\nimport { Context, Effect } from 'effect';\n\nimport { ConfigurationRepositoryTag } from './config.repository.js';\nimport { EntityRepositoryTag } from './entity.repository.js';\n\n/**\n * DI tag for the {@link EntityLinkResolver} an environment provides so entity\n * links can materialize their targets. The composition root supplies a resolver\n * backed by the appropriate adapters (REST on the web, Mongo on the backend),\n * and use-cases that follow links yield this tag rather than depending on any\n * concrete repository.\n */\nexport class EntityLinkResolverTag extends Context.Tag('EntityLinkResolverTag')<\n EntityLinkResolverTag,\n EntityLinkResolver\n>() {}\n\n/** Maps a link target constructor to the repository adapter that loads it. */\nexport type EntityLinkRegistration = readonly [\n EntityConstructor<Entity>,\n Context.Context<EntityRepositoryTag>,\n];\n\n/**\n * Assembles an {@link EntityLinkResolverTag} context from repository adapters\n * that already exist. Each registration's `get` is closed over the\n * configuration context, so the resulting resolver effects have no outstanding\n * requirements.\n *\n * It lives here, beside the tag, rather than with React's `useEntityLinkResolver`\n * — which is only a `useMemo` around it — because nothing about it is React.\n * A server component that imported it from the React package would pull that\n * package's whole barrel, hooks included, and Next rejects `useState` reaching\n * a server module. Framework-free code belongs at a framework-free layer.\n */\nexport function createEntityLinkResolver(\n configurationStore: Context.Context<ConfigurationRepositoryTag>,\n registrations: ReadonlyArray<EntityLinkRegistration>,\n): Context.Context<EntityLinkResolverTag> {\n const repositoryByConstructor = new Map(\n registrations.map(\n ([entityConstructor, repositoryContext]) =>\n [\n entityConstructor,\n Context.get(repositoryContext, EntityRepositoryTag),\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 link resolver registered for entity \"${entityConstructor.name}\"`,\n undefined,\n { entity: entityConstructor.name },\n ),\n );\n }\n return Effect.provide(\n repository.get(id),\n configurationStore,\n ) as Effect.Effect<never, EntifixError>;\n },\n };\n\n return Context.make(EntityLinkResolverTag, resolver);\n}\n"],"names":["EntifixLogicError","Context","Effect","EntityRepositoryTag","EntityLinkResolverTag","Tag","createEntityLinkResolver","configurationStore","registrations","repositoryByConstructor","Map","map","entityConstructor","repositoryContext","get","resolver","resolve","id","repository","fail","name","undefined","entity","provide","make"],"mappings":"AAAA,SAEEA,iBAAiB,QAIZ,gBAAgB;AACvB,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AAGzC,SAASC,mBAAmB,QAAQ,yBAAyB;AAE7D;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8BH,QAAQI,GAAG,CAAC;AAGlD;AAQL;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,yBACdC,kBAA+D,EAC/DC,aAAoD;IAEpD,MAAMC,0BAA0B,IAAIC,IAClCF,cAAcG,GAAG,CACf,CAAC,CAACC,mBAAmBC,kBAAkB,GACrC;YACED;YACAX,QAAQa,GAAG,CAACD,mBAAmBV;SAChC;IAIP,MAAMY,WAA+B;QACnCC,SAAQJ,iBAAiB,EAAEK,EAAE;YAC3B,MAAMC,aAAaT,wBAAwBK,GAAG,CAC5CF;YAEF,IAAI,CAACM,YAAY;gBACf,OAAOhB,OAAOiB,IAAI,CAChB,IAAInB,kBACF,CAAC,wCAAwC,EAAEY,kBAAkBQ,IAAI,CAAC,CAAC,CAAC,EACpEC,WACA;oBAAEC,QAAQV,kBAAkBQ,IAAI;gBAAC;YAGvC;YACA,OAAOlB,OAAOqB,OAAO,CACnBL,WAAWJ,GAAG,CAACG,KACfV;QAEJ;IACF;IAEA,OAAON,QAAQuB,IAAI,CAACpB,uBAAuBW;AAC7C"}
1
+ {"version":3,"sources":["../../src/repository/entity-link-resolver.repository.ts"],"sourcesContent":["import {\n type EntifixError,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityLinkResolver,\n} from '@entifix/core';\nimport { Context, Effect } from 'effect';\n\nimport { ConfigurationRepositoryTag } from './config.repository.js';\nimport { EntityRepositoryTag } from './entity.repository.js';\n\n/**\n * DI tag for the {@link EntityLinkResolver} an environment provides so entity\n * links can materialize their targets. The composition root supplies a resolver\n * backed by the appropriate adapters (REST on the web, Mongo on the backend),\n * and use-cases that follow links yield this tag rather than depending on any\n * concrete repository.\n */\nexport class EntityLinkResolverTag extends Context.Service<\n EntityLinkResolverTag,\n EntityLinkResolver\n>()('EntityLinkResolverTag') {}\n\n/** Maps a link target constructor to the repository adapter that loads it. */\nexport type EntityLinkRegistration = readonly [\n EntityConstructor<Entity>,\n Context.Context<EntityRepositoryTag>,\n];\n\n/**\n * Assembles an {@link EntityLinkResolverTag} context from repository adapters\n * that already exist. Each registration's `get` is closed over the\n * configuration context, so the resulting resolver effects have no outstanding\n * requirements.\n *\n * It lives here, beside the tag, rather than with React's `useEntityLinkResolver`\n * — which is only a `useMemo` around it — because nothing about it is React.\n * A server component that imported it from the React package would pull that\n * package's whole barrel, hooks included, and Next rejects `useState` reaching\n * a server module. Framework-free code belongs at a framework-free layer.\n */\nexport function createEntityLinkResolver(\n configurationStore: Context.Context<ConfigurationRepositoryTag>,\n registrations: ReadonlyArray<EntityLinkRegistration>,\n): Context.Context<EntityLinkResolverTag> {\n const repositoryByConstructor = new Map(\n registrations.map(\n ([entityConstructor, repositoryContext]) =>\n [\n entityConstructor,\n Context.get(repositoryContext, EntityRepositoryTag),\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 link resolver registered for entity \"${entityConstructor.name}\"`,\n undefined,\n { entity: entityConstructor.name },\n ),\n );\n }\n return Effect.provide(\n repository.get(id),\n configurationStore,\n ) as Effect.Effect<never, EntifixError>;\n },\n };\n\n return Context.make(EntityLinkResolverTag, resolver);\n}\n"],"names":["EntifixLogicError","Context","Effect","EntityRepositoryTag","EntityLinkResolverTag","Service","createEntityLinkResolver","configurationStore","registrations","repositoryByConstructor","Map","map","entityConstructor","repositoryContext","get","resolver","resolve","id","repository","fail","name","undefined","entity","provide","make"],"mappings":"AAAA,SAEEA,iBAAiB,QAIZ,gBAAgB;AACvB,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AAGzC,SAASC,mBAAmB,QAAQ,yBAAyB;AAE7D;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8BH,QAAQI,OAAO,GAGtD;AAA0B;AAQ9B;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,yBACdC,kBAA+D,EAC/DC,aAAoD;IAEpD,MAAMC,0BAA0B,IAAIC,IAClCF,cAAcG,GAAG,CACf,CAAC,CAACC,mBAAmBC,kBAAkB,GACrC;YACED;YACAX,QAAQa,GAAG,CAACD,mBAAmBV;SAChC;IAIP,MAAMY,WAA+B;QACnCC,SAAQJ,iBAAiB,EAAEK,EAAE;YAC3B,MAAMC,aAAaT,wBAAwBK,GAAG,CAC5CF;YAEF,IAAI,CAACM,YAAY;gBACf,OAAOhB,OAAOiB,IAAI,CAChB,IAAInB,kBACF,CAAC,wCAAwC,EAAEY,kBAAkBQ,IAAI,CAAC,CAAC,CAAC,EACpEC,WACA;oBAAEC,QAAQV,kBAAkBQ,IAAI;gBAAC;YAGvC;YACA,OAAOlB,OAAOqB,OAAO,CACnBL,WAAWJ,GAAG,CAACG,KACfV;QAEJ;IACF;IAEA,OAAON,QAAQuB,IAAI,CAACpB,uBAAuBW;AAC7C"}
@@ -1,12 +1,12 @@
1
1
  import { Entity, EntityId, EntityLoadRequest } from '@entifix/core';
2
2
  import { Context } from 'effect';
3
- declare const EntityIdTag_base: Context.TagClass<EntityIdTag, "EntityIdTag", EntityId>;
3
+ declare const EntityIdTag_base: Context.ServiceClass<EntityIdTag, "EntityIdTag", EntityId>;
4
4
  export declare class EntityIdTag extends EntityIdTag_base {
5
5
  }
6
- declare const EntityLoadRequestTag_base: Context.TagClass<EntityLoadRequestTag, "EntityLoadRequestTag", EntityLoadRequest<Entity>>;
6
+ declare const EntityLoadRequestTag_base: Context.ServiceClass<EntityLoadRequestTag, "EntityLoadRequestTag", EntityLoadRequest<Entity>>;
7
7
  export declare class EntityLoadRequestTag extends EntityLoadRequestTag_base {
8
8
  }
9
- declare const EntityTag_base: Context.TagClass<EntityTag, "EntityTag", Entity>;
9
+ declare const EntityTag_base: Context.ServiceClass<EntityTag, "EntityTag", Entity>;
10
10
  export declare class EntityTag extends EntityTag_base {
11
11
  }
12
12
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"entity-mixins.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity-mixins.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;;AAEjC,qBAAa,WAAY,SAAQ,gBAG9B;CAAG;;AAEN,qBAAa,oBAAqB,SAAQ,yBAGvC;CAAG;;AAEN,qBAAa,SAAU,SAAQ,cAA6C;CAAG"}
1
+ {"version":3,"file":"entity-mixins.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity-mixins.repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;;AAEjC,qBAAa,WAAY,SAAQ,gBAEhC;CAAG;;AAEJ,qBAAa,oBAAqB,SAAQ,yBAGf;CAAG;;AAE9B,qBAAa,SAAU,SAAQ,cAE9B;CAAG"}
@@ -1,9 +1,9 @@
1
1
  import { Context } from 'effect';
2
- export class EntityIdTag extends Context.Tag('EntityIdTag')() {
2
+ export class EntityIdTag extends Context.Service()('EntityIdTag') {
3
3
  }
4
- export class EntityLoadRequestTag extends Context.Tag('EntityLoadRequestTag')() {
4
+ export class EntityLoadRequestTag extends Context.Service()('EntityLoadRequestTag') {
5
5
  }
6
- export class EntityTag extends Context.Tag('EntityTag')() {
6
+ export class EntityTag extends Context.Service()('EntityTag') {
7
7
  }
8
8
 
9
9
  //# sourceMappingURL=entity-mixins.repository.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/repository/entity-mixins.repository.ts"],"sourcesContent":["import { Entity, EntityId, EntityLoadRequest } from '@entifix/core';\nimport { Context } from 'effect';\n\nexport class EntityIdTag extends Context.Tag('EntityIdTag')<\n EntityIdTag,\n EntityId\n>() {}\n\nexport class EntityLoadRequestTag extends Context.Tag('EntityLoadRequestTag')<\n EntityLoadRequestTag,\n EntityLoadRequest\n>() {}\n\nexport class EntityTag extends Context.Tag('EntityTag')<EntityTag, Entity>() {}\n"],"names":["Context","EntityIdTag","Tag","EntityLoadRequestTag","EntityTag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAEjC,OAAO,MAAMC,oBAAoBD,QAAQE,GAAG,CAAC;AAGxC;AAEL,OAAO,MAAMC,6BAA6BH,QAAQE,GAAG,CAAC;AAGjD;AAEL,OAAO,MAAME,kBAAkBJ,QAAQE,GAAG,CAAC;AAAmC"}
1
+ {"version":3,"sources":["../../src/repository/entity-mixins.repository.ts"],"sourcesContent":["import { Entity, EntityId, EntityLoadRequest } from '@entifix/core';\nimport { Context } from 'effect';\n\nexport class EntityIdTag extends Context.Service<EntityIdTag, EntityId>()(\n 'EntityIdTag',\n) {}\n\nexport class EntityLoadRequestTag extends Context.Service<\n EntityLoadRequestTag,\n EntityLoadRequest\n>()('EntityLoadRequestTag') {}\n\nexport class EntityTag extends Context.Service<EntityTag, Entity>()(\n 'EntityTag',\n) {}\n"],"names":["Context","EntityIdTag","Service","EntityLoadRequestTag","EntityTag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAEjC,OAAO,MAAMC,oBAAoBD,QAAQE,OAAO,GAC9C;AACC;AAEH,OAAO,MAAMC,6BAA6BH,QAAQE,OAAO,GAGrD;AAAyB;AAE7B,OAAO,MAAME,kBAAkBJ,QAAQE,OAAO,GAC5C;AACC"}
@@ -8,7 +8,7 @@ export interface EntityRepository {
8
8
  save<TEntity extends Entity>(entity: TEntity): Effect<TEntity, EntifixError, ConfigurationRepositoryTag>;
9
9
  delete<TEntity extends Entity>(entityOrId: EntityId | TEntity): Effect<void, EntifixError, ConfigurationRepositoryTag>;
10
10
  }
11
- declare const EntityRepositoryTag_base: Context.TagClass<EntityRepositoryTag, "EntityRepositoryTag", EntityRepository>;
11
+ declare const EntityRepositoryTag_base: Context.ServiceClass<EntityRepositoryTag, "EntityRepositoryTag", EntityRepository>;
12
12
  export declare class EntityRepositoryTag extends EntityRepositoryTag_base {
13
13
  }
14
14
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"entity.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity.repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,OAAO,SAAS,MAAM,EACxB,EAAE,EAAE,QAAQ,GACX,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IAC7D,IAAI,CAAC,OAAO,SAAS,MAAM,EACzB,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAClC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IACzE,IAAI,CAAC,OAAO,SAAS,MAAM,EACzB,MAAM,EAAE,OAAO,GACd,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IAC7D,MAAM,CAAC,OAAO,SAAS,MAAM,EAC3B,UAAU,EAAE,QAAQ,GAAG,OAAO,GAC7B,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;CAC3D;;AAED,qBAAa,mBAAoB,SAAQ,wBAGtC;CAAG"}
1
+ {"version":3,"file":"entity.repository.d.ts","sourceRoot":"","sources":["../../src/repository/entity.repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,OAAO,SAAS,MAAM,EACxB,EAAE,EAAE,QAAQ,GACX,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IAC7D,IAAI,CAAC,OAAO,SAAS,MAAM,EACzB,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAClC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IACzE,IAAI,CAAC,OAAO,SAAS,MAAM,EACzB,MAAM,EAAE,OAAO,GACd,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;IAC7D,MAAM,CAAC,OAAO,SAAS,MAAM,EAC3B,UAAU,EAAE,QAAQ,GAAG,OAAO,GAC7B,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;CAC3D;;AAED,qBAAa,mBAAoB,SAAQ,wBAGf;CAAG"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'effect';
2
- export class EntityRepositoryTag extends Context.Tag('EntityRepositoryTag')() {
2
+ export class EntityRepositoryTag extends Context.Service()('EntityRepositoryTag') {
3
3
  }
4
4
 
5
5
  //# sourceMappingURL=entity.repository.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/repository/entity.repository.ts"],"sourcesContent":["import type {\n EntifixError,\n Entity,\n EntityId,\n EntityLoadRequest,\n EntityPage,\n} from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\nimport { ConfigurationRepositoryTag } from './config.repository.js';\n\nexport interface EntityRepository {\n get<TEntity extends Entity>(\n id: EntityId,\n ): Effect<TEntity, EntifixError, ConfigurationRepositoryTag>;\n load<TEntity extends Entity>(\n request: EntityLoadRequest<TEntity>,\n ): Effect<EntityPage<TEntity>, EntifixError, ConfigurationRepositoryTag>;\n save<TEntity extends Entity>(\n entity: TEntity,\n ): Effect<TEntity, EntifixError, ConfigurationRepositoryTag>;\n delete<TEntity extends Entity>(\n entityOrId: EntityId | TEntity,\n ): Effect<void, EntifixError, ConfigurationRepositoryTag>;\n}\n\nexport class EntityRepositoryTag extends Context.Tag('EntityRepositoryTag')<\n EntityRepositoryTag,\n EntityRepository\n>() {}\n"],"names":["Context","EntityRepositoryTag","Tag"],"mappings":"AAOA,SAASA,OAAO,QAAQ,SAAS;AAoBjC,OAAO,MAAMC,4BAA4BD,QAAQE,GAAG,CAAC;AAGhD"}
1
+ {"version":3,"sources":["../../src/repository/entity.repository.ts"],"sourcesContent":["import type {\n EntifixError,\n Entity,\n EntityId,\n EntityLoadRequest,\n EntityPage,\n} from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\nimport { ConfigurationRepositoryTag } from './config.repository.js';\n\nexport interface EntityRepository {\n get<TEntity extends Entity>(\n id: EntityId,\n ): Effect<TEntity, EntifixError, ConfigurationRepositoryTag>;\n load<TEntity extends Entity>(\n request: EntityLoadRequest<TEntity>,\n ): Effect<EntityPage<TEntity>, EntifixError, ConfigurationRepositoryTag>;\n save<TEntity extends Entity>(\n entity: TEntity,\n ): Effect<TEntity, EntifixError, ConfigurationRepositoryTag>;\n delete<TEntity extends Entity>(\n entityOrId: EntityId | TEntity,\n ): Effect<void, EntifixError, ConfigurationRepositoryTag>;\n}\n\nexport class EntityRepositoryTag extends Context.Service<\n EntityRepositoryTag,\n EntityRepository\n>()('EntityRepositoryTag') {}\n"],"names":["Context","EntityRepositoryTag","Service"],"mappings":"AAOA,SAASA,OAAO,QAAQ,SAAS;AAoBjC,OAAO,MAAMC,4BAA4BD,QAAQE,OAAO,GAGpD;AAAwB"}
@@ -32,7 +32,7 @@ export interface OneTimeTokenStore {
32
32
  */
33
33
  consume(purpose: TokenPurpose, token: string): Effect<string, EntifixError>;
34
34
  }
35
- declare const OneTimeTokenStoreTag_base: Context.TagClass<OneTimeTokenStoreTag, "OneTimeTokenStoreTag", OneTimeTokenStore>;
35
+ declare const OneTimeTokenStoreTag_base: Context.ServiceClass<OneTimeTokenStoreTag, "OneTimeTokenStoreTag", OneTimeTokenStore>;
36
36
  /** DI tag the composition root binds to a concrete {@link OneTimeTokenStore}. */
37
37
  export declare class OneTimeTokenStoreTag extends OneTimeTokenStoreTag_base {
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"one-time-token-store.d.ts","sourceRoot":"","sources":["../../src/sessions/one-time-token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,KAAK,CACH,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC7E;;AAED,iFAAiF;AACjF,qBAAa,oBAAqB,SAAQ,yBAGvC;CAAG"}
1
+ {"version":3,"file":"one-time-token-store.d.ts","sourceRoot":"","sources":["../../src/sessions/one-time-token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,KAAK,CACH,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC7E;;AAED,iFAAiF;AACjF,qBAAa,oBAAqB,SAAQ,yBAGf;CAAG"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'effect';
2
- /** DI tag the composition root binds to a concrete {@link OneTimeTokenStore}. */ export class OneTimeTokenStoreTag extends Context.Tag('OneTimeTokenStoreTag')() {
2
+ /** DI tag the composition root binds to a concrete {@link OneTimeTokenStore}. */ export class OneTimeTokenStoreTag extends Context.Service()('OneTimeTokenStoreTag') {
3
3
  }
4
4
 
5
5
  //# sourceMappingURL=one-time-token-store.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sessions/one-time-token-store.ts"],"sourcesContent":["import type { EntifixError } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * What a token is for. Namespacing them keeps a token minted for one purpose\n * from being redeemed as another — a password-reset token must not double as\n * an email-verification token.\n */\nexport type TokenPurpose = string;\n\n/**\n * Short-lived, single-use tokens handed to a user out of band (a link in an\n * email) and redeemed once.\n *\n * The store keeps only a HASH of the token, exactly as it would a password: the\n * plaintext exists in the response to {@link OneTimeTokenStore.issue} and in the\n * message sent to the user, and nowhere else. A store dump then yields nothing\n * an attacker can redeem.\n */\nexport interface OneTimeTokenStore {\n /**\n * Mint a token for `subject`, returning the plaintext exactly once.\n *\n * The caller must send it onward immediately; there is no way to read it back.\n */\n issue(\n purpose: TokenPurpose,\n subject: string,\n ttlSeconds: number,\n ): Effect<string, EntifixError>;\n /**\n * Redeem a token, returning the subject it was minted for.\n *\n * Consuming deletes it, so a link that leaks later — from a browser history, a\n * mail archive, a proxy log — is already spent. Fails when the token is\n * unknown, expired, or already used; the caller must not distinguish those.\n */\n consume(purpose: TokenPurpose, token: string): Effect<string, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link OneTimeTokenStore}. */\nexport class OneTimeTokenStoreTag extends Context.Tag('OneTimeTokenStoreTag')<\n OneTimeTokenStoreTag,\n OneTimeTokenStore\n>() {}\n"],"names":["Context","OneTimeTokenStoreTag","Tag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAwCjC,+EAA+E,GAC/E,OAAO,MAAMC,6BAA6BD,QAAQE,GAAG,CAAC;AAGjD"}
1
+ {"version":3,"sources":["../../src/sessions/one-time-token-store.ts"],"sourcesContent":["import type { EntifixError } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * What a token is for. Namespacing them keeps a token minted for one purpose\n * from being redeemed as another — a password-reset token must not double as\n * an email-verification token.\n */\nexport type TokenPurpose = string;\n\n/**\n * Short-lived, single-use tokens handed to a user out of band (a link in an\n * email) and redeemed once.\n *\n * The store keeps only a HASH of the token, exactly as it would a password: the\n * plaintext exists in the response to {@link OneTimeTokenStore.issue} and in the\n * message sent to the user, and nowhere else. A store dump then yields nothing\n * an attacker can redeem.\n */\nexport interface OneTimeTokenStore {\n /**\n * Mint a token for `subject`, returning the plaintext exactly once.\n *\n * The caller must send it onward immediately; there is no way to read it back.\n */\n issue(\n purpose: TokenPurpose,\n subject: string,\n ttlSeconds: number,\n ): Effect<string, EntifixError>;\n /**\n * Redeem a token, returning the subject it was minted for.\n *\n * Consuming deletes it, so a link that leaks later — from a browser history, a\n * mail archive, a proxy log — is already spent. Fails when the token is\n * unknown, expired, or already used; the caller must not distinguish those.\n */\n consume(purpose: TokenPurpose, token: string): Effect<string, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link OneTimeTokenStore}. */\nexport class OneTimeTokenStoreTag extends Context.Service<\n OneTimeTokenStoreTag,\n OneTimeTokenStore\n>()('OneTimeTokenStoreTag') {}\n"],"names":["Context","OneTimeTokenStoreTag","Service"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAwCjC,+EAA+E,GAC/E,OAAO,MAAMC,6BAA6BD,QAAQE,OAAO,GAGrD;AAAyB"}
@@ -137,7 +137,7 @@ export interface SessionStore {
137
137
  */
138
138
  revokeAllForUserExcept(userId: EntityId, keepSessionId: string): Effect<void, EntifixError>;
139
139
  }
140
- declare const SessionStoreTag_base: Context.TagClass<SessionStoreTag, "SessionStoreTag", SessionStore>;
140
+ declare const SessionStoreTag_base: Context.ServiceClass<SessionStoreTag, "SessionStoreTag", SessionStore>;
141
141
  /** DI tag the composition root binds to a concrete {@link SessionStore}. */
142
142
  export declare class SessionStoreTag extends SessionStoreTag_base {
143
143
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/sessions/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;;GAKG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,kDAAkD;AAClD,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,sDAAsD;IACtD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,MAAM,CACJ,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,sBAAsB,GAC/B,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC,0EAA0E;IAC1E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC7D;;;;OAIG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7E;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,SAAS,aAAa,EAAE,EAAE,YAAY,CAAC,CAAC;IAC9E,yEAAyE;IACzE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACtD,0EAA0E;IAC1E,gBAAgB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC/D;;;;;;OAMG;IACH,sBAAsB,CACpB,MAAM,EAAE,QAAQ,EAChB,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;CAC/B;;AAED,4EAA4E;AAC5E,qBAAa,eAAgB,SAAQ,oBAGlC;CAAG"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/sessions/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;;GAKG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,kDAAkD;AAClD,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,sDAAsD;IACtD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,MAAM,CACJ,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,sBAAsB,GAC/B,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC,0EAA0E;IAC1E,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC7D;;;;OAIG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7E;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,SAAS,aAAa,EAAE,EAAE,YAAY,CAAC,CAAC;IAC9E,yEAAyE;IACzE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACtD,0EAA0E;IAC1E,gBAAgB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC/D;;;;;;OAMG;IACH,sBAAsB,CACpB,MAAM,EAAE,QAAQ,EAChB,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;CAC/B;;AAED,4EAA4E;AAC5E,qBAAa,eAAgB,SAAQ,oBAGf;CAAG"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'effect';
2
- /** DI tag the composition root binds to a concrete {@link SessionStore}. */ export class SessionStoreTag extends Context.Tag('SessionStoreTag')() {
2
+ /** DI tag the composition root binds to a concrete {@link SessionStore}. */ export class SessionStoreTag extends Context.Service()('SessionStoreTag') {
3
3
  }
4
4
 
5
5
  //# sourceMappingURL=session-store.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sessions/session-store.ts"],"sourcesContent":["import type { EntifixError, EntityId } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * The subject state a session carries. Deliberately structural (no `Principal`\n * import — that lives in the authn layer above this one) so any domain can\n * store its authenticated subject here. A resolved `Principal` is assignable to\n * it as-is.\n */\n/**\n * What the session was opened from, for display and for notifying the owner.\n *\n * **A label, never an authorization input.** Every field is client-supplied or\n * client-derived, so a copied cookie copies the device — which is acceptable\n * precisely because nothing here decides anything. It powers \"you are signed in\n * on these devices\" and \"a new device signed in\"; it never grants or refuses.\n */\nexport interface DeviceContext {\n /** Opaque, server-minted id carried in a long-lived cookie. */\n readonly deviceId: string;\n readonly browser?: string;\n readonly os?: string;\n /** `desktop` / `mobile` / `tablet`, as the user-agent reports it. */\n readonly type?: string;\n /** Truncated to a /24 — enough for \"somewhere else\", less PII at rest. */\n readonly ip?: string;\n}\n\nexport interface SessionData {\n readonly userId: EntityId;\n /** Upstream identity subject (e.g. an IdP `sub`, or the canonical user id). */\n readonly subject: string;\n readonly roles: readonly string[];\n readonly attributes: Readonly<Record<string, unknown>>;\n /**\n * The organization this session is acting for. It lives here rather than on\n * the user because a party may belong to several organizations, so switching\n * is a session operation that re-mints the access token — modelling it on the\n * user would make someone serving two vendors keep two accounts.\n *\n * Absent for a session with no organization: a buyer, or an operator, who\n * holds no tenant scope by default.\n */\n readonly activeOrganizationId?: string;\n /**\n * Which population this session belongs to — buyer, vendor staff, or platform\n * staff. Resolved once when the session opens and carried from there into\n * every access token minted from it, so no service re-derives it per request.\n *\n * A plain string for the same reason `roles` is: the closed set lives in the\n * business layer above this one.\n */\n readonly partyRole?: string;\n /**\n * The party behind the account — the `Individual` id, not the account id.\n *\n * Resolved once when the session opens, from the only store that holds the\n * user → party mapping, and carried from here into every access token: a\n * service scoping a read to *whose records these are* asks about a party, and\n * has no way to make that hop itself.\n *\n * Absent for an account with no party record, and a consumer must read that\n * as \"no party\" rather than as \"any party\".\n */\n readonly partyId?: string;\n /**\n * The business domains {@link SessionData.activeOrganizationId} is\n * provisioned for. Resolved once when the session opens and carried from here\n * into every access token minted from it, so no service re-reads an\n * entitlement per request — the same arrangement `partyRole` already has.\n *\n * Absent for a session with no organization, which is a different statement\n * from an empty list: the ceiling does not apply rather than denying\n * everything.\n */\n readonly entitlements?: readonly string[];\n /** Absent for sessions opened by a non-browser caller. */\n readonly device?: DeviceContext;\n}\n\n/** A stored session as read back — {@link SessionData} plus lifecycle metadata. */\nexport interface SessionRecord extends SessionData {\n /** The opaque session id this record is keyed by; the revocation handle. */\n readonly sessionId: string;\n readonly createdAt: string;\n /** End of the current sliding window; moves forward on every {@link SessionStore.touch}. */\n readonly expiresAt: string;\n /**\n * The ceiling, stamped at creation and never moved. A sliding window with no\n * ceiling renews forever, so this is what guarantees every session eventually\n * ends — including one an attacker is keeping warm.\n */\n readonly absoluteExpiresAt: string;\n}\n\n/** The two durations a session is opened with. */\nexport interface SessionLifetimeSeconds {\n /** Sliding window, renewed on each touch. */\n readonly idleTtlSeconds: number;\n /** Ceiling measured from creation; never extended. */\n readonly absoluteTtlSeconds: number;\n}\n\n/**\n * The session persistence seam. An opaque, revocable, TTL-bounded store keyed by\n * a server-minted session id — the single source of truth every service can\n * read a live session from, and the handle an admin/logout revokes through.\n *\n * Framework- and store-agnostic: a Redis adapter backs it in production, a fake\n * in tests, and the use-cases that consume {@link SessionStoreTag} never change.\n */\nexport interface SessionStore {\n /** Persist a new session and return its freshly minted opaque id. */\n create(\n data: SessionData,\n lifetime: SessionLifetimeSeconds,\n ): Effect<string, EntifixError>;\n /** Read a live session, failing if it is unknown, revoked, or expired. */\n read(sessionId: string): Effect<SessionRecord, EntifixError>;\n /**\n * Renew the sliding window, **clamped to the session's own\n * {@link SessionRecord.absoluteExpiresAt}**. Fails once the ceiling is passed,\n * which is how a long-lived session finally ends.\n */\n touch(sessionId: string, idleTtlSeconds: number): Effect<void, EntifixError>;\n /**\n * Every live session a user holds.\n *\n * The read side of the index `revokeAllForUser` already walks. Without it\n * there is no way to show someone where they are signed in, and \"sign out\n * everywhere\" is an action with no visible subject.\n */\n listForUser(userId: EntityId): Effect<readonly SessionRecord[], EntifixError>;\n /** Revoke a single session — the next {@link read} fails immediately. */\n revoke(sessionId: string): Effect<void, EntifixError>;\n /** Revoke every session for a user (e.g. password change, admin kick). */\n revokeAllForUser(userId: EntityId): Effect<void, EntifixError>;\n /**\n * Revoke every session for a user EXCEPT one.\n *\n * What a password change should do: end every other session, but leave the\n * person who just changed it signed in. `revokeAllForUser` would log them out\n * of the screen they are standing on.\n */\n revokeAllForUserExcept(\n userId: EntityId,\n keepSessionId: string,\n ): Effect<void, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link SessionStore}. */\nexport class SessionStoreTag extends Context.Tag('SessionStoreTag')<\n SessionStoreTag,\n SessionStore\n>() {}\n"],"names":["Context","SessionStoreTag","Tag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAsJjC,0EAA0E,GAC1E,OAAO,MAAMC,wBAAwBD,QAAQE,GAAG,CAAC;AAG5C"}
1
+ {"version":3,"sources":["../../src/sessions/session-store.ts"],"sourcesContent":["import type { EntifixError, EntityId } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * The subject state a session carries. Deliberately structural (no `Principal`\n * import — that lives in the authn layer above this one) so any domain can\n * store its authenticated subject here. A resolved `Principal` is assignable to\n * it as-is.\n */\n/**\n * What the session was opened from, for display and for notifying the owner.\n *\n * **A label, never an authorization input.** Every field is client-supplied or\n * client-derived, so a copied cookie copies the device — which is acceptable\n * precisely because nothing here decides anything. It powers \"you are signed in\n * on these devices\" and \"a new device signed in\"; it never grants or refuses.\n */\nexport interface DeviceContext {\n /** Opaque, server-minted id carried in a long-lived cookie. */\n readonly deviceId: string;\n readonly browser?: string;\n readonly os?: string;\n /** `desktop` / `mobile` / `tablet`, as the user-agent reports it. */\n readonly type?: string;\n /** Truncated to a /24 — enough for \"somewhere else\", less PII at rest. */\n readonly ip?: string;\n}\n\nexport interface SessionData {\n readonly userId: EntityId;\n /** Upstream identity subject (e.g. an IdP `sub`, or the canonical user id). */\n readonly subject: string;\n readonly roles: readonly string[];\n readonly attributes: Readonly<Record<string, unknown>>;\n /**\n * The organization this session is acting for. It lives here rather than on\n * the user because a party may belong to several organizations, so switching\n * is a session operation that re-mints the access token — modelling it on the\n * user would make someone serving two vendors keep two accounts.\n *\n * Absent for a session with no organization: a buyer, or an operator, who\n * holds no tenant scope by default.\n */\n readonly activeOrganizationId?: string;\n /**\n * Which population this session belongs to — buyer, vendor staff, or platform\n * staff. Resolved once when the session opens and carried from there into\n * every access token minted from it, so no service re-derives it per request.\n *\n * A plain string for the same reason `roles` is: the closed set lives in the\n * business layer above this one.\n */\n readonly partyRole?: string;\n /**\n * The party behind the account — the `Individual` id, not the account id.\n *\n * Resolved once when the session opens, from the only store that holds the\n * user → party mapping, and carried from here into every access token: a\n * service scoping a read to *whose records these are* asks about a party, and\n * has no way to make that hop itself.\n *\n * Absent for an account with no party record, and a consumer must read that\n * as \"no party\" rather than as \"any party\".\n */\n readonly partyId?: string;\n /**\n * The business domains {@link SessionData.activeOrganizationId} is\n * provisioned for. Resolved once when the session opens and carried from here\n * into every access token minted from it, so no service re-reads an\n * entitlement per request — the same arrangement `partyRole` already has.\n *\n * Absent for a session with no organization, which is a different statement\n * from an empty list: the ceiling does not apply rather than denying\n * everything.\n */\n readonly entitlements?: readonly string[];\n /** Absent for sessions opened by a non-browser caller. */\n readonly device?: DeviceContext;\n}\n\n/** A stored session as read back — {@link SessionData} plus lifecycle metadata. */\nexport interface SessionRecord extends SessionData {\n /** The opaque session id this record is keyed by; the revocation handle. */\n readonly sessionId: string;\n readonly createdAt: string;\n /** End of the current sliding window; moves forward on every {@link SessionStore.touch}. */\n readonly expiresAt: string;\n /**\n * The ceiling, stamped at creation and never moved. A sliding window with no\n * ceiling renews forever, so this is what guarantees every session eventually\n * ends — including one an attacker is keeping warm.\n */\n readonly absoluteExpiresAt: string;\n}\n\n/** The two durations a session is opened with. */\nexport interface SessionLifetimeSeconds {\n /** Sliding window, renewed on each touch. */\n readonly idleTtlSeconds: number;\n /** Ceiling measured from creation; never extended. */\n readonly absoluteTtlSeconds: number;\n}\n\n/**\n * The session persistence seam. An opaque, revocable, TTL-bounded store keyed by\n * a server-minted session id — the single source of truth every service can\n * read a live session from, and the handle an admin/logout revokes through.\n *\n * Framework- and store-agnostic: a Redis adapter backs it in production, a fake\n * in tests, and the use-cases that consume {@link SessionStoreTag} never change.\n */\nexport interface SessionStore {\n /** Persist a new session and return its freshly minted opaque id. */\n create(\n data: SessionData,\n lifetime: SessionLifetimeSeconds,\n ): Effect<string, EntifixError>;\n /** Read a live session, failing if it is unknown, revoked, or expired. */\n read(sessionId: string): Effect<SessionRecord, EntifixError>;\n /**\n * Renew the sliding window, **clamped to the session's own\n * {@link SessionRecord.absoluteExpiresAt}**. Fails once the ceiling is passed,\n * which is how a long-lived session finally ends.\n */\n touch(sessionId: string, idleTtlSeconds: number): Effect<void, EntifixError>;\n /**\n * Every live session a user holds.\n *\n * The read side of the index `revokeAllForUser` already walks. Without it\n * there is no way to show someone where they are signed in, and \"sign out\n * everywhere\" is an action with no visible subject.\n */\n listForUser(userId: EntityId): Effect<readonly SessionRecord[], EntifixError>;\n /** Revoke a single session — the next {@link read} fails immediately. */\n revoke(sessionId: string): Effect<void, EntifixError>;\n /** Revoke every session for a user (e.g. password change, admin kick). */\n revokeAllForUser(userId: EntityId): Effect<void, EntifixError>;\n /**\n * Revoke every session for a user EXCEPT one.\n *\n * What a password change should do: end every other session, but leave the\n * person who just changed it signed in. `revokeAllForUser` would log them out\n * of the screen they are standing on.\n */\n revokeAllForUserExcept(\n userId: EntityId,\n keepSessionId: string,\n ): Effect<void, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link SessionStore}. */\nexport class SessionStoreTag extends Context.Service<\n SessionStoreTag,\n SessionStore\n>()('SessionStoreTag') {}\n"],"names":["Context","SessionStoreTag","Service"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAsJjC,0EAA0E,GAC1E,OAAO,MAAMC,wBAAwBD,QAAQE,OAAO,GAGhD;AAAoB"}
@@ -56,7 +56,7 @@ export interface ShutdownRegistry {
56
56
  */
57
57
  readonly drain: Effect.Effect<void>;
58
58
  }
59
- declare const ShutdownRegistryTag_base: Context.TagClass<ShutdownRegistryTag, "ShutdownRegistryTag", ShutdownRegistry>;
59
+ declare const ShutdownRegistryTag_base: Context.ServiceClass<ShutdownRegistryTag, "ShutdownRegistryTag", ShutdownRegistry>;
60
60
  /**
61
61
  * DI tag for the shutdown registry.
62
62
  *
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown-registry.d.ts","sourceRoot":"","sources":["../../src/shutdown/shutdown-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,mCAAoC,CAAC;AAEjE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACnC;AAaD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACrC;;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,wBAGtC;CAAG;AA0BN;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,QAAO;IACtC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;CAsCnB,CAAC"}
1
+ {"version":3,"file":"shutdown-registry.d.ts","sourceRoot":"","sources":["../../src/shutdown/shutdown-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,mCAAoC,CAAC;AAEjE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACnC;AAaD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACrC;;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,wBAGf;CAAG;AA0B7B;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,QAAO;IACtC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;CAsCnB,CAAC"}
@@ -28,7 +28,7 @@ import { Cause, Context, Effect } from 'effect';
28
28
  * In the contract layer beside `HealthRegistryTag` so an
29
29
  * `entifix-ts-*-client` package can register a hook without depending on a
30
30
  * shell.
31
- */ export class ShutdownRegistryTag extends Context.Tag('ShutdownRegistryTag')() {
31
+ */ export class ShutdownRegistryTag extends Context.Service()('ShutdownRegistryTag') {
32
32
  }
33
33
  /**
34
34
  * Runs one hook and swallows whatever it does.
@@ -37,7 +37,7 @@ import { Cause, Context, Effect } from 'effect';
37
37
  * hook did not do its job, both leave the ones behind it still worth running,
38
38
  * and neither is something a terminating process can act on. `Cause.pretty`
39
39
  * keeps them distinguishable in Loki, where someone reading them actually is.
40
- */ const runHook = (hook)=>hook.run.pipe(Effect.timeout(HOOK_TIMEOUT), Effect.catchAllCause((cause)=>Effect.logError('shutdown hook did not complete').pipe(// Annotations, not a rendered message: the tooling logger emits these
40
+ */ const runHook = (hook)=>hook.run.pipe(Effect.timeout(HOOK_TIMEOUT), Effect.catchCause((cause)=>Effect.logError('shutdown hook did not complete').pipe(// Annotations, not a rendered message: the tooling logger emits these
41
41
  // as structured fields, so which hook stalled a rollout is queryable.
42
42
  Effect.annotateLogs({
43
43
  hook: hook.name,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shutdown/shutdown-registry.ts"],"sourcesContent":["import { Cause, Context, Effect } from 'effect';\n\n/**\n * The order a shutdown runs in (ADR 0030).\n *\n * **`stop-intake`** stops the process taking new work — cancelling the bus's\n * consumers, interrupting a polling daemon. **`flush`** carries out what is\n * already committed but not yet delivered: the outbox relay's final pass. The\n * two are separate phases rather than one list because they are ordered against\n * each other and not merely against the connections closing — flushing while\n * consumers are still folding events would race a drain against fresh work, and\n * the relay would go on finding entries the handlers keep writing.\n */\nexport const SHUTDOWN_PHASES = ['stop-intake', 'flush'] as const;\n\nexport type ShutdownPhase = (typeof SHUTDOWN_PHASES)[number];\n\n/**\n * One thing to do before this process's connections close.\n *\n * `run` is fully applied, exactly like `HealthProbe.check`: whoever\n * registers the hook has already closed over the connector, the fiber or the\n * store, so the drain can run every hook without knowing what any of them\n * touches.\n */\nexport interface ShutdownHook {\n /** Logged when the hook runs, and when it fails. Never rendered to a caller. */\n readonly name: string;\n readonly phase: ShutdownPhase;\n readonly run: Effect.Effect<void>;\n}\n\n/**\n * How long one hook may take before the drain moves on without it.\n *\n * Not belt-and-braces. Kubernetes sends SIGKILL at the end of\n * `terminationGracePeriodSeconds` whatever the process is doing, so an\n * unbounded hook does not buy time — it spends the budget of the hooks behind\n * it and then loses them entirely. A hook that cannot finish in time has\n * already failed; the difference is whether the *next* one gets to run.\n */\nconst HOOK_TIMEOUT = '10 seconds';\n\n/**\n * What a service does on its way out, registered by whoever opened the thing\n * that needs draining.\n *\n * A sibling of `HealthRegistry` and `WiringRegistry`, and for the same reason:\n * a `-service` that gains a bus gains its consumer drain by merging that\n * adapter's layer, and nothing in the service's own `main.ts` has to remember\n * to list it. A hand-written sequence per composition root drifts the first\n * time someone adds a dependency; this cannot, because the hook ships with the\n * client that needs it.\n */\nexport interface ShutdownRegistry {\n /** Add a hook. Called by an adapter layer as it constructs its client. */\n readonly register: (hook: ShutdownHook) => Effect.Effect<void>;\n /**\n * Whether this process has begun terminating.\n *\n * Read by `GET /api/health/ready`, which answers `503` from here **before**\n * it consults any probe: nothing is failing, the process is leaving.\n */\n readonly terminating: Effect.Effect<boolean>;\n /**\n * Run every hook, phase by phase, in registration order. Never fails.\n *\n * Called from a `Layer` finalizer, which is what orders it: the drain layer\n * is built after the composition root, so it is released before it, and the\n * connections a hook needs are still open while it runs.\n */\n readonly drain: Effect.Effect<void>;\n}\n\n/**\n * DI tag for the shutdown registry.\n *\n * In the contract layer beside `HealthRegistryTag` so an\n * `entifix-ts-*-client` package can register a hook without depending on a\n * shell.\n */\nexport class ShutdownRegistryTag extends Context.Tag('ShutdownRegistryTag')<\n ShutdownRegistryTag,\n ShutdownRegistry\n>() {}\n\n/**\n * Runs one hook and swallows whatever it does.\n *\n * A failure and a timeout are logged the same way on purpose: both mean this\n * hook did not do its job, both leave the ones behind it still worth running,\n * and neither is something a terminating process can act on. `Cause.pretty`\n * keeps them distinguishable in Loki, where someone reading them actually is.\n */\nconst runHook = (hook: ShutdownHook): Effect.Effect<void> =>\n hook.run.pipe(\n Effect.timeout(HOOK_TIMEOUT),\n Effect.catchAllCause(cause =>\n Effect.logError('shutdown hook did not complete').pipe(\n // Annotations, not a rendered message: the tooling logger emits these\n // as structured fields, so which hook stalled a rollout is queryable.\n Effect.annotateLogs({\n hook: hook.name,\n phase: hook.phase,\n cause: Cause.pretty(cause),\n }),\n ),\n ),\n );\n\n/**\n * Builds the registry and the synchronous latch that flips it.\n *\n * Two halves for two callers, the `makeAmqpConnector` shape: the\n * `registry` goes into the layer graph, while `begin` is called from a\n * `process.on('SIGTERM')` handler — outside Effect, in the same tick the\n * runtime starts interrupting — so `/api/health/ready` stops claiming to be\n * ready the instant the signal lands rather than whenever the finalizers get\n * around to running.\n */\nexport const makeShutdownRegistry = (): {\n registry: ShutdownRegistry;\n begin: () => void;\n} => {\n const hooks: ShutdownHook[] = [];\n let terminating = false;\n\n const registry: ShutdownRegistry = {\n register: hook =>\n Effect.sync(() => {\n hooks.push(hook);\n }),\n\n terminating: Effect.sync(() => terminating),\n\n drain: Effect.gen(function* () {\n terminating = true;\n yield* Effect.logInfo('draining before shutdown').pipe(\n Effect.annotateLogs({ hooks: hooks.length }),\n );\n for (const phase of SHUTDOWN_PHASES) {\n // Sequential, and in registration order: `stop-intake` before `flush`\n // is the whole contract, and within a phase a later hook may depend on\n // an earlier one having stopped.\n yield* Effect.forEach(\n hooks.filter(hook => hook.phase === phase),\n runHook,\n { discard: true },\n );\n }\n yield* Effect.logInfo('drained');\n }),\n };\n\n return {\n registry,\n begin: () => {\n terminating = true;\n },\n };\n};\n"],"names":["Cause","Context","Effect","SHUTDOWN_PHASES","HOOK_TIMEOUT","ShutdownRegistryTag","Tag","runHook","hook","run","pipe","timeout","catchAllCause","cause","logError","annotateLogs","name","phase","pretty","makeShutdownRegistry","hooks","terminating","registry","register","sync","push","drain","gen","logInfo","length","forEach","filter","discard","begin"],"mappings":"AAAA,SAASA,KAAK,EAAEC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AAEhD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,kBAAkB;IAAC;IAAe;CAAQ,CAAU;AAmBjE;;;;;;;;CAQC,GACD,MAAMC,eAAe;AAiCrB;;;;;;CAMC,GACD,OAAO,MAAMC,4BAA4BJ,QAAQK,GAAG,CAAC;AAGhD;AAEL;;;;;;;CAOC,GACD,MAAMC,UAAU,CAACC,OACfA,KAAKC,GAAG,CAACC,IAAI,CACXR,OAAOS,OAAO,CAACP,eACfF,OAAOU,aAAa,CAACC,CAAAA,QACnBX,OAAOY,QAAQ,CAAC,kCAAkCJ,IAAI,CACpD,sEAAsE;QACtE,sEAAsE;QACtER,OAAOa,YAAY,CAAC;YAClBP,MAAMA,KAAKQ,IAAI;YACfC,OAAOT,KAAKS,KAAK;YACjBJ,OAAOb,MAAMkB,MAAM,CAACL;QACtB;AAKR;;;;;;;;;CASC,GACD,OAAO,MAAMM,uBAAuB;IAIlC,MAAMC,QAAwB,EAAE;IAChC,IAAIC,cAAc;IAElB,MAAMC,WAA6B;QACjCC,UAAUf,CAAAA,OACRN,OAAOsB,IAAI,CAAC;gBACVJ,MAAMK,IAAI,CAACjB;YACb;QAEFa,aAAanB,OAAOsB,IAAI,CAAC,IAAMH;QAE/BK,OAAOxB,OAAOyB,GAAG,CAAC;YAChBN,cAAc;YACd,OAAOnB,OAAO0B,OAAO,CAAC,4BAA4BlB,IAAI,CACpDR,OAAOa,YAAY,CAAC;gBAAEK,OAAOA,MAAMS,MAAM;YAAC;YAE5C,KAAK,MAAMZ,SAASd,gBAAiB;gBACnC,sEAAsE;gBACtE,uEAAuE;gBACvE,iCAAiC;gBACjC,OAAOD,OAAO4B,OAAO,CACnBV,MAAMW,MAAM,CAACvB,CAAAA,OAAQA,KAAKS,KAAK,KAAKA,QACpCV,SACA;oBAAEyB,SAAS;gBAAK;YAEpB;YACA,OAAO9B,OAAO0B,OAAO,CAAC;QACxB;IACF;IAEA,OAAO;QACLN;QACAW,OAAO;YACLZ,cAAc;QAChB;IACF;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/shutdown/shutdown-registry.ts"],"sourcesContent":["import { Cause, Context, Effect } from 'effect';\n\n/**\n * The order a shutdown runs in (ADR 0030).\n *\n * **`stop-intake`** stops the process taking new work — cancelling the bus's\n * consumers, interrupting a polling daemon. **`flush`** carries out what is\n * already committed but not yet delivered: the outbox relay's final pass. The\n * two are separate phases rather than one list because they are ordered against\n * each other and not merely against the connections closing — flushing while\n * consumers are still folding events would race a drain against fresh work, and\n * the relay would go on finding entries the handlers keep writing.\n */\nexport const SHUTDOWN_PHASES = ['stop-intake', 'flush'] as const;\n\nexport type ShutdownPhase = (typeof SHUTDOWN_PHASES)[number];\n\n/**\n * One thing to do before this process's connections close.\n *\n * `run` is fully applied, exactly like `HealthProbe.check`: whoever\n * registers the hook has already closed over the connector, the fiber or the\n * store, so the drain can run every hook without knowing what any of them\n * touches.\n */\nexport interface ShutdownHook {\n /** Logged when the hook runs, and when it fails. Never rendered to a caller. */\n readonly name: string;\n readonly phase: ShutdownPhase;\n readonly run: Effect.Effect<void>;\n}\n\n/**\n * How long one hook may take before the drain moves on without it.\n *\n * Not belt-and-braces. Kubernetes sends SIGKILL at the end of\n * `terminationGracePeriodSeconds` whatever the process is doing, so an\n * unbounded hook does not buy time — it spends the budget of the hooks behind\n * it and then loses them entirely. A hook that cannot finish in time has\n * already failed; the difference is whether the *next* one gets to run.\n */\nconst HOOK_TIMEOUT = '10 seconds';\n\n/**\n * What a service does on its way out, registered by whoever opened the thing\n * that needs draining.\n *\n * A sibling of `HealthRegistry` and `WiringRegistry`, and for the same reason:\n * a `-service` that gains a bus gains its consumer drain by merging that\n * adapter's layer, and nothing in the service's own `main.ts` has to remember\n * to list it. A hand-written sequence per composition root drifts the first\n * time someone adds a dependency; this cannot, because the hook ships with the\n * client that needs it.\n */\nexport interface ShutdownRegistry {\n /** Add a hook. Called by an adapter layer as it constructs its client. */\n readonly register: (hook: ShutdownHook) => Effect.Effect<void>;\n /**\n * Whether this process has begun terminating.\n *\n * Read by `GET /api/health/ready`, which answers `503` from here **before**\n * it consults any probe: nothing is failing, the process is leaving.\n */\n readonly terminating: Effect.Effect<boolean>;\n /**\n * Run every hook, phase by phase, in registration order. Never fails.\n *\n * Called from a `Layer` finalizer, which is what orders it: the drain layer\n * is built after the composition root, so it is released before it, and the\n * connections a hook needs are still open while it runs.\n */\n readonly drain: Effect.Effect<void>;\n}\n\n/**\n * DI tag for the shutdown registry.\n *\n * In the contract layer beside `HealthRegistryTag` so an\n * `entifix-ts-*-client` package can register a hook without depending on a\n * shell.\n */\nexport class ShutdownRegistryTag extends Context.Service<\n ShutdownRegistryTag,\n ShutdownRegistry\n>()('ShutdownRegistryTag') {}\n\n/**\n * Runs one hook and swallows whatever it does.\n *\n * A failure and a timeout are logged the same way on purpose: both mean this\n * hook did not do its job, both leave the ones behind it still worth running,\n * and neither is something a terminating process can act on. `Cause.pretty`\n * keeps them distinguishable in Loki, where someone reading them actually is.\n */\nconst runHook = (hook: ShutdownHook): Effect.Effect<void> =>\n hook.run.pipe(\n Effect.timeout(HOOK_TIMEOUT),\n Effect.catchCause(cause =>\n Effect.logError('shutdown hook did not complete').pipe(\n // Annotations, not a rendered message: the tooling logger emits these\n // as structured fields, so which hook stalled a rollout is queryable.\n Effect.annotateLogs({\n hook: hook.name,\n phase: hook.phase,\n cause: Cause.pretty(cause),\n }),\n ),\n ),\n );\n\n/**\n * Builds the registry and the synchronous latch that flips it.\n *\n * Two halves for two callers, the `makeAmqpConnector` shape: the\n * `registry` goes into the layer graph, while `begin` is called from a\n * `process.on('SIGTERM')` handler — outside Effect, in the same tick the\n * runtime starts interrupting — so `/api/health/ready` stops claiming to be\n * ready the instant the signal lands rather than whenever the finalizers get\n * around to running.\n */\nexport const makeShutdownRegistry = (): {\n registry: ShutdownRegistry;\n begin: () => void;\n} => {\n const hooks: ShutdownHook[] = [];\n let terminating = false;\n\n const registry: ShutdownRegistry = {\n register: hook =>\n Effect.sync(() => {\n hooks.push(hook);\n }),\n\n terminating: Effect.sync(() => terminating),\n\n drain: Effect.gen(function* () {\n terminating = true;\n yield* Effect.logInfo('draining before shutdown').pipe(\n Effect.annotateLogs({ hooks: hooks.length }),\n );\n for (const phase of SHUTDOWN_PHASES) {\n // Sequential, and in registration order: `stop-intake` before `flush`\n // is the whole contract, and within a phase a later hook may depend on\n // an earlier one having stopped.\n yield* Effect.forEach(\n hooks.filter(hook => hook.phase === phase),\n runHook,\n { discard: true },\n );\n }\n yield* Effect.logInfo('drained');\n }),\n };\n\n return {\n registry,\n begin: () => {\n terminating = true;\n },\n };\n};\n"],"names":["Cause","Context","Effect","SHUTDOWN_PHASES","HOOK_TIMEOUT","ShutdownRegistryTag","Service","runHook","hook","run","pipe","timeout","catchCause","cause","logError","annotateLogs","name","phase","pretty","makeShutdownRegistry","hooks","terminating","registry","register","sync","push","drain","gen","logInfo","length","forEach","filter","discard","begin"],"mappings":"AAAA,SAASA,KAAK,EAAEC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AAEhD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,kBAAkB;IAAC;IAAe;CAAQ,CAAU;AAmBjE;;;;;;;;CAQC,GACD,MAAMC,eAAe;AAiCrB;;;;;;CAMC,GACD,OAAO,MAAMC,4BAA4BJ,QAAQK,OAAO,GAGpD;AAAwB;AAE5B;;;;;;;CAOC,GACD,MAAMC,UAAU,CAACC,OACfA,KAAKC,GAAG,CAACC,IAAI,CACXR,OAAOS,OAAO,CAACP,eACfF,OAAOU,UAAU,CAACC,CAAAA,QAChBX,OAAOY,QAAQ,CAAC,kCAAkCJ,IAAI,CACpD,sEAAsE;QACtE,sEAAsE;QACtER,OAAOa,YAAY,CAAC;YAClBP,MAAMA,KAAKQ,IAAI;YACfC,OAAOT,KAAKS,KAAK;YACjBJ,OAAOb,MAAMkB,MAAM,CAACL;QACtB;AAKR;;;;;;;;;CASC,GACD,OAAO,MAAMM,uBAAuB;IAIlC,MAAMC,QAAwB,EAAE;IAChC,IAAIC,cAAc;IAElB,MAAMC,WAA6B;QACjCC,UAAUf,CAAAA,OACRN,OAAOsB,IAAI,CAAC;gBACVJ,MAAMK,IAAI,CAACjB;YACb;QAEFa,aAAanB,OAAOsB,IAAI,CAAC,IAAMH;QAE/BK,OAAOxB,OAAOyB,GAAG,CAAC;YAChBN,cAAc;YACd,OAAOnB,OAAO0B,OAAO,CAAC,4BAA4BlB,IAAI,CACpDR,OAAOa,YAAY,CAAC;gBAAEK,OAAOA,MAAMS,MAAM;YAAC;YAE5C,KAAK,MAAMZ,SAASd,gBAAiB;gBACnC,sEAAsE;gBACtE,uEAAuE;gBACvE,iCAAiC;gBACjC,OAAOD,OAAO4B,OAAO,CACnBV,MAAMW,MAAM,CAACvB,CAAAA,OAAQA,KAAKS,KAAK,KAAKA,QACpCV,SACA;oBAAEyB,SAAS;gBAAK;YAEpB;YACA,OAAO9B,OAAO0B,OAAO,CAAC;QACxB;IACF;IAEA,OAAO;QACLN;QACAW,OAAO;YACLZ,cAAc;QAChB;IACF;AACF,EAAE"}
@@ -11,7 +11,7 @@ import { Context, type Effect } from 'effect';
11
11
  * Two rules the implementations must keep:
12
12
  *
13
13
  * - **Resolve per request, never per `Layer`.** The connection pool is acquired
14
- * once at boot (`MongoDatabaseLayer`'s `Layer.scoped`); a handle is not a
14
+ * once at boot (`MongoDatabaseLayer`'s `Layer.effect`); a handle is not a
15
15
  * connection. Building a `Layer` per request would rebuild the pool per
16
16
  * request.
17
17
  * - **Derive the storage name from the organization id**, never from a mutable
@@ -26,7 +26,7 @@ import { Context, type Effect } from 'effect';
26
26
  export interface TenantDatabaseResolver<THandle> {
27
27
  readonly forOrganization: (organizationId: string) => Effect.Effect<THandle, EntifixConnError>;
28
28
  }
29
- declare const TenantDatabaseResolverTag_base: Context.TagClass<TenantDatabaseResolverTag, "TenantDatabaseResolverTag", TenantDatabaseResolver<unknown>>;
29
+ declare const TenantDatabaseResolverTag_base: Context.ServiceClass<TenantDatabaseResolverTag, "TenantDatabaseResolverTag", TenantDatabaseResolver<unknown>>;
30
30
  /**
31
31
  * DI tag for the resolver. Declared with an `unknown` handle because the tag is
32
32
  * datastore-agnostic; a service narrows it at the composition root, where it
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-database-resolver.d.ts","sourceRoot":"","sources":["../../src/tenancy/tenant-database-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC7C,QAAQ,CAAC,eAAe,EAAE,CACxB,cAAc,EAAE,MAAM,KACnB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;CAC/C;;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,8BAEgB;CAAG"}
1
+ {"version":3,"file":"tenant-database-resolver.d.ts","sourceRoot":"","sources":["../../src/tenancy/tenant-database-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC7C,QAAQ,CAAC,eAAe,EAAE,CACxB,cAAc,EAAE,MAAM,KACnB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;CAC/C;;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,8BAGf;CAAG"}
@@ -3,7 +3,7 @@ import { Context } from 'effect';
3
3
  * DI tag for the resolver. Declared with an `unknown` handle because the tag is
4
4
  * datastore-agnostic; a service narrows it at the composition root, where it
5
5
  * also knows which driver it provided.
6
- */ export class TenantDatabaseResolverTag extends Context.Tag('TenantDatabaseResolverTag')() {
6
+ */ export class TenantDatabaseResolverTag extends Context.Service()('TenantDatabaseResolverTag') {
7
7
  }
8
8
 
9
9
  //# sourceMappingURL=tenant-database-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tenancy/tenant-database-resolver.ts"],"sourcesContent":["import type { EntifixConnError } from '@entifix/core';\nimport { Context, type Effect } from 'effect';\n\n/**\n * Resolves the storage handle for one organization.\n *\n * The seam the whole tenancy model turns on. Business entities carry no\n * `organizationId` and no tenant filter, so isolation is *which handle a request\n * resolves to* — which means no query can leak by omission, because there is no\n * column to forget.\n *\n * Two rules the implementations must keep:\n *\n * - **Resolve per request, never per `Layer`.** The connection pool is acquired\n * once at boot (`MongoDatabaseLayer`'s `Layer.scoped`); a handle is not a\n * connection. Building a `Layer` per request would rebuild the pool per\n * request.\n * - **Derive the storage name from the organization id**, never from a mutable\n * attribute such as a slug, so renaming an organization can never strand its\n * data.\n *\n * `THandle` is the datastore's own handle type (`Db` for Mongo). Keeping it\n * generic is what lets a Postgres adapter — where the same idea is a schema on\n * a shared pool rather than a separate database — satisfy the same port without\n * a call-site change.\n */\nexport interface TenantDatabaseResolver<THandle> {\n readonly forOrganization: (\n organizationId: string,\n ) => Effect.Effect<THandle, EntifixConnError>;\n}\n\n/**\n * DI tag for the resolver. Declared with an `unknown` handle because the tag is\n * datastore-agnostic; a service narrows it at the composition root, where it\n * also knows which driver it provided.\n */\nexport class TenantDatabaseResolverTag extends Context.Tag(\n 'TenantDatabaseResolverTag',\n)<TenantDatabaseResolverTag, TenantDatabaseResolver<unknown>>() {}\n"],"names":["Context","TenantDatabaseResolverTag","Tag"],"mappings":"AACA,SAASA,OAAO,QAAqB,SAAS;AA+B9C;;;;CAIC,GACD,OAAO,MAAMC,kCAAkCD,QAAQE,GAAG,CACxD;AAC+D"}
1
+ {"version":3,"sources":["../../src/tenancy/tenant-database-resolver.ts"],"sourcesContent":["import type { EntifixConnError } from '@entifix/core';\nimport { Context, type Effect } from 'effect';\n\n/**\n * Resolves the storage handle for one organization.\n *\n * The seam the whole tenancy model turns on. Business entities carry no\n * `organizationId` and no tenant filter, so isolation is *which handle a request\n * resolves to* — which means no query can leak by omission, because there is no\n * column to forget.\n *\n * Two rules the implementations must keep:\n *\n * - **Resolve per request, never per `Layer`.** The connection pool is acquired\n * once at boot (`MongoDatabaseLayer`'s `Layer.effect`); a handle is not a\n * connection. Building a `Layer` per request would rebuild the pool per\n * request.\n * - **Derive the storage name from the organization id**, never from a mutable\n * attribute such as a slug, so renaming an organization can never strand its\n * data.\n *\n * `THandle` is the datastore's own handle type (`Db` for Mongo). Keeping it\n * generic is what lets a Postgres adapter — where the same idea is a schema on\n * a shared pool rather than a separate database — satisfy the same port without\n * a call-site change.\n */\nexport interface TenantDatabaseResolver<THandle> {\n readonly forOrganization: (\n organizationId: string,\n ) => Effect.Effect<THandle, EntifixConnError>;\n}\n\n/**\n * DI tag for the resolver. Declared with an `unknown` handle because the tag is\n * datastore-agnostic; a service narrows it at the composition root, where it\n * also knows which driver it provided.\n */\nexport class TenantDatabaseResolverTag extends Context.Service<\n TenantDatabaseResolverTag,\n TenantDatabaseResolver<unknown>\n>()('TenantDatabaseResolverTag') {}\n"],"names":["Context","TenantDatabaseResolverTag","Service"],"mappings":"AACA,SAASA,OAAO,QAAqB,SAAS;AA+B9C;;;;CAIC,GACD,OAAO,MAAMC,kCAAkCD,QAAQE,OAAO,GAG1D;AAA8B"}
@@ -93,7 +93,7 @@ export interface TokenService {
93
93
  /** Verify signature + expiry and return the claims, or fail. */
94
94
  verify(token: string): Effect<TokenClaims, EntifixError>;
95
95
  }
96
- declare const TokenServiceTag_base: Context.TagClass<TokenServiceTag, "TokenServiceTag", TokenService>;
96
+ declare const TokenServiceTag_base: Context.ServiceClass<TokenServiceTag, "TokenServiceTag", TokenService>;
97
97
  /** DI tag the composition root binds to a concrete {@link TokenService}. */
98
98
  export declare class TokenServiceTag extends TokenServiceTag_base {
99
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-service.d.ts","sourceRoot":"","sources":["../../src/tokens/token-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,gEAAgE;IAChE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5E,gEAAgE;IAChE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;CAC1D;;AAED,4EAA4E;AAC5E,qBAAa,eAAgB,SAAQ,oBAGlC;CAAG"}
1
+ {"version":3,"file":"token-service.d.ts","sourceRoot":"","sources":["../../src/tokens/token-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,gEAAgE;IAChE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5E,gEAAgE;IAChE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;CAC1D;;AAED,4EAA4E;AAC5E,qBAAa,eAAgB,SAAQ,oBAGf;CAAG"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from 'effect';
2
- /** DI tag the composition root binds to a concrete {@link TokenService}. */ export class TokenServiceTag extends Context.Tag('TokenServiceTag')() {
2
+ /** DI tag the composition root binds to a concrete {@link TokenService}. */ export class TokenServiceTag extends Context.Service()('TokenServiceTag') {
3
3
  }
4
4
 
5
5
  //# sourceMappingURL=token-service.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tokens/token-service.ts"],"sourcesContent":["import type { EntifixError, EntityId } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * The claims a stateless access token carries between the perimeter that mints\n * it and the services that verify it. Kept small on purpose: everything here is\n * copied into every request and can only refresh when a new token is minted, so\n * only authorization-critical, stable subject data belongs in it. Richer or\n * volatile state stays in the session record and is read on demand.\n */\nexport interface TokenClaims {\n /** Canonical user id — the value services key their data against. */\n readonly userId: EntityId;\n /** Upstream identity subject that authenticated the session. */\n readonly subject: string;\n /** The session id the token was minted from; the revocation linkage. */\n readonly sessionId: string;\n readonly roles: readonly string[];\n /**\n * The organization this session is acting for, and the value every\n * tenant-plane storage handle is resolved from.\n *\n * A property of the **session**, not of the user: a party may hold several\n * memberships, and switching re-mints the token. Absent for a session with no\n * organization — a buyer, or an operator, who holds no tenant scope at all.\n *\n * Note this is a *routing* key, not an authorization input. Grants still come\n * from `roles` resolved at the consumer.\n */\n readonly activeOrganizationId?: string;\n /**\n * Which population the session belongs to — a buyer, a vendor's staff, or\n * platform staff. Typed as a plain string here because the closed set lives\n * in the business layer (`PartyRoles` in `business-ts-party-management`),\n * which this layer may not import; consumers narrow it, exactly as they\n * already do for `roles`.\n *\n * It exists because the alternative was inferring it from an *absent*\n * `activeOrganizationId`, which cannot tell a buyer from an operator — two\n * populations whose reach differs enormously. A capability that sensitive\n * must not be signalled by a missing field.\n *\n * Like `activeOrganizationId` this is routing/context, not a grant: what a\n * principal may do still comes from `roles` resolved at the consumer.\n */\n readonly partyRole?: string;\n /**\n * The party behind the account — the `Individual` id that domains recording a\n * party key against, and the value a `ProductOrder.buyerId` holds.\n *\n * It is carried because the hop from an account to a party exists in exactly\n * one store, the one that mints this token. A service scoping a read to the\n * caller's own records needs the party and cannot derive it from\n * {@link TokenClaims.userId} without reading another slice's collections.\n *\n * Absent for an account with no party record. A consumer must read the\n * absence as *no party* — scoping to nothing — never as *any party*.\n *\n * Like `activeOrganizationId` and `partyRole` this is context, not a grant:\n * it says whose records these are, never what may be done with them.\n */\n readonly partyId?: string;\n /**\n * The business domains the acting organization is provisioned for — the\n * second assignment ceiling (ADR 0007), carried so a server-rendered menu can\n * shape itself without a lookup on every render.\n *\n * Absent for a session with no organization, and that is **not** the same as\n * an empty list: an operator holds no tenant scope, so the ceiling does not\n * apply at all, while a member of an organization provisioned for nothing is\n * entitled to nothing. Consumers therefore decide from\n * {@link TokenClaims.activeOrganizationId} whether to consult this at all.\n *\n * Plain strings for the same reason `roles` is: the domain names live in the\n * business layer, which this one may not import.\n *\n * Like `activeOrganizationId` and `partyRole` this is context, not a grant.\n */\n readonly entitlements?: readonly string[];\n /** Room for a few extra stable claims without a type change. */\n readonly [key: string]: unknown;\n}\n\n/**\n * Signs and verifies short-lived access tokens. The perimeter provides it to\n * mint a token from a resolved session; every downstream service provides it to\n * verify one statelessly (no store round trip on the hot path). Vendor-neutral:\n * a `jose`/HS256 adapter backs it now, an RS256/JWKS one can later without\n * touching a consumer.\n */\nexport interface TokenService {\n /** Sign the claims into a compact token that expires after `ttlSeconds`. */\n sign(claims: TokenClaims, ttlSeconds: number): Effect<string, EntifixError>;\n /** Verify signature + expiry and return the claims, or fail. */\n verify(token: string): Effect<TokenClaims, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link TokenService}. */\nexport class TokenServiceTag extends Context.Tag('TokenServiceTag')<\n TokenServiceTag,\n TokenService\n>() {}\n"],"names":["Context","TokenServiceTag","Tag"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAiGjC,0EAA0E,GAC1E,OAAO,MAAMC,wBAAwBD,QAAQE,GAAG,CAAC;AAG5C"}
1
+ {"version":3,"sources":["../../src/tokens/token-service.ts"],"sourcesContent":["import type { EntifixError, EntityId } from '@entifix/core';\nimport { Context } from 'effect';\nimport { Effect } from 'effect/Effect';\n\n/**\n * The claims a stateless access token carries between the perimeter that mints\n * it and the services that verify it. Kept small on purpose: everything here is\n * copied into every request and can only refresh when a new token is minted, so\n * only authorization-critical, stable subject data belongs in it. Richer or\n * volatile state stays in the session record and is read on demand.\n */\nexport interface TokenClaims {\n /** Canonical user id — the value services key their data against. */\n readonly userId: EntityId;\n /** Upstream identity subject that authenticated the session. */\n readonly subject: string;\n /** The session id the token was minted from; the revocation linkage. */\n readonly sessionId: string;\n readonly roles: readonly string[];\n /**\n * The organization this session is acting for, and the value every\n * tenant-plane storage handle is resolved from.\n *\n * A property of the **session**, not of the user: a party may hold several\n * memberships, and switching re-mints the token. Absent for a session with no\n * organization — a buyer, or an operator, who holds no tenant scope at all.\n *\n * Note this is a *routing* key, not an authorization input. Grants still come\n * from `roles` resolved at the consumer.\n */\n readonly activeOrganizationId?: string;\n /**\n * Which population the session belongs to — a buyer, a vendor's staff, or\n * platform staff. Typed as a plain string here because the closed set lives\n * in the business layer (`PartyRoles` in `business-ts-party-management`),\n * which this layer may not import; consumers narrow it, exactly as they\n * already do for `roles`.\n *\n * It exists because the alternative was inferring it from an *absent*\n * `activeOrganizationId`, which cannot tell a buyer from an operator — two\n * populations whose reach differs enormously. A capability that sensitive\n * must not be signalled by a missing field.\n *\n * Like `activeOrganizationId` this is routing/context, not a grant: what a\n * principal may do still comes from `roles` resolved at the consumer.\n */\n readonly partyRole?: string;\n /**\n * The party behind the account — the `Individual` id that domains recording a\n * party key against, and the value a `ProductOrder.buyerId` holds.\n *\n * It is carried because the hop from an account to a party exists in exactly\n * one store, the one that mints this token. A service scoping a read to the\n * caller's own records needs the party and cannot derive it from\n * {@link TokenClaims.userId} without reading another slice's collections.\n *\n * Absent for an account with no party record. A consumer must read the\n * absence as *no party* — scoping to nothing — never as *any party*.\n *\n * Like `activeOrganizationId` and `partyRole` this is context, not a grant:\n * it says whose records these are, never what may be done with them.\n */\n readonly partyId?: string;\n /**\n * The business domains the acting organization is provisioned for — the\n * second assignment ceiling (ADR 0007), carried so a server-rendered menu can\n * shape itself without a lookup on every render.\n *\n * Absent for a session with no organization, and that is **not** the same as\n * an empty list: an operator holds no tenant scope, so the ceiling does not\n * apply at all, while a member of an organization provisioned for nothing is\n * entitled to nothing. Consumers therefore decide from\n * {@link TokenClaims.activeOrganizationId} whether to consult this at all.\n *\n * Plain strings for the same reason `roles` is: the domain names live in the\n * business layer, which this one may not import.\n *\n * Like `activeOrganizationId` and `partyRole` this is context, not a grant.\n */\n readonly entitlements?: readonly string[];\n /** Room for a few extra stable claims without a type change. */\n readonly [key: string]: unknown;\n}\n\n/**\n * Signs and verifies short-lived access tokens. The perimeter provides it to\n * mint a token from a resolved session; every downstream service provides it to\n * verify one statelessly (no store round trip on the hot path). Vendor-neutral:\n * a `jose`/HS256 adapter backs it now, an RS256/JWKS one can later without\n * touching a consumer.\n */\nexport interface TokenService {\n /** Sign the claims into a compact token that expires after `ttlSeconds`. */\n sign(claims: TokenClaims, ttlSeconds: number): Effect<string, EntifixError>;\n /** Verify signature + expiry and return the claims, or fail. */\n verify(token: string): Effect<TokenClaims, EntifixError>;\n}\n\n/** DI tag the composition root binds to a concrete {@link TokenService}. */\nexport class TokenServiceTag extends Context.Service<\n TokenServiceTag,\n TokenService\n>()('TokenServiceTag') {}\n"],"names":["Context","TokenServiceTag","Service"],"mappings":"AACA,SAASA,OAAO,QAAQ,SAAS;AAiGjC,0EAA0E,GAC1E,OAAO,MAAMC,wBAAwBD,QAAQE,OAAO,GAGhD;AAAoB"}
@@ -12,5 +12,5 @@ import { EntityTag } from '../../repository/entity-mixins.repository.js';
12
12
  * taking them as arguments, so the same use-case saves over REST on the web and
13
13
  * over Mongo on a backend.
14
14
  */
15
- export declare function saveUCFactory<TEntity extends Entity>(): Effect.Effect<TEntity, [import("effect/Utils").YieldWrap<import("effect/Context").Tag<EntityRepositoryTag, import("../../index.js").EntityRepository>> | import("effect/Utils").YieldWrap<import("effect/Context").Tag<EntityTag, Entity>> | import("effect/Utils").YieldWrap<Effect.Effect<TEntity, import("@entifix/core").EntifixError, import("../../index.js").ConfigurationRepositoryTag>>] extends [import("effect/Utils").YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E : never, [import("effect/Utils").YieldWrap<import("effect/Context").Tag<EntityRepositoryTag, import("../../index.js").EntityRepository>> | import("effect/Utils").YieldWrap<import("effect/Context").Tag<EntityTag, Entity>> | import("effect/Utils").YieldWrap<Effect.Effect<TEntity, import("@entifix/core").EntifixError, import("../../index.js").ConfigurationRepositoryTag>>] extends [import("effect/Utils").YieldWrap<Effect.Effect<infer _A_1, infer _E, infer R>>] ? R : never>;
15
+ export declare function saveUCFactory<TEntity extends Entity>(): Effect.Effect<TEntity, [Effect.Effect<import("../../index.js").EntityRepository, never, EntityRepositoryTag> | Effect.Effect<Entity, never, EntityTag> | Effect.Effect<TEntity, import("@entifix/core").EntifixError, import("../../index.js").ConfigurationRepositoryTag>] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never, [Effect.Effect<import("../../index.js").EntityRepository, never, EntityRepositoryTag> | Effect.Effect<Entity, never, EntityTag> | Effect.Effect<TEntity, import("@entifix/core").EntifixError, import("../../index.js").ConfigurationRepositoryTag>] extends [Effect.Effect<infer _A_1, infer _E, infer R>] ? R : never>;
16
16
  //# sourceMappingURL=save.uc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"save.uc.d.ts","sourceRoot":"","sources":["../../../src/use-case/save/save.uc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AAEzE;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,47BAWnD"}
1
+ {"version":3,"file":"save.uc.d.ts","sourceRoot":"","sources":["../../../src/use-case/save/save.uc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AAEzE;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,4oBAWnD"}
@@ -48,7 +48,7 @@ export interface WiringRegistry {
48
48
  /** Every distinct event name emitted since boot, in first-seen order. */
49
49
  readonly published: Effect.Effect<readonly string[]>;
50
50
  }
51
- declare const WiringRegistryTag_base: Context.TagClass<WiringRegistryTag, "WiringRegistryTag", WiringRegistry>;
51
+ declare const WiringRegistryTag_base: Context.ServiceClass<WiringRegistryTag, "WiringRegistryTag", WiringRegistry>;
52
52
  /**
53
53
  * DI tag for the wiring registry.
54
54
  *
@@ -1 +1 @@
1
- {"version":3,"file":"wiring-registry.d.ts","sourceRoot":"","sources":["../../src/wiring/wiring-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAO,MAAM,QAAQ,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,gCAAiC,CAAC;AAEvE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,kBAAkB,EAAE,CAC3B,KAAK,EAAE,iBAAiB,KACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnE,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;IACpE,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;CACtD;;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,sBAGpC;CAAG;AASN;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAuB9D,CAAC"}
1
+ {"version":3,"file":"wiring-registry.d.ts","sourceRoot":"","sources":["../../src/wiring/wiring-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAO,MAAM,QAAQ,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,gCAAiC,CAAC;AAEvE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,kBAAkB,EAAE,CAC3B,KAAK,EAAE,iBAAiB,KACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnE,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;IACpE,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;CACtD;;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,sBAGf;CAAG;AAS3B;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAuB9D,CAAC"}
@@ -16,7 +16,7 @@ import { Context, Effect, Layer, Ref } from 'effect';
16
16
  * It lives here, in the contract layer, so `@entifix/amqp` can record
17
17
  * into it without depending on a shell — the same placement, and the same
18
18
  * reason, as {@link HealthRegistryTag}.
19
- */ export class WiringRegistryTag extends Context.Tag('WiringRegistryTag')() {
19
+ */ export class WiringRegistryTag extends Context.Service()('WiringRegistryTag') {
20
20
  }
21
21
  /** Whether two bindings are the same binding. */ const same = (a, b)=>a.slice === b.slice && a.pattern === b.pattern && a.mode === b.mode && a.queue === b.queue;
22
22
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/wiring/wiring-registry.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from 'effect';\n\n/**\n * The queue shapes a subscriber may ask for (ADR 0030).\n *\n * Duplicated from `SubscriptionMode` in `@entifix/transactions` rather than\n * imported: that package is T2, **above** this one at T1, so the edge would\n * point upward and the tier rule fails the build on it. The same duplication exists in `tools/slices/src/types.ts`\n * for the same reason, and both lists are pinned by a spec.\n */\nexport const BOUND_SUBSCRIPTION_MODES = ['work', 'broadcast'] as const;\n\nexport type BoundSubscriptionMode = (typeof BOUND_SUBSCRIPTION_MODES)[number];\n\n/**\n * One subscription a running process **actually bound**, recorded by the bus\n * adapter as `subscribe` runs.\n *\n * Observed rather than declared, which is the whole point: a document generated\n * from `tools/slices/` would agree with `tools/slices/` by construction and\n * catch nothing (ADR 0031). `queue` is the broker's own name for it, so someone\n * looking at depth in the management UI can match the two up.\n */\nexport interface BoundSubscription {\n /** The **subscribing** slice — never the emitter. See `Subscription.slice`. */\n readonly slice: string;\n readonly pattern: string;\n readonly mode: BoundSubscriptionMode;\n readonly queue: string;\n}\n\n/**\n * What a process did with its bus, as opposed to what it can still reach.\n *\n * A sibling of `HealthRegistry` rather than part of it: a subscription is not a\n * readiness fact — a bound queue says nothing about whether the broker is\n * answering right now — and folding the two together would put entries in the\n * readiness response that no probe can check.\n *\n * Both halves are cumulative and never cleared. `published` is a set of event\n * **names** and nothing else: no payloads, no counts, no timestamps. Counts\n * would be per-replica and un-alertable, which is the telemetry path ADR 0001\n * already chose OTLP for.\n */\nexport interface WiringRegistry {\n /** Called by the bus adapter once per binding, including after a reconnect. */\n readonly recordSubscription: (\n bound: BoundSubscription,\n ) => Effect.Effect<void>;\n /** Called by the bus adapter on every publish. Idempotent per name. */\n readonly recordPublish: (eventName: string) => Effect.Effect<void>;\n /** Every distinct binding, in the order it was first made. */\n readonly subscriptions: Effect.Effect<readonly BoundSubscription[]>;\n /** Every distinct event name emitted since boot, in first-seen order. */\n readonly published: Effect.Effect<readonly string[]>;\n}\n\n/**\n * DI tag for the wiring registry.\n *\n * It lives here, in the contract layer, so `@entifix/amqp` can record\n * into it without depending on a shell — the same placement, and the same\n * reason, as {@link HealthRegistryTag}.\n */\nexport class WiringRegistryTag extends Context.Tag('WiringRegistryTag')<\n WiringRegistryTag,\n WiringRegistry\n>() {}\n\n/** Whether two bindings are the same binding. */\nconst same = (a: BoundSubscription, b: BoundSubscription): boolean =>\n a.slice === b.slice &&\n a.pattern === b.pattern &&\n a.mode === b.mode &&\n a.queue === b.queue;\n\n/**\n * Live registry over a `Ref`. Provide it **once** per service and share the\n * instance (`Layer.provideMerge`), or the bus records into a registry the\n * description route never reads.\n *\n * Both recorders deduplicate. A reconnect re-runs every consumer's setup\n * against the new channel (`AmqpConnector.addConsumer`), so without it a broker\n * restart would list the same binding twice; and a publisher emits the same\n * event name continuously, so without it the list would grow without bound.\n */\nexport const WiringRegistryLayer: Layer.Layer<WiringRegistryTag> = Layer.effect(\n WiringRegistryTag,\n Effect.gen(function* () {\n const subscriptions = yield* Ref.make<readonly BoundSubscription[]>([]);\n const published = yield* Ref.make<readonly string[]>([]);\n\n return {\n recordSubscription: bound =>\n Ref.update(subscriptions, current =>\n current.some(existing => same(existing, bound))\n ? current\n : [...current, bound],\n ),\n\n recordPublish: eventName =>\n Ref.update(published, current =>\n current.includes(eventName) ? current : [...current, eventName],\n ),\n\n subscriptions: Ref.get(subscriptions),\n published: Ref.get(published),\n };\n }),\n);\n"],"names":["Context","Effect","Layer","Ref","BOUND_SUBSCRIPTION_MODES","WiringRegistryTag","Tag","same","a","b","slice","pattern","mode","queue","WiringRegistryLayer","effect","gen","subscriptions","make","published","recordSubscription","bound","update","current","some","existing","recordPublish","eventName","includes","get"],"mappings":"AAAA,SAASA,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,GAAG,QAAQ,SAAS;AAErD;;;;;;;CAOC,GACD,OAAO,MAAMC,2BAA2B;IAAC;IAAQ;CAAY,CAAU;AA+CvE;;;;;;CAMC,GACD,OAAO,MAAMC,0BAA0BL,QAAQM,GAAG,CAAC;AAG9C;AAEL,+CAA+C,GAC/C,MAAMC,OAAO,CAACC,GAAsBC,IAClCD,EAAEE,KAAK,KAAKD,EAAEC,KAAK,IACnBF,EAAEG,OAAO,KAAKF,EAAEE,OAAO,IACvBH,EAAEI,IAAI,KAAKH,EAAEG,IAAI,IACjBJ,EAAEK,KAAK,KAAKJ,EAAEI,KAAK;AAErB;;;;;;;;;CASC,GACD,OAAO,MAAMC,sBAAsDZ,MAAMa,MAAM,CAC7EV,mBACAJ,OAAOe,GAAG,CAAC;IACT,MAAMC,gBAAgB,OAAOd,IAAIe,IAAI,CAA+B,EAAE;IACtE,MAAMC,YAAY,OAAOhB,IAAIe,IAAI,CAAoB,EAAE;IAEvD,OAAO;QACLE,oBAAoBC,CAAAA,QAClBlB,IAAImB,MAAM,CAACL,eAAeM,CAAAA,UACxBA,QAAQC,IAAI,CAACC,CAAAA,WAAYlB,KAAKkB,UAAUJ,UACpCE,UACA;uBAAIA;oBAASF;iBAAM;QAG3BK,eAAeC,CAAAA,YACbxB,IAAImB,MAAM,CAACH,WAAWI,CAAAA,UACpBA,QAAQK,QAAQ,CAACD,aAAaJ,UAAU;uBAAIA;oBAASI;iBAAU;QAGnEV,eAAed,IAAI0B,GAAG,CAACZ;QACvBE,WAAWhB,IAAI0B,GAAG,CAACV;IACrB;AACF,IACA"}
1
+ {"version":3,"sources":["../../src/wiring/wiring-registry.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from 'effect';\n\n/**\n * The queue shapes a subscriber may ask for (ADR 0030).\n *\n * Duplicated from `SubscriptionMode` in `@entifix/transactions` rather than\n * imported: that package is T2, **above** this one at T1, so the edge would\n * point upward and the tier rule fails the build on it. The same duplication exists in `tools/slices/src/types.ts`\n * for the same reason, and both lists are pinned by a spec.\n */\nexport const BOUND_SUBSCRIPTION_MODES = ['work', 'broadcast'] as const;\n\nexport type BoundSubscriptionMode = (typeof BOUND_SUBSCRIPTION_MODES)[number];\n\n/**\n * One subscription a running process **actually bound**, recorded by the bus\n * adapter as `subscribe` runs.\n *\n * Observed rather than declared, which is the whole point: a document generated\n * from `tools/slices/` would agree with `tools/slices/` by construction and\n * catch nothing (ADR 0031). `queue` is the broker's own name for it, so someone\n * looking at depth in the management UI can match the two up.\n */\nexport interface BoundSubscription {\n /** The **subscribing** slice — never the emitter. See `Subscription.slice`. */\n readonly slice: string;\n readonly pattern: string;\n readonly mode: BoundSubscriptionMode;\n readonly queue: string;\n}\n\n/**\n * What a process did with its bus, as opposed to what it can still reach.\n *\n * A sibling of `HealthRegistry` rather than part of it: a subscription is not a\n * readiness fact — a bound queue says nothing about whether the broker is\n * answering right now — and folding the two together would put entries in the\n * readiness response that no probe can check.\n *\n * Both halves are cumulative and never cleared. `published` is a set of event\n * **names** and nothing else: no payloads, no counts, no timestamps. Counts\n * would be per-replica and un-alertable, which is the telemetry path ADR 0001\n * already chose OTLP for.\n */\nexport interface WiringRegistry {\n /** Called by the bus adapter once per binding, including after a reconnect. */\n readonly recordSubscription: (\n bound: BoundSubscription,\n ) => Effect.Effect<void>;\n /** Called by the bus adapter on every publish. Idempotent per name. */\n readonly recordPublish: (eventName: string) => Effect.Effect<void>;\n /** Every distinct binding, in the order it was first made. */\n readonly subscriptions: Effect.Effect<readonly BoundSubscription[]>;\n /** Every distinct event name emitted since boot, in first-seen order. */\n readonly published: Effect.Effect<readonly string[]>;\n}\n\n/**\n * DI tag for the wiring registry.\n *\n * It lives here, in the contract layer, so `@entifix/amqp` can record\n * into it without depending on a shell — the same placement, and the same\n * reason, as {@link HealthRegistryTag}.\n */\nexport class WiringRegistryTag extends Context.Service<\n WiringRegistryTag,\n WiringRegistry\n>()('WiringRegistryTag') {}\n\n/** Whether two bindings are the same binding. */\nconst same = (a: BoundSubscription, b: BoundSubscription): boolean =>\n a.slice === b.slice &&\n a.pattern === b.pattern &&\n a.mode === b.mode &&\n a.queue === b.queue;\n\n/**\n * Live registry over a `Ref`. Provide it **once** per service and share the\n * instance (`Layer.provideMerge`), or the bus records into a registry the\n * description route never reads.\n *\n * Both recorders deduplicate. A reconnect re-runs every consumer's setup\n * against the new channel (`AmqpConnector.addConsumer`), so without it a broker\n * restart would list the same binding twice; and a publisher emits the same\n * event name continuously, so without it the list would grow without bound.\n */\nexport const WiringRegistryLayer: Layer.Layer<WiringRegistryTag> = Layer.effect(\n WiringRegistryTag,\n Effect.gen(function* () {\n const subscriptions = yield* Ref.make<readonly BoundSubscription[]>([]);\n const published = yield* Ref.make<readonly string[]>([]);\n\n return {\n recordSubscription: bound =>\n Ref.update(subscriptions, current =>\n current.some(existing => same(existing, bound))\n ? current\n : [...current, bound],\n ),\n\n recordPublish: eventName =>\n Ref.update(published, current =>\n current.includes(eventName) ? current : [...current, eventName],\n ),\n\n subscriptions: Ref.get(subscriptions),\n published: Ref.get(published),\n };\n }),\n);\n"],"names":["Context","Effect","Layer","Ref","BOUND_SUBSCRIPTION_MODES","WiringRegistryTag","Service","same","a","b","slice","pattern","mode","queue","WiringRegistryLayer","effect","gen","subscriptions","make","published","recordSubscription","bound","update","current","some","existing","recordPublish","eventName","includes","get"],"mappings":"AAAA,SAASA,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,GAAG,QAAQ,SAAS;AAErD;;;;;;;CAOC,GACD,OAAO,MAAMC,2BAA2B;IAAC;IAAQ;CAAY,CAAU;AA+CvE;;;;;;CAMC,GACD,OAAO,MAAMC,0BAA0BL,QAAQM,OAAO,GAGlD;AAAsB;AAE1B,+CAA+C,GAC/C,MAAMC,OAAO,CAACC,GAAsBC,IAClCD,EAAEE,KAAK,KAAKD,EAAEC,KAAK,IACnBF,EAAEG,OAAO,KAAKF,EAAEE,OAAO,IACvBH,EAAEI,IAAI,KAAKH,EAAEG,IAAI,IACjBJ,EAAEK,KAAK,KAAKJ,EAAEI,KAAK;AAErB;;;;;;;;;CASC,GACD,OAAO,MAAMC,sBAAsDZ,MAAMa,MAAM,CAC7EV,mBACAJ,OAAOe,GAAG,CAAC;IACT,MAAMC,gBAAgB,OAAOd,IAAIe,IAAI,CAA+B,EAAE;IACtE,MAAMC,YAAY,OAAOhB,IAAIe,IAAI,CAAoB,EAAE;IAEvD,OAAO;QACLE,oBAAoBC,CAAAA,QAClBlB,IAAImB,MAAM,CAACL,eAAeM,CAAAA,UACxBA,QAAQC,IAAI,CAACC,CAAAA,WAAYlB,KAAKkB,UAAUJ,UACpCE,UACA;uBAAIA;oBAASF;iBAAM;QAG3BK,eAAeC,CAAAA,YACbxB,IAAImB,MAAM,CAACH,WAAWI,CAAAA,UACpBA,QAAQK,QAAQ,CAACD,aAAaJ,UAAU;uBAAIA;oBAASI;iBAAU;QAGnEV,eAAed,IAAI0B,GAAG,CAACZ;QACvBE,WAAWhB,IAAI0B,GAAG,CAACV;IACrB;AACF,IACA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/business",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "The ports every adapter implements — repository, session, token, health — and the generic get, load, save and delete use-cases.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,7 +18,6 @@
18
18
  "exports": {
19
19
  "./package.json": "./package.json",
20
20
  ".": {
21
- "@entifix/source": "./src/index.ts",
22
21
  "types": "./dist/index.d.ts",
23
22
  "import": "./dist/index.js",
24
23
  "default": "./dist/index.js"
@@ -52,12 +51,12 @@
52
51
  },
53
52
  "dependencies": {
54
53
  "@swc/helpers": "0.5.23",
55
- "@entifix/core": "0.1.4"
54
+ "@entifix/core": "0.3.0"
56
55
  },
57
56
  "peerDependencies": {
58
- "effect": ">=3.22.2 <4"
57
+ "effect": "^4.0.5"
59
58
  },
60
59
  "devDependencies": {
61
- "effect": "3.22.2"
60
+ "effect": "4.0.5"
62
61
  }
63
62
  }