@adcp/sdk 14.0.0 → 14.1.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/lib/adapters/implicit-account-store.d.mts +12 -7
- package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
- package/dist/lib/adapters/implicit-account-store.js +69 -15
- package/dist/lib/adapters/implicit-account-store.mjs +69 -15
- package/dist/lib/core/AgentClient.d.mts +1 -0
- package/dist/lib/core/AgentClient.d.ts +1 -0
- package/dist/lib/core/AgentClient.js +3 -0
- package/dist/lib/core/AgentClient.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +31 -1
- package/dist/lib/core/SingleAgentClient.d.ts +31 -1
- package/dist/lib/core/SingleAgentClient.js +355 -35
- package/dist/lib/core/SingleAgentClient.mjs +365 -37
- package/dist/lib/core/TaskExecutor.js +2 -1
- package/dist/lib/core/TaskExecutor.mjs +2 -1
- package/dist/lib/core/account-key.d.mts +3 -0
- package/dist/lib/core/account-key.d.ts +3 -0
- package/dist/lib/core/account-key.js +41 -0
- package/dist/lib/core/account-key.mjs +17 -0
- package/dist/lib/core/account-resolution.d.mts +2 -0
- package/dist/lib/core/account-resolution.d.ts +2 -0
- package/dist/lib/core/buyer-account-registry.d.mts +65 -0
- package/dist/lib/core/buyer-account-registry.d.ts +65 -0
- package/dist/lib/core/buyer-account-registry.js +518 -0
- package/dist/lib/core/buyer-account-registry.mjs +494 -0
- package/dist/lib/core/product-cache.d.mts +18 -0
- package/dist/lib/core/product-cache.d.ts +18 -0
- package/dist/lib/core/product-cache.js +137 -0
- package/dist/lib/core/product-cache.mjs +112 -0
- package/dist/lib/errors/index.d.mts +40 -1
- package/dist/lib/errors/index.d.ts +40 -1
- package/dist/lib/errors/index.js +69 -3
- package/dist/lib/errors/index.mjs +64 -3
- package/dist/lib/governance/authorization.d.mts +17 -1
- package/dist/lib/governance/authorization.d.ts +17 -1
- package/dist/lib/governance/authorization.js +55 -7
- package/dist/lib/governance/authorization.mjs +59 -7
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +2 -0
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/index.d.mts +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.mjs +19 -0
- package/dist/lib/protocols/a2a.js +9 -1
- package/dist/lib/protocols/a2a.mjs +9 -1
- package/dist/lib/protocols/index.js +9 -2
- package/dist/lib/protocols/index.mjs +9 -2
- package/dist/lib/protocols/mcp-modern.js +2 -1
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +5 -2
- package/dist/lib/protocols/mcp.mjs +5 -2
- package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
- package/dist/lib/protocols/rawResponseCapture.js +41 -29
- package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
- package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
- package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
- package/dist/lib/protocols/signedRequestRejection.js +209 -0
- package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
- package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
- package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
- package/dist/lib/protocols/transportDiagnostics.js +2 -0
- package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
- package/dist/lib/registry/types.generated.d.mts +112 -45
- package/dist/lib/registry/types.generated.d.ts +112 -45
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-provisioning.d.mts +2 -0
- package/dist/lib/server/account-provisioning.d.ts +2 -0
- package/dist/lib/server/account-provisioning.js +30 -0
- package/dist/lib/server/account-provisioning.mjs +6 -0
- package/dist/lib/server/account-reference-warnings.d.mts +12 -0
- package/dist/lib/server/account-reference-warnings.d.ts +12 -0
- package/dist/lib/server/account-reference-warnings.js +48 -0
- package/dist/lib/server/account-reference-warnings.mjs +23 -0
- package/dist/lib/server/auth-signature.js +1 -0
- package/dist/lib/server/auth-signature.mjs +1 -0
- package/dist/lib/server/create-adcp-server.d.mts +34 -0
- package/dist/lib/server/create-adcp-server.d.ts +34 -0
- package/dist/lib/server/create-adcp-server.js +225 -14
- package/dist/lib/server/create-adcp-server.mjs +225 -14
- package/dist/lib/server/decisioning/account.d.mts +2 -0
- package/dist/lib/server/decisioning/account.d.ts +2 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.js +0 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
- package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
- package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
- package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
- package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
- package/dist/lib/signing/brand-jwks.d.mts +27 -75
- package/dist/lib/signing/brand-jwks.d.ts +27 -75
- package/dist/lib/signing/brand-jwks.js +112 -182
- package/dist/lib/signing/brand-jwks.mjs +112 -182
- package/dist/lib/signing/errors.d.mts +3 -1
- package/dist/lib/signing/errors.d.ts +3 -1
- package/dist/lib/signing/errors.js +4 -1
- package/dist/lib/signing/errors.mjs +4 -1
- package/dist/lib/signing/jwks-https.d.mts +7 -0
- package/dist/lib/signing/jwks-https.d.ts +7 -0
- package/dist/lib/signing/jwks-https.js +31 -8
- package/dist/lib/signing/jwks-https.mjs +31 -8
- package/dist/lib/signing/jwks.d.mts +8 -0
- package/dist/lib/signing/jwks.d.ts +8 -0
- package/dist/lib/signing/middleware.js +2 -1
- package/dist/lib/signing/middleware.mjs +2 -1
- package/dist/lib/signing/publisher-pins.d.mts +11 -0
- package/dist/lib/signing/publisher-pins.d.ts +11 -0
- package/dist/lib/signing/publisher-pins.js +125 -0
- package/dist/lib/signing/publisher-pins.mjs +101 -0
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/types.d.mts +5 -0
- package/dist/lib/signing/types.d.ts +5 -0
- package/dist/lib/signing/verifier.js +49 -4
- package/dist/lib/signing/verifier.mjs +49 -4
- package/dist/lib/signing/webhook-verifier.d.mts +7 -2
- package/dist/lib/signing/webhook-verifier.d.ts +7 -2
- package/dist/lib/signing/webhook-verifier.js +42 -2
- package/dist/lib/signing/webhook-verifier.mjs +43 -3
- package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
- package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
- package/dist/lib/testing/storyboard/account-policy.js +35 -0
- package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
- package/dist/lib/testing/storyboard/context.js +6 -0
- package/dist/lib/testing/storyboard/context.mjs +6 -0
- package/dist/lib/testing/storyboard/request-builder.js +12 -2
- package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
- package/dist/lib/testing/storyboard/runner.js +3 -2
- package/dist/lib/testing/storyboard/runner.mjs +3 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/wholesale-feed-sync/sync.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.js +92 -21
- package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +2 -2
- package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +86 -0
- package/docs/llms.txt +3 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-4.x-to-5.x.md +1 -0
- package/docs/migration-agent-resolution-3.3.md +123 -0
- package/docs/recipes/verifying-inbound-webhooks.md +56 -15
- package/package.json +2 -2
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { accountReferenceKey } from "./account-key.mjs";
|
|
4
|
+
import { sameCountrySet } from "./account-resolution.mjs";
|
|
5
|
+
import { adcpErrorToTypedError, AccountNotFoundError, AccountSetupRequiredError } from "../errors/index.mjs";
|
|
6
|
+
import { isAbortOrTimeoutError, MAX_TIMER_DELAY_MS, throwIfAborted, withAbortSignal } from "../protocols/abort.mjs";
|
|
7
|
+
import { withTaskDeadline } from "./task-deadline.mjs";
|
|
8
|
+
function setupTerms(options) {
|
|
9
|
+
const stable = (value) => Array.isArray(value) ? value.map(stable) : value && typeof value === "object" ? Object.fromEntries(
|
|
10
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stable(item)])
|
|
11
|
+
) : value;
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries(options).map(([key, value]) => [
|
|
14
|
+
key,
|
|
15
|
+
createHash("sha256").update(JSON.stringify(stable(value))).digest("hex")
|
|
16
|
+
])
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
class BuyerAccountRegistry {
|
|
20
|
+
constructor(scope, provision, storage, repair, options = {}) {
|
|
21
|
+
this.scope = scope;
|
|
22
|
+
this.provision = provision;
|
|
23
|
+
this.storage = storage;
|
|
24
|
+
this.repair = repair;
|
|
25
|
+
this.maxEntries = options.maxEntries ?? 1e4;
|
|
26
|
+
this.normalizeOptions = options.normalizeOptions;
|
|
27
|
+
if (!Number.isSafeInteger(this.maxEntries) || this.maxEntries < 2)
|
|
28
|
+
throw new Error("Account registry maxEntries must be an integer >= 2.");
|
|
29
|
+
}
|
|
30
|
+
scope;
|
|
31
|
+
provision;
|
|
32
|
+
storage;
|
|
33
|
+
repair;
|
|
34
|
+
entries = /* @__PURE__ */ new Map();
|
|
35
|
+
pending = /* @__PURE__ */ new Map();
|
|
36
|
+
writes = Promise.resolve();
|
|
37
|
+
repairs = /* @__PURE__ */ new Map();
|
|
38
|
+
repairWaiters = /* @__PURE__ */ new Map();
|
|
39
|
+
invalidatedHandles = /* @__PURE__ */ new Set();
|
|
40
|
+
observationEpoch = 0;
|
|
41
|
+
observationFloor = 0;
|
|
42
|
+
statusEpochs = /* @__PURE__ */ new Map();
|
|
43
|
+
maxEntries;
|
|
44
|
+
normalizeOptions;
|
|
45
|
+
key(account, scope = this.scope()) {
|
|
46
|
+
return `${scope}:${accountReferenceKey(account)}`;
|
|
47
|
+
}
|
|
48
|
+
/** Capture before a seller request; observations older than a status repair are ignored. */
|
|
49
|
+
captureObservation() {
|
|
50
|
+
return this.observationEpoch;
|
|
51
|
+
}
|
|
52
|
+
observationIsCurrent(accountId, epoch, scope) {
|
|
53
|
+
return epoch === void 0 || epoch >= this.observationFloor && (!accountId || (this.statusEpochs.get(this.key({ account_id: accountId }, scope)) ?? 0) <= epoch);
|
|
54
|
+
}
|
|
55
|
+
remember(key, entry) {
|
|
56
|
+
if (!this.entries.has(key) && this.entries.size >= this.maxEntries) {
|
|
57
|
+
if (!this.storage)
|
|
58
|
+
throw new AccountSetupRequiredError(
|
|
59
|
+
"Account registry capacity reached; configure durable storage or increase maxEntries."
|
|
60
|
+
);
|
|
61
|
+
this.entries.delete(this.entries.keys().next().value);
|
|
62
|
+
}
|
|
63
|
+
this.entries.set(key, structuredClone(entry));
|
|
64
|
+
}
|
|
65
|
+
async get(account) {
|
|
66
|
+
const key = this.key(account);
|
|
67
|
+
let entry = this.entries.get(key);
|
|
68
|
+
if (!entry) {
|
|
69
|
+
const persisted = await this.storage?.get(key);
|
|
70
|
+
entry = this.entries.get(key) ?? persisted;
|
|
71
|
+
}
|
|
72
|
+
if (key !== this.key(account) || entry && accountReferenceKey(entry.account) !== accountReferenceKey(account))
|
|
73
|
+
return void 0;
|
|
74
|
+
if (entry) this.remember(key, entry);
|
|
75
|
+
if (entry?.account_id && this.invalidatedHandles.has(this.key({ account_id: entry.account_id })))
|
|
76
|
+
return { ...structuredClone(entry), status: "unknown" };
|
|
77
|
+
return entry ? structuredClone(entry) : void 0;
|
|
78
|
+
}
|
|
79
|
+
async ensure(account, options = {}, taskOptions, onTaskResult) {
|
|
80
|
+
throwIfAborted(taskOptions?.signal);
|
|
81
|
+
if (taskOptions?.timeout !== void 0 && (!Number.isFinite(taskOptions.timeout) || taskOptions.timeout < 0 || taskOptions.timeout > MAX_TIMER_DELAY_MS))
|
|
82
|
+
throw new RangeError(`timeout must be a finite non-negative number <= ${MAX_TIMER_DELAY_MS}`);
|
|
83
|
+
account = structuredClone(account);
|
|
84
|
+
if (this.normalizeOptions) options = await this.normalizeOptions(structuredClone(options), taskOptions);
|
|
85
|
+
options = structuredClone(Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0)));
|
|
86
|
+
const scope = this.scope();
|
|
87
|
+
const key = this.key(account, scope);
|
|
88
|
+
const current = await this.get(account);
|
|
89
|
+
throwIfAborted(taskOptions?.signal);
|
|
90
|
+
if (scope !== this.scope()) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
91
|
+
const shared = this.pending.get(key);
|
|
92
|
+
const wait = (promise) => withTaskDeadline(
|
|
93
|
+
taskOptions,
|
|
94
|
+
(options2) => withAbortSignal([options2.signal], void 0, () => promise.then((entry2) => structuredClone(entry2)))
|
|
95
|
+
);
|
|
96
|
+
if (shared) {
|
|
97
|
+
if (!isDeepStrictEqual(shared.options, options))
|
|
98
|
+
throw new AccountSetupRequiredError("Provisioning is already in progress with different billing terms.");
|
|
99
|
+
const entry2 = await wait(shared.promise);
|
|
100
|
+
if (shared.result) onTaskResult?.(shared.result);
|
|
101
|
+
return entry2;
|
|
102
|
+
}
|
|
103
|
+
const terms = setupTerms(options);
|
|
104
|
+
if (current?.status === "failed_provisioning" && current.setupTerms && !isDeepStrictEqual(current.setupTerms, terms))
|
|
105
|
+
throw new AccountSetupRequiredError(
|
|
106
|
+
"The pending account was provisioned with different billing terms. Call syncAccounts explicitly to change terms."
|
|
107
|
+
);
|
|
108
|
+
if (current && current.status !== "failed_provisioning") {
|
|
109
|
+
if (Object.entries(terms ?? {}).some(
|
|
110
|
+
([name, value]) => current.setupTerms?.[name] !== void 0 && current.setupTerms[name] !== value
|
|
111
|
+
))
|
|
112
|
+
throw new AccountSetupRequiredError(
|
|
113
|
+
"The account was provisioned with different billing terms. Call syncAccounts explicitly to change terms."
|
|
114
|
+
);
|
|
115
|
+
if (current.status !== "active" && current.account_id && this.repair) {
|
|
116
|
+
try {
|
|
117
|
+
await this.repairStatus(current.account_id, taskOptions, false);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throwIfAborted(taskOptions?.signal);
|
|
120
|
+
if (isAbortOrTimeoutError(error)) throw error;
|
|
121
|
+
if (scope !== this.scope())
|
|
122
|
+
throw new AccountNotFoundError("Caller credentials changed during reconciliation.");
|
|
123
|
+
return current;
|
|
124
|
+
}
|
|
125
|
+
const refreshed = await this.get(account) ?? current;
|
|
126
|
+
if (scope !== this.scope()) throw new AccountNotFoundError("Caller credentials changed during reconciliation.");
|
|
127
|
+
if (refreshed.status === "unknown")
|
|
128
|
+
throw new AccountNotFoundError(
|
|
129
|
+
"The seller no longer lists this account. Call syncAccounts explicitly to reestablish it."
|
|
130
|
+
);
|
|
131
|
+
return refreshed;
|
|
132
|
+
}
|
|
133
|
+
if (current.status === "unknown")
|
|
134
|
+
throw new AccountNotFoundError(
|
|
135
|
+
"Reconcile this account with listAccounts or explicitly reestablish it with syncAccounts."
|
|
136
|
+
);
|
|
137
|
+
if (current.status === "provisioning" && current.pendingTaskId)
|
|
138
|
+
throw new AccountSetupRequiredError(
|
|
139
|
+
`Reconcile pending sync_accounts task ${current.pendingTaskId} and observe its completed account rows before provisioning again.`
|
|
140
|
+
);
|
|
141
|
+
if (current.status !== "pending_approval" || current.account_id) return current;
|
|
142
|
+
if (!current.setupTerms || !isDeepStrictEqual(current.setupTerms, terms))
|
|
143
|
+
throw new AccountSetupRequiredError(
|
|
144
|
+
"Provide the original billing options to refresh a legacy account without a handle, or call syncAccounts explicitly."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const requiredEntries = current ? 1 : 2;
|
|
148
|
+
if (!this.storage && this.entries.size + this.pending.size * 2 + requiredEntries > this.maxEntries) {
|
|
149
|
+
throw new AccountSetupRequiredError(
|
|
150
|
+
"Account registry capacity reached; configure durable storage or increase maxEntries."
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const pending = { promise: void 0, options: structuredClone(options) };
|
|
154
|
+
let callbackFailed = false;
|
|
155
|
+
let callbackError;
|
|
156
|
+
const notify = (result) => {
|
|
157
|
+
try {
|
|
158
|
+
onTaskResult?.(result);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
callbackFailed = true;
|
|
161
|
+
callbackError = error;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const operationOptions = { ...taskOptions, signal: void 0, timeout: void 0 };
|
|
165
|
+
const observationEpoch = this.captureObservation();
|
|
166
|
+
const request = this.provision(structuredClone(account), pending.options, operationOptions).then(async (initial) => {
|
|
167
|
+
let result = initial;
|
|
168
|
+
pending.result = result;
|
|
169
|
+
notify(result);
|
|
170
|
+
if (this.key(account) !== key) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
171
|
+
if (!result.success)
|
|
172
|
+
throw adcpErrorToTypedError(result.adcpError ?? { code: "ACCOUNT_NOT_FOUND", message: result.error }) ?? new Error(result.error);
|
|
173
|
+
if (result.status !== "completed") {
|
|
174
|
+
const provisional = {
|
|
175
|
+
account: structuredClone(account),
|
|
176
|
+
status: "provisioning",
|
|
177
|
+
setupTerms: current?.setupTerms ?? terms,
|
|
178
|
+
...result.metadata.taskId && { pendingTaskId: result.metadata.taskId }
|
|
179
|
+
};
|
|
180
|
+
await this.enqueue(async () => {
|
|
181
|
+
const latest = await this.get(account);
|
|
182
|
+
if (!latest || latest.status === "failed_provisioning" || latest.status === current?.status && latest.account_id === current?.account_id)
|
|
183
|
+
await this.write(provisional, scope);
|
|
184
|
+
});
|
|
185
|
+
if (!result.submitted) return provisional;
|
|
186
|
+
result = await result.submitted.waitForCompletion();
|
|
187
|
+
pending.result = result;
|
|
188
|
+
notify(result);
|
|
189
|
+
if (!result.success) {
|
|
190
|
+
await this.enqueue(async () => {
|
|
191
|
+
const latest = await this.get(account);
|
|
192
|
+
if (!latest || latest.status === "provisioning")
|
|
193
|
+
await this.write(
|
|
194
|
+
{
|
|
195
|
+
account: structuredClone(account),
|
|
196
|
+
status: "failed_provisioning",
|
|
197
|
+
setupTerms: current?.setupTerms ?? terms
|
|
198
|
+
},
|
|
199
|
+
scope
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
throw adcpErrorToTypedError(result.adcpError ?? { code: "ACCOUNT_SETUP_REQUIRED", message: result.error }) ?? new Error(result.error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (this.key(account) !== key) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
206
|
+
if (result.status !== "completed" || !result.data || !("accounts" in result.data))
|
|
207
|
+
throw new AccountSetupRequiredError(
|
|
208
|
+
"sync_accounts has not completed; observe its completion before provisioning again."
|
|
209
|
+
);
|
|
210
|
+
await this.observeSync([account], result.data.accounts, false, observationEpoch);
|
|
211
|
+
const entry2 = await this.get(account);
|
|
212
|
+
if (scope !== this.scope()) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
213
|
+
if (!entry2 || entry2.status === "provisioning" || entry2.status === "failed_provisioning") {
|
|
214
|
+
await this.enqueue(async () => {
|
|
215
|
+
const latest = await this.get(account);
|
|
216
|
+
if (!latest || ["provisioning", "failed_provisioning"].includes(latest.status))
|
|
217
|
+
await this.write(
|
|
218
|
+
{
|
|
219
|
+
account,
|
|
220
|
+
status: "failed_provisioning",
|
|
221
|
+
setupTerms: current?.setupTerms ?? terms
|
|
222
|
+
},
|
|
223
|
+
scope
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
const failed = result.data.accounts.find((row) => row.action === "failed");
|
|
227
|
+
const error = failed?.errors?.[0];
|
|
228
|
+
throw (error && adcpErrorToTypedError(error)) ?? new AccountNotFoundError("sync_accounts did not establish the requested account.");
|
|
229
|
+
}
|
|
230
|
+
let settled = entry2;
|
|
231
|
+
await this.enqueue(async () => {
|
|
232
|
+
const latest = await this.get(account);
|
|
233
|
+
if (scope !== this.scope()) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
234
|
+
if (!latest) throw new AccountNotFoundError("The provisioned account is no longer registered.");
|
|
235
|
+
settled = latest;
|
|
236
|
+
if (latest.account_id !== entry2.account_id) return;
|
|
237
|
+
settled = { ...latest, setupTerms: setupTerms(options) };
|
|
238
|
+
await this.write(settled, scope);
|
|
239
|
+
if (settled.account_id && !("account_id" in account)) {
|
|
240
|
+
const idEntry = await this.get({ account_id: settled.account_id });
|
|
241
|
+
if (scope !== this.scope()) throw new AccountNotFoundError("Caller credentials changed during provisioning.");
|
|
242
|
+
if (idEntry) await this.write({ ...idEntry, setupTerms: settled.setupTerms }, scope);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return settled;
|
|
246
|
+
});
|
|
247
|
+
pending.promise = request;
|
|
248
|
+
this.pending.set(key, pending);
|
|
249
|
+
const cleanup = () => {
|
|
250
|
+
if (this.pending.get(key) === pending) this.pending.delete(key);
|
|
251
|
+
};
|
|
252
|
+
void request.then(cleanup, cleanup);
|
|
253
|
+
const entry = await wait(request);
|
|
254
|
+
if (callbackFailed) throw callbackError;
|
|
255
|
+
return entry;
|
|
256
|
+
}
|
|
257
|
+
async write(entry, scope, isCurrent = () => true) {
|
|
258
|
+
if (!isCurrent()) return;
|
|
259
|
+
const key = this.key(entry.account, scope);
|
|
260
|
+
if (scope === this.scope()) this.remember(key, entry);
|
|
261
|
+
await this.storage?.set(key, structuredClone(entry));
|
|
262
|
+
}
|
|
263
|
+
async record(ref, row, scope, preserveTerms = false, isCurrent = () => true) {
|
|
264
|
+
const entry = {
|
|
265
|
+
account: structuredClone(ref),
|
|
266
|
+
status: row.status,
|
|
267
|
+
...row.account_id && { account_id: row.account_id }
|
|
268
|
+
};
|
|
269
|
+
const prior = await this.get(ref);
|
|
270
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
271
|
+
const idRef = row.account_id && !("account_id" in ref) ? { account_id: row.account_id } : void 0;
|
|
272
|
+
const priorId = idRef ? await this.get(idRef) : void 0;
|
|
273
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
274
|
+
const aliases = new Map((priorId?.aliases ?? []).map((alias) => [accountReferenceKey(alias), alias]));
|
|
275
|
+
if (idRef) {
|
|
276
|
+
aliases.set(accountReferenceKey(ref), structuredClone(ref));
|
|
277
|
+
if (aliases.size > this.maxEntries)
|
|
278
|
+
throw new AccountSetupRequiredError("Account alias capacity reached; increase registry maxEntries.");
|
|
279
|
+
}
|
|
280
|
+
if (!("account_id" in ref) && prior?.account_id && prior.account_id !== row.account_id) {
|
|
281
|
+
const oldId = await this.get({ account_id: prior.account_id });
|
|
282
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
283
|
+
if (oldId?.aliases) {
|
|
284
|
+
await this.write(
|
|
285
|
+
{ ...oldId, aliases: oldId.aliases.filter((alias) => accountReferenceKey(alias) !== accountReferenceKey(ref)) },
|
|
286
|
+
scope,
|
|
287
|
+
isCurrent
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (preserveTerms && prior?.setupTerms) entry.setupTerms = prior.setupTerms;
|
|
292
|
+
if ("account_id" in ref) {
|
|
293
|
+
if (prior?.aliases) entry.aliases = prior.aliases;
|
|
294
|
+
}
|
|
295
|
+
await this.write(entry, scope, isCurrent);
|
|
296
|
+
if (!idRef || scope !== this.scope() || !isCurrent()) return;
|
|
297
|
+
await this.write(
|
|
298
|
+
{
|
|
299
|
+
account: idRef,
|
|
300
|
+
account_id: row.account_id,
|
|
301
|
+
status: row.status,
|
|
302
|
+
aliases: [...aliases.values()],
|
|
303
|
+
...preserveTerms && priorId?.setupTerms && { setupTerms: priorId.setupTerms }
|
|
304
|
+
},
|
|
305
|
+
scope,
|
|
306
|
+
isCurrent
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
enqueue(run) {
|
|
310
|
+
const next = this.writes.then(run);
|
|
311
|
+
this.writes = next.catch(() => {
|
|
312
|
+
});
|
|
313
|
+
return next;
|
|
314
|
+
}
|
|
315
|
+
async observeSync(refs, rows, dryRun = false, observationEpoch) {
|
|
316
|
+
if (dryRun) return;
|
|
317
|
+
const scope = this.scope();
|
|
318
|
+
return this.enqueue(async () => {
|
|
319
|
+
if (scope !== this.scope()) return;
|
|
320
|
+
for (const ref of refs) {
|
|
321
|
+
const candidates = rows.filter((value) => {
|
|
322
|
+
if (!value || typeof value !== "object") return false;
|
|
323
|
+
const row2 = value;
|
|
324
|
+
if ("account_id" in ref) return row2.account_id === ref.account_id;
|
|
325
|
+
return row2.brand?.domain === ref.brand.domain && row2.operator === ref.operator && (!ref.brand.brand_id || row2.brand.brand_id === ref.brand.brand_id) && sameCountrySet(ref.brand.countries, row2.brand.countries) && (!ref.operator_unit || row2.operator_unit?.id === ref.operator_unit.id) && (!ref.currency || row2.currency === ref.currency) && (!ref.timezone || row2.timezone === ref.timezone) && (row2.sandbox === void 0 || row2.sandbox === true === (ref.sandbox === true));
|
|
326
|
+
});
|
|
327
|
+
if (candidates.length !== 1) continue;
|
|
328
|
+
let row = candidates[0];
|
|
329
|
+
if (row.action === "failed" || typeof row.status !== "string") continue;
|
|
330
|
+
const prior = await this.get(ref);
|
|
331
|
+
if (scope !== this.scope()) return;
|
|
332
|
+
let rowEpoch = observationEpoch;
|
|
333
|
+
let repairedBinding = false;
|
|
334
|
+
if (!this.observationIsCurrent(row.account_id, rowEpoch, scope)) {
|
|
335
|
+
if (!row.account_id || prior?.account_id && prior.account_id !== row.account_id) continue;
|
|
336
|
+
const captured = this.captureObservation();
|
|
337
|
+
const authoritative = await this.get({ account_id: row.account_id });
|
|
338
|
+
if (scope !== this.scope()) return;
|
|
339
|
+
row = {
|
|
340
|
+
...row,
|
|
341
|
+
status: captured === this.captureObservation() ? authoritative?.status ?? "unknown" : "unknown"
|
|
342
|
+
};
|
|
343
|
+
rowEpoch = this.captureObservation();
|
|
344
|
+
repairedBinding = true;
|
|
345
|
+
}
|
|
346
|
+
const isCurrent = () => scope === this.scope() && this.observationIsCurrent(row.account_id, rowEpoch, scope);
|
|
347
|
+
await this.record(ref, row, scope, false, isCurrent);
|
|
348
|
+
if (!isCurrent() || repairedBinding) continue;
|
|
349
|
+
for (const id of [row.account_id])
|
|
350
|
+
if (id) {
|
|
351
|
+
const key = this.key({ account_id: id }, scope);
|
|
352
|
+
this.repairs.delete(key);
|
|
353
|
+
this.invalidatedHandles.delete(key);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async reconcileId(accountId, status, scope, isCurrent = () => true) {
|
|
359
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
360
|
+
const idRef = { account_id: accountId };
|
|
361
|
+
const indexed = await this.get(idRef);
|
|
362
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
363
|
+
const aliases = new Map((indexed?.aliases ?? []).map((ref) => [accountReferenceKey(ref), ref]));
|
|
364
|
+
for (const [key, entry] of this.entries) {
|
|
365
|
+
if (key === this.key(entry.account, scope) && entry.account_id === accountId && !("account_id" in entry.account)) {
|
|
366
|
+
aliases.set(accountReferenceKey(entry.account), entry.account);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const ref of aliases.values()) {
|
|
370
|
+
const prior = await this.get(ref);
|
|
371
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
372
|
+
if (!prior || prior.account_id !== accountId) {
|
|
373
|
+
aliases.delete(accountReferenceKey(ref));
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
await this.write({ ...prior, account: ref, account_id: accountId, status }, scope, isCurrent);
|
|
377
|
+
}
|
|
378
|
+
if (scope !== this.scope() || !isCurrent()) return;
|
|
379
|
+
if (indexed || aliases.size)
|
|
380
|
+
await this.write(
|
|
381
|
+
{ ...indexed, account: idRef, account_id: accountId, status, aliases: [...aliases.values()] },
|
|
382
|
+
scope,
|
|
383
|
+
isCurrent
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
async observeList(rows, queriedAccountId, observationEpoch) {
|
|
387
|
+
const scope = this.scope();
|
|
388
|
+
return this.observeListAtScope(rows, queriedAccountId, scope, void 0, observationEpoch);
|
|
389
|
+
}
|
|
390
|
+
async observeListAtScope(rows, queriedAccountId, scope, isCurrent, observationEpoch) {
|
|
391
|
+
return this.enqueue(async () => {
|
|
392
|
+
if (scope !== this.scope() || isCurrent && !isCurrent()) return;
|
|
393
|
+
if (!this.observationIsCurrent(queriedAccountId, observationEpoch, scope)) return;
|
|
394
|
+
let foundQuery = false;
|
|
395
|
+
for (const value of rows) {
|
|
396
|
+
if (!value || typeof value !== "object") continue;
|
|
397
|
+
const row = value;
|
|
398
|
+
if (typeof row.account_id !== "string" || typeof row.status !== "string") continue;
|
|
399
|
+
if (!this.observationIsCurrent(row.account_id, observationEpoch, scope)) continue;
|
|
400
|
+
const current = () => scope === this.scope() && (!isCurrent || isCurrent()) && this.observationIsCurrent(row.account_id, observationEpoch, scope);
|
|
401
|
+
if (row.account_id === queriedAccountId) foundQuery = true;
|
|
402
|
+
await this.reconcileId(row.account_id, row.status, scope, current);
|
|
403
|
+
await this.record({ account_id: row.account_id }, row, scope, true, current);
|
|
404
|
+
if (row.brand?.domain && row.operator) {
|
|
405
|
+
const ref = {
|
|
406
|
+
brand: row.brand,
|
|
407
|
+
operator: row.operator,
|
|
408
|
+
...row.operator_unit && { operator_unit: row.operator_unit },
|
|
409
|
+
...row.currency && { currency: row.currency },
|
|
410
|
+
...row.timezone && { timezone: row.timezone },
|
|
411
|
+
...row.sandbox !== void 0 && { sandbox: row.sandbox }
|
|
412
|
+
};
|
|
413
|
+
const prior = await this.get(ref);
|
|
414
|
+
if (scope !== this.scope()) return;
|
|
415
|
+
if (!prior?.account_id || prior.account_id === row.account_id)
|
|
416
|
+
await this.record(ref, row, scope, true, current);
|
|
417
|
+
}
|
|
418
|
+
if (!isCurrent && current()) {
|
|
419
|
+
const key = this.key({ account_id: row.account_id }, scope);
|
|
420
|
+
this.repairs.delete(key);
|
|
421
|
+
this.invalidatedHandles.delete(key);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (queriedAccountId && !foundQuery) {
|
|
425
|
+
const current = () => scope === this.scope() && (!isCurrent || isCurrent()) && this.observationIsCurrent(queriedAccountId, observationEpoch, scope);
|
|
426
|
+
await this.reconcileId(queriedAccountId, "unknown", scope, current);
|
|
427
|
+
if (!isCurrent && current()) {
|
|
428
|
+
const key = this.key({ account_id: queriedAccountId }, scope);
|
|
429
|
+
this.repairs.delete(key);
|
|
430
|
+
this.invalidatedHandles.delete(key);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/** After verifying account.status_changed, repair aliases by handle from the authoritative seller. */
|
|
436
|
+
async applyStatusChange(notification, options) {
|
|
437
|
+
return this.repairStatus(notification.account_id, options, true);
|
|
438
|
+
}
|
|
439
|
+
async repairStatus(accountId, options, invalidate) {
|
|
440
|
+
throwIfAborted(options?.signal);
|
|
441
|
+
const key = this.key({ account_id: accountId });
|
|
442
|
+
let operation = !invalidate ? this.repairWaiters.get(key) : void 0;
|
|
443
|
+
if (!operation) {
|
|
444
|
+
operation = this.runRepair(accountId, { ...options, signal: void 0, timeout: void 0 }, invalidate);
|
|
445
|
+
this.repairWaiters.set(key, operation);
|
|
446
|
+
const cleanup = () => {
|
|
447
|
+
if (this.repairWaiters.get(key) === operation) this.repairWaiters.delete(key);
|
|
448
|
+
};
|
|
449
|
+
void operation.then(cleanup, cleanup);
|
|
450
|
+
}
|
|
451
|
+
const wait = (promise) => withTaskDeadline(options, (taskOptions) => withAbortSignal([taskOptions.signal], void 0, () => promise));
|
|
452
|
+
await wait(operation);
|
|
453
|
+
let newer = this.repairWaiters.get(key);
|
|
454
|
+
while (newer && newer !== operation) {
|
|
455
|
+
operation = newer;
|
|
456
|
+
await wait(operation);
|
|
457
|
+
newer = this.repairWaiters.get(key);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async runRepair(accountId, options, invalidate) {
|
|
461
|
+
if (!this.repair) throw new Error("Account registry requires a list_accounts repair callback.");
|
|
462
|
+
const scope = this.scope();
|
|
463
|
+
const key = this.key({ account_id: accountId }, scope);
|
|
464
|
+
const token = {};
|
|
465
|
+
this.statusEpochs.delete(key);
|
|
466
|
+
this.statusEpochs.set(key, ++this.observationEpoch);
|
|
467
|
+
if (this.statusEpochs.size > this.maxEntries) {
|
|
468
|
+
const oldestKey = this.statusEpochs.keys().next().value;
|
|
469
|
+
this.observationFloor = this.statusEpochs.get(oldestKey);
|
|
470
|
+
this.statusEpochs.delete(oldestKey);
|
|
471
|
+
}
|
|
472
|
+
this.repairs.set(key, token);
|
|
473
|
+
if (invalidate) this.invalidatedHandles.add(key);
|
|
474
|
+
let invalidationPersisted = !invalidate;
|
|
475
|
+
try {
|
|
476
|
+
if (invalidate) {
|
|
477
|
+
await this.enqueue(() => this.reconcileId(accountId, "unknown", scope, () => this.repairs.get(key) === token));
|
|
478
|
+
invalidationPersisted = true;
|
|
479
|
+
}
|
|
480
|
+
const rows = (await this.repair(accountId, options)).filter(
|
|
481
|
+
(row) => row !== null && typeof row === "object" && row.account_id === accountId
|
|
482
|
+
);
|
|
483
|
+
await this.observeListAtScope(rows, accountId, scope, () => this.repairs.get(key) === token);
|
|
484
|
+
} finally {
|
|
485
|
+
if (this.repairs.get(key) === token) {
|
|
486
|
+
this.repairs.delete(key);
|
|
487
|
+
if (invalidationPersisted) this.invalidatedHandles.delete(key);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
export {
|
|
493
|
+
BuyerAccountRegistry
|
|
494
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TaskResult } from './ConversationTypes.mjs';
|
|
2
|
+
export interface ProductCacheOptions {
|
|
3
|
+
maxEntries?: number;
|
|
4
|
+
publicTtl?: number;
|
|
5
|
+
accountTtl?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Cache response scope and both feed tokens together. Account overlays never share entries. */
|
|
8
|
+
export declare class ProductCache {
|
|
9
|
+
private options;
|
|
10
|
+
private entries;
|
|
11
|
+
constructor(options?: ProductCacheOptions);
|
|
12
|
+
private key;
|
|
13
|
+
read<T>(scope: string, params: Record<string, unknown>, allowExpired?: boolean): TaskResult<T> | undefined;
|
|
14
|
+
conditionalParams(scope: string, params: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>): TaskResult<T>;
|
|
16
|
+
clear(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function createProductCache(options?: ProductCacheOptions): ProductCache;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TaskResult } from './ConversationTypes';
|
|
2
|
+
export interface ProductCacheOptions {
|
|
3
|
+
maxEntries?: number;
|
|
4
|
+
publicTtl?: number;
|
|
5
|
+
accountTtl?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Cache response scope and both feed tokens together. Account overlays never share entries. */
|
|
8
|
+
export declare class ProductCache {
|
|
9
|
+
private options;
|
|
10
|
+
private entries;
|
|
11
|
+
constructor(options?: ProductCacheOptions);
|
|
12
|
+
private key;
|
|
13
|
+
read<T>(scope: string, params: Record<string, unknown>, allowExpired?: boolean): TaskResult<T> | undefined;
|
|
14
|
+
conditionalParams(scope: string, params: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>): TaskResult<T>;
|
|
16
|
+
clear(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function createProductCache(options?: ProductCacheOptions): ProductCache;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var product_cache_exports = {};
|
|
20
|
+
__export(product_cache_exports, {
|
|
21
|
+
ProductCache: () => ProductCache,
|
|
22
|
+
createProductCache: () => createProductCache
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(product_cache_exports);
|
|
25
|
+
var import_account_key = require('./account-key.js');
|
|
26
|
+
function stable(value) {
|
|
27
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
28
|
+
if (value && typeof value === "object")
|
|
29
|
+
return Object.fromEntries(
|
|
30
|
+
Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, stable(v)])
|
|
31
|
+
);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
class ProductCache {
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
if (options.maxEntries !== void 0 && (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1))
|
|
38
|
+
throw new Error("Product cache maxEntries must be a positive integer.");
|
|
39
|
+
for (const ttl of [options.publicTtl, options.accountTtl]) {
|
|
40
|
+
if (ttl !== void 0 && (!Number.isFinite(ttl) || ttl < 0))
|
|
41
|
+
throw new Error("Product cache TTLs must be nonnegative milliseconds.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
options;
|
|
45
|
+
entries = /* @__PURE__ */ new Map();
|
|
46
|
+
key(sellerScope, params) {
|
|
47
|
+
const { account, if_wholesale_feed_version: _feed, if_pricing_version: _price, ...query } = params;
|
|
48
|
+
if (account !== void 0) {
|
|
49
|
+
if (!account || typeof account !== "object") return void 0;
|
|
50
|
+
const ref = account;
|
|
51
|
+
if ("account_id" in ref) {
|
|
52
|
+
if (typeof ref.account_id !== "string" || !ref.account_id) return void 0;
|
|
53
|
+
} else if (!ref.brand || typeof ref.brand.domain !== "string" || typeof ref.operator !== "string") {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify([
|
|
58
|
+
sellerScope,
|
|
59
|
+
account ? (0, import_account_key.accountReferenceKey)(account) : "public",
|
|
60
|
+
stable(query)
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
read(scope, params, allowExpired = false) {
|
|
64
|
+
const key = this.key(scope, params);
|
|
65
|
+
if (!key) return void 0;
|
|
66
|
+
const entry = this.entries.get(key);
|
|
67
|
+
if (!entry) return void 0;
|
|
68
|
+
const ttl = entry.scope === "public" ? this.options.publicTtl ?? 6e4 : this.options.accountTtl ?? 3e4;
|
|
69
|
+
if (!allowExpired && Date.now() - entry.storedAt >= ttl) return void 0;
|
|
70
|
+
return {
|
|
71
|
+
...entry.result,
|
|
72
|
+
data: structuredClone(entry.result.data),
|
|
73
|
+
metadata: structuredClone(entry.result.metadata),
|
|
74
|
+
...entry.result.debug_logs && { debug_logs: structuredClone(entry.result.debug_logs) }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
conditionalParams(scope, params) {
|
|
78
|
+
if (params.if_wholesale_feed_version !== void 0 || params.if_pricing_version !== void 0) return params;
|
|
79
|
+
const result = this.read(scope, params, true);
|
|
80
|
+
const data = result?.data;
|
|
81
|
+
if (typeof data?.wholesale_feed_version !== "string") return params;
|
|
82
|
+
return {
|
|
83
|
+
...params,
|
|
84
|
+
if_wholesale_feed_version: data.wholesale_feed_version,
|
|
85
|
+
...typeof data.pricing_version === "string" && { if_pricing_version: data.pricing_version }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
write(scope, params, result, conditionalSnapshot) {
|
|
89
|
+
const key = this.key(scope, params);
|
|
90
|
+
if (!key) return result;
|
|
91
|
+
if (!result.success || result.status !== "completed" || !result.data || typeof result.data !== "object")
|
|
92
|
+
return result;
|
|
93
|
+
const data = result.data;
|
|
94
|
+
if (data.cache_scope !== "public" && data.cache_scope !== "account") {
|
|
95
|
+
this.entries.delete(key);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
if (data.cache_scope === "account" && !params.account) {
|
|
99
|
+
this.entries.delete(key);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
let completed = result;
|
|
103
|
+
if (data.unchanged === true) {
|
|
104
|
+
if (typeof data.wholesale_feed_version !== "string") return result;
|
|
105
|
+
const old = conditionalSnapshot?.data ? { result: conditionalSnapshot, scope: conditionalSnapshot.data.cache_scope } : this.entries.get(key);
|
|
106
|
+
const oldData = old?.result.data;
|
|
107
|
+
if (!old || old.scope !== data.cache_scope || !oldData || data.wholesale_feed_version !== oldData.wholesale_feed_version || data.pricing_version !== void 0 && data.pricing_version !== oldData.pricing_version)
|
|
108
|
+
return result;
|
|
109
|
+
if (params.if_wholesale_feed_version !== void 0 || params.if_pricing_version !== void 0) return result;
|
|
110
|
+
completed = { ...result, data: structuredClone(oldData) };
|
|
111
|
+
} else if (!Array.isArray(data.products)) return result;
|
|
112
|
+
if (!this.entries.has(key) && this.entries.size >= (this.options.maxEntries ?? 1e3))
|
|
113
|
+
this.entries.delete(this.entries.keys().next().value);
|
|
114
|
+
this.entries.set(key, {
|
|
115
|
+
result: {
|
|
116
|
+
...completed,
|
|
117
|
+
data: structuredClone(completed.data),
|
|
118
|
+
metadata: structuredClone(completed.metadata),
|
|
119
|
+
...completed.debug_logs && { debug_logs: structuredClone(completed.debug_logs) }
|
|
120
|
+
},
|
|
121
|
+
storedAt: Date.now(),
|
|
122
|
+
scope: data.cache_scope
|
|
123
|
+
});
|
|
124
|
+
return completed;
|
|
125
|
+
}
|
|
126
|
+
clear() {
|
|
127
|
+
this.entries.clear();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function createProductCache(options = {}) {
|
|
131
|
+
return new ProductCache(options);
|
|
132
|
+
}
|
|
133
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
+
0 && (module.exports = {
|
|
135
|
+
ProductCache,
|
|
136
|
+
createProductCache
|
|
137
|
+
});
|