@entifix/rest 0.5.0 → 0.6.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 +1 -1
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js +2 -2
- package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js.map +1 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.d.ts +1 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.js +1 -1
- package/dist/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.js.map +1 -1
- package/package.json +4 -4
package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { BuildEntityRestOptions } from '../types.js';
|
|
|
16
16
|
* `202` describing a transaction rather than an entity, so there is no stored
|
|
17
17
|
* entity to read back yet: the client mints the transaction id, which *is* the
|
|
18
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
|
|
19
|
+
* That is the point of the client owning the id — before it, this
|
|
20
20
|
* adapter parsed the `202` as an entity envelope, failed, and reported an error
|
|
21
21
|
* for a create that was in fact about to succeed.
|
|
22
22
|
*/
|
package/dist/adapters/entity-rest/build-entity-rest-adapter-save/build-entity-rest-adapter-save.js
CHANGED
|
@@ -18,7 +18,7 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-r
|
|
|
18
18
|
* `202` describing a transaction rather than an entity, so there is no stored
|
|
19
19
|
* entity to read back yet: the client mints the transaction id, which *is* the
|
|
20
20
|
* id the entity will be stored under, and returns the entity it already holds.
|
|
21
|
-
* That is the point of the client owning the id
|
|
21
|
+
* That is the point of the client owning the id — before it, this
|
|
22
22
|
* adapter parsed the `202` as an entity envelope, failed, and reported an error
|
|
23
23
|
* for a create that was in fact about to succeed.
|
|
24
24
|
*/ export const buildEntityRestAdapterSave = (entityConstructor, { uriConfig, create = 'entity', configuration })=>// Deliberately not shadowing `TEntity` here (as the read builders do): the
|
|
@@ -57,7 +57,7 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../build-entity-r
|
|
|
57
57
|
// point in the system that knows it — the entity returned below is
|
|
58
58
|
// indistinguishable from a plain REST create, so without this the
|
|
59
59
|
// browser navigates away at the `202` and a later failure leaves no row,
|
|
60
|
-
// no message and nothing to retry from
|
|
60
|
+
// no message and nothing to retry from.
|
|
61
61
|
//
|
|
62
62
|
// `serviceOption`, deliberately: reading the tag this way keeps it out
|
|
63
63
|
// of this function's `R`, so the storefront, the plain REST adapters and
|
|
@@ -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
|
|
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"}
|
|
@@ -9,7 +9,7 @@ import type { BuildEntityRestOptions } from '../../entity-rest/types.js';
|
|
|
9
9
|
* commits — the outbox is in the same Mongo transaction — while no event is ever
|
|
10
10
|
* published and the tracker holds no record at all. A caller that treated this
|
|
11
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
|
|
12
|
+
* the moment nobody can tell a UI bug from an outage. The route also
|
|
13
13
|
* answers `404` for another tenant's record, deliberately, so it cannot be
|
|
14
14
|
* walked to probe ids that are also primary keys — indistinguishable here, and
|
|
15
15
|
* correctly so: neither is a record this caller may reconcile against.
|
|
@@ -17,7 +17,7 @@ import { buildEntityRestAdapterMixins as adapterMixins } from '../../entity-rest
|
|
|
17
17
|
* commits — the outbox is in the same Mongo transaction — while no event is ever
|
|
18
18
|
* published and the tracker holds no record at all. A caller that treated this
|
|
19
19
|
* as terminal would un-render a healthy write during a broker outage, which is
|
|
20
|
-
* the moment nobody can tell a UI bug from an outage
|
|
20
|
+
* the moment nobody can tell a UI bug from an outage. The route also
|
|
21
21
|
* answers `404` for another tenant's record, deliberately, so it cannot be
|
|
22
22
|
* walked to probe ids that are also primary keys — indistinguishable here, and
|
|
23
23
|
* correctly so: neither is a record this caller may reconcile against.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.ts"],"sourcesContent":["import type { EntifixError } 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';\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
|
|
1
|
+
{"version":3,"sources":["../../../../src/adapters/transaction-status/build-transaction-status-reader/build-transaction-status-reader.ts"],"sourcesContent":["import type { EntifixError } 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';\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 */\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, EntifixError> =>\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 error.details?.['status'] === 404\n ? Effect.succeed(undefined)\n : Effect.fail(error),\n ),\n ),\n };\n};\n"],"names":["readTransactionRecordEnvelope","Effect","performHttpRequestThroughFetch","buildEntityRestAdapterMixins","adapterMixins","TRANSACTION_PATH","buildTransactionStatusReader","options","configuration","read","transactionId","gen","url","buildEntityBaseUrl","response","buildEntityRequest","method","body","pipe","catch","error","details","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;AAG5H;;;;;CAKC,GACD,MAAMC,mBAAmB;AAEzB;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,+BAA+B,CAC1CC;IAEA,MAAM,EAAEC,aAAa,EAAE,GAAGD;IAE1B,OAAO;QACLE,MAAM,CACJC,gBAEAT,OAAOU,GAAG,CAAC;gBACT,MAAMC,MAAM,OAAOR,cAAcS,kBAAkB,CACjDL,eACAD,SACAF,kBACAK;gBAGF,MAAMI,WAAW,OAAOZ,+BACtBE,cAAcW,kBAAkB,CAAC;oBAAEC,QAAQ;oBAAOJ;gBAAI;gBAGxD,OAAO,OAAOZ,8BAA8Bc,SAASG,IAAI;YAC3D,GAAGC,IAAI,CACLjB,OAAOkB,KAAK,CAACC,CAAAA;oBACXA;uBAAAA,EAAAA,iBAAAA,MAAMC,OAAO,qBAAbD,cAAe,CAAC,SAAS,MAAK,MAC1BnB,OAAOqB,OAAO,CAACC,aACftB,OAAOuB,IAAI,CAACJ;;IAGxB;AACF,EAAE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entifix/rest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": {
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@swc/helpers": "0.5.23",
|
|
54
|
-
"@entifix/core": "0.
|
|
55
|
-
"@entifix/transactions": "0.
|
|
54
|
+
"@entifix/core": "0.6.0",
|
|
55
|
+
"@entifix/transactions": "0.6.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"effect": "4.0.5",
|
|
59
|
-
"@entifix/testing-unit": "0.
|
|
59
|
+
"@entifix/testing-unit": "0.6.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"effect": "^4.0.5"
|