@entifix/rest 0.10.2 → 0.12.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 (33) hide show
  1. package/dist/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.d.ts +4 -2
  2. package/dist/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.d.ts.map +1 -1
  3. package/dist/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.js +12 -5
  4. package/dist/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.js.map +1 -1
  5. package/dist/adapters/entity-rest/build-entity-rest-adapter-mixins/index.d.ts +1 -1
  6. package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.d.ts.map +1 -1
  7. package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js +29 -5
  8. package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js.map +1 -1
  9. package/dist/adapters/entity-use-case/index.d.ts +2 -0
  10. package/dist/adapters/entity-use-case/index.d.ts.map +1 -0
  11. package/dist/adapters/entity-use-case/index.js +3 -0
  12. package/dist/adapters/entity-use-case/index.js.map +1 -0
  13. package/dist/adapters/entity-use-case/make-entity-rest-use-case-runner.d.ts +13 -0
  14. package/dist/adapters/entity-use-case/make-entity-rest-use-case-runner.d.ts.map +1 -0
  15. package/dist/adapters/entity-use-case/make-entity-rest-use-case-runner.js +56 -0
  16. package/dist/adapters/entity-use-case/make-entity-rest-use-case-runner.js.map +1 -0
  17. package/dist/endpoint/endpoint-client.d.ts +39 -0
  18. package/dist/endpoint/endpoint-client.d.ts.map +1 -0
  19. package/dist/endpoint/endpoint-client.js +95 -0
  20. package/dist/endpoint/endpoint-client.js.map +1 -0
  21. package/dist/endpoint/index.d.ts +3 -0
  22. package/dist/endpoint/index.d.ts.map +1 -0
  23. package/dist/endpoint/index.js +4 -0
  24. package/dist/endpoint/index.js.map +1 -0
  25. package/dist/endpoint/participant-client.d.ts +23 -0
  26. package/dist/endpoint/participant-client.d.ts.map +1 -0
  27. package/dist/endpoint/participant-client.js +75 -0
  28. package/dist/endpoint/participant-client.js.map +1 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/package.json +9 -4
@@ -1,4 +1,4 @@
1
- import type { EntifixEnvelope } from '@entifix/core';
1
+ import { type EntifixEnvelope } from '@entifix/core';
2
2
  import { HttpMethod, HttpRequest } from '../../../clients/types.js';
3
3
  export interface BuildEntityRequestOptions {
4
4
  method: HttpMethod;
@@ -8,6 +8,8 @@ export interface BuildEntityRequestOptions {
8
8
  * Omitted for reads and deletes, which carry no payload.
9
9
  */
10
10
  envelope?: EntifixEnvelope<unknown>;
11
+ /** A write's `Idempotency-Key`, already minted by the caller. */
12
+ idempotencyKey?: string;
11
13
  }
12
14
  /**
13
15
  * Assembles the {@link HttpRequest} the fetch client executes. Centralized so
@@ -15,5 +17,5 @@ export interface BuildEntityRequestOptions {
15
17
  * body is always an {@link EntifixEnvelope}, and that the JSON content type is
16
18
  * declared only when there is one to declare.
17
19
  */
18
- export declare const buildEntityRequest: ({ method, url, envelope, }: BuildEntityRequestOptions) => HttpRequest<EntifixEnvelope<unknown>>;
20
+ export declare const buildEntityRequest: ({ method, url, envelope, idempotencyKey, }: BuildEntityRequestOptions) => HttpRequest<EntifixEnvelope<unknown>>;
19
21
  //# sourceMappingURL=build-entity-request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-entity-request.d.ts","sourceRoot":"","sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;CACrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,4BAIhC,yBAAyB,KAAG,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAMjE,CAAC"}
1
+ {"version":3,"file":"build-entity-request.d.ts","sourceRoot":"","sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAwB,MAAM,eAAe,CAAC;AAE3E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,4CAKhC,yBAAyB,KAAG,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAalE,CAAC"}
@@ -1,17 +1,24 @@
1
1
  import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { formatIdempotencyKey } from '@entifix/core';
2
3
  /**
3
4
  * Assembles the {@link HttpRequest} the fetch client executes. Centralized so
4
5
  * the read and write adapters agree on how a message is framed — notably that a
5
6
  * body is always an {@link EntifixEnvelope}, and that the JSON content type is
6
7
  * declared only when there is one to declare.
7
- */ export const buildEntityRequest = ({ method, url, envelope })=>_extends({
8
+ */ export const buildEntityRequest = ({ method, url, envelope, idempotencyKey })=>{
9
+ const headers = _extends({}, envelope ? {
10
+ 'Content-Type': 'application/json'
11
+ } : {}, idempotencyKey === undefined ? {} : {
12
+ 'Idempotency-Key': formatIdempotencyKey(idempotencyKey)
13
+ });
14
+ return _extends({
8
15
  method,
9
16
  url
10
- }, envelope ? {
11
- headers: {
12
- 'Content-Type': 'application/json'
13
- },
17
+ }, Object.keys(headers).length > 0 ? {
18
+ headers
19
+ } : {}, envelope ? {
14
20
  body: envelope
15
21
  } : {});
22
+ };
16
23
 
17
24
  //# sourceMappingURL=build-entity-request.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.ts"],"sourcesContent":["import type { EntifixEnvelope } from '@entifix/core';\n\nimport { HttpMethod, HttpRequest } from '../../../clients/types.js';\n\nexport interface BuildEntityRequestOptions {\n method: HttpMethod;\n url: string;\n /**\n * The body, already wrapped as an envelope — every entifix message is one.\n * Omitted for reads and deletes, which carry no payload.\n */\n envelope?: EntifixEnvelope<unknown>;\n}\n\n/**\n * Assembles the {@link HttpRequest} the fetch client executes. Centralized so\n * the read and write adapters agree on how a message is framed — notably that a\n * body is always an {@link EntifixEnvelope}, and that the JSON content type is\n * declared only when there is one to declare.\n */\nexport const buildEntityRequest = ({\n method,\n url,\n envelope,\n}: BuildEntityRequestOptions): HttpRequest<EntifixEnvelope<unknown>> => ({\n method,\n url,\n ...(envelope\n ? { headers: { 'Content-Type': 'application/json' }, body: envelope }\n : {}),\n});\n"],"names":["buildEntityRequest","method","url","envelope","headers","body"],"mappings":";AAcA;;;;;CAKC,GACD,OAAO,MAAMA,qBAAqB,CAAC,EACjCC,MAAM,EACNC,GAAG,EACHC,QAAQ,EACkB,GAA6C;QACvEF;QACAC;OACIC,WACA;QAAEC,SAAS;YAAE,gBAAgB;QAAmB;QAAGC,MAAMF;IAAS,IAClE,CAAC,GACJ"}
1
+ {"version":3,"sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-mixins/build-entity-request.ts"],"sourcesContent":["import { type EntifixEnvelope, formatIdempotencyKey } from '@entifix/core';\n\nimport { HttpMethod, HttpRequest } from '../../../clients/types.js';\n\nexport interface BuildEntityRequestOptions {\n method: HttpMethod;\n url: string;\n /**\n * The body, already wrapped as an envelope — every entifix message is one.\n * Omitted for reads and deletes, which carry no payload.\n */\n envelope?: EntifixEnvelope<unknown>;\n /** A write's `Idempotency-Key`, already minted by the caller. */\n idempotencyKey?: string;\n}\n\n/**\n * Assembles the {@link HttpRequest} the fetch client executes. Centralized so\n * the read and write adapters agree on how a message is framed — notably that a\n * body is always an {@link EntifixEnvelope}, and that the JSON content type is\n * declared only when there is one to declare.\n */\nexport const buildEntityRequest = ({\n method,\n url,\n envelope,\n idempotencyKey,\n}: BuildEntityRequestOptions): HttpRequest<EntifixEnvelope<unknown>> => {\n const headers: Record<string, string> = {\n ...(envelope ? { 'Content-Type': 'application/json' } : {}),\n ...(idempotencyKey === undefined\n ? {}\n : { 'Idempotency-Key': formatIdempotencyKey(idempotencyKey) }),\n };\n return {\n method,\n url,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n ...(envelope ? { body: envelope } : {}),\n };\n};\n"],"names":["formatIdempotencyKey","buildEntityRequest","method","url","envelope","idempotencyKey","headers","undefined","Object","keys","length","body"],"mappings":";AAAA,SAA+BA,oBAAoB,QAAQ,gBAAgB;AAgB3E;;;;;CAKC,GACD,OAAO,MAAMC,qBAAqB,CAAC,EACjCC,MAAM,EACNC,GAAG,EACHC,QAAQ,EACRC,cAAc,EACY;IAC1B,MAAMC,UAAkC,aAClCF,WAAW;QAAE,gBAAgB;IAAmB,IAAI,CAAC,GACrDC,mBAAmBE,YACnB,CAAC,IACD;QAAE,mBAAmBP,qBAAqBK;IAAgB;IAEhE,OAAO;QACLH;QACAC;OACIK,OAAOC,IAAI,CAACH,SAASI,MAAM,GAAG,IAAI;QAAEJ;IAAQ,IAAI,CAAC,GACjDF,WAAW;QAAEO,MAAMP;IAAS,IAAI,CAAC;AAEzC,EAAE"}
@@ -1,5 +1,5 @@
1
1
  export declare const buildEntityRestAdapterMixins: {
2
2
  buildEntityBaseUrl: (configurationStore: import("@entifix/core").ConfigurationClient, options: Pick<import("../types.js").BuildEntityRestOptions, "uriConfig">, entityName: string, entityId?: string) => import("effect/Effect").Effect<string, import("@entifix/core").EntifixBuildError, never>;
3
- buildEntityRequest: ({ method, url, envelope, }: import("./build-entity-request.js").BuildEntityRequestOptions) => import("../../../index.js").HttpRequest<import("@entifix/core").EntifixEnvelope<unknown>>;
3
+ buildEntityRequest: ({ method, url, envelope, idempotencyKey, }: import("./build-entity-request.js").BuildEntityRequestOptions) => import("../../../index.js").HttpRequest<import("@entifix/core").EntifixEnvelope<unknown>>;
4
4
  };
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-entity-rest-adapter-save.d.ts","sourceRoot":"","sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EAKlB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B,GACpC,OAAO,SAAS,MAAM,EACrB,mBAAmB,iBAAiB,CAAC,OAAO,CAAC,EAC7C,sCAAiD,sBAAsB,MAKxE,MAAM,SAAS,MAAM,EAAE,QAAQ,MAAM,8KAiFlC,CAAC"}
1
+ {"version":3,"file":"build-entity-rest-adapter-save.d.ts","sourceRoot":"","sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EAKlB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAkBrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B,GACpC,OAAO,SAAS,MAAM,EACrB,mBAAmB,iBAAiB,CAAC,OAAO,CAAC,EAC7C,sCAAiD,sBAAsB,MAKxE,MAAM,SAAS,MAAM,EAAE,QAAQ,MAAM,8KA4FlC,CAAC"}
@@ -3,6 +3,18 @@ import { makeCommandEnvelope, readTransactionAcceptedEnvelope, TransactionSinkTa
3
3
  import { Effect, Option } from 'effect';
4
4
  import { performHttpRequestThroughFetch } from '../../../clients/fetch/index.js';
5
5
  import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-rest-adapter-mixins/index.js';
6
+ /**
7
+ * The `Idempotency-Key` each entity's pending save was sent under, kept until
8
+ * that save succeeds.
9
+ *
10
+ * Keyed by the entity instance, so a save called again on the same object
11
+ * after a network error — the case the key exists for — sends the same key and
12
+ * the service replays its first answer instead of writing twice. A save that
13
+ * succeeded forgets its key, so the next edit of that object is a new request.
14
+ * The browser itself never retries (ADR 0014); this is what makes a person's
15
+ * retry safe.
16
+ */ const pendingKeys = new WeakMap();
17
+ /** A transactional create whose `202` never arrived: same command, same id. */ const pendingCreates = new WeakSet();
6
18
  /**
7
19
  * Persists an entity over HTTP.
8
20
  *
@@ -25,18 +37,23 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-r
25
37
  // envelope has to be built against the *constructor's* entity type, which a
26
38
  // shadowed parameter would make unreachable.
27
39
  (entity)=>Effect.gen(function*() {
28
- var _metaEntity_key;
40
+ var _metaEntity_key, _pendingKeys_get;
29
41
  const metaEntity = extractMetaEntity(entityConstructor);
30
42
  const key = (_metaEntity_key = metaEntity.key) != null ? _metaEntity_key : metaEntity.name;
31
- const isCreate = entity.id == null;
43
+ const isCreate = entity.id == null || pendingCreates.has(entity);
32
44
  const url = yield* adapterMixins.buildEntityBaseUrl(configuration, {
33
45
  uriConfig
34
46
  }, key, isCreate ? undefined : String(entity.id));
35
47
  if (isCreate && create === 'command') {
48
+ var _pendingKeys_get1;
36
49
  // `crypto.randomUUID` is available in every browser this ships to and in
37
50
  // Node — and the id must be a UUID, because the service constrains the
38
51
  // key space a caller may address.
39
- const transactionId = crypto.randomUUID();
52
+ // A retried create reuses the id it was first sent with: that id *is*
53
+ // the idempotency key, so a new one would write a second record.
54
+ const transactionId = (_pendingKeys_get1 = pendingKeys.get(entity)) != null ? _pendingKeys_get1 : crypto.randomUUID();
55
+ pendingKeys.set(entity, transactionId);
56
+ pendingCreates.add(entity);
40
57
  entity.id = transactionId;
41
58
  const command = {
42
59
  transactionId,
@@ -47,12 +64,15 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-r
47
64
  const accepted = yield* performHttpRequestThroughFetch(adapterMixins.buildEntityRequest({
48
65
  method: 'POST',
49
66
  url,
50
- envelope: makeCommandEnvelope(command)
67
+ envelope: makeCommandEnvelope(command),
68
+ idempotencyKey: transactionId
51
69
  }));
52
70
  // Read for its shape, not its contents: this asserts the service really
53
71
  // accepted a transaction rather than answering something else with a
54
72
  // 2xx, which is the failure the old code path could not distinguish.
55
73
  yield* readTransactionAcceptedEnvelope(accepted.body);
74
+ pendingKeys.delete(entity);
75
+ pendingCreates.delete(entity);
56
76
  // Tell whoever is watching that a write is in flight. This is the only
57
77
  // point in the system that knows it — the entity returned below is
58
78
  // indistinguishable from a plain REST create, so without this the
@@ -77,13 +97,17 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-r
77
97
  }
78
98
  return entity;
79
99
  }
100
+ const idempotencyKey = (_pendingKeys_get = pendingKeys.get(entity)) != null ? _pendingKeys_get : crypto.randomUUID();
101
+ pendingKeys.set(entity, idempotencyKey);
80
102
  const httpRequest = adapterMixins.buildEntityRequest({
81
103
  method: isCreate ? 'POST' : 'PUT',
82
104
  url,
83
- envelope: makeEntityEnvelope(entityConstructor, entity)
105
+ envelope: makeEntityEnvelope(entityConstructor, entity),
106
+ idempotencyKey
84
107
  });
85
108
  const response = yield* performHttpRequestThroughFetch(httpRequest);
86
109
  const saved = yield* readEntityEnvelope(entityConstructor, response.body);
110
+ pendingKeys.delete(entity);
87
111
  return saved;
88
112
  });
89
113
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.ts"],"sourcesContent":["import {\n Entity,\n EntityConstructor,\n extractMetaEntity,\n makeEntityEnvelope,\n readEntityEnvelope,\n serializeEntity,\n} from '@entifix/core';\nimport {\n makeCommandEnvelope,\n readTransactionAcceptedEnvelope,\n type TransactionCommand,\n TransactionSinkTag,\n} from '@entifix/transactions';\nimport { Effect, Option } from 'effect';\n\nimport { performHttpRequestThroughFetch } from '../../../clients/fetch/index.js';\nimport { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-rest-adapter-mixins/index.js';\nimport { BuildEntityRestOptions } from '../types.js';\n\n/**\n * Persists an entity over HTTP.\n *\n * An entity without an id has never been stored, so it is created with `POST`\n * against the collection; one with an id is replaced with `PUT` against its own\n * URL.\n *\n * For a plain create the response envelope — not the request — is what gets\n * deserialized and returned, because the service is the authority on the stored\n * entity (it assigns ids and may normalize fields).\n *\n * A **transactional** create inverts that, and deliberately. The service answers\n * `202` describing a transaction rather than an entity, so there is no stored\n * entity to read back yet: the client mints the transaction id, which *is* the\n * id the entity will be stored under, and returns the entity it already holds.\n * That is the point of the client owning the id — before it, this\n * adapter parsed the `202` as an entity envelope, failed, and reported an error\n * for a create that was in fact about to succeed.\n */\nexport const buildEntityRestAdapterSave =\n <TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n { uriConfig, create = 'entity', configuration }: BuildEntityRestOptions,\n ) =>\n // Deliberately not shadowing `TEntity` here (as the read builders do): the\n // envelope has to be built against the *constructor's* entity type, which a\n // shadowed parameter would make unreachable.\n <TInput extends Entity>(entity: TInput) =>\n Effect.gen(function* () {\n const metaEntity = extractMetaEntity(entityConstructor);\n const key = metaEntity.key ?? metaEntity.name;\n const isCreate = entity.id == null;\n\n const url = yield* adapterMixins.buildEntityBaseUrl(\n configuration,\n { uriConfig },\n key,\n isCreate ? undefined : String(entity.id),\n );\n\n if (isCreate && create === 'command') {\n // `crypto.randomUUID` is available in every browser this ships to and in\n // Node — and the id must be a UUID, because the service constrains the\n // key space a caller may address.\n const transactionId = crypto.randomUUID();\n entity.id = transactionId;\n\n const command: TransactionCommand = {\n transactionId,\n type: 'create',\n entity: key,\n payload: serializeEntity(\n entityConstructor,\n entity as unknown as TEntity,\n ),\n };\n\n const accepted = yield* performHttpRequestThroughFetch(\n adapterMixins.buildEntityRequest({\n method: 'POST',\n url,\n envelope: makeCommandEnvelope(command),\n }),\n );\n // Read for its shape, not its contents: this asserts the service really\n // accepted a transaction rather than answering something else with a\n // 2xx, which is the failure the old code path could not distinguish.\n yield* readTransactionAcceptedEnvelope(accepted.body);\n\n // Tell whoever is watching that a write is in flight. This is the only\n // point in the system that knows it — the entity returned below is\n // indistinguishable from a plain REST create, so without this the\n // browser navigates away at the `202` and a later failure leaves no row,\n // no message and nothing to retry from.\n //\n // `serviceOption`, deliberately: reading the tag this way keeps it out\n // of this function's `R`, so the storefront, the plain REST adapters and\n // every existing spec compile and run with no layer to provide. The cost\n // is that nothing *forces* a composition root to provide it, which is\n // why `mergeContext` has a spec pinning that it does.\n const sink = yield* Effect.serviceOption(TransactionSinkTag);\n if (Option.isSome(sink)) {\n // After the assertion above, never before: a `2xx` that is not a\n // transaction envelope must not register a pending id, because nothing\n // would ever settle it and the entry becomes a permanent phantom.\n sink.value.began({\n transactionId,\n entity: key,\n at: new Date().toISOString(),\n });\n }\n\n return entity;\n }\n\n const httpRequest = adapterMixins.buildEntityRequest({\n method: isCreate ? 'POST' : 'PUT',\n url,\n envelope: makeEntityEnvelope(\n entityConstructor,\n entity as unknown as TEntity,\n ),\n });\n\n const response = yield* performHttpRequestThroughFetch(httpRequest);\n const saved = yield* readEntityEnvelope(entityConstructor, response.body);\n\n return saved as unknown as TInput;\n });\n"],"names":["extractMetaEntity","makeEntityEnvelope","readEntityEnvelope","serializeEntity","makeCommandEnvelope","readTransactionAcceptedEnvelope","TransactionSinkTag","Effect","Option","performHttpRequestThroughFetch","buildEntityRestAdapterMixins","adapterMixins","buildEntityRestAdapterSave","entityConstructor","uriConfig","create","configuration","entity","gen","metaEntity","key","name","isCreate","id","url","buildEntityBaseUrl","undefined","String","transactionId","crypto","randomUUID","command","type","payload","accepted","buildEntityRequest","method","envelope","body","sink","serviceOption","isSome","value","began","at","Date","toISOString","httpRequest","response","saved"],"mappings":"AAAA,SAGEA,iBAAiB,EACjBC,kBAAkB,EAClBC,kBAAkB,EAClBC,eAAe,QACV,gBAAgB;AACvB,SACEC,mBAAmB,EACnBC,+BAA+B,EAE/BC,kBAAkB,QACb,wBAAwB;AAC/B,SAASC,MAAM,EAAEC,MAAM,QAAQ,SAAS;AAExC,SAASC,8BAA8B,QAAQ,kCAAkC;AACjF,SAASC,gCAAgCC,aAAa,QAAQ,+CAA+C;AAG7G;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,MAAMC,6BACX,CACEC,mBACA,EAAEC,SAAS,EAAEC,SAAS,QAAQ,EAAEC,aAAa,EAA0B,GAEzE,2EAA2E;IAC3E,4EAA4E;IAC5E,6CAA6C;IAC7C,CAAwBC,SACtBV,OAAOW,GAAG,CAAC;gBAEGC;YADZ,MAAMA,aAAanB,kBAAkBa;YACrC,MAAMO,OAAMD,kBAAAA,WAAWC,GAAG,YAAdD,kBAAkBA,WAAWE,IAAI;YAC7C,MAAMC,WAAWL,OAAOM,EAAE,IAAI;YAE9B,MAAMC,MAAM,OAAOb,cAAcc,kBAAkB,CACjDT,eACA;gBAAEF;YAAU,GACZM,KACAE,WAAWI,YAAYC,OAAOV,OAAOM,EAAE;YAGzC,IAAID,YAAYP,WAAW,WAAW;gBACpC,yEAAyE;gBACzE,uEAAuE;gBACvE,kCAAkC;gBAClC,MAAMa,gBAAgBC,OAAOC,UAAU;gBACvCb,OAAOM,EAAE,GAAGK;gBAEZ,MAAMG,UAA8B;oBAClCH;oBACAI,MAAM;oBACNf,QAAQG;oBACRa,SAAS9B,gBACPU,mBACAI;gBAEJ;gBAEA,MAAMiB,WAAW,OAAOzB,+BACtBE,cAAcwB,kBAAkB,CAAC;oBAC/BC,QAAQ;oBACRZ;oBACAa,UAAUjC,oBAAoB2B;gBAChC;gBAEF,wEAAwE;gBACxE,qEAAqE;gBACrE,qEAAqE;gBACrE,OAAO1B,gCAAgC6B,SAASI,IAAI;gBAEpD,uEAAuE;gBACvE,mEAAmE;gBACnE,kEAAkE;gBAClE,yEAAyE;gBACzE,wCAAwC;gBACxC,EAAE;gBACF,uEAAuE;gBACvE,yEAAyE;gBACzE,yEAAyE;gBACzE,sEAAsE;gBACtE,sDAAsD;gBACtD,MAAMC,OAAO,OAAOhC,OAAOiC,aAAa,CAAClC;gBACzC,IAAIE,OAAOiC,MAAM,CAACF,OAAO;oBACvB,iEAAiE;oBACjE,uEAAuE;oBACvE,kEAAkE;oBAClEA,KAAKG,KAAK,CAACC,KAAK,CAAC;wBACff;wBACAX,QAAQG;wBACRwB,IAAI,IAAIC,OAAOC,WAAW;oBAC5B;gBACF;gBAEA,OAAO7B;YACT;YAEA,MAAM8B,cAAcpC,cAAcwB,kBAAkB,CAAC;gBACnDC,QAAQd,WAAW,SAAS;gBAC5BE;gBACAa,UAAUpC,mBACRY,mBACAI;YAEJ;YAEA,MAAM+B,WAAW,OAAOvC,+BAA+BsC;YACvD,MAAME,QAAQ,OAAO/C,mBAAmBW,mBAAmBmC,SAASV,IAAI;YAExE,OAAOW;QACT,GAAG"}
1
+ {"version":3,"sources":["../../../../src/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.ts"],"sourcesContent":["import {\n Entity,\n EntityConstructor,\n extractMetaEntity,\n makeEntityEnvelope,\n readEntityEnvelope,\n serializeEntity,\n} from '@entifix/core';\nimport {\n makeCommandEnvelope,\n readTransactionAcceptedEnvelope,\n type TransactionCommand,\n TransactionSinkTag,\n} from '@entifix/transactions';\nimport { Effect, Option } from 'effect';\n\nimport { performHttpRequestThroughFetch } from '../../../clients/fetch/index.js';\nimport { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-rest-adapter-mixins/index.js';\nimport { BuildEntityRestOptions } from '../types.js';\n\n/**\n * The `Idempotency-Key` each entity's pending save was sent under, kept until\n * that save succeeds.\n *\n * Keyed by the entity instance, so a save called again on the same object\n * after a network error — the case the key exists for — sends the same key and\n * the service replays its first answer instead of writing twice. A save that\n * succeeded forgets its key, so the next edit of that object is a new request.\n * The browser itself never retries (ADR 0014); this is what makes a person's\n * retry safe.\n */\nconst pendingKeys = new WeakMap<object, string>();\n\n/** A transactional create whose `202` never arrived: same command, same id. */\nconst pendingCreates = new WeakSet<object>();\n\n/**\n * Persists an entity over HTTP.\n *\n * An entity without an id has never been stored, so it is created with `POST`\n * against the collection; one with an id is replaced with `PUT` against its own\n * URL.\n *\n * For a plain create the response envelope — not the request — is what gets\n * deserialized and returned, because the service is the authority on the stored\n * entity (it assigns ids and may normalize fields).\n *\n * A **transactional** create inverts that, and deliberately. The service answers\n * `202` describing a transaction rather than an entity, so there is no stored\n * entity to read back yet: the client mints the transaction id, which *is* the\n * id the entity will be stored under, and returns the entity it already holds.\n * That is the point of the client owning the id — before it, this\n * adapter parsed the `202` as an entity envelope, failed, and reported an error\n * for a create that was in fact about to succeed.\n */\nexport const buildEntityRestAdapterSave =\n <TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n { uriConfig, create = 'entity', configuration }: BuildEntityRestOptions,\n ) =>\n // Deliberately not shadowing `TEntity` here (as the read builders do): the\n // envelope has to be built against the *constructor's* entity type, which a\n // shadowed parameter would make unreachable.\n <TInput extends Entity>(entity: TInput) =>\n Effect.gen(function* () {\n const metaEntity = extractMetaEntity(entityConstructor);\n const key = metaEntity.key ?? metaEntity.name;\n const isCreate = entity.id == null || pendingCreates.has(entity);\n\n const url = yield* adapterMixins.buildEntityBaseUrl(\n configuration,\n { uriConfig },\n key,\n isCreate ? undefined : String(entity.id),\n );\n\n if (isCreate && create === 'command') {\n // `crypto.randomUUID` is available in every browser this ships to and in\n // Node — and the id must be a UUID, because the service constrains the\n // key space a caller may address.\n // A retried create reuses the id it was first sent with: that id *is*\n // the idempotency key, so a new one would write a second record.\n const transactionId = pendingKeys.get(entity) ?? crypto.randomUUID();\n pendingKeys.set(entity, transactionId);\n pendingCreates.add(entity);\n entity.id = transactionId;\n\n const command: TransactionCommand = {\n transactionId,\n type: 'create',\n entity: key,\n payload: serializeEntity(\n entityConstructor,\n entity as unknown as TEntity,\n ),\n };\n\n const accepted = yield* performHttpRequestThroughFetch(\n adapterMixins.buildEntityRequest({\n method: 'POST',\n url,\n envelope: makeCommandEnvelope(command),\n idempotencyKey: transactionId,\n }),\n );\n // Read for its shape, not its contents: this asserts the service really\n // accepted a transaction rather than answering something else with a\n // 2xx, which is the failure the old code path could not distinguish.\n yield* readTransactionAcceptedEnvelope(accepted.body);\n pendingKeys.delete(entity);\n pendingCreates.delete(entity);\n\n // Tell whoever is watching that a write is in flight. This is the only\n // point in the system that knows it — the entity returned below is\n // indistinguishable from a plain REST create, so without this the\n // browser navigates away at the `202` and a later failure leaves no row,\n // no message and nothing to retry from.\n //\n // `serviceOption`, deliberately: reading the tag this way keeps it out\n // of this function's `R`, so the storefront, the plain REST adapters and\n // every existing spec compile and run with no layer to provide. The cost\n // is that nothing *forces* a composition root to provide it, which is\n // why `mergeContext` has a spec pinning that it does.\n const sink = yield* Effect.serviceOption(TransactionSinkTag);\n if (Option.isSome(sink)) {\n // After the assertion above, never before: a `2xx` that is not a\n // transaction envelope must not register a pending id, because nothing\n // would ever settle it and the entry becomes a permanent phantom.\n sink.value.began({\n transactionId,\n entity: key,\n at: new Date().toISOString(),\n });\n }\n\n return entity;\n }\n\n const idempotencyKey = pendingKeys.get(entity) ?? crypto.randomUUID();\n pendingKeys.set(entity, idempotencyKey);\n const httpRequest = adapterMixins.buildEntityRequest({\n method: isCreate ? 'POST' : 'PUT',\n url,\n envelope: makeEntityEnvelope(\n entityConstructor,\n entity as unknown as TEntity,\n ),\n idempotencyKey,\n });\n\n const response = yield* performHttpRequestThroughFetch(httpRequest);\n const saved = yield* readEntityEnvelope(entityConstructor, response.body);\n pendingKeys.delete(entity);\n\n return saved as unknown as TInput;\n });\n"],"names":["extractMetaEntity","makeEntityEnvelope","readEntityEnvelope","serializeEntity","makeCommandEnvelope","readTransactionAcceptedEnvelope","TransactionSinkTag","Effect","Option","performHttpRequestThroughFetch","buildEntityRestAdapterMixins","adapterMixins","pendingKeys","WeakMap","pendingCreates","WeakSet","buildEntityRestAdapterSave","entityConstructor","uriConfig","create","configuration","entity","gen","metaEntity","key","name","isCreate","id","has","url","buildEntityBaseUrl","undefined","String","transactionId","get","crypto","randomUUID","set","add","command","type","payload","accepted","buildEntityRequest","method","envelope","idempotencyKey","body","delete","sink","serviceOption","isSome","value","began","at","Date","toISOString","httpRequest","response","saved"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from './make-entity-rest-use-case-runner.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/entity-use-case/index.ts"],"names":[],"mappings":"AAAA,cAAc,uCAAuC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './make-entity-rest-use-case-runner.js';
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/entity-use-case/index.ts"],"sourcesContent":["export * from './make-entity-rest-use-case-runner.js';\n"],"names":[],"mappings":"AAAA,cAAc,wCAAwC"}
@@ -0,0 +1,13 @@
1
+ import { type Entity, type EntityConstructor, type EntityUseCaseRunner } from '@entifix/core';
2
+ import type { BuildEntityRestOptions } from '../entity-rest/types.js';
3
+ /**
4
+ * Runs an entity's verbs against the service that serves them, at the path the
5
+ * shell derives from each verb's key and binding — under the same base URL the
6
+ * entity's other adapters use, so a same-origin proxy is followed the same way
7
+ * ([ADR 0020](../../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)).
8
+ *
9
+ * A refusal arrives as the failure the service sent, decoded from its problem
10
+ * body, so a screen shows the coded message rather than a generic error.
11
+ */
12
+ export declare const makeEntityRestUseCaseRunner: <TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>, { uriConfig, configuration }: BuildEntityRestOptions) => EntityUseCaseRunner;
13
+ //# sourceMappingURL=make-entity-rest-use-case-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make-entity-rest-use-case-runner.d.ts","sourceRoot":"","sources":["../../../src/adapters/entity-use-case/make-entity-rest-use-case-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAIzB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,SAAS,MAAM,EAChE,mBAAmB,iBAAiB,CAAC,OAAO,CAAC,EAC7C,8BAA8B,sBAAsB,KACnD,mBA0CF,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { extractMetaEntity, ProtocolFailure, toWireSelection } from '@entifix/core';
2
+ import { Effect } from 'effect';
3
+ import { performHttpRequestThroughFetch } from '../../clients/fetch/index.js';
4
+ import { buildEntityRestAdapterMixins as adapterMixins } from '../entity-rest/build-entity-rest-adapter-mixins/index.js';
5
+ /**
6
+ * Runs an entity's verbs against the service that serves them, at the path the
7
+ * shell derives from each verb's key and binding — under the same base URL the
8
+ * entity's other adapters use, so a same-origin proxy is followed the same way
9
+ * ([ADR 0020](../../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)).
10
+ *
11
+ * A refusal arrives as the failure the service sent, decoded from its problem
12
+ * body, so a screen shows the coded message rather than a generic error.
13
+ */ export const makeEntityRestUseCaseRunner = (entityConstructor, { uriConfig, configuration })=>{
14
+ var _metaEntity_key;
15
+ const metaEntity = extractMetaEntity(entityConstructor);
16
+ const name = (_metaEntity_key = metaEntity.key) != null ? _metaEntity_key : metaEntity.name;
17
+ const baseUrl = (id)=>adapterMixins.buildEntityBaseUrl(configuration, {
18
+ uriConfig
19
+ }, name, id);
20
+ return {
21
+ run: (useCase, id)=>Effect.gen(function*() {
22
+ const url = yield* baseUrl(useCase.binding === 'entity' && id != null ? String(id) : undefined);
23
+ yield* performHttpRequestThroughFetch({
24
+ method: 'POST',
25
+ url: `${url}/${useCase.key}`
26
+ });
27
+ }),
28
+ bulk: (useCase, selection)=>Effect.gen(function*() {
29
+ var _response_body;
30
+ const url = yield* baseUrl();
31
+ // The wire form, never the selection itself: a `Set` serializes to
32
+ // `{}`, so a selection sent raw would lose its ids.
33
+ const response = yield* performHttpRequestThroughFetch({
34
+ method: 'POST',
35
+ url: `${url}/${useCase.key}`,
36
+ headers: {
37
+ 'Content-Type': 'application/json'
38
+ },
39
+ body: {
40
+ selection: toWireSelection(selection)
41
+ }
42
+ });
43
+ // A bulk answer is a `bulkOutcome` envelope, one outcome per record in
44
+ // its `data`; anything else is a peer speaking something else.
45
+ const outcomes = (_response_body = response.body) == null ? void 0 : _response_body.data;
46
+ if (!Array.isArray(outcomes)) {
47
+ return yield* new ProtocolFailure({
48
+ reason: 'a bulk answer carries its outcomes in a bulkOutcome envelope'
49
+ });
50
+ }
51
+ return outcomes;
52
+ })
53
+ };
54
+ };
55
+
56
+ //# sourceMappingURL=make-entity-rest-use-case-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/entity-use-case/make-entity-rest-use-case-runner.ts"],"sourcesContent":["import {\n type BulkOutcome,\n type Entity,\n type EntityConstructor,\n type EntityUseCaseRunner,\n extractMetaEntity,\n ProtocolFailure,\n toWireSelection,\n} from '@entifix/core';\nimport { Effect } from 'effect';\n\nimport { performHttpRequestThroughFetch } from '../../clients/fetch/index.js';\nimport { buildEntityRestAdapterMixins as adapterMixins } from '../entity-rest/build-entity-rest-adapter-mixins/index.js';\nimport type { BuildEntityRestOptions } from '../entity-rest/types.js';\n\n/**\n * Runs an entity's verbs against the service that serves them, at the path the\n * shell derives from each verb's key and binding — under the same base URL the\n * entity's other adapters use, so a same-origin proxy is followed the same way\n * ([ADR 0020](../../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)).\n *\n * A refusal arrives as the failure the service sent, decoded from its problem\n * body, so a screen shows the coded message rather than a generic error.\n */\nexport const makeEntityRestUseCaseRunner = <TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n { uriConfig, configuration }: BuildEntityRestOptions,\n): EntityUseCaseRunner => {\n const metaEntity = extractMetaEntity(entityConstructor);\n const name = metaEntity.key ?? metaEntity.name;\n const baseUrl = (id?: string) =>\n adapterMixins.buildEntityBaseUrl(configuration, { uriConfig }, name, id);\n\n return {\n run: (useCase, id) =>\n Effect.gen(function* () {\n const url = yield* baseUrl(\n useCase.binding === 'entity' && id != null ? String(id) : undefined,\n );\n yield* performHttpRequestThroughFetch({\n method: 'POST',\n url: `${url}/${useCase.key}`,\n });\n }),\n bulk: (useCase, selection) =>\n Effect.gen(function* () {\n const url = yield* baseUrl();\n // The wire form, never the selection itself: a `Set` serializes to\n // `{}`, so a selection sent raw would lose its ids.\n const response = yield* performHttpRequestThroughFetch<{\n readonly data?: unknown;\n }>({\n method: 'POST',\n url: `${url}/${useCase.key}`,\n headers: { 'Content-Type': 'application/json' },\n body: { selection: toWireSelection(selection) },\n });\n // A bulk answer is a `bulkOutcome` envelope, one outcome per record in\n // its `data`; anything else is a peer speaking something else.\n const outcomes = response.body?.data;\n if (!Array.isArray(outcomes)) {\n return yield* new ProtocolFailure({\n reason:\n 'a bulk answer carries its outcomes in a bulkOutcome envelope',\n });\n }\n return outcomes as readonly BulkOutcome[];\n }),\n };\n};\n"],"names":["extractMetaEntity","ProtocolFailure","toWireSelection","Effect","performHttpRequestThroughFetch","buildEntityRestAdapterMixins","adapterMixins","makeEntityRestUseCaseRunner","entityConstructor","uriConfig","configuration","metaEntity","name","key","baseUrl","id","buildEntityBaseUrl","run","useCase","gen","url","binding","String","undefined","method","bulk","selection","response","headers","body","outcomes","data","Array","isArray","reason"],"mappings":"AAAA,SAKEA,iBAAiB,EACjBC,eAAe,EACfC,eAAe,QACV,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAEhC,SAASC,8BAA8B,QAAQ,+BAA+B;AAC9E,SAASC,gCAAgCC,aAAa,QAAQ,2DAA2D;AAGzH;;;;;;;;CAQC,GACD,OAAO,MAAMC,8BAA8B,CACzCC,mBACA,EAAEC,SAAS,EAAEC,aAAa,EAA0B;QAGvCC;IADb,MAAMA,aAAaX,kBAAkBQ;IACrC,MAAMI,QAAOD,kBAAAA,WAAWE,GAAG,YAAdF,kBAAkBA,WAAWC,IAAI;IAC9C,MAAME,UAAU,CAACC,KACfT,cAAcU,kBAAkB,CAACN,eAAe;YAAED;QAAU,GAAGG,MAAMG;IAEvE,OAAO;QACLE,KAAK,CAACC,SAASH,KACbZ,OAAOgB,GAAG,CAAC;gBACT,MAAMC,MAAM,OAAON,QACjBI,QAAQG,OAAO,KAAK,YAAYN,MAAM,OAAOO,OAAOP,MAAMQ;gBAE5D,OAAOnB,+BAA+B;oBACpCoB,QAAQ;oBACRJ,KAAK,GAAGA,IAAI,CAAC,EAAEF,QAAQL,GAAG,EAAE;gBAC9B;YACF;QACFY,MAAM,CAACP,SAASQ,YACdvB,OAAOgB,GAAG,CAAC;oBAcQQ;gBAbjB,MAAMP,MAAM,OAAON;gBACnB,mEAAmE;gBACnE,oDAAoD;gBACpD,MAAMa,WAAW,OAAOvB,+BAErB;oBACDoB,QAAQ;oBACRJ,KAAK,GAAGA,IAAI,CAAC,EAAEF,QAAQL,GAAG,EAAE;oBAC5Be,SAAS;wBAAE,gBAAgB;oBAAmB;oBAC9CC,MAAM;wBAAEH,WAAWxB,gBAAgBwB;oBAAW;gBAChD;gBACA,uEAAuE;gBACvE,+DAA+D;gBAC/D,MAAMI,YAAWH,iBAAAA,SAASE,IAAI,qBAAbF,eAAeI,IAAI;gBACpC,IAAI,CAACC,MAAMC,OAAO,CAACH,WAAW;oBAC5B,OAAO,OAAO,IAAI7B,gBAAgB;wBAChCiC,QACE;oBACJ;gBACF;gBACA,OAAOJ;YACT;IACJ;AACF,EAAE"}
@@ -0,0 +1,39 @@
1
+ import { type AnyEndpoint, type DeclaredFailure, type EndpointRequest, type EndpointSuccess, type EntifixFailure, type TechnicalFailure } from '@entifix/core';
2
+ import { Effect } from 'effect';
3
+ import { HttpClient } from 'effect/http';
4
+ /**
5
+ * A refusal any guarded endpoint may answer without declaring it: the caller
6
+ * is not signed in, may not, or sent something the endpoint cannot read.
7
+ */
8
+ export type GuardFailure = EntifixFailure<string, string, 'unauthenticated' | 'forbidden' | 'invalid'>;
9
+ /** Everything a call may fail with: what it declares, and what any call may. */
10
+ export type EndpointCallFailure<D> = DeclaredFailure<D> | GuardFailure | TechnicalFailure;
11
+ /** What a caller sends: the endpoint's decoded parts, and its key when idempotent. */
12
+ export type EndpointCallRequest<D> = EndpointRequest<D> & (D extends {
13
+ readonly idempotent?: infer Mode;
14
+ } ? [NonNullable<Mode>] extends [never] ? unknown : {
15
+ readonly idempotencyKey: string;
16
+ } : unknown);
17
+ /** One call per endpoint, by its name. */
18
+ export type EndpointClient<Endpoints extends ReadonlyArray<AnyEndpoint>> = {
19
+ readonly [D in Endpoints[number] as D['name']]: (request: EndpointCallRequest<D>) => Effect.Effect<EndpointSuccess<D>, EndpointCallFailure<D>, HttpClient.HttpClient>;
20
+ };
21
+ export interface EndpointClientOptions {
22
+ /** The origin and any prefix the endpoints' paths are joined to. */
23
+ readonly baseUrl: string;
24
+ /**
25
+ * Shapes the client before each call — forwarding a cookie from a Next
26
+ * server component, or adding a crossing token for a participant call.
27
+ */
28
+ readonly transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
29
+ }
30
+ /**
31
+ * A typed client over a set of endpoint descriptors
32
+ * ([ADR 0020](../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)):
33
+ * one call per endpoint, by name, typed from its descriptor. It runs wherever
34
+ * an `HttpClient` does — `FetchHttpClient` in a browser, a Next server with a
35
+ * forwarded cookie, an Effect service calling a participant — and needs
36
+ * nothing of the shell that serves the endpoints, its status table included.
37
+ */
38
+ export declare const makeEndpointClient: <const Endpoints extends ReadonlyArray<AnyEndpoint>>(endpoints: Endpoints, options: EndpointClientOptions) => EndpointClient<Endpoints>;
39
+ //# sourceMappingURL=endpoint-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-client.d.ts","sourceRoot":"","sources":["../../src/endpoint/endpoint-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAGhB,KAAK,eAAe,EAEpB,KAAK,eAAe,EAEpB,KAAK,eAAe,EACpB,KAAK,cAAc,EAMnB,KAAK,gBAAgB,EAGtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EACL,UAAU,EAGX,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CACvC,MAAM,EACN,MAAM,EACN,iBAAiB,GAAG,WAAW,GAAG,SAAS,CAC5C,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAC/B,eAAe,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,gBAAgB,CAAC;AAEvD,sFAAsF;AACtF,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,GACrD,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,GAC3C,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACjC,OAAO,GACP;IAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACrC,OAAO,CAAC,CAAC;AAEf,0CAA0C;AAC1C,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,aAAa,CAAC,WAAW,CAAC,IAAI;IACzE,QAAQ,EAAE,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAC9C,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAC5B,MAAM,CAAC,MAAM,CAChB,eAAe,CAAC,CAAC,CAAC,EAClB,mBAAmB,CAAC,CAAC,CAAC,EACtB,UAAU,CAAC,UAAU,CACtB;CACF,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,MAAM,EAAE,UAAU,CAAC,UAAU,KAC1B,UAAU,CAAC,UAAU,CAAC;CAC5B;AAgLD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,KAAK,CAAC,SAAS,SAAS,aAAa,CAAC,WAAW,CAAC,EAElD,WAAW,SAAS,EACpB,SAAS,qBAAqB,KAC7B,cAAc,CAAC,SAAS,CAMgB,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { currentRemainingMs, DEADLINE_HEADER, decodeProblem, formatIdempotencyKey, IDEMPOTENCY_KEY_HEADER, Overloaded, PROBLEM_CONTENT_TYPE, ProtocolFailure, Timeout, Unavailable } from '@entifix/core';
2
+ import { Effect, Schema } from 'effect';
3
+ import { HttpClient, HttpClientRequest } from 'effect/http';
4
+ const toSchema = (schema)=>Schema.isSchema(schema) ? schema : Schema.Struct(schema);
5
+ const encode = (schema, value)=>schema === undefined ? Effect.succeed(undefined) : Schema.encodeUnknownEffect(toSchema(schema))(value).pipe(Effect.mapError((error)=>new ProtocolFailure({
6
+ reason: `the request does not encode: ${error.message}`
7
+ })));
8
+ /**
9
+ * The path with every `:param` replaced by its encoded value. A path that
10
+ * names a parameter is one whose descriptor declares it, so the encoded
11
+ * params carry every name the path does.
12
+ */ const pathOf = (path, params)=>path.replace(/:([A-Za-z0-9_]+)/g, (_match, name)=>encodeURIComponent(String(params[name])));
13
+ const queryOf = (query)=>Object.fromEntries(Object.entries(query != null ? query : {}).filter(([, value])=>value !== undefined).map(([key, value])=>[
14
+ key,
15
+ Array.isArray(value) ? value.map(String) : String(value)
16
+ ]));
17
+ const ALWAYS = new Set([
18
+ 'unauthenticated',
19
+ 'forbidden',
20
+ 'invalid'
21
+ ]);
22
+ /**
23
+ * The failure a response names, decoded by its `code` against what the
24
+ * endpoint may answer — never by its status, which is the shell's to choose.
25
+ * A business failure the endpoint does not declare, a code nobody registered
26
+ * and a body that is not a problem are all `protocol`: the peer said something
27
+ * this contract does not admit.
28
+ */ const failureOf = (descriptor, response)=>Effect.gen(function*() {
29
+ var _response_headers_contenttype;
30
+ const text = yield* response.text.pipe(Effect.orElseSucceed(()=>''));
31
+ const isProblem = ((_response_headers_contenttype = response.headers['content-type']) == null ? void 0 : _response_headers_contenttype.startsWith(PROBLEM_CONTENT_TYPE)) === true;
32
+ if (!isProblem) return new ProtocolFailure({
33
+ reason: `${descriptor.method} ${descriptor.path} answered ${String(response.status)} without a problem body`,
34
+ upstreamStatus: response.status
35
+ });
36
+ const failure = decodeProblem(parse(text), response.status);
37
+ const declared = descriptor.errors.some((failureClass)=>failureClass.identifier === failure._tag);
38
+ if (failure.family === 'business' && !declared && !ALWAYS.has(failure.category)) return new ProtocolFailure({
39
+ reason: `${descriptor.method} ${descriptor.path} answered '${failure.code}', which it does not declare`,
40
+ upstreamStatus: response.status
41
+ });
42
+ const retryAfter = Number(response.headers['retry-after']);
43
+ return failure._tag === 'Overloaded' && !('retryAfterSeconds' in failure) && Number.isFinite(retryAfter) && retryAfter > 0 ? new Overloaded({
44
+ retryAfterSeconds: retryAfter
45
+ }) : failure;
46
+ });
47
+ const parse = (text)=>{
48
+ try {
49
+ return JSON.parse(text);
50
+ } catch (unused) {
51
+ return undefined;
52
+ }
53
+ };
54
+ const call = (descriptor, options, request)=>Effect.gen(function*() {
55
+ var _ref;
56
+ // The caller's remaining budget travels on, and a budget already spent is
57
+ // not worth a round trip.
58
+ const remaining = yield* currentRemainingMs;
59
+ if (remaining === 0) return yield* Effect.fail(new Timeout({
60
+ dependency: `${descriptor.method} ${descriptor.path}`
61
+ }));
62
+ const params = yield* encode(descriptor.params, request['params']);
63
+ const query = yield* encode(descriptor.query, request['query']);
64
+ const payload = yield* encode(descriptor.payload, request['payload']);
65
+ let outgoing = HttpClientRequest.make(descriptor.method)(`${options.baseUrl}${pathOf(descriptor.path, params)}`).pipe(HttpClientRequest.setUrlParams(queryOf(query)), HttpClientRequest.setHeader('accept', 'application/json'));
66
+ if (descriptor.payload !== undefined) outgoing = HttpClientRequest.bodyText(outgoing, JSON.stringify(payload), 'application/json');
67
+ if (descriptor.idempotent !== undefined) outgoing = HttpClientRequest.setHeader(outgoing, IDEMPOTENCY_KEY_HEADER, formatIdempotencyKey(request['idempotencyKey']));
68
+ if (remaining !== undefined) outgoing = HttpClientRequest.setHeader(outgoing, DEADLINE_HEADER, String(remaining));
69
+ const base = yield* HttpClient.HttpClient;
70
+ const client = (_ref = options.transformClient == null ? void 0 : options.transformClient.call(options, base)) != null ? _ref : base;
71
+ const response = yield* client.execute(outgoing).pipe(Effect.mapError(()=>new Unavailable({
72
+ dependency: `${descriptor.method} ${descriptor.path}`
73
+ })));
74
+ if (response.status >= 400) return yield* Effect.fail((yield* failureOf(descriptor, response)));
75
+ const text = yield* response.text.pipe(Effect.mapError(()=>new Unavailable({
76
+ dependency: `${descriptor.method} ${descriptor.path}`
77
+ })));
78
+ return yield* Schema.decodeUnknownEffect(descriptor.success)(text === '' ? undefined : parse(text)).pipe(Effect.mapError((error)=>new ProtocolFailure({
79
+ reason: `${descriptor.method} ${descriptor.path} answered a body that does not decode: ${error.message}`,
80
+ upstreamStatus: response.status
81
+ })));
82
+ });
83
+ /**
84
+ * A typed client over a set of endpoint descriptors
85
+ * ([ADR 0020](../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)):
86
+ * one call per endpoint, by name, typed from its descriptor. It runs wherever
87
+ * an `HttpClient` does — `FetchHttpClient` in a browser, a Next server with a
88
+ * forwarded cookie, an Effect service calling a participant — and needs
89
+ * nothing of the shell that serves the endpoints, its status table included.
90
+ */ export const makeEndpointClient = (endpoints, options)=>Object.fromEntries(endpoints.map((descriptor)=>[
91
+ descriptor.name,
92
+ (request)=>call(descriptor, options, request)
93
+ ]));
94
+
95
+ //# sourceMappingURL=endpoint-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoint/endpoint-client.ts"],"sourcesContent":["import {\n type AnyEndpoint,\n currentRemainingMs,\n DEADLINE_HEADER,\n type DeclaredFailure,\n decodeProblem,\n type EndpointRequest,\n type EndpointSchema,\n type EndpointSuccess,\n type EntifixFailure,\n formatIdempotencyKey,\n IDEMPOTENCY_KEY_HEADER,\n Overloaded,\n PROBLEM_CONTENT_TYPE,\n ProtocolFailure,\n type TechnicalFailure,\n Timeout,\n Unavailable,\n} from '@entifix/core';\nimport { Effect, Schema } from 'effect';\nimport {\n HttpClient,\n HttpClientRequest,\n type HttpClientResponse,\n} from 'effect/http';\n\n/**\n * A refusal any guarded endpoint may answer without declaring it: the caller\n * is not signed in, may not, or sent something the endpoint cannot read.\n */\nexport type GuardFailure = EntifixFailure<\n string,\n string,\n 'unauthenticated' | 'forbidden' | 'invalid'\n>;\n\n/** Everything a call may fail with: what it declares, and what any call may. */\nexport type EndpointCallFailure<D> =\n DeclaredFailure<D> | GuardFailure | TechnicalFailure;\n\n/** What a caller sends: the endpoint's decoded parts, and its key when idempotent. */\nexport type EndpointCallRequest<D> = EndpointRequest<D> &\n (D extends { readonly idempotent?: infer Mode }\n ? [NonNullable<Mode>] extends [never]\n ? unknown\n : { readonly idempotencyKey: string }\n : unknown);\n\n/** One call per endpoint, by its name. */\nexport type EndpointClient<Endpoints extends ReadonlyArray<AnyEndpoint>> = {\n readonly [D in Endpoints[number] as D['name']]: (\n request: EndpointCallRequest<D>,\n ) => Effect.Effect<\n EndpointSuccess<D>,\n EndpointCallFailure<D>,\n HttpClient.HttpClient\n >;\n};\n\nexport interface EndpointClientOptions {\n /** The origin and any prefix the endpoints' paths are joined to. */\n readonly baseUrl: string;\n /**\n * Shapes the client before each call — forwarding a cookie from a Next\n * server component, or adding a crossing token for a participant call.\n */\n readonly transformClient?: (\n client: HttpClient.HttpClient,\n ) => HttpClient.HttpClient;\n}\n\nconst toSchema = (schema: EndpointSchema): Schema.Top =>\n Schema.isSchema(schema) ? schema : Schema.Struct(schema);\n\nconst encode = (schema: EndpointSchema | undefined, value: unknown) =>\n schema === undefined\n ? Effect.succeed(undefined)\n : Schema.encodeUnknownEffect(\n toSchema(schema) as Schema.Codec<unknown, unknown>,\n )(value).pipe(\n Effect.mapError(\n error =>\n new ProtocolFailure({\n reason: `the request does not encode: ${error.message}`,\n }),\n ),\n );\n\n/**\n * The path with every `:param` replaced by its encoded value. A path that\n * names a parameter is one whose descriptor declares it, so the encoded\n * params carry every name the path does.\n */\nconst pathOf = (path: string, params: unknown): string =>\n path.replace(/:([A-Za-z0-9_]+)/g, (_match, name: string) =>\n encodeURIComponent(String((params as Record<string, unknown>)[name])),\n );\n\nconst queryOf = (query: unknown): Record<string, string | string[]> =>\n Object.fromEntries(\n Object.entries((query ?? {}) as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [\n key,\n Array.isArray(value) ? value.map(String) : String(value),\n ]),\n );\n\nconst ALWAYS: ReadonlySet<string> = new Set([\n 'unauthenticated',\n 'forbidden',\n 'invalid',\n]);\n\n/**\n * The failure a response names, decoded by its `code` against what the\n * endpoint may answer — never by its status, which is the shell's to choose.\n * A business failure the endpoint does not declare, a code nobody registered\n * and a body that is not a problem are all `protocol`: the peer said something\n * this contract does not admit.\n */\nconst failureOf = (\n descriptor: AnyEndpoint,\n response: HttpClientResponse.HttpClientResponse,\n) =>\n Effect.gen(function* () {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => ''));\n const isProblem =\n response.headers['content-type']?.startsWith(PROBLEM_CONTENT_TYPE) ===\n true;\n if (!isProblem)\n return new ProtocolFailure({\n reason: `${descriptor.method} ${descriptor.path} answered ${String(response.status)} without a problem body`,\n upstreamStatus: response.status,\n }) as EntifixFailure;\n\n const failure = decodeProblem(parse(text), response.status);\n const declared = descriptor.errors.some(\n failureClass => failureClass.identifier === failure._tag,\n );\n if (\n failure.family === 'business' &&\n !declared &&\n !ALWAYS.has(failure.category)\n )\n return new ProtocolFailure({\n reason: `${descriptor.method} ${descriptor.path} answered '${failure.code}', which it does not declare`,\n upstreamStatus: response.status,\n });\n\n const retryAfter = Number(response.headers['retry-after']);\n return failure._tag === 'Overloaded' &&\n !('retryAfterSeconds' in failure) &&\n Number.isFinite(retryAfter) &&\n retryAfter > 0\n ? new Overloaded({ retryAfterSeconds: retryAfter })\n : failure;\n });\n\nconst parse = (text: string): unknown => {\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n};\n\nconst call = (\n descriptor: AnyEndpoint,\n options: EndpointClientOptions,\n request: Record<string, unknown>,\n) =>\n Effect.gen(function* () {\n // The caller's remaining budget travels on, and a budget already spent is\n // not worth a round trip.\n const remaining = yield* currentRemainingMs;\n if (remaining === 0)\n return yield* Effect.fail(\n new Timeout({ dependency: `${descriptor.method} ${descriptor.path}` }),\n );\n\n const params = yield* encode(descriptor.params, request['params']);\n const query = yield* encode(descriptor.query, request['query']);\n const payload = yield* encode(descriptor.payload, request['payload']);\n\n let outgoing = HttpClientRequest.make(descriptor.method)(\n `${options.baseUrl}${pathOf(descriptor.path, params)}`,\n ).pipe(\n HttpClientRequest.setUrlParams(queryOf(query)),\n HttpClientRequest.setHeader('accept', 'application/json'),\n );\n if (descriptor.payload !== undefined)\n outgoing = HttpClientRequest.bodyText(\n outgoing,\n JSON.stringify(payload),\n 'application/json',\n );\n if (descriptor.idempotent !== undefined)\n outgoing = HttpClientRequest.setHeader(\n outgoing,\n IDEMPOTENCY_KEY_HEADER,\n formatIdempotencyKey(request['idempotencyKey'] as string),\n );\n if (remaining !== undefined)\n outgoing = HttpClientRequest.setHeader(\n outgoing,\n DEADLINE_HEADER,\n String(remaining),\n );\n\n const base = yield* HttpClient.HttpClient;\n const client = options.transformClient?.(base) ?? base;\n const response = yield* client.execute(outgoing).pipe(\n Effect.mapError(\n () =>\n new Unavailable({\n dependency: `${descriptor.method} ${descriptor.path}`,\n }),\n ),\n );\n\n if (response.status >= 400)\n return yield* Effect.fail(yield* failureOf(descriptor, response));\n\n const text = yield* response.text.pipe(\n Effect.mapError(\n () =>\n new Unavailable({\n dependency: `${descriptor.method} ${descriptor.path}`,\n }),\n ),\n );\n return yield* Schema.decodeUnknownEffect(\n descriptor.success as Schema.Codec<unknown, unknown>,\n )(text === '' ? undefined : parse(text)).pipe(\n Effect.mapError(\n error =>\n new ProtocolFailure({\n reason: `${descriptor.method} ${descriptor.path} answered a body that does not decode: ${error.message}`,\n upstreamStatus: response.status,\n }),\n ),\n );\n });\n\n/**\n * A typed client over a set of endpoint descriptors\n * ([ADR 0020](../../../../../docs/adr/0020-endpoints-are-descriptors-and-the-shell-builds-an-httpapi.md)):\n * one call per endpoint, by name, typed from its descriptor. It runs wherever\n * an `HttpClient` does — `FetchHttpClient` in a browser, a Next server with a\n * forwarded cookie, an Effect service calling a participant — and needs\n * nothing of the shell that serves the endpoints, its status table included.\n */\nexport const makeEndpointClient = <\n const Endpoints extends ReadonlyArray<AnyEndpoint>,\n>(\n endpoints: Endpoints,\n options: EndpointClientOptions,\n): EndpointClient<Endpoints> =>\n Object.fromEntries(\n endpoints.map(descriptor => [\n descriptor.name,\n (request: Record<string, unknown>) => call(descriptor, options, request),\n ]),\n ) as unknown as EndpointClient<Endpoints>;\n"],"names":["currentRemainingMs","DEADLINE_HEADER","decodeProblem","formatIdempotencyKey","IDEMPOTENCY_KEY_HEADER","Overloaded","PROBLEM_CONTENT_TYPE","ProtocolFailure","Timeout","Unavailable","Effect","Schema","HttpClient","HttpClientRequest","toSchema","schema","isSchema","Struct","encode","value","undefined","succeed","encodeUnknownEffect","pipe","mapError","error","reason","message","pathOf","path","params","replace","_match","name","encodeURIComponent","String","queryOf","query","Object","fromEntries","entries","filter","map","key","Array","isArray","ALWAYS","Set","failureOf","descriptor","response","gen","text","orElseSucceed","isProblem","headers","startsWith","method","status","upstreamStatus","failure","parse","declared","errors","some","failureClass","identifier","_tag","family","has","category","code","retryAfter","Number","isFinite","retryAfterSeconds","JSON","call","options","request","remaining","fail","dependency","payload","outgoing","make","baseUrl","setUrlParams","setHeader","bodyText","stringify","idempotent","base","client","transformClient","execute","decodeUnknownEffect","success","makeEndpointClient","endpoints"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './endpoint-client.js';
2
+ export * from './participant-client.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoint/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './endpoint-client.js';
2
+ export * from './participant-client.js';
3
+
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoint/index.ts"],"sourcesContent":["export * from './endpoint-client.js';\nexport * from './participant-client.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,0BAA0B"}
@@ -0,0 +1,23 @@
1
+ import { type ParticipantClient, ParticipantClientTag } from '@entifix/transactions';
2
+ import { Layer, Redacted } from 'effect';
3
+ import { HttpClient } from 'effect/http';
4
+ /** Where one participant is reached, and the token it takes. */
5
+ export interface ParticipantAddress {
6
+ readonly baseUrl: string;
7
+ /** The crossing token this caller presents to that participant. */
8
+ readonly crossingToken: Redacted.Redacted<string>;
9
+ }
10
+ /**
11
+ * Calls participants over HTTP, as the slip pinned each step's call
12
+ * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):
13
+ * the crossing token the participant takes, the step's `Idempotency-Key`, the
14
+ * flow's remaining deadline, and a body of the step's parameters and what
15
+ * earlier steps answered. A refusal decodes into the failure the participant
16
+ * sent, by its code; a body that is not a problem is `protocol`; no answer at
17
+ * all is `unavailable`. A step with no call — the commit point that names no
18
+ * participant — answers at once.
19
+ */
20
+ export declare const makeHttpParticipantClient: (client: HttpClient.HttpClient, participants: Readonly<Record<string, ParticipantAddress>>) => ParticipantClient;
21
+ /** The HTTP participant client over the host's `HttpClient`. */
22
+ export declare const HttpParticipantClientLayer: (participants: Readonly<Record<string, ParticipantAddress>>) => Layer.Layer<ParticipantClientTag, never, HttpClient.HttpClient>;
23
+ //# sourceMappingURL=participant-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"participant-client.d.ts","sourceRoot":"","sources":["../../src/endpoint/participant-client.ts"],"names":[],"mappings":"AAaA,OAAO,EAEL,KAAK,iBAAiB,EACtB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAU,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,UAAU,EAAqB,MAAM,aAAa,CAAC;AAE5D,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CACnD;AA8BD;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,UAAU,CAAC,UAAU,EAC7B,cAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,KACzD,iBA+DD,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,0BAA0B,GACrC,cAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,oEAOzD,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { currentRemainingMs, DEADLINE_HEADER, decodeProblem, formatIdempotencyKey, IDEMPOTENCY_KEY_HEADER, Internal, PROBLEM_CONTENT_TYPE, ProtocolFailure, Timeout, Unavailable } from '@entifix/core';
3
+ import { ParticipantClientTag } from '@entifix/transactions';
4
+ import { Effect, Layer, Redacted } from 'effect';
5
+ import { HttpClient, HttpClientRequest } from 'effect/http';
6
+ /** The header a crossing token travels in. */ const CROSSING_TOKEN_HEADER = 'x-crossing-token';
7
+ /** The header a crossing names the organization it acts for in. */ const ORGANIZATION_HEADER = 'x-organization-id';
8
+ const parse = (text)=>{
9
+ try {
10
+ return JSON.parse(text);
11
+ } catch (unused) {
12
+ return undefined;
13
+ }
14
+ };
15
+ /**
16
+ * The step's path with each `:name` taken from its parameters, then from what
17
+ * earlier steps answered — the order a flow learns ids in.
18
+ */ const pathOf = (call, path)=>path.replace(/:([A-Za-z0-9_]+)/g, (_match, name)=>{
19
+ var _ref, _ref1;
20
+ var _call_step_parameters, _call_outputs_name;
21
+ return encodeURIComponent(String((_ref = (_ref1 = (_call_step_parameters = call.step.parameters) == null ? void 0 : _call_step_parameters[name]) != null ? _ref1 : (_call_outputs_name = call.outputs[name]) == null ? void 0 : _call_outputs_name['id']) != null ? _ref : ''));
22
+ });
23
+ /**
24
+ * Calls participants over HTTP, as the slip pinned each step's call
25
+ * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):
26
+ * the crossing token the participant takes, the step's `Idempotency-Key`, the
27
+ * flow's remaining deadline, and a body of the step's parameters and what
28
+ * earlier steps answered. A refusal decodes into the failure the participant
29
+ * sent, by its code; a body that is not a problem is `protocol`; no answer at
30
+ * all is `unavailable`. A step with no call — the commit point that names no
31
+ * participant — answers at once.
32
+ */ export const makeHttpParticipantClient = (client, participants)=>({
33
+ call: (request)=>Effect.gen(function*() {
34
+ var _request_step_participant, _request_step_parameters;
35
+ var _request_step_parameters1, _response_headers_contenttype;
36
+ const target = request.kind === 'action' ? request.step.call : request.step.compensation;
37
+ if (target === undefined) return undefined;
38
+ const name = (_request_step_participant = request.step.participant) != null ? _request_step_participant : '';
39
+ const address = participants[name];
40
+ if (address === undefined) return yield* Effect.fail(new Internal());
41
+ const dependency = `${name} ${target.method} ${target.path}`;
42
+ const remaining = yield* currentRemainingMs;
43
+ if (remaining === 0) return yield* Effect.fail(new Timeout({
44
+ dependency
45
+ }));
46
+ const organizationId = (_request_step_parameters1 = request.step.parameters) == null ? void 0 : _request_step_parameters1['organizationId'];
47
+ const outgoing = HttpClientRequest.make(target.method)(`${address.baseUrl}${pathOf(request, target.path)}`).pipe(HttpClientRequest.setHeaders(_extends({
48
+ accept: 'application/json',
49
+ [CROSSING_TOKEN_HEADER]: Redacted.value(address.crossingToken),
50
+ [IDEMPOTENCY_KEY_HEADER]: formatIdempotencyKey(request.idempotencyKey)
51
+ }, typeof organizationId === 'string' ? {
52
+ [ORGANIZATION_HEADER]: organizationId
53
+ } : {}, remaining === undefined ? {} : {
54
+ [DEADLINE_HEADER]: String(remaining)
55
+ })), HttpClientRequest.bodyText(JSON.stringify({
56
+ parameters: (_request_step_parameters = request.step.parameters) != null ? _request_step_parameters : {},
57
+ outputs: request.outputs
58
+ }), 'application/json'));
59
+ const response = yield* client.execute(outgoing).pipe(Effect.mapError(()=>new Unavailable({
60
+ dependency
61
+ })));
62
+ const text = yield* response.text.pipe(Effect.mapError(()=>new Unavailable({
63
+ dependency
64
+ })));
65
+ if (response.status < 400) return text === '' ? undefined : parse(text);
66
+ const isProblem = ((_response_headers_contenttype = response.headers['content-type']) == null ? void 0 : _response_headers_contenttype.startsWith(PROBLEM_CONTENT_TYPE)) === true;
67
+ return yield* Effect.fail(isProblem ? decodeProblem(parse(text), response.status) : new ProtocolFailure({
68
+ reason: `${dependency} answered ${String(response.status)} without a problem body`,
69
+ upstreamStatus: response.status
70
+ }));
71
+ })
72
+ });
73
+ /** The HTTP participant client over the host's `HttpClient`. */ export const HttpParticipantClientLayer = (participants)=>Layer.effect(ParticipantClientTag, Effect.map(HttpClient.HttpClient, (client)=>makeHttpParticipantClient(client, participants)));
74
+
75
+ //# sourceMappingURL=participant-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoint/participant-client.ts"],"sourcesContent":["import {\n currentRemainingMs,\n DEADLINE_HEADER,\n decodeProblem,\n type EntifixFailure,\n formatIdempotencyKey,\n IDEMPOTENCY_KEY_HEADER,\n Internal,\n PROBLEM_CONTENT_TYPE,\n ProtocolFailure,\n Timeout,\n Unavailable,\n} from '@entifix/core';\nimport {\n type ParticipantCall,\n type ParticipantClient,\n ParticipantClientTag,\n} from '@entifix/transactions';\nimport { Effect, Layer, Redacted } from 'effect';\nimport { HttpClient, HttpClientRequest } from 'effect/http';\n\n/** Where one participant is reached, and the token it takes. */\nexport interface ParticipantAddress {\n readonly baseUrl: string;\n /** The crossing token this caller presents to that participant. */\n readonly crossingToken: Redacted.Redacted<string>;\n}\n\n/** The header a crossing token travels in. */\nconst CROSSING_TOKEN_HEADER = 'x-crossing-token';\n/** The header a crossing names the organization it acts for in. */\nconst ORGANIZATION_HEADER = 'x-organization-id';\n\nconst parse = (text: string): unknown => {\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * The step's path with each `:name` taken from its parameters, then from what\n * earlier steps answered — the order a flow learns ids in.\n */\nconst pathOf = (call: ParticipantCall, path: string) =>\n path.replace(/:([A-Za-z0-9_]+)/g, (_match, name: string) =>\n encodeURIComponent(\n String(\n call.step.parameters?.[name] ??\n (call.outputs[name] as Record<string, unknown> | undefined)?.['id'] ??\n '',\n ),\n ),\n );\n\n/**\n * Calls participants over HTTP, as the slip pinned each step's call\n * ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):\n * the crossing token the participant takes, the step's `Idempotency-Key`, the\n * flow's remaining deadline, and a body of the step's parameters and what\n * earlier steps answered. A refusal decodes into the failure the participant\n * sent, by its code; a body that is not a problem is `protocol`; no answer at\n * all is `unavailable`. A step with no call — the commit point that names no\n * participant — answers at once.\n */\nexport const makeHttpParticipantClient = (\n client: HttpClient.HttpClient,\n participants: Readonly<Record<string, ParticipantAddress>>,\n): ParticipantClient => ({\n call: request =>\n Effect.gen(function* () {\n const target =\n request.kind === 'action'\n ? request.step.call\n : request.step.compensation;\n if (target === undefined) return undefined;\n const name = request.step.participant ?? '';\n const address = participants[name];\n if (address === undefined)\n return yield* Effect.fail(new Internal() as EntifixFailure);\n const dependency = `${name} ${target.method} ${target.path}`;\n\n const remaining = yield* currentRemainingMs;\n if (remaining === 0)\n return yield* Effect.fail(new Timeout({ dependency }));\n\n const organizationId = request.step.parameters?.['organizationId'];\n const outgoing = HttpClientRequest.make(target.method)(\n `${address.baseUrl}${pathOf(request, target.path)}`,\n ).pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n [CROSSING_TOKEN_HEADER]: Redacted.value(address.crossingToken),\n [IDEMPOTENCY_KEY_HEADER]: formatIdempotencyKey(\n request.idempotencyKey,\n ),\n ...(typeof organizationId === 'string'\n ? { [ORGANIZATION_HEADER]: organizationId }\n : {}),\n ...(remaining === undefined\n ? {}\n : { [DEADLINE_HEADER]: String(remaining) }),\n }),\n HttpClientRequest.bodyText(\n JSON.stringify({\n parameters: request.step.parameters ?? {},\n outputs: request.outputs,\n }),\n 'application/json',\n ),\n );\n\n const response = yield* client\n .execute(outgoing)\n .pipe(Effect.mapError(() => new Unavailable({ dependency })));\n const text = yield* response.text.pipe(\n Effect.mapError(() => new Unavailable({ dependency })),\n );\n if (response.status < 400) return text === '' ? undefined : parse(text);\n const isProblem =\n response.headers['content-type']?.startsWith(PROBLEM_CONTENT_TYPE) ===\n true;\n return yield* Effect.fail(\n isProblem\n ? decodeProblem(parse(text), response.status)\n : new ProtocolFailure({\n reason: `${dependency} answered ${String(response.status)} without a problem body`,\n upstreamStatus: response.status,\n }),\n );\n }),\n});\n\n/** The HTTP participant client over the host's `HttpClient`. */\nexport const HttpParticipantClientLayer = (\n participants: Readonly<Record<string, ParticipantAddress>>,\n) =>\n Layer.effect(\n ParticipantClientTag,\n Effect.map(HttpClient.HttpClient, client =>\n makeHttpParticipantClient(client, participants),\n ),\n );\n"],"names":["currentRemainingMs","DEADLINE_HEADER","decodeProblem","formatIdempotencyKey","IDEMPOTENCY_KEY_HEADER","Internal","PROBLEM_CONTENT_TYPE","ProtocolFailure","Timeout","Unavailable","ParticipantClientTag","Effect","Layer","Redacted","HttpClient","HttpClientRequest","CROSSING_TOKEN_HEADER","ORGANIZATION_HEADER","parse","text","JSON","undefined","pathOf","call","path","replace","_match","name","encodeURIComponent","String","step","parameters","outputs","makeHttpParticipantClient","client","participants","request","gen","response","target","kind","compensation","participant","address","fail","dependency","method","remaining","organizationId","outgoing","make","baseUrl","pipe","setHeaders","accept","value","crossingToken","idempotencyKey","bodyText","stringify","execute","mapError","status","isProblem","headers","startsWith","reason","upstreamStatus","HttpParticipantClientLayer","effect","map"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './adapters/entity-rest/build-entity-rest-adapter-get/index.js';
5
5
  export * from './adapters/entity-rest/build-entity-rest-adapter-load/index.js';
6
6
  export * from './adapters/entity-rest/build-entity-rest-adapter-save/index.js';
7
7
  export * from './adapters/entity-rest/types.js';
8
+ export * from './adapters/entity-use-case/index.js';
8
9
  export * from './adapters/transaction-status/build-transaction-status-reader/index.js';
9
10
  export * from './clients/fetch/index.js';
10
11
  export * from './clients/types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iEAAiE,CAAC;AAChF,cAAc,kEAAkE,CAAC;AACjF,cAAc,kEAAkE,CAAC;AACjF,cAAc,+DAA+D,CAAC;AAC9E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,iCAAiC,CAAC;AAChD,cAAc,wEAAwE,CAAC;AACvF,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iCAAiC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iEAAiE,CAAC;AAChF,cAAc,kEAAkE,CAAC;AACjF,cAAc,kEAAkE,CAAC;AACjF,cAAc,+DAA+D,CAAC;AAC9E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,gEAAgE,CAAC;AAC/E,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wEAAwE,CAAC;AACvF,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iCAAiC,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './adapters/entity-rest/build-entity-rest-adapter-get/index.js';
5
5
  export * from './adapters/entity-rest/build-entity-rest-adapter-load/index.js';
6
6
  export * from './adapters/entity-rest/build-entity-rest-adapter-save/index.js';
7
7
  export * from './adapters/entity-rest/types.js';
8
+ export * from './adapters/entity-use-case/index.js';
8
9
  export * from './adapters/transaction-status/build-transaction-status-reader/index.js';
9
10
  export * from './clients/fetch/index.js';
10
11
  export * from './clients/types.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/entity-metadata/make-entity-metadata-source/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-delete/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-facets/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-get/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-load/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-save/index.js';\nexport * from './adapters/entity-rest/types.js';\nexport * from './adapters/transaction-status/build-transaction-status-reader/index.js';\nexport * from './clients/fetch/index.js';\nexport * from './clients/types.js';\nexport * from './configuration-client/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,kEAAkE;AAChF,cAAc,mEAAmE;AACjF,cAAc,mEAAmE;AACjF,cAAc,gEAAgE;AAC9E,cAAc,iEAAiE;AAC/E,cAAc,iEAAiE;AAC/E,cAAc,kCAAkC;AAChD,cAAc,yEAAyE;AACvF,cAAc,2BAA2B;AACzC,cAAc,qBAAqB;AACnC,cAAc,kCAAkC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './adapters/entity-metadata/make-entity-metadata-source/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-delete/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-facets/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-get/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-load/index.js';\nexport * from './adapters/entity-rest/build-entity-rest-adapter-save/index.js';\nexport * from './adapters/entity-rest/types.js';\nexport * from './adapters/entity-use-case/index.js';\nexport * from './adapters/transaction-status/build-transaction-status-reader/index.js';\nexport * from './clients/fetch/index.js';\nexport * from './clients/types.js';\nexport * from './configuration-client/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,kEAAkE;AAChF,cAAc,mEAAmE;AACjF,cAAc,mEAAmE;AACjF,cAAc,gEAAgE;AAC9E,cAAc,iEAAiE;AAC/E,cAAc,iEAAiE;AAC/E,cAAc,kCAAkC;AAChD,cAAc,sCAAsC;AACpD,cAAc,yEAAyE;AACvF,cAAc,2BAA2B;AACzC,cAAc,qBAAqB;AACnC,cAAc,kCAAkC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/rest",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "description": "HTTP entity adapter for the browser: get, load, save and delete against an entifix service, plus served metadata and transaction status.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,6 +21,11 @@
21
21
  "types": "./dist/index.d.ts",
22
22
  "import": "./dist/index.js",
23
23
  "default": "./dist/index.js"
24
+ },
25
+ "./endpoint": {
26
+ "types": "./dist/endpoint/index.d.ts",
27
+ "import": "./dist/endpoint/index.js",
28
+ "default": "./dist/endpoint/index.js"
24
29
  }
25
30
  },
26
31
  "files": [
@@ -51,12 +56,12 @@
51
56
  },
52
57
  "dependencies": {
53
58
  "@swc/helpers": "0.5.23",
54
- "@entifix/transactions": "0.10.2",
55
- "@entifix/core": "0.10.2"
59
+ "@entifix/core": "0.12.0",
60
+ "@entifix/transactions": "0.12.0"
56
61
  },
57
62
  "devDependencies": {
58
63
  "effect": "4.0.6",
59
- "@entifix/testing-unit": "0.10.2"
64
+ "@entifix/testing-unit": "0.12.0"
60
65
  },
61
66
  "peerDependencies": {
62
67
  "effect": "^4.0.6"