@entifix/rest 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.d.ts +3 -7
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.d.ts.map +1 -1
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js +5 -72
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js.map +1 -1
- package/dist/adapters/entity-rest/types.d.ts +0 -15
- package/dist/adapters/entity-rest/types.d.ts.map +1 -1
- package/dist/adapters/entity-rest/types.js.map +1 -1
- package/dist/endpoint/participant-client.d.ts +3 -2
- package/dist/endpoint/participant-client.d.ts.map +1 -1
- package/dist/endpoint/participant-client.js +10 -16
- package/dist/endpoint/participant-client.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.d.ts +0 -21
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.d.ts.map +0 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.js +0 -42
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.js.map +0 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/index.d.ts +0 -2
- package/dist/adapters/transaction-status/build-transaction-status-reader/index.d.ts.map +0 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/index.js +0 -3
- package/dist/adapters/transaction-status/build-transaction-status-reader/index.js.map +0 -1
package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.d.ts
CHANGED
|
@@ -12,13 +12,9 @@ import { BuildEntityRestOptions } from '../types.js';
|
|
|
12
12
|
* deserialized and returned, because the service is the authority on the stored
|
|
13
13
|
* entity (it assigns ids and may normalize fields).
|
|
14
14
|
*
|
|
15
|
-
* A
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* id the entity will be stored under, and returns the entity it already holds.
|
|
19
|
-
* That is the point of the client owning the id — before it, this
|
|
20
|
-
* adapter parsed the `202` as an entity envelope, failed, and reported an error
|
|
21
|
-
* for a create that was in fact about to succeed.
|
|
15
|
+
* A write the service hands to a flow answers `202`; a repository save has no
|
|
16
|
+
* stored entity to return for it, so such an entity is written through
|
|
17
|
+
* `buildEntityRestAdapterWrite`, which answers the flow to follow.
|
|
22
18
|
*/
|
|
23
19
|
export declare const buildEntityRestAdapterSave: <TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>, options: BuildEntityRestOptions) => <TInput extends Entity>(entity: TInput) => Effect.Effect<TInput, import("@entifix/core").EntifixFailure<string, string, import("@entifix/core").FailureCategory>, never>;
|
|
24
20
|
//# sourceMappingURL=build-entity-rest-adapter-save.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,
|
|
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,EAGlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GACpC,OAAO,SAAS,MAAM,EACrB,mBAAmB,iBAAiB,CAAC,OAAO,CAAC,EAC7C,SAAS,sBAAsB,MAKhC,MAAM,SAAS,MAAM,EAAE,QAAQ,MAAM,kIAgBlC,CAAC"}
|
package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { extractMetaEntity, ProtocolFailure
|
|
2
|
-
import {
|
|
3
|
-
import { Effect, Option } from 'effect';
|
|
4
|
-
import { performHttpRequestThroughFetch } from '../../../clients/fetch/index.js';
|
|
5
|
-
import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-rest-adapter-mixins/index.js';
|
|
1
|
+
import { extractMetaEntity, ProtocolFailure } from '@entifix/core';
|
|
2
|
+
import { Effect } from 'effect';
|
|
6
3
|
import { buildEntityRestAdapterWrite } from '../build-entity-rest-adapter-write/index.js';
|
|
7
|
-
import { pendingSaveKeys } from '../pending-save-keys.js';
|
|
8
|
-
/** A transactional create whose `202` never arrived: same command, same id. */ const pendingCreates = new WeakSet();
|
|
9
4
|
/**
|
|
10
5
|
* Persists an entity over HTTP.
|
|
11
6
|
*
|
|
@@ -17,78 +12,16 @@ import { pendingSaveKeys } from '../pending-save-keys.js';
|
|
|
17
12
|
* deserialized and returned, because the service is the authority on the stored
|
|
18
13
|
* entity (it assigns ids and may normalize fields).
|
|
19
14
|
*
|
|
20
|
-
* A
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* id the entity will be stored under, and returns the entity it already holds.
|
|
24
|
-
* That is the point of the client owning the id — before it, this
|
|
25
|
-
* adapter parsed the `202` as an entity envelope, failed, and reported an error
|
|
26
|
-
* for a create that was in fact about to succeed.
|
|
15
|
+
* A write the service hands to a flow answers `202`; a repository save has no
|
|
16
|
+
* stored entity to return for it, so such an entity is written through
|
|
17
|
+
* `buildEntityRestAdapterWrite`, which answers the flow to follow.
|
|
27
18
|
*/ export const buildEntityRestAdapterSave = (entityConstructor, options)=>// Deliberately not shadowing `TEntity` here (as the read builders do): the
|
|
28
19
|
// envelope has to be built against the *constructor's* entity type, which a
|
|
29
20
|
// shadowed parameter would make unreachable.
|
|
30
21
|
(entity)=>Effect.gen(function*() {
|
|
31
22
|
var _metaEntity_key;
|
|
32
|
-
const { uriConfig, create = 'entity', configuration } = options;
|
|
33
23
|
const metaEntity = extractMetaEntity(entityConstructor);
|
|
34
24
|
const key = (_metaEntity_key = metaEntity.key) != null ? _metaEntity_key : metaEntity.name;
|
|
35
|
-
const isCreate = entity.id == null || pendingCreates.has(entity);
|
|
36
|
-
if (isCreate && create === 'command') {
|
|
37
|
-
var _pendingSaveKeys_get;
|
|
38
|
-
const url = yield* adapterMixins.buildEntityBaseUrl(configuration, {
|
|
39
|
-
uriConfig
|
|
40
|
-
}, key);
|
|
41
|
-
// `crypto.randomUUID` is available in every browser this ships to and in
|
|
42
|
-
// Node — and the id must be a UUID, because the service constrains the
|
|
43
|
-
// key space a caller may address.
|
|
44
|
-
// A retried create reuses the id it was first sent with: that id *is*
|
|
45
|
-
// the idempotency key, so a new one would write a second record.
|
|
46
|
-
const transactionId = (_pendingSaveKeys_get = pendingSaveKeys.get(entity)) != null ? _pendingSaveKeys_get : crypto.randomUUID();
|
|
47
|
-
pendingSaveKeys.set(entity, transactionId);
|
|
48
|
-
pendingCreates.add(entity);
|
|
49
|
-
entity.id = transactionId;
|
|
50
|
-
const command = {
|
|
51
|
-
transactionId,
|
|
52
|
-
type: 'create',
|
|
53
|
-
entity: key,
|
|
54
|
-
payload: serializeEntity(entityConstructor, entity)
|
|
55
|
-
};
|
|
56
|
-
const accepted = yield* performHttpRequestThroughFetch(adapterMixins.buildEntityRequest({
|
|
57
|
-
method: 'POST',
|
|
58
|
-
url,
|
|
59
|
-
envelope: makeCommandEnvelope(command),
|
|
60
|
-
idempotencyKey: transactionId
|
|
61
|
-
}));
|
|
62
|
-
// Read for its shape, not its contents: this asserts the service really
|
|
63
|
-
// accepted a transaction rather than answering something else with a
|
|
64
|
-
// 2xx, which is the failure the old code path could not distinguish.
|
|
65
|
-
yield* readTransactionAcceptedEnvelope(accepted.body);
|
|
66
|
-
pendingSaveKeys.delete(entity);
|
|
67
|
-
pendingCreates.delete(entity);
|
|
68
|
-
// Tell whoever is watching that a write is in flight. This is the only
|
|
69
|
-
// point in the system that knows it — the entity returned below is
|
|
70
|
-
// indistinguishable from a plain REST create, so without this the
|
|
71
|
-
// browser navigates away at the `202` and a later failure leaves no row,
|
|
72
|
-
// no message and nothing to retry from.
|
|
73
|
-
//
|
|
74
|
-
// `serviceOption`, deliberately: reading the tag this way keeps it out
|
|
75
|
-
// of this function's `R`, so the storefront, the plain REST adapters and
|
|
76
|
-
// every existing spec compile and run with no layer to provide. The cost
|
|
77
|
-
// is that nothing *forces* a composition root to provide it, which is
|
|
78
|
-
// why `mergeContext` has a spec pinning that it does.
|
|
79
|
-
const sink = yield* Effect.serviceOption(TransactionSinkTag);
|
|
80
|
-
if (Option.isSome(sink)) {
|
|
81
|
-
// After the assertion above, never before: a `2xx` that is not a
|
|
82
|
-
// transaction envelope must not register a pending id, because nothing
|
|
83
|
-
// would ever settle it and the entry becomes a permanent phantom.
|
|
84
|
-
sink.value.began({
|
|
85
|
-
transactionId,
|
|
86
|
-
entity: key,
|
|
87
|
-
at: new Date().toISOString()
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return entity;
|
|
91
|
-
}
|
|
92
25
|
const result = yield* buildEntityRestAdapterWrite(entityConstructor, options).save(entity);
|
|
93
26
|
// A repository save answers the stored entity, so it has nothing to
|
|
94
27
|
// return for a write the service handed to a flow; a screen writes such
|
|
@@ -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 ProtocolFailure,\n
|
|
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 ProtocolFailure,\n} from '@entifix/core';\nimport { Effect } from 'effect';\n\nimport { buildEntityRestAdapterWrite } from '../build-entity-rest-adapter-write/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 write the service hands to a flow answers `202`; a repository save has no\n * stored entity to return for it, so such an entity is written through\n * `buildEntityRestAdapterWrite`, which answers the flow to follow.\n */\nexport const buildEntityRestAdapterSave =\n <TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n options: 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 result = yield* buildEntityRestAdapterWrite(\n entityConstructor,\n options,\n ).save(entity);\n // A repository save answers the stored entity, so it has nothing to\n // return for a write the service handed to a flow; a screen writes such\n // an entity through `buildEntityRestAdapterWrite` instead.\n if (result._tag === 'pending')\n return yield* new ProtocolFailure({\n reason: `'${key}' answered a save with 202 flowAccepted; write it through buildEntityRestAdapterWrite`,\n });\n return result.value;\n });\n"],"names":["extractMetaEntity","ProtocolFailure","Effect","buildEntityRestAdapterWrite","buildEntityRestAdapterSave","entityConstructor","options","entity","gen","metaEntity","key","name","result","save","_tag","reason","value"],"mappings":"AAAA,SAGEA,iBAAiB,EACjBC,eAAe,QACV,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAEhC,SAASC,2BAA2B,QAAQ,8CAA8C;AAG1F;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMC,6BACX,CACEC,mBACAC,UAEF,2EAA2E;IAC3E,4EAA4E;IAC5E,6CAA6C;IAC7C,CAAwBC,SACtBL,OAAOM,GAAG,CAAC;gBAEGC;YADZ,MAAMA,aAAaT,kBAAkBK;YACrC,MAAMK,OAAMD,kBAAAA,WAAWC,GAAG,YAAdD,kBAAkBA,WAAWE,IAAI;YAC7C,MAAMC,SAAS,OAAOT,4BACpBE,mBACAC,SACAO,IAAI,CAACN;YACP,oEAAoE;YACpE,wEAAwE;YACxE,2DAA2D;YAC3D,IAAIK,OAAOE,IAAI,KAAK,WAClB,OAAO,OAAO,IAAIb,gBAAgB;gBAChCc,QAAQ,CAAC,CAAC,EAAEL,IAAI,qFAAqF,CAAC;YACxG;YACF,OAAOE,OAAOI,KAAK;QACrB,GAAG"}
|
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import type { ConfigurationClient, ConfigurationExtractMode } from '@entifix/core';
|
|
2
|
-
/**
|
|
3
|
-
* How a create is sent to this entity's service.
|
|
4
|
-
*
|
|
5
|
-
* `entity` is the plain REST write: `POST` an entity envelope, get the stored
|
|
6
|
-
* entity back. `command` is the CQRS write path — the client mints the
|
|
7
|
-
* transaction id, `POST`s a `command` envelope and gets a `202` naming the
|
|
8
|
-
* transaction, because the write completes asynchronously behind a saga.
|
|
9
|
-
*
|
|
10
|
-
* It is per entity rather than global because the two live side by side today:
|
|
11
|
-
* `ProductSpecification` is transactional, while `ProductBrand` and
|
|
12
|
-
* `ProductCategory` are plain writes on another service.
|
|
13
|
-
*/
|
|
14
|
-
export type EntityCreateProtocol = 'entity' | 'command';
|
|
15
2
|
export interface BuildEntityRestOptions {
|
|
16
3
|
uriConfig: {
|
|
17
4
|
key: string;
|
|
18
5
|
group?: string;
|
|
19
6
|
extractionMode?: ConfigurationExtractMode;
|
|
20
7
|
};
|
|
21
|
-
/** Defaults to `'entity'` — the plain REST write. */
|
|
22
|
-
create?: EntityCreateProtocol;
|
|
23
8
|
/**
|
|
24
9
|
* The method an update is sent with: `PUT` replaces the stored entity,
|
|
25
10
|
* `PATCH` merges into it. Defaults to `'PUT'`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/adapters/entity-rest/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAEvB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/adapters/entity-rest/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,wBAAwB,CAAC;KAC3C,CAAC;IACF;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,aAAa,EAAE,mBAAmB,CAAC;CACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/adapters/entity-rest/types.ts"],"sourcesContent":["import type {\n ConfigurationClient,\n ConfigurationExtractMode,\n} from '@entifix/core';\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/entity-rest/types.ts"],"sourcesContent":["import type {\n ConfigurationClient,\n ConfigurationExtractMode,\n} from '@entifix/core';\n\nexport interface BuildEntityRestOptions {\n uriConfig: {\n key: string;\n group?: string;\n extractionMode?: ConfigurationExtractMode;\n };\n /**\n * The method an update is sent with: `PUT` replaces the stored entity,\n * `PATCH` merges into it. Defaults to `'PUT'`.\n */\n update?: 'PUT' | 'PATCH';\n /**\n * Where `uriConfig` is looked up, bound when the adapter is built.\n *\n * A value rather than a requirement on each call: the URL lookup is this\n * adapter's mechanism, and putting it in the repository port's environment\n * made every use case — and every static or database-backed caller — provide\n * a configuration it never read.\n */\n configuration: ConfigurationClient;\n}\n"],"names":[],"mappings":"AAKA,WAoBC"}
|
|
@@ -11,8 +11,9 @@ export interface ParticipantAddress {
|
|
|
11
11
|
* Calls participants over HTTP, as the slip pinned each step's call
|
|
12
12
|
* ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):
|
|
13
13
|
* the crossing token the participant takes, the step's `Idempotency-Key`, the
|
|
14
|
-
* flow's remaining deadline,
|
|
15
|
-
*
|
|
14
|
+
* flow's remaining deadline, the organization the inputs name, and the inputs
|
|
15
|
+
* themselves as a flat JSON body — what the participant's own route declares,
|
|
16
|
+
* never a wrapper of the flow's. A refusal decodes into the failure the participant
|
|
16
17
|
* sent, by its code; a body that is not a problem is `protocol`; no answer at
|
|
17
18
|
* all is `unavailable`. A step with no call — the commit point that names no
|
|
18
19
|
* participant — answers at once.
|
|
@@ -1 +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;
|
|
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;AAqBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,UAAU,CAAC,UAAU,EAC7B,cAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,KACzD,iBA8DD,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,0BAA0B,GACrC,cAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,oEAOzD,CAAC"}
|
|
@@ -12,27 +12,24 @@ const parse = (text)=>{
|
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
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 : ''));
|
|
15
|
+
/** The step's path with each `:name` taken from the call's inputs. */ const pathOf = (call, path)=>path.replace(/:([A-Za-z0-9_]+)/g, (_match, name)=>{
|
|
16
|
+
var _call_inputs_name;
|
|
17
|
+
return encodeURIComponent(String((_call_inputs_name = call.inputs[name]) != null ? _call_inputs_name : ''));
|
|
22
18
|
});
|
|
23
19
|
/**
|
|
24
20
|
* Calls participants over HTTP, as the slip pinned each step's call
|
|
25
21
|
* ([ADR 0016](../../../../../docs/adr/0016-a-flow-engine-port-and-a-durable-workflow-adapter.md)):
|
|
26
22
|
* the crossing token the participant takes, the step's `Idempotency-Key`, the
|
|
27
|
-
* flow's remaining deadline,
|
|
28
|
-
*
|
|
23
|
+
* flow's remaining deadline, the organization the inputs name, and the inputs
|
|
24
|
+
* themselves as a flat JSON body — what the participant's own route declares,
|
|
25
|
+
* never a wrapper of the flow's. A refusal decodes into the failure the participant
|
|
29
26
|
* sent, by its code; a body that is not a problem is `protocol`; no answer at
|
|
30
27
|
* all is `unavailable`. A step with no call — the commit point that names no
|
|
31
28
|
* participant — answers at once.
|
|
32
29
|
*/ export const makeHttpParticipantClient = (client, participants)=>({
|
|
33
30
|
call: (request)=>Effect.gen(function*() {
|
|
34
|
-
var _request_step_participant
|
|
35
|
-
var
|
|
31
|
+
var _request_step_participant;
|
|
32
|
+
var _response_headers_contenttype;
|
|
36
33
|
const target = request.kind === 'action' ? request.step.call : request.step.compensation;
|
|
37
34
|
if (target === undefined) return undefined;
|
|
38
35
|
const name = (_request_step_participant = request.step.participant) != null ? _request_step_participant : '';
|
|
@@ -43,7 +40,7 @@ const parse = (text)=>{
|
|
|
43
40
|
if (remaining === 0) return yield* Effect.fail(new Timeout({
|
|
44
41
|
dependency
|
|
45
42
|
}));
|
|
46
|
-
const organizationId =
|
|
43
|
+
const organizationId = request.inputs['organizationId'];
|
|
47
44
|
const outgoing = HttpClientRequest.make(target.method)(`${address.baseUrl}${pathOf(request, target.path)}`).pipe(HttpClientRequest.setHeaders(_extends({
|
|
48
45
|
accept: 'application/json',
|
|
49
46
|
[CROSSING_TOKEN_HEADER]: Redacted.value(address.crossingToken),
|
|
@@ -52,10 +49,7 @@ const parse = (text)=>{
|
|
|
52
49
|
[ORGANIZATION_HEADER]: organizationId
|
|
53
50
|
} : {}, remaining === undefined ? {} : {
|
|
54
51
|
[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'));
|
|
52
|
+
})), target.method === 'GET' ? (outgoing)=>outgoing : HttpClientRequest.bodyText(JSON.stringify(request.inputs), 'application/json'));
|
|
59
53
|
const response = yield* client.execute(outgoing).pipe(Effect.mapError(()=>new Unavailable({
|
|
60
54
|
dependency
|
|
61
55
|
})));
|
|
@@ -1 +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
|
|
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/** The step's path with each `:name` taken from the call's inputs. */\nconst pathOf = (call: ParticipantCall, path: string) =>\n path.replace(/:([A-Za-z0-9_]+)/g, (_match, name: string) =>\n encodeURIComponent(String(call.inputs[name] ?? '')),\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, the organization the inputs name, and the inputs\n * themselves as a flat JSON body — what the participant's own route declares,\n * never a wrapper of the flow's. 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.inputs['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 target.method === 'GET'\n ? (outgoing: HttpClientRequest.HttpClientRequest) => outgoing\n : HttpClientRequest.bodyText(\n JSON.stringify(request.inputs),\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","inputs","makeHttpParticipantClient","client","participants","request","gen","response","target","kind","step","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
|
@@ -8,7 +8,6 @@ export * from './adapters/entity-rest/build-entity-rest-adapter-write/index.js';
|
|
|
8
8
|
export * from './adapters/entity-rest/types.js';
|
|
9
9
|
export * from './adapters/entity-use-case/index.js';
|
|
10
10
|
export * from './adapters/flow-status/build-flow-status-reader/index.js';
|
|
11
|
-
export * from './adapters/transaction-status/build-transaction-status-reader/index.js';
|
|
12
11
|
export * from './clients/fetch/index.js';
|
|
13
12
|
export * from './clients/types.js';
|
|
14
13
|
export * from './configuration-client/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,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,iEAAiE,CAAC;AAChF,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0DAA0D,CAAC;AACzE,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,iEAAiE,CAAC;AAChF,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0DAA0D,CAAC;AACzE,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ export * from './adapters/entity-rest/build-entity-rest-adapter-write/index.js';
|
|
|
8
8
|
export * from './adapters/entity-rest/types.js';
|
|
9
9
|
export * from './adapters/entity-use-case/index.js';
|
|
10
10
|
export * from './adapters/flow-status/build-flow-status-reader/index.js';
|
|
11
|
-
export * from './adapters/transaction-status/build-transaction-status-reader/index.js';
|
|
12
11
|
export * from './clients/fetch/index.js';
|
|
13
12
|
export * from './clients/types.js';
|
|
14
13
|
export * from './configuration-client/index.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/build-entity-rest-adapter-write/index.js';\nexport * from './adapters/entity-rest/types.js';\nexport * from './adapters/entity-use-case/index.js';\nexport * from './adapters/flow-status/build-flow-status-reader/index.js';\nexport * from './
|
|
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/build-entity-rest-adapter-write/index.js';\nexport * from './adapters/entity-rest/types.js';\nexport * from './adapters/entity-use-case/index.js';\nexport * from './adapters/flow-status/build-flow-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,kEAAkE;AAChF,cAAc,kCAAkC;AAChD,cAAc,sCAAsC;AACpD,cAAc,2DAA2D;AACzE,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.
|
|
3
|
+
"version": "0.15.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": {
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@swc/helpers": "0.5.23",
|
|
59
|
-
"@entifix/core": "0.
|
|
60
|
-
"@entifix/transactions": "0.
|
|
59
|
+
"@entifix/core": "0.15.0",
|
|
60
|
+
"@entifix/transactions": "0.15.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"effect": "4.0.7",
|
|
64
|
-
"@entifix/testing-unit": "0.
|
|
64
|
+
"@entifix/testing-unit": "0.15.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"effect": "^4.0.7"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type TransactionStatusReader } from '@entifix/transactions';
|
|
2
|
-
import type { BuildEntityRestOptions } from '../../entity-rest/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Reads one transaction's record over HTTP, for a browser reconciling a write it
|
|
5
|
-
* started.
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ **A `404` answers `undefined`, and that is not a failure.** `accepted`
|
|
8
|
-
* reaches the tracker over the bus, so with the broker down the entity write
|
|
9
|
-
* commits — the outbox is in the same Mongo transaction — while no event is ever
|
|
10
|
-
* published and the tracker holds no record at all. A caller that treated this
|
|
11
|
-
* as terminal would un-render a healthy write during a broker outage, which is
|
|
12
|
-
* the moment nobody can tell a UI bug from an outage. The route also
|
|
13
|
-
* answers `404` for another tenant's record, deliberately, so it cannot be
|
|
14
|
-
* walked to probe ids that are also primary keys — indistinguishable here, and
|
|
15
|
-
* correctly so: neither is a record this caller may reconcile against.
|
|
16
|
-
*
|
|
17
|
-
* Every other status stays an error, so an unreachable service is not quietly
|
|
18
|
-
* read as "no such transaction".
|
|
19
|
-
*/
|
|
20
|
-
export declare const buildTransactionStatusReader: (options: Pick<BuildEntityRestOptions, "uriConfig" | "configuration">) => TransactionStatusReader;
|
|
21
|
-
//# sourceMappingURL=build-transaction-status-reader.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build-transaction-status-reader.d.ts","sourceRoot":"","sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAWzE;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,4BAA4B,GACvC,SAAS,IAAI,CAAC,sBAAsB,EAAE,WAAW,GAAG,eAAe,CAAC,KACnE,uBA0BF,CAAC"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { readTransactionRecordEnvelope } from '@entifix/transactions';
|
|
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
|
-
import { isNotFound } from '../../is-not-found.js';
|
|
6
|
-
/**
|
|
7
|
-
* The tracker's own path segment. It is not an entity — nothing decorates a
|
|
8
|
-
* `TransactionRecord` — but the URL is composed the same way every entity URL
|
|
9
|
-
* is, so `buildEntityBaseUrl` (already generic over an arbitrary name) needs no
|
|
10
|
-
* transaction-shaped sibling.
|
|
11
|
-
*/ const TRANSACTION_PATH = 'transaction';
|
|
12
|
-
/**
|
|
13
|
-
* Reads one transaction's record over HTTP, for a browser reconciling a write it
|
|
14
|
-
* started.
|
|
15
|
-
*
|
|
16
|
-
* ⚠️ **A `404` answers `undefined`, and that is not a failure.** `accepted`
|
|
17
|
-
* reaches the tracker over the bus, so with the broker down the entity write
|
|
18
|
-
* commits — the outbox is in the same Mongo transaction — while no event is ever
|
|
19
|
-
* published and the tracker holds no record at all. A caller that treated this
|
|
20
|
-
* as terminal would un-render a healthy write during a broker outage, which is
|
|
21
|
-
* the moment nobody can tell a UI bug from an outage. The route also
|
|
22
|
-
* answers `404` for another tenant's record, deliberately, so it cannot be
|
|
23
|
-
* walked to probe ids that are also primary keys — indistinguishable here, and
|
|
24
|
-
* correctly so: neither is a record this caller may reconcile against.
|
|
25
|
-
*
|
|
26
|
-
* Every other status stays an error, so an unreachable service is not quietly
|
|
27
|
-
* read as "no such transaction".
|
|
28
|
-
*/ export const buildTransactionStatusReader = (options)=>{
|
|
29
|
-
const { configuration } = options;
|
|
30
|
-
return {
|
|
31
|
-
read: (transactionId)=>Effect.gen(function*() {
|
|
32
|
-
const url = yield* adapterMixins.buildEntityBaseUrl(configuration, options, TRANSACTION_PATH, transactionId);
|
|
33
|
-
const response = yield* performHttpRequestThroughFetch(adapterMixins.buildEntityRequest({
|
|
34
|
-
method: 'GET',
|
|
35
|
-
url
|
|
36
|
-
}));
|
|
37
|
-
return yield* readTransactionRecordEnvelope(response.body);
|
|
38
|
-
}).pipe(Effect.catch((error)=>isNotFound(error) ? Effect.succeed(undefined) : Effect.fail(error)))
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
//# sourceMappingURL=build-transaction-status-reader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.ts"],"sourcesContent":["import type { EntifixFailure } from '@entifix/core';\nimport {\n readTransactionRecordEnvelope,\n type TransactionRecord,\n type TransactionStatusReader,\n} from '@entifix/transactions';\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';\nimport { isNotFound } from '../../is-not-found.js';\n\n/**\n * The tracker's own path segment. It is not an entity — nothing decorates a\n * `TransactionRecord` — but the URL is composed the same way every entity URL\n * is, so `buildEntityBaseUrl` (already generic over an arbitrary name) needs no\n * transaction-shaped sibling.\n */\nconst TRANSACTION_PATH = 'transaction';\n\n/**\n * Reads one transaction's record over HTTP, for a browser reconciling a write it\n * started.\n *\n * ⚠️ **A `404` answers `undefined`, and that is not a failure.** `accepted`\n * reaches the tracker over the bus, so with the broker down the entity write\n * commits — the outbox is in the same Mongo transaction — while no event is ever\n * published and the tracker holds no record at all. A caller that treated this\n * as terminal would un-render a healthy write during a broker outage, which is\n * the moment nobody can tell a UI bug from an outage. The route also\n * answers `404` for another tenant's record, deliberately, so it cannot be\n * walked to probe ids that are also primary keys — indistinguishable here, and\n * correctly so: neither is a record this caller may reconcile against.\n *\n * Every other status stays an error, so an unreachable service is not quietly\n * read as \"no such transaction\".\n */\n\nexport const buildTransactionStatusReader = (\n options: Pick<BuildEntityRestOptions, 'uriConfig' | 'configuration'>,\n): TransactionStatusReader => {\n const { configuration } = options;\n\n return {\n read: (\n transactionId: string,\n ): Effect.Effect<TransactionRecord | undefined, EntifixFailure> =>\n Effect.gen(function* () {\n const url = yield* adapterMixins.buildEntityBaseUrl(\n configuration,\n options,\n TRANSACTION_PATH,\n transactionId,\n );\n\n const response = yield* performHttpRequestThroughFetch(\n adapterMixins.buildEntityRequest({ method: 'GET', url }),\n );\n\n return yield* readTransactionRecordEnvelope(response.body);\n }).pipe(\n Effect.catch(error =>\n isNotFound(error) ? Effect.succeed(undefined) : Effect.fail(error),\n ),\n ),\n };\n};\n"],"names":["readTransactionRecordEnvelope","Effect","performHttpRequestThroughFetch","buildEntityRestAdapterMixins","adapterMixins","isNotFound","TRANSACTION_PATH","buildTransactionStatusReader","options","configuration","read","transactionId","gen","url","buildEntityBaseUrl","response","buildEntityRequest","method","body","pipe","catch","error","succeed","undefined","fail"],"mappings":"AACA,SACEA,6BAA6B,QAGxB,wBAAwB;AAC/B,SAASC,MAAM,QAAQ,SAAS;AAEhC,SAASC,8BAA8B,QAAQ,kCAAkC;AACjF,SAASC,gCAAgCC,aAAa,QAAQ,8DAA8D;AAE5H,SAASC,UAAU,QAAQ,wBAAwB;AAEnD;;;;;CAKC,GACD,MAAMC,mBAAmB;AAEzB;;;;;;;;;;;;;;;;CAgBC,GAED,OAAO,MAAMC,+BAA+B,CAC1CC;IAEA,MAAM,EAAEC,aAAa,EAAE,GAAGD;IAE1B,OAAO;QACLE,MAAM,CACJC,gBAEAV,OAAOW,GAAG,CAAC;gBACT,MAAMC,MAAM,OAAOT,cAAcU,kBAAkB,CACjDL,eACAD,SACAF,kBACAK;gBAGF,MAAMI,WAAW,OAAOb,+BACtBE,cAAcY,kBAAkB,CAAC;oBAAEC,QAAQ;oBAAOJ;gBAAI;gBAGxD,OAAO,OAAOb,8BAA8Be,SAASG,IAAI;YAC3D,GAAGC,IAAI,CACLlB,OAAOmB,KAAK,CAACC,CAAAA,QACXhB,WAAWgB,SAASpB,OAAOqB,OAAO,CAACC,aAAatB,OAAOuB,IAAI,CAACH;IAGpE;AACF,EAAE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/index.ts"],"names":[],"mappings":"AAAA,cAAc,sCAAsC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/index.ts"],"sourcesContent":["export * from './build-transaction-status-reader.js';\n"],"names":[],"mappings":"AAAA,cAAc,uCAAuC"}
|