@entifix/rest 0.3.0 → 0.4.1

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.
@@ -18,7 +18,14 @@ export interface ConfigurationClientRestClientOptions {
18
18
  * {@link ConfigurationClient} that sources its `ConfigurationPlain` over HTTP
19
19
  * (by default the same-origin `/api/config` route, which proxies
20
20
  * marketplace-config-api). The fetched configuration is memoized so it is
21
- * requested at most once per client instance.
21
+ * requested at most once per client instance — **once it has arrived**.
22
+ *
23
+ * ⚠️ A failed load is not memoized. Clients are usually built once per process
24
+ * (a server component's module scope), and in a fleet that starts together the
25
+ * first read routinely races config-service's boot. Caching that rejection made
26
+ * the failure permanent: a storefront rendered an empty catalog until it was
27
+ * restarted, while every service it reads from was healthy. Callers that arrive
28
+ * while a request is in flight still share it.
22
29
  */
23
30
  export declare class ConfigurationClientRestClient implements ConfigurationClient {
24
31
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"configuration-client-rest-client.d.ts","sourceRoot":"","sources":["../../src/configuration-client/configuration-client-rest-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EAKzB,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,oCAAoC;IACnD,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5C;AAuED;;;;;GAKG;AACH,qBAAa,6BAA8B,YAAW,mBAAmB;;gBAG3D,OAAO,CAAC,EAAE,oCAAoC;IAoB1D,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB;CAG5C"}
1
+ {"version":3,"file":"configuration-client-rest-client.d.ts","sourceRoot":"","sources":["../../src/configuration-client/configuration-client-rest-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EAKzB,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,oCAAoC;IACnD,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5C;AAuED;;;;;;;;;;;;GAYG;AACH,qBAAa,6BAA8B,YAAW,mBAAmB;;gBAG3D,OAAO,CAAC,EAAE,oCAAoC;IA+B1D,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB;CAG5C"}
@@ -61,7 +61,14 @@ var _loadPlain = /*#__PURE__*/ _class_private_field_loose_key("_loadPlain");
61
61
  * {@link ConfigurationClient} that sources its `ConfigurationPlain` over HTTP
62
62
  * (by default the same-origin `/api/config` route, which proxies
63
63
  * marketplace-config-api). The fetched configuration is memoized so it is
64
- * requested at most once per client instance.
64
+ * requested at most once per client instance — **once it has arrived**.
65
+ *
66
+ * ⚠️ A failed load is not memoized. Clients are usually built once per process
67
+ * (a server component's module scope), and in a fleet that starts together the
68
+ * first read routinely races config-service's boot. Caching that rejection made
69
+ * the failure permanent: a storefront rendered an empty catalog until it was
70
+ * restarted, while every service it reads from was healthy. Callers that arrive
71
+ * while a request is in flight still share it.
65
72
  */ export class ConfigurationClientRestClient {
66
73
  in(group) {
67
74
  return new ConfigurationClientGroupRestClient(_class_private_field_loose_base(this, _loadPlain)[_loadPlain], group);
@@ -75,15 +82,26 @@ var _loadPlain = /*#__PURE__*/ _class_private_field_loose_key("_loadPlain");
75
82
  const url = (_ref = options == null ? void 0 : options.url) != null ? _ref : DEFAULT_CONFIG_URL;
76
83
  const headers = options == null ? void 0 : options.headers;
77
84
  let cache;
85
+ const load = ()=>{
86
+ if (cache !== undefined) return cache;
87
+ const pending = fetch(url, {
88
+ headers
89
+ }).then((response)=>{
90
+ if (!response.ok) {
91
+ throw new Error(`Configuration request to ${url} failed with status ${response.status}`);
92
+ }
93
+ return response.json();
94
+ });
95
+ cache = pending;
96
+ // Forget a failure so the next read asks again. Nothing else clears the
97
+ // cache, so the entry being cleared is always this request's own.
98
+ pending.catch(()=>{
99
+ cache = undefined;
100
+ });
101
+ return pending;
102
+ };
78
103
  _class_private_field_loose_base(this, _loadPlain)[_loadPlain] = Effect.tryPromise({
79
- try: ()=>cache != null ? cache : cache = fetch(url, {
80
- headers
81
- }).then((response)=>{
82
- if (!response.ok) {
83
- throw new Error(`Configuration request to ${url} failed with status ${response.status}`);
84
- }
85
- return response.json();
86
- }),
104
+ try: load,
87
105
  catch: (error)=>new EntifixBuildError('Failed to load configuration', error, {
88
106
  url
89
107
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/configuration-client/configuration-client-rest-client.ts"],"sourcesContent":["import {\n ConfigurationClient,\n ConfigurationClientGroup,\n ConfigurationClientInMemory,\n ConfigurationExtractMode,\n ConfigurationPlain,\n EntifixBuildError,\n} from '@entifix/core';\nimport { Effect } from 'effect';\n\nexport interface ConfigurationClientRestClientOptions {\n /** Endpoint returning this service's `ConfigurationPlain`. */\n url?: string;\n /**\n * Headers to send with the lookup.\n *\n * Empty for the browser, which reads its own app's same-origin `/api/config`\n * and needs nothing. A **server** component reads config-service directly —\n * there is no origin to proxy through and no browser to hide an address from\n * — and that endpoint is gated on the shared fleet token, so the caller has\n * to carry it. The header itself stays out of this package: the token is a\n * Next server concern and lives in `@entifix/next-shell`.\n */\n headers?: Readonly<Record<string, string>>;\n}\n\nconst DEFAULT_CONFIG_URL = '/api/config';\n\n/**\n * {@link ConfigurationClientGroup} view over an HTTP-sourced\n * {@link ConfigurationPlain}. Resolution is delegated to an in-memory store\n * once the plain configuration has been fetched.\n */\nclass ConfigurationClientGroupRestClient implements ConfigurationClientGroup {\n constructor(\n private readonly loadPlain: Effect.Effect<\n ConfigurationPlain,\n EntifixBuildError\n >,\n private readonly group: string,\n ) {}\n\n #pending() {\n return Effect.die(\n new Error(\n 'ConfigurationClientGroupRestClient getter not implemented yet',\n ),\n );\n }\n\n getString(key: string, extractMode?: ConfigurationExtractMode) {\n return this.loadPlain.pipe(\n Effect.flatMap(plain =>\n new ConfigurationClientInMemory(plain)\n .in(this.group)\n .getString(key, extractMode),\n ),\n );\n }\n\n getNumber() {\n return this.#pending();\n }\n getDate() {\n return this.#pending();\n }\n getArrayNumber() {\n return this.#pending();\n }\n getArrayString() {\n return this.#pending();\n }\n getArrayDate() {\n return this.#pending();\n }\n getOptionalNumber() {\n return this.#pending();\n }\n getOptionalString() {\n return this.#pending();\n }\n getOptionalDate() {\n return this.#pending();\n }\n getOptionalArrayNumber() {\n return this.#pending();\n }\n getOptionalArrayString() {\n return this.#pending();\n }\n getOptionalArrayDate() {\n return this.#pending();\n }\n}\n\n/**\n * {@link ConfigurationClient} that sources its `ConfigurationPlain` over HTTP\n * (by default the same-origin `/api/config` route, which proxies\n * marketplace-config-api). The fetched configuration is memoized so it is\n * requested at most once per client instance.\n */\nexport class ConfigurationClientRestClient implements ConfigurationClient {\n #loadPlain: Effect.Effect<ConfigurationPlain, EntifixBuildError>;\n\n constructor(options?: ConfigurationClientRestClientOptions) {\n const url = options?.url ?? DEFAULT_CONFIG_URL;\n const headers = options?.headers;\n let cache: Promise<ConfigurationPlain> | undefined;\n\n this.#loadPlain = Effect.tryPromise({\n try: () =>\n (cache ??= fetch(url, { headers }).then(response => {\n if (!response.ok) {\n throw new Error(\n `Configuration request to ${url} failed with status ${response.status}`,\n );\n }\n return response.json() as Promise<ConfigurationPlain>;\n })),\n catch: error =>\n new EntifixBuildError('Failed to load configuration', error, { url }),\n });\n }\n\n in(group: string): ConfigurationClientGroup {\n return new ConfigurationClientGroupRestClient(this.#loadPlain, group);\n }\n}\n"],"names":["ConfigurationClientInMemory","EntifixBuildError","Effect","DEFAULT_CONFIG_URL","ConfigurationClientGroupRestClient","getString","key","extractMode","loadPlain","pipe","flatMap","plain","in","group","getNumber","getDate","getArrayNumber","getArrayString","getArrayDate","getOptionalNumber","getOptionalString","getOptionalDate","getOptionalArrayNumber","getOptionalArrayString","getOptionalArrayDate","die","Error","ConfigurationClientRestClient","options","url","headers","cache","tryPromise","try","fetch","then","response","ok","status","json","catch","error"],"mappings":";;AAAA,SAGEA,2BAA2B,EAG3BC,iBAAiB,QACZ,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAkBhC,MAAMC,qBAAqB;IAgBzB;AAdF;;;;CAIC,GACD,IAAA,AAAMC,qCAAN,MAAMA;IAiBJC,UAAUC,GAAW,EAAEC,WAAsC,EAAE;QAC7D,OAAO,IAAI,CAACC,SAAS,CAACC,IAAI,CACxBP,OAAOQ,OAAO,CAACC,CAAAA,QACb,IAAIX,4BAA4BW,OAC7BC,EAAE,CAAC,IAAI,CAACC,KAAK,EACbR,SAAS,CAACC,KAAKC;IAGxB;IAEAO,YAAY;QACV,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,UAAU;QACR,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,iBAAiB;QACf,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,iBAAiB;QACf,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,eAAe;QACb,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,oBAAoB;QAClB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,oBAAoB;QAClB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,kBAAkB;QAChB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,yBAAyB;QACvB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,yBAAyB;QACvB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,uBAAuB;QACrB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IA1DA,YACE,AAAiBhB,SAGhB,EACD,AAAiBK,KAAa,CAC9B;QAEF,4BAAA;mBAAA;;aAPmBL,YAAAA;aAIAK,QAAAA;IAChB;AAqDL;AAnDE,SAAA;IACE,OAAOX,OAAOuB,GAAG,CACf,IAAIC,MACF;AAGN;IAsDA;AAPF;;;;;CAKC,GACD,OAAO,MAAMC;IAuBXf,GAAGC,KAAa,EAA4B;QAC1C,OAAO,IAAIT,mCAAmC,gCAAA,IAAI,EAAC,YAAA,aAAYS;IACjE;IAtBA,YAAYe,OAA8C,CAAE;;QAF5D,4BAAA;;mBAAA,KAAA;;QAGE,MAAMC,cAAMD,2BAAAA,QAASC,GAAG,mBAAI1B;QAC5B,MAAM2B,UAAUF,2BAAAA,QAASE,OAAO;QAChC,IAAIC;QAEJ,gCAAA,IAAI,EAAC,YAAA,cAAa7B,OAAO8B,UAAU,CAAC;YAClCC,KAAK,IACFF,gBAAAA,QAAAA,QAAUG,MAAML,KAAK;oBAAEC;gBAAQ,GAAGK,IAAI,CAACC,CAAAA;oBACtC,IAAI,CAACA,SAASC,EAAE,EAAE;wBAChB,MAAM,IAAIX,MACR,CAAC,yBAAyB,EAAEG,IAAI,oBAAoB,EAAEO,SAASE,MAAM,EAAE;oBAE3E;oBACA,OAAOF,SAASG,IAAI;gBACtB;YACFC,OAAOC,CAAAA,QACL,IAAIxC,kBAAkB,gCAAgCwC,OAAO;oBAAEZ;gBAAI;QACvE;IACF;AAKF"}
1
+ {"version":3,"sources":["../../src/configuration-client/configuration-client-rest-client.ts"],"sourcesContent":["import {\n ConfigurationClient,\n ConfigurationClientGroup,\n ConfigurationClientInMemory,\n ConfigurationExtractMode,\n ConfigurationPlain,\n EntifixBuildError,\n} from '@entifix/core';\nimport { Effect } from 'effect';\n\nexport interface ConfigurationClientRestClientOptions {\n /** Endpoint returning this service's `ConfigurationPlain`. */\n url?: string;\n /**\n * Headers to send with the lookup.\n *\n * Empty for the browser, which reads its own app's same-origin `/api/config`\n * and needs nothing. A **server** component reads config-service directly —\n * there is no origin to proxy through and no browser to hide an address from\n * — and that endpoint is gated on the shared fleet token, so the caller has\n * to carry it. The header itself stays out of this package: the token is a\n * Next server concern and lives in `@entifix/next-shell`.\n */\n headers?: Readonly<Record<string, string>>;\n}\n\nconst DEFAULT_CONFIG_URL = '/api/config';\n\n/**\n * {@link ConfigurationClientGroup} view over an HTTP-sourced\n * {@link ConfigurationPlain}. Resolution is delegated to an in-memory store\n * once the plain configuration has been fetched.\n */\nclass ConfigurationClientGroupRestClient implements ConfigurationClientGroup {\n constructor(\n private readonly loadPlain: Effect.Effect<\n ConfigurationPlain,\n EntifixBuildError\n >,\n private readonly group: string,\n ) {}\n\n #pending() {\n return Effect.die(\n new Error(\n 'ConfigurationClientGroupRestClient getter not implemented yet',\n ),\n );\n }\n\n getString(key: string, extractMode?: ConfigurationExtractMode) {\n return this.loadPlain.pipe(\n Effect.flatMap(plain =>\n new ConfigurationClientInMemory(plain)\n .in(this.group)\n .getString(key, extractMode),\n ),\n );\n }\n\n getNumber() {\n return this.#pending();\n }\n getDate() {\n return this.#pending();\n }\n getArrayNumber() {\n return this.#pending();\n }\n getArrayString() {\n return this.#pending();\n }\n getArrayDate() {\n return this.#pending();\n }\n getOptionalNumber() {\n return this.#pending();\n }\n getOptionalString() {\n return this.#pending();\n }\n getOptionalDate() {\n return this.#pending();\n }\n getOptionalArrayNumber() {\n return this.#pending();\n }\n getOptionalArrayString() {\n return this.#pending();\n }\n getOptionalArrayDate() {\n return this.#pending();\n }\n}\n\n/**\n * {@link ConfigurationClient} that sources its `ConfigurationPlain` over HTTP\n * (by default the same-origin `/api/config` route, which proxies\n * marketplace-config-api). The fetched configuration is memoized so it is\n * requested at most once per client instance — **once it has arrived**.\n *\n * ⚠️ A failed load is not memoized. Clients are usually built once per process\n * (a server component's module scope), and in a fleet that starts together the\n * first read routinely races config-service's boot. Caching that rejection made\n * the failure permanent: a storefront rendered an empty catalog until it was\n * restarted, while every service it reads from was healthy. Callers that arrive\n * while a request is in flight still share it.\n */\nexport class ConfigurationClientRestClient implements ConfigurationClient {\n #loadPlain: Effect.Effect<ConfigurationPlain, EntifixBuildError>;\n\n constructor(options?: ConfigurationClientRestClientOptions) {\n const url = options?.url ?? DEFAULT_CONFIG_URL;\n const headers = options?.headers;\n let cache: Promise<ConfigurationPlain> | undefined;\n\n const load = (): Promise<ConfigurationPlain> => {\n if (cache !== undefined) return cache;\n const pending = fetch(url, { headers }).then(response => {\n if (!response.ok) {\n throw new Error(\n `Configuration request to ${url} failed with status ${response.status}`,\n );\n }\n return response.json() as Promise<ConfigurationPlain>;\n });\n cache = pending;\n // Forget a failure so the next read asks again. Nothing else clears the\n // cache, so the entry being cleared is always this request's own.\n pending.catch(() => {\n cache = undefined;\n });\n return pending;\n };\n\n this.#loadPlain = Effect.tryPromise({\n try: load,\n catch: error =>\n new EntifixBuildError('Failed to load configuration', error, { url }),\n });\n }\n\n in(group: string): ConfigurationClientGroup {\n return new ConfigurationClientGroupRestClient(this.#loadPlain, group);\n }\n}\n"],"names":["ConfigurationClientInMemory","EntifixBuildError","Effect","DEFAULT_CONFIG_URL","ConfigurationClientGroupRestClient","getString","key","extractMode","loadPlain","pipe","flatMap","plain","in","group","getNumber","getDate","getArrayNumber","getArrayString","getArrayDate","getOptionalNumber","getOptionalString","getOptionalDate","getOptionalArrayNumber","getOptionalArrayString","getOptionalArrayDate","die","Error","ConfigurationClientRestClient","options","url","headers","cache","load","undefined","pending","fetch","then","response","ok","status","json","catch","tryPromise","try","error"],"mappings":";;AAAA,SAGEA,2BAA2B,EAG3BC,iBAAiB,QACZ,gBAAgB;AACvB,SAASC,MAAM,QAAQ,SAAS;AAkBhC,MAAMC,qBAAqB;IAgBzB;AAdF;;;;CAIC,GACD,IAAA,AAAMC,qCAAN,MAAMA;IAiBJC,UAAUC,GAAW,EAAEC,WAAsC,EAAE;QAC7D,OAAO,IAAI,CAACC,SAAS,CAACC,IAAI,CACxBP,OAAOQ,OAAO,CAACC,CAAAA,QACb,IAAIX,4BAA4BW,OAC7BC,EAAE,CAAC,IAAI,CAACC,KAAK,EACbR,SAAS,CAACC,KAAKC;IAGxB;IAEAO,YAAY;QACV,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,UAAU;QACR,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,iBAAiB;QACf,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,iBAAiB;QACf,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,eAAe;QACb,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,oBAAoB;QAClB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,oBAAoB;QAClB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,kBAAkB;QAChB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,yBAAyB;QACvB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,yBAAyB;QACvB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IACAC,uBAAuB;QACrB,OAAO,gCAAA,IAAI,EAAC,UAAA;IACd;IA1DA,YACE,AAAiBhB,SAGhB,EACD,AAAiBK,KAAa,CAC9B;QAEF,4BAAA;mBAAA;;aAPmBL,YAAAA;aAIAK,QAAAA;IAChB;AAqDL;AAnDE,SAAA;IACE,OAAOX,OAAOuB,GAAG,CACf,IAAIC,MACF;AAGN;IA6DA;AAdF;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC;IAkCXf,GAAGC,KAAa,EAA4B;QAC1C,OAAO,IAAIT,mCAAmC,gCAAA,IAAI,EAAC,YAAA,aAAYS;IACjE;IAjCA,YAAYe,OAA8C,CAAE;;QAF5D,4BAAA;;mBAAA,KAAA;;QAGE,MAAMC,cAAMD,2BAAAA,QAASC,GAAG,mBAAI1B;QAC5B,MAAM2B,UAAUF,2BAAAA,QAASE,OAAO;QAChC,IAAIC;QAEJ,MAAMC,OAAO;YACX,IAAID,UAAUE,WAAW,OAAOF;YAChC,MAAMG,UAAUC,MAAMN,KAAK;gBAAEC;YAAQ,GAAGM,IAAI,CAACC,CAAAA;gBAC3C,IAAI,CAACA,SAASC,EAAE,EAAE;oBAChB,MAAM,IAAIZ,MACR,CAAC,yBAAyB,EAAEG,IAAI,oBAAoB,EAAEQ,SAASE,MAAM,EAAE;gBAE3E;gBACA,OAAOF,SAASG,IAAI;YACtB;YACAT,QAAQG;YACR,wEAAwE;YACxE,kEAAkE;YAClEA,QAAQO,KAAK,CAAC;gBACZV,QAAQE;YACV;YACA,OAAOC;QACT;QAEA,gCAAA,IAAI,EAAC,YAAA,cAAahC,OAAOwC,UAAU,CAAC;YAClCC,KAAKX;YACLS,OAAOG,CAAAA,QACL,IAAI3C,kBAAkB,gCAAgC2C,OAAO;oBAAEf;gBAAI;QACvE;IACF;AAKF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/rest",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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,13 +51,13 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@swc/helpers": "0.5.23",
54
- "@entifix/transactions": "0.3.0",
55
- "@entifix/core": "0.3.0",
56
- "@entifix/business": "0.3.0"
54
+ "@entifix/core": "0.4.1",
55
+ "@entifix/business": "0.4.1",
56
+ "@entifix/transactions": "0.4.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "effect": "4.0.5",
60
- "@entifix/testing-unit": "0.3.0"
60
+ "@entifix/testing-unit": "0.4.1"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "effect": "^4.0.5"