@conduit-client/service-renewable-resource-manager 3.22.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/LICENSE.txt +27 -0
- package/README.md +78 -0
- package/dist/main/index.js +2 -0
- package/dist/main/index.js.map +1 -0
- package/dist/types/index.d.ts +0 -0
- package/dist/types/v1/__tests__/basic-renewable-resource-manager.spec.d.ts +1 -0
- package/dist/types/v1/__tests__/index.spec.d.ts +1 -0
- package/dist/types/v1/basic-renewable-resource-manager.d.ts +51 -0
- package/dist/types/v1/index.d.ts +14 -0
- package/dist/types/v1/renewable-resource-manager.d.ts +49 -0
- package/dist/v1/index.js +48 -0
- package/dist/v1/index.js.map +1 -0
- package/package.json +45 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
*Attorney/Client Privileged + Confidential*
|
|
2
|
+
|
|
3
|
+
Terms of Use for Public Code (Non-OSS)
|
|
4
|
+
|
|
5
|
+
*NOTE:* Before publishing code under this license/these Terms of Use, please review https://salesforce.quip.com/WFfvAMKB18AL and confirm that you’ve completed all prerequisites described therein. *These Terms of Use may not be used or modified without input from IP and Product Legal.*
|
|
6
|
+
|
|
7
|
+
*Terms of Use*
|
|
8
|
+
|
|
9
|
+
Copyright 2022 Salesforce, Inc. All rights reserved.
|
|
10
|
+
|
|
11
|
+
These Terms of Use govern the download, installation, and/or use of this software provided by Salesforce, Inc. (“Salesforce”) (the “Software”), were last updated on April 15, 2022, ** and constitute a legally binding agreement between you and Salesforce. If you do not agree to these Terms of Use, do not install or use the Software.
|
|
12
|
+
|
|
13
|
+
Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the Software and derivative works subject to these Terms. These Terms shall be included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title, and interest in and to all intellectual property subsisting in the Software. No rights are granted to you hereunder other than as expressly set forth herein. Users residing in countries on the United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US export embargo, may not use the Software.
|
|
16
|
+
|
|
17
|
+
Implementation of the Software may require development work, for which you are responsible. The Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis without support, updates, or service level commitments.
|
|
18
|
+
|
|
19
|
+
Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any third party for any modification, suspension, or discontinuance.
|
|
20
|
+
|
|
21
|
+
You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b) any application you develop with the Software that infringes any copyright, trademark, trade secret, trade dress, patent, or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy (each a “Claim Against Salesforce”), and will indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim Against Salesforce (except that you may not settle any Claim Against Salesforce unless it unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance, at your expense.
|
|
22
|
+
|
|
23
|
+
WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
24
|
+
|
|
25
|
+
These Terms of Use shall be governed exclusively by the internal laws of the State of California, without regard to its conflicts of laws rules. Each party hereby consents to the exclusive jurisdiction of the state and federal courts located in San Francisco County, California to adjudicate any dispute arising out of or relating to these Terms of Use and the download, installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use constitute the entire agreement between the parties, and supersede all prior and contemporaneous agreements, proposals, or representations, written or oral, concerning their subject matter. No modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless it is by an update to these Terms of Use that Salesforce makes available, or is in writing and signed by the party against whom the modification, amendment, or waiver is to be asserted.
|
|
26
|
+
|
|
27
|
+
_*Data Privacy*_: Salesforce may collect, process, and store device, system, and other information related to your use of the Software. This information includes, but is not limited to, IP address, user metrics, and other data (“Usage Data”). Salesforce may use Usage Data for analytics, product development, and marketing purposes. You acknowledge that files generated in conjunction with the Software may contain sensitive or confidential data, and you are solely responsible for anonymizing and protecting such data.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# `@conduit-client/service-renewable-resource-manager`
|
|
2
|
+
|
|
3
|
+
A generic, pluggable service for managing a single **renewable resource** — a
|
|
4
|
+
value you fetch, cache, hand out on demand, and re-fetch when it goes stale.
|
|
5
|
+
CSRF tokens are the first consumer; SFAP JWTs and other renewable credentials
|
|
6
|
+
fit the same shape.
|
|
7
|
+
|
|
8
|
+
See the design rationale in
|
|
9
|
+
[`adrs/2026-05-13-renewable-resource-manager-as-service.md`](../../../documentation/adrs/2026-05-13-renewable-resource-manager-as-service.md).
|
|
10
|
+
|
|
11
|
+
## What it is
|
|
12
|
+
|
|
13
|
+
- **Lazy & externally-triggered.** No TTL, no expiry tracking, no autonomous
|
|
14
|
+
renewal. It fetches on first `get()` and re-fetches only when a caller invokes
|
|
15
|
+
`refresh()`. "Renewable" describes the resource (it _can_ be re-fetched), not
|
|
16
|
+
a behavior of the manager.
|
|
17
|
+
- **Single-flight.** Concurrent `get()`/`refresh()` calls share one in-flight
|
|
18
|
+
fetch.
|
|
19
|
+
- **Refresh-storm bounded.** When many callers fail against the same stale value
|
|
20
|
+
at once, `refresh(staleValue)` collapses them to a single network round-trip
|
|
21
|
+
(see below).
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
BasicRenewableResourceManager,
|
|
28
|
+
buildRenewableResourceManagerDescriptor,
|
|
29
|
+
} from '@conduit-client/service-renewable-resource-manager/v1';
|
|
30
|
+
|
|
31
|
+
const manager = new BasicRenewableResourceManager<string>({
|
|
32
|
+
fetch: () => fetchTokenFromServer(), // "go to the network"
|
|
33
|
+
storage: {
|
|
34
|
+
get: () => durableStorage.get(KEY),
|
|
35
|
+
set: (v) => durableStorage.set(KEY, v),
|
|
36
|
+
clear: () => durableStorage.remove(KEY),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// register as a named, versioned service
|
|
41
|
+
const descriptor = buildRenewableResourceManagerDescriptor('csrfToken', manager);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### `RenewableResourceManager<T extends string | number | boolean>`
|
|
47
|
+
|
|
48
|
+
- `get(): PromiseLike<T | undefined>` — cached value, or fetch+cache if empty.
|
|
49
|
+
- `refresh(staleValue?: T): PromiseLike<T | undefined>` — fetch fresh, overwrite
|
|
50
|
+
cache. With `staleValue`, short-circuits if storage already moved past it.
|
|
51
|
+
- `clear(): PromiseLike<void>` — discard the cache (logout, org switch,
|
|
52
|
+
teardown). Routine staleness uses `refresh`, which is non-destructive.
|
|
53
|
+
|
|
54
|
+
`T` is constrained to primitives so the storm-dedup comparison can use `===`.
|
|
55
|
+
|
|
56
|
+
### Error contract
|
|
57
|
+
|
|
58
|
+
- A **rejected** `PromiseLike` ⇒ the fetch could not complete (network/server
|
|
59
|
+
error). Rejections propagate; they are never swallowed into `undefined`.
|
|
60
|
+
- A **resolved `undefined`** ⇒ the fetch completed and there is genuinely no
|
|
61
|
+
value. The cache is **not** cleared in this case — use `clear()` for that.
|
|
62
|
+
|
|
63
|
+
### Refresh-storm dedup, by timing regime
|
|
64
|
+
|
|
65
|
+
1. **Concurrent** failures (arriving while a fetch is in flight) share that
|
|
66
|
+
fetch via single-flight.
|
|
67
|
+
2. **Late** failures (arriving after the first refresh stored a fresh value) are
|
|
68
|
+
caught by `refresh(staleValue)`: storage no longer holds `staleValue`, so the
|
|
69
|
+
stored value is returned with no network hop.
|
|
70
|
+
|
|
71
|
+
### Storage adapter preconditions
|
|
72
|
+
|
|
73
|
+
The manager relies on (but cannot enforce):
|
|
74
|
+
|
|
75
|
+
- writes are durable before the returned `PromiseLike` resolves;
|
|
76
|
+
- reads observe the most recent settled write (read-your-writes);
|
|
77
|
+
- the manager is the **sole writer** of the slot, and replacement is monotonic —
|
|
78
|
+
the storm-dedup treats "stored differs from stale" as "someone refreshed."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { RenewableResourceManager } from './renewable-resource-manager';
|
|
2
|
+
/**
|
|
3
|
+
* Storage adapter supplied by the runtime. Preconditions the manager relies on
|
|
4
|
+
* but cannot enforce:
|
|
5
|
+
*
|
|
6
|
+
* - **Durable before resolution**: the `PromiseLike` returned by `set`/`clear`
|
|
7
|
+
* resolves only after the write is observable by a subsequent `get`.
|
|
8
|
+
* - **Read-your-writes**: a `get` after a settled `set` observes that value.
|
|
9
|
+
* - **Sole writer / monotonic replacement**: the manager is the only writer of
|
|
10
|
+
* this slot. The storm-dedup in {@link BasicRenewableResourceManager.refresh}
|
|
11
|
+
* treats "stored value differs from the stale value" as "someone already
|
|
12
|
+
* refreshed"; a third party mutating storage out from under the manager
|
|
13
|
+
* (e.g. a server-pushed value) breaks that assumption.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResourceStorage<T> {
|
|
16
|
+
get: () => PromiseLike<T | undefined>;
|
|
17
|
+
set: (value: T) => PromiseLike<void>;
|
|
18
|
+
clear: () => PromiseLike<void>;
|
|
19
|
+
}
|
|
20
|
+
export interface BasicRenewableResourceManagerConfig<T extends string | number | boolean> {
|
|
21
|
+
/** Fetches a fresh value from the source of truth ("go to the network"). */
|
|
22
|
+
fetch: () => PromiseLike<T | undefined>;
|
|
23
|
+
storage: ResourceStorage<T>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configurable building block implementing {@link RenewableResourceManager}.
|
|
27
|
+
*
|
|
28
|
+
* The runtime supplies `fetch` and `storage`; the building block adds
|
|
29
|
+
* single-flight fetch coalescing and refresh-storm deduplication. It holds no
|
|
30
|
+
* opinion on storage backend, TTL, or initialization strategy — a consumer with
|
|
31
|
+
* a preloaded value seeds `storage` before constructing the manager, so the
|
|
32
|
+
* first `get` finds it and skips `fetch`.
|
|
33
|
+
*
|
|
34
|
+
* The implementation chains `PromiseLike` directly rather than using
|
|
35
|
+
* `async`/`await` so a synchronous source stays synchronous and avoids a
|
|
36
|
+
* microtask hop.
|
|
37
|
+
*/
|
|
38
|
+
export declare class BasicRenewableResourceManager<T extends string | number | boolean> implements RenewableResourceManager<T> {
|
|
39
|
+
private readonly config;
|
|
40
|
+
private inFlightFetch;
|
|
41
|
+
constructor(config: BasicRenewableResourceManagerConfig<T>);
|
|
42
|
+
get(): PromiseLike<T | undefined>;
|
|
43
|
+
refresh(staleValue?: T): PromiseLike<T | undefined>;
|
|
44
|
+
clear(): PromiseLike<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Single in-flight fetch shared by all concurrent `get`/`refresh` callers.
|
|
47
|
+
* Overwrites storage on a successful fetch of a defined value only; a
|
|
48
|
+
* rejected fetch or a resolved-`undefined` leaves the cached value intact.
|
|
49
|
+
*/
|
|
50
|
+
private fetchAndStore;
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NamedService, ServiceDescriptor } from '@conduit-client/utils';
|
|
2
|
+
import type { RenewableResourceManager } from './renewable-resource-manager';
|
|
3
|
+
export type { RenewableResourceManager } from './renewable-resource-manager';
|
|
4
|
+
export { BasicRenewableResourceManager, type BasicRenewableResourceManagerConfig, type ResourceStorage, } from './basic-renewable-resource-manager';
|
|
5
|
+
export type NamedRenewableResourceManagerService<Name extends string> = NamedService<Name, RenewableResourceManager<any>>;
|
|
6
|
+
export type RenewableResourceManagerServiceDescriptor<Name extends string> = ServiceDescriptor<RenewableResourceManager<any>, Name, '1.0'>;
|
|
7
|
+
/**
|
|
8
|
+
* Builds a versioned service descriptor wrapping a {@link RenewableResourceManager}.
|
|
9
|
+
*
|
|
10
|
+
* The `type` is the resource name (e.g. `'csrfToken'`), so a runtime can
|
|
11
|
+
* register multiple independent managers — one per renewable resource — each
|
|
12
|
+
* resolvable by name through the provisioner.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildRenewableResourceManagerDescriptor<Name extends string, T extends string | number | boolean>(type: Name, service: RenewableResourceManager<T>): RenewableResourceManagerServiceDescriptor<Name>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `RenewableResourceManager` owns a single renewable resource: it caches a
|
|
3
|
+
* value, hands it out on demand, and re-fetches a fresh one when a caller
|
|
4
|
+
* signals the cached value is stale.
|
|
5
|
+
*
|
|
6
|
+
* It is **lazy** and **externally-triggered**: it has no TTL, no expiry
|
|
7
|
+
* tracking, and no autonomous renewal. It fetches on first demand and
|
|
8
|
+
* re-fetches only when a caller invokes {@link refresh}. "Renewable" describes
|
|
9
|
+
* the resource (it *can* be re-fetched), not an autonomous behavior of the
|
|
10
|
+
* manager.
|
|
11
|
+
*
|
|
12
|
+
* `T` is constrained to primitive types so the storm-dedup comparison in
|
|
13
|
+
* {@link refresh} can use `===` correctly. See {@link BasicRenewableResourceManager}.
|
|
14
|
+
*
|
|
15
|
+
* ## Error contract
|
|
16
|
+
*
|
|
17
|
+
* The methods distinguish *failure* from *absence*:
|
|
18
|
+
* - A **rejected** `PromiseLike` means the fetch could not complete (network or
|
|
19
|
+
* server error). The caller (or a retry policy) decides whether to retry.
|
|
20
|
+
* Rejections propagate; they are never swallowed into `undefined`.
|
|
21
|
+
* - A **resolved `undefined`** means the fetch completed and there is genuinely
|
|
22
|
+
* no value (the configured `fetch` returned `undefined`).
|
|
23
|
+
*/
|
|
24
|
+
export interface RenewableResourceManager<T extends string | number | boolean> {
|
|
25
|
+
/**
|
|
26
|
+
* Returns the cached resource, or fetches and caches one if storage is empty.
|
|
27
|
+
* Concurrent calls (with `refresh`) share a single in-flight fetch.
|
|
28
|
+
*
|
|
29
|
+
* If called while a `refresh` is in flight, returns the *currently cached*
|
|
30
|
+
* value (what we have), not the value being fetched.
|
|
31
|
+
*/
|
|
32
|
+
get(): PromiseLike<T | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetches a fresh resource and overwrites the cached one (on success only).
|
|
35
|
+
*
|
|
36
|
+
* @param staleValue the value the caller held when it determined a refresh
|
|
37
|
+
* was needed. When provided, the manager compares it to the current cached
|
|
38
|
+
* value; if they differ, another caller already refreshed and the cached
|
|
39
|
+
* value is returned with no network round-trip. This bounds the refresh
|
|
40
|
+
* storm when many callers fail against the same stale value at once.
|
|
41
|
+
*/
|
|
42
|
+
refresh(staleValue?: T): PromiseLike<T | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Discards the cached resource. The next `get`/`refresh` fetches afresh.
|
|
45
|
+
* Intended for logout, org switch, and test teardown — not for routine
|
|
46
|
+
* staleness, which `refresh` handles non-destructively.
|
|
47
|
+
*/
|
|
48
|
+
clear(): PromiseLike<void>;
|
|
49
|
+
}
|
package/dist/v1/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class BasicRenewableResourceManager {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = config;
|
|
4
|
+
}
|
|
5
|
+
get() {
|
|
6
|
+
return this.config.storage.get().then((cached) => cached !== void 0 ? cached : this.fetchAndStore());
|
|
7
|
+
}
|
|
8
|
+
refresh(staleValue) {
|
|
9
|
+
if (staleValue === void 0) {
|
|
10
|
+
return this.fetchAndStore();
|
|
11
|
+
}
|
|
12
|
+
return this.config.storage.get().then(
|
|
13
|
+
(current) => current !== void 0 && current !== staleValue ? current : this.fetchAndStore()
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
clear() {
|
|
17
|
+
return this.config.storage.clear();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Single in-flight fetch shared by all concurrent `get`/`refresh` callers.
|
|
21
|
+
* Overwrites storage on a successful fetch of a defined value only; a
|
|
22
|
+
* rejected fetch or a resolved-`undefined` leaves the cached value intact.
|
|
23
|
+
*/
|
|
24
|
+
fetchAndStore() {
|
|
25
|
+
if (this.inFlightFetch) return this.inFlightFetch;
|
|
26
|
+
const pending = this.config.fetch().then(
|
|
27
|
+
(fetched) => fetched === void 0 ? fetched : this.config.storage.set(fetched).then(() => fetched)
|
|
28
|
+
);
|
|
29
|
+
this.inFlightFetch = pending;
|
|
30
|
+
const releaseSlot = () => {
|
|
31
|
+
if (this.inFlightFetch === pending) this.inFlightFetch = void 0;
|
|
32
|
+
};
|
|
33
|
+
pending.then(releaseSlot, releaseSlot);
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function buildRenewableResourceManagerDescriptor(type, service) {
|
|
38
|
+
return {
|
|
39
|
+
version: "1.0",
|
|
40
|
+
service,
|
|
41
|
+
type
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
BasicRenewableResourceManager,
|
|
46
|
+
buildRenewableResourceManagerDescriptor
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/v1/basic-renewable-resource-manager.ts","../../src/v1/index.ts"],"sourcesContent":["import type { RenewableResourceManager } from './renewable-resource-manager';\n\n/**\n * Storage adapter supplied by the runtime. Preconditions the manager relies on\n * but cannot enforce:\n *\n * - **Durable before resolution**: the `PromiseLike` returned by `set`/`clear`\n * resolves only after the write is observable by a subsequent `get`.\n * - **Read-your-writes**: a `get` after a settled `set` observes that value.\n * - **Sole writer / monotonic replacement**: the manager is the only writer of\n * this slot. The storm-dedup in {@link BasicRenewableResourceManager.refresh}\n * treats \"stored value differs from the stale value\" as \"someone already\n * refreshed\"; a third party mutating storage out from under the manager\n * (e.g. a server-pushed value) breaks that assumption.\n */\nexport interface ResourceStorage<T> {\n get: () => PromiseLike<T | undefined>;\n set: (value: T) => PromiseLike<void>;\n clear: () => PromiseLike<void>;\n}\n\nexport interface BasicRenewableResourceManagerConfig<T extends string | number | boolean> {\n /** Fetches a fresh value from the source of truth (\"go to the network\"). */\n fetch: () => PromiseLike<T | undefined>;\n storage: ResourceStorage<T>;\n}\n\n/**\n * Configurable building block implementing {@link RenewableResourceManager}.\n *\n * The runtime supplies `fetch` and `storage`; the building block adds\n * single-flight fetch coalescing and refresh-storm deduplication. It holds no\n * opinion on storage backend, TTL, or initialization strategy — a consumer with\n * a preloaded value seeds `storage` before constructing the manager, so the\n * first `get` finds it and skips `fetch`.\n *\n * The implementation chains `PromiseLike` directly rather than using\n * `async`/`await` so a synchronous source stays synchronous and avoids a\n * microtask hop.\n */\nexport class BasicRenewableResourceManager<T extends string | number | boolean>\n implements RenewableResourceManager<T>\n{\n private inFlightFetch: PromiseLike<T | undefined> | undefined;\n\n constructor(private readonly config: BasicRenewableResourceManagerConfig<T>) {}\n\n get(): PromiseLike<T | undefined> {\n return this.config.storage\n .get()\n .then((cached) => (cached !== undefined ? cached : this.fetchAndStore()));\n }\n\n refresh(staleValue?: T): PromiseLike<T | undefined> {\n if (staleValue === undefined) {\n return this.fetchAndStore();\n }\n // Storm dedup: if storage already holds a value other than the one this\n // caller saw fail, another caller refreshed — return it, skip the network.\n return this.config.storage\n .get()\n .then((current) =>\n current !== undefined && current !== staleValue ? current : this.fetchAndStore()\n );\n }\n\n clear(): PromiseLike<void> {\n return this.config.storage.clear();\n }\n\n /**\n * Single in-flight fetch shared by all concurrent `get`/`refresh` callers.\n * Overwrites storage on a successful fetch of a defined value only; a\n * rejected fetch or a resolved-`undefined` leaves the cached value intact.\n */\n private fetchAndStore(): PromiseLike<T | undefined> {\n if (this.inFlightFetch) return this.inFlightFetch;\n\n const pending = this.config\n .fetch()\n .then((fetched) =>\n fetched === undefined\n ? fetched\n : this.config.storage.set(fetched).then(() => fetched)\n );\n\n // Assign the slot BEFORE attaching the release handler. PromiseLike\n // resolves synchronously when fetch + set are synchronous, so the\n // handler can run inline; if the assignment came after, releaseSlot\n // would see `inFlightFetch === undefined`, no-op, and the slot set on\n // the next line would leak forever.\n this.inFlightFetch = pending;\n\n const releaseSlot = () => {\n if (this.inFlightFetch === pending) this.inFlightFetch = undefined;\n };\n pending.then(releaseSlot, releaseSlot);\n\n return pending;\n }\n}\n","import type { NamedService, ServiceDescriptor } from '@conduit-client/utils';\nimport type { RenewableResourceManager } from './renewable-resource-manager';\n\nexport type { RenewableResourceManager } from './renewable-resource-manager';\nexport {\n BasicRenewableResourceManager,\n type BasicRenewableResourceManagerConfig,\n type ResourceStorage,\n} from './basic-renewable-resource-manager';\n\nexport type NamedRenewableResourceManagerService<Name extends string> = NamedService<\n Name,\n RenewableResourceManager<any>\n>;\n\nexport type RenewableResourceManagerServiceDescriptor<Name extends string> = ServiceDescriptor<\n RenewableResourceManager<any>,\n Name,\n '1.0'\n>;\n\n/**\n * Builds a versioned service descriptor wrapping a {@link RenewableResourceManager}.\n *\n * The `type` is the resource name (e.g. `'csrfToken'`), so a runtime can\n * register multiple independent managers — one per renewable resource — each\n * resolvable by name through the provisioner.\n */\nexport function buildRenewableResourceManagerDescriptor<\n Name extends string,\n T extends string | number | boolean,\n>(\n type: Name,\n service: RenewableResourceManager<T>\n): RenewableResourceManagerServiceDescriptor<Name> {\n return {\n version: '1.0' as const,\n service: service as RenewableResourceManager<any>,\n type,\n };\n}\n"],"names":[],"mappings":"AAwCO,MAAM,8BAEb;AAAA,EAGI,YAA6B,QAAgD;AAAhD,SAAA,SAAA;AAAA,EAAiD;AAAA,EAE9E,MAAkC;AAC9B,WAAO,KAAK,OAAO,QACd,IAAA,EACA,KAAK,CAAC,WAAY,WAAW,SAAY,SAAS,KAAK,eAAgB;AAAA,EAChF;AAAA,EAEA,QAAQ,YAA4C;AAChD,QAAI,eAAe,QAAW;AAC1B,aAAO,KAAK,cAAA;AAAA,IAChB;AAGA,WAAO,KAAK,OAAO,QACd,IAAA,EACA;AAAA,MAAK,CAAC,YACH,YAAY,UAAa,YAAY,aAAa,UAAU,KAAK,cAAA;AAAA,IAAc;AAAA,EAE3F;AAAA,EAEA,QAA2B;AACvB,WAAO,KAAK,OAAO,QAAQ,MAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAA4C;AAChD,QAAI,KAAK,cAAe,QAAO,KAAK;AAEpC,UAAM,UAAU,KAAK,OAChB,MAAA,EACA;AAAA,MAAK,CAAC,YACH,YAAY,SACN,UACA,KAAK,OAAO,QAAQ,IAAI,OAAO,EAAE,KAAK,MAAM,OAAO;AAAA,IAAA;AAQjE,SAAK,gBAAgB;AAErB,UAAM,cAAc,MAAM;AACtB,UAAI,KAAK,kBAAkB,QAAS,MAAK,gBAAgB;AAAA,IAC7D;AACA,YAAQ,KAAK,aAAa,WAAW;AAErC,WAAO;AAAA,EACX;AACJ;ACxEO,SAAS,wCAIZ,MACA,SAC+C;AAC/C,SAAO;AAAA,IACH,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EAAA;AAER;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@conduit-client/service-renewable-resource-manager",
|
|
3
|
+
"version": "3.22.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Generic renewable-resource manager service (lazy cache + single-flight fetch + refresh-storm dedup)",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/salesforce-experience-platform-emu/onestore.git",
|
|
10
|
+
"directory": "packages/@conduit-client/services/renewable-resource-manager"
|
|
11
|
+
},
|
|
12
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
13
|
+
"exports": {
|
|
14
|
+
"./v1": {
|
|
15
|
+
"import": "./dist/v1/index.js",
|
|
16
|
+
"types": "./dist/types/v1/index.d.ts",
|
|
17
|
+
"require": "./dist/v1/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/main/index.js",
|
|
21
|
+
"module": "./dist/main/index.js",
|
|
22
|
+
"types": "./dist/main/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "vite build && tsc --build --emitDeclarationOnly",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:size": "size-limit",
|
|
31
|
+
"watch": "npm run build --watch"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@conduit-client/utils": "3.20.0"
|
|
35
|
+
},
|
|
36
|
+
"volta": {
|
|
37
|
+
"extends": "../../../../package.json"
|
|
38
|
+
},
|
|
39
|
+
"size-limit": [
|
|
40
|
+
{
|
|
41
|
+
"path": "./dist/v1/index.js",
|
|
42
|
+
"limit": "1 kB"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|