@salesforce/mrt-utilities 0.2.1 → 0.3.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/cjs/data-store/production.d.ts +23 -0
- package/dist/cjs/data-store/production.js +128 -5
- package/dist/cjs/data-store/production.js.map +1 -1
- package/dist/esm/data-store/production.d.ts +23 -0
- package/dist/esm/data-store/production.js +127 -5
- package/dist/esm/data-store/production.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
1
2
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
2
3
|
export { DataStoreNotFoundError, DataStoreServiceError, DataStoreUnavailableError } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create the DynamoDB client used by the data store, configured for resilient reads under
|
|
6
|
+
* throttling: adaptive retries, bounded attempts, and hard per-attempt timeouts.
|
|
7
|
+
*
|
|
8
|
+
* @returns A configured DynamoDB client
|
|
9
|
+
*/
|
|
10
|
+
export declare function createDalDynamoDBClient(): DynamoDBClient;
|
|
3
11
|
/**
|
|
4
12
|
* A class for reading entries from the data store.
|
|
5
13
|
*
|
|
@@ -14,6 +22,8 @@ export declare class DataStore {
|
|
|
14
22
|
static _testDocumentClient: DynamoDBDocumentClient | null;
|
|
15
23
|
/** @internal Test hook: inject logMRTError for unit tests */
|
|
16
24
|
static _testLogMRTError: ((namespace: string, err: unknown, context?: Record<string, unknown>) => void) | null;
|
|
25
|
+
/** @internal Test hook: inject a deterministic random source (returns [0, 1)) for unit tests */
|
|
26
|
+
static _testRandom: (() => number) | null;
|
|
17
27
|
private constructor();
|
|
18
28
|
/**
|
|
19
29
|
* Get or create a DynamoDB document client (for abstraction of attribute values).
|
|
@@ -23,6 +33,19 @@ export declare class DataStore {
|
|
|
23
33
|
* @throws {DataStoreUnavailableError} The data store is unavailable
|
|
24
34
|
*/
|
|
25
35
|
private getClient;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the DynamoDB partition key for a read, applying shard selection.
|
|
38
|
+
*
|
|
39
|
+
* Reads the shard count from `MRT_NUM_SHARDS` (default 1) and picks a random
|
|
40
|
+
* shard `i` in `[0, N)`. Shard 0 is the legacy unsuffixed partition key, so
|
|
41
|
+
* when `MRT_NUM_SHARDS` is unset or 1 this is identical to today's behavior.
|
|
42
|
+
* There is no runtime fallback: writers fan out to every shard, so every shard
|
|
43
|
+
* a reader can pick is guaranteed to exist.
|
|
44
|
+
*
|
|
45
|
+
* @private
|
|
46
|
+
* @returns The `projectEnvironment` partition key value to read
|
|
47
|
+
*/
|
|
48
|
+
private resolveShardPartitionKey;
|
|
26
49
|
/**
|
|
27
50
|
* Get or create the singleton DataStore instance.
|
|
28
51
|
*
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.DataStore = exports.DataStoreUnavailableError = exports.DataStoreServiceError = exports.DataStoreNotFoundError = void 0;
|
|
9
|
+
exports.createDalDynamoDBClient = createDalDynamoDBClient;
|
|
9
10
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
10
11
|
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
11
12
|
const errors_js_1 = require("./errors.js");
|
|
@@ -14,6 +15,101 @@ var errors_js_2 = require("./errors.js");
|
|
|
14
15
|
Object.defineProperty(exports, "DataStoreNotFoundError", { enumerable: true, get: function () { return errors_js_2.DataStoreNotFoundError; } });
|
|
15
16
|
Object.defineProperty(exports, "DataStoreServiceError", { enumerable: true, get: function () { return errors_js_2.DataStoreServiceError; } });
|
|
16
17
|
Object.defineProperty(exports, "DataStoreUnavailableError", { enumerable: true, get: function () { return errors_js_2.DataStoreUnavailableError; } });
|
|
18
|
+
/**
|
|
19
|
+
* Retry mode for the data store DynamoDB client.
|
|
20
|
+
*
|
|
21
|
+
* `'adaptive'` adds a client-side rate limiter that backs off proactively when the table
|
|
22
|
+
* signals throttling, instead of retrying blindly into an already-saturated table. The
|
|
23
|
+
* rate limiter keeps state on the client instance, so it only helps across invocations on
|
|
24
|
+
* a warm container — the memoized client preserves that state.
|
|
25
|
+
*/
|
|
26
|
+
const DAL_RETRY_MODE = 'adaptive';
|
|
27
|
+
/**
|
|
28
|
+
* Maximum number of attempts (initial request + retries) per data store request.
|
|
29
|
+
*
|
|
30
|
+
* Bounds retry fan-out under sustained throttling. Chosen together with
|
|
31
|
+
* {@link DAL_REQUEST_TIMEOUT_MS} so that `DAL_MAX_ATTEMPTS × DAL_REQUEST_TIMEOUT_MS` stays
|
|
32
|
+
* comfortably under the surrounding request/function timeout.
|
|
33
|
+
*/
|
|
34
|
+
const DAL_MAX_ATTEMPTS = 2;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum time (ms) to wait for a connection to be established per attempt.
|
|
37
|
+
*
|
|
38
|
+
* A hard per-attempt ceiling so a slow/hung connection cannot consume the whole budget.
|
|
39
|
+
* The client is memoized on a warm container and reuses keep-alive connections, so most
|
|
40
|
+
* attempts do not open a new connection; a fresh connect exceeding this is already
|
|
41
|
+
* abnormal.
|
|
42
|
+
*/
|
|
43
|
+
const DAL_CONNECTION_TIMEOUT_MS = 300;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum time (ms) to wait for a response per attempt.
|
|
46
|
+
*
|
|
47
|
+
* A hard per-attempt ceiling. A single-key DynamoDB read is typically single-digit ms, so
|
|
48
|
+
* this leaves a large multiple of headroom over p99 while still failing fast on a genuine
|
|
49
|
+
* hang. See {@link DAL_MAX_ATTEMPTS} for the timeout/attempt invariant relative to the
|
|
50
|
+
* surrounding function timeout — with these defaults the worst-case timeout path is
|
|
51
|
+
* roughly `DAL_MAX_ATTEMPTS × DAL_REQUEST_TIMEOUT_MS` (≈1s) plus adaptive-retry backoff
|
|
52
|
+
* between attempts.
|
|
53
|
+
*/
|
|
54
|
+
const DAL_REQUEST_TIMEOUT_MS = 500;
|
|
55
|
+
/**
|
|
56
|
+
* Error names that indicate the request was throttled.
|
|
57
|
+
*
|
|
58
|
+
* Mirrors the AWS SDK's own throttling classification so the telemetry flag agrees with
|
|
59
|
+
* what actually drove adaptive backoff, rather than a hand-maintained subset.
|
|
60
|
+
*/
|
|
61
|
+
const THROTTLING_ERROR_NAMES = new Set([
|
|
62
|
+
'ThrottlingException',
|
|
63
|
+
'ProvisionedThroughputExceededException',
|
|
64
|
+
'RequestLimitExceeded',
|
|
65
|
+
'RequestThrottled',
|
|
66
|
+
'RequestThrottledException',
|
|
67
|
+
'TooManyRequestsException',
|
|
68
|
+
'ThrottledException',
|
|
69
|
+
'Throttling',
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Whether an error represents a throttling response.
|
|
73
|
+
*
|
|
74
|
+
* Checks the SDK's retryable-throttling trait and an HTTP 429 status in addition to the
|
|
75
|
+
* known throttling error names, so throttles surfaced only via metadata are still flagged.
|
|
76
|
+
*/
|
|
77
|
+
function isThrottlingError(error) {
|
|
78
|
+
if (typeof error !== 'object' || error === null) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const candidate = error;
|
|
82
|
+
if (candidate.$retryable?.throttling === true) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (candidate.$metadata?.httpStatusCode === 429) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return typeof candidate.name === 'string' && THROTTLING_ERROR_NAMES.has(candidate.name);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create the DynamoDB client used by the data store, configured for resilient reads under
|
|
92
|
+
* throttling: adaptive retries, bounded attempts, and hard per-attempt timeouts.
|
|
93
|
+
*
|
|
94
|
+
* @returns A configured DynamoDB client
|
|
95
|
+
*/
|
|
96
|
+
function createDalDynamoDBClient() {
|
|
97
|
+
return new client_dynamodb_1.DynamoDBClient({
|
|
98
|
+
region: process.env.AWS_REGION,
|
|
99
|
+
retryMode: DAL_RETRY_MODE,
|
|
100
|
+
maxAttempts: DAL_MAX_ATTEMPTS,
|
|
101
|
+
// Passing a plain object lets the SDK construct its default NodeHttpHandler with these
|
|
102
|
+
// bounds — no direct dependency on the handler package required. `throwOnRequestTimeout`
|
|
103
|
+
// is required for `requestTimeout` to actually abort a hung request: without it the
|
|
104
|
+
// handler only logs a warning and lets the request run on. Safe here because a DAL read
|
|
105
|
+
// is a simple request/response, not a long-lived stream.
|
|
106
|
+
requestHandler: {
|
|
107
|
+
connectionTimeout: DAL_CONNECTION_TIMEOUT_MS,
|
|
108
|
+
requestTimeout: DAL_REQUEST_TIMEOUT_MS,
|
|
109
|
+
throwOnRequestTimeout: true,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
17
113
|
/**
|
|
18
114
|
* A class for reading entries from the data store.
|
|
19
115
|
*
|
|
@@ -28,6 +124,8 @@ class DataStore {
|
|
|
28
124
|
static _testDocumentClient = null;
|
|
29
125
|
/** @internal Test hook: inject logMRTError for unit tests */
|
|
30
126
|
static _testLogMRTError = null;
|
|
127
|
+
/** @internal Test hook: inject a deterministic random source (returns [0, 1)) for unit tests */
|
|
128
|
+
static _testRandom = null;
|
|
31
129
|
constructor() {
|
|
32
130
|
// Private constructor for singleton; use DataStore.getDataStore() instead.
|
|
33
131
|
}
|
|
@@ -48,12 +146,34 @@ class DataStore {
|
|
|
48
146
|
}
|
|
49
147
|
if (!this._ddb) {
|
|
50
148
|
this._tableName = `DataAccessLayer-${process.env.AWS_REGION}`;
|
|
51
|
-
this._ddb = lib_dynamodb_1.DynamoDBDocumentClient.from(
|
|
52
|
-
region: process.env.AWS_REGION,
|
|
53
|
-
}));
|
|
149
|
+
this._ddb = lib_dynamodb_1.DynamoDBDocumentClient.from(createDalDynamoDBClient());
|
|
54
150
|
}
|
|
55
151
|
return this._ddb;
|
|
56
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve the DynamoDB partition key for a read, applying shard selection.
|
|
155
|
+
*
|
|
156
|
+
* Reads the shard count from `MRT_NUM_SHARDS` (default 1) and picks a random
|
|
157
|
+
* shard `i` in `[0, N)`. Shard 0 is the legacy unsuffixed partition key, so
|
|
158
|
+
* when `MRT_NUM_SHARDS` is unset or 1 this is identical to today's behavior.
|
|
159
|
+
* There is no runtime fallback: writers fan out to every shard, so every shard
|
|
160
|
+
* a reader can pick is guaranteed to exist.
|
|
161
|
+
*
|
|
162
|
+
* @private
|
|
163
|
+
* @returns The `projectEnvironment` partition key value to read
|
|
164
|
+
*/
|
|
165
|
+
resolveShardPartitionKey() {
|
|
166
|
+
const base = `${process.env.MOBIFY_PROPERTY_ID} ${process.env.DEPLOY_TARGET}`;
|
|
167
|
+
const parsed = Number(process.env.MRT_NUM_SHARDS);
|
|
168
|
+
const numShards = Number.isInteger(parsed) && parsed > 1 ? parsed : 1;
|
|
169
|
+
if (numShards === 1) {
|
|
170
|
+
return base;
|
|
171
|
+
}
|
|
172
|
+
const random = DataStore._testRandom ?? Math.random;
|
|
173
|
+
const shard = Math.floor(random() * numShards);
|
|
174
|
+
// shard 0 is the legacy unsuffixed partition; shards 1..N-1 carry a suffix.
|
|
175
|
+
return shard === 0 ? base : `${base} ${shard}`;
|
|
176
|
+
}
|
|
57
177
|
/**
|
|
58
178
|
* Get or create the singleton DataStore instance.
|
|
59
179
|
*
|
|
@@ -87,19 +207,22 @@ class DataStore {
|
|
|
87
207
|
throw new errors_js_1.DataStoreUnavailableError('The data store is unavailable.');
|
|
88
208
|
}
|
|
89
209
|
const ddb = this.getClient();
|
|
210
|
+
const projectEnvironment = this.resolveShardPartitionKey();
|
|
90
211
|
let response;
|
|
91
212
|
try {
|
|
92
213
|
response = await ddb.send(new lib_dynamodb_1.GetCommand({
|
|
93
214
|
TableName: this._tableName,
|
|
94
215
|
Key: {
|
|
95
|
-
projectEnvironment
|
|
216
|
+
projectEnvironment,
|
|
96
217
|
key,
|
|
97
218
|
},
|
|
98
219
|
}));
|
|
99
220
|
}
|
|
100
221
|
catch (error) {
|
|
222
|
+
const errorName = error instanceof Error ? error.name : undefined;
|
|
223
|
+
const throttled = isThrottlingError(error);
|
|
101
224
|
const logFn = DataStore._testLogMRTError ?? utils_js_1.logMRTError;
|
|
102
|
-
logFn('data_store', error, { key, tableName: this._tableName });
|
|
225
|
+
logFn('data_store', error, { key, projectEnvironment, tableName: this._tableName, errorName, throttled });
|
|
103
226
|
throw new errors_js_1.DataStoreServiceError('Data store request failed.');
|
|
104
227
|
}
|
|
105
228
|
if (!response.Item?.value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"production.js","sourceRoot":"","sources":["../../../src/data-store/production.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;
|
|
1
|
+
{"version":3,"file":"production.js","sourceRoot":"","sources":["../../../src/data-store/production.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
1
2
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
2
3
|
export { DataStoreNotFoundError, DataStoreServiceError, DataStoreUnavailableError } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create the DynamoDB client used by the data store, configured for resilient reads under
|
|
6
|
+
* throttling: adaptive retries, bounded attempts, and hard per-attempt timeouts.
|
|
7
|
+
*
|
|
8
|
+
* @returns A configured DynamoDB client
|
|
9
|
+
*/
|
|
10
|
+
export declare function createDalDynamoDBClient(): DynamoDBClient;
|
|
3
11
|
/**
|
|
4
12
|
* A class for reading entries from the data store.
|
|
5
13
|
*
|
|
@@ -14,6 +22,8 @@ export declare class DataStore {
|
|
|
14
22
|
static _testDocumentClient: DynamoDBDocumentClient | null;
|
|
15
23
|
/** @internal Test hook: inject logMRTError for unit tests */
|
|
16
24
|
static _testLogMRTError: ((namespace: string, err: unknown, context?: Record<string, unknown>) => void) | null;
|
|
25
|
+
/** @internal Test hook: inject a deterministic random source (returns [0, 1)) for unit tests */
|
|
26
|
+
static _testRandom: (() => number) | null;
|
|
17
27
|
private constructor();
|
|
18
28
|
/**
|
|
19
29
|
* Get or create a DynamoDB document client (for abstraction of attribute values).
|
|
@@ -23,6 +33,19 @@ export declare class DataStore {
|
|
|
23
33
|
* @throws {DataStoreUnavailableError} The data store is unavailable
|
|
24
34
|
*/
|
|
25
35
|
private getClient;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the DynamoDB partition key for a read, applying shard selection.
|
|
38
|
+
*
|
|
39
|
+
* Reads the shard count from `MRT_NUM_SHARDS` (default 1) and picks a random
|
|
40
|
+
* shard `i` in `[0, N)`. Shard 0 is the legacy unsuffixed partition key, so
|
|
41
|
+
* when `MRT_NUM_SHARDS` is unset or 1 this is identical to today's behavior.
|
|
42
|
+
* There is no runtime fallback: writers fan out to every shard, so every shard
|
|
43
|
+
* a reader can pick is guaranteed to exist.
|
|
44
|
+
*
|
|
45
|
+
* @private
|
|
46
|
+
* @returns The `projectEnvironment` partition key value to read
|
|
47
|
+
*/
|
|
48
|
+
private resolveShardPartitionKey;
|
|
26
49
|
/**
|
|
27
50
|
* Get or create the singleton DataStore instance.
|
|
28
51
|
*
|
|
@@ -8,6 +8,101 @@ import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
|
|
|
8
8
|
import { DataStoreNotFoundError, DataStoreServiceError, DataStoreUnavailableError } from './errors.js';
|
|
9
9
|
import { logMRTError } from '../utils/utils.js';
|
|
10
10
|
export { DataStoreNotFoundError, DataStoreServiceError, DataStoreUnavailableError } from './errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Retry mode for the data store DynamoDB client.
|
|
13
|
+
*
|
|
14
|
+
* `'adaptive'` adds a client-side rate limiter that backs off proactively when the table
|
|
15
|
+
* signals throttling, instead of retrying blindly into an already-saturated table. The
|
|
16
|
+
* rate limiter keeps state on the client instance, so it only helps across invocations on
|
|
17
|
+
* a warm container — the memoized client preserves that state.
|
|
18
|
+
*/
|
|
19
|
+
const DAL_RETRY_MODE = 'adaptive';
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of attempts (initial request + retries) per data store request.
|
|
22
|
+
*
|
|
23
|
+
* Bounds retry fan-out under sustained throttling. Chosen together with
|
|
24
|
+
* {@link DAL_REQUEST_TIMEOUT_MS} so that `DAL_MAX_ATTEMPTS × DAL_REQUEST_TIMEOUT_MS` stays
|
|
25
|
+
* comfortably under the surrounding request/function timeout.
|
|
26
|
+
*/
|
|
27
|
+
const DAL_MAX_ATTEMPTS = 2;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum time (ms) to wait for a connection to be established per attempt.
|
|
30
|
+
*
|
|
31
|
+
* A hard per-attempt ceiling so a slow/hung connection cannot consume the whole budget.
|
|
32
|
+
* The client is memoized on a warm container and reuses keep-alive connections, so most
|
|
33
|
+
* attempts do not open a new connection; a fresh connect exceeding this is already
|
|
34
|
+
* abnormal.
|
|
35
|
+
*/
|
|
36
|
+
const DAL_CONNECTION_TIMEOUT_MS = 300;
|
|
37
|
+
/**
|
|
38
|
+
* Maximum time (ms) to wait for a response per attempt.
|
|
39
|
+
*
|
|
40
|
+
* A hard per-attempt ceiling. A single-key DynamoDB read is typically single-digit ms, so
|
|
41
|
+
* this leaves a large multiple of headroom over p99 while still failing fast on a genuine
|
|
42
|
+
* hang. See {@link DAL_MAX_ATTEMPTS} for the timeout/attempt invariant relative to the
|
|
43
|
+
* surrounding function timeout — with these defaults the worst-case timeout path is
|
|
44
|
+
* roughly `DAL_MAX_ATTEMPTS × DAL_REQUEST_TIMEOUT_MS` (≈1s) plus adaptive-retry backoff
|
|
45
|
+
* between attempts.
|
|
46
|
+
*/
|
|
47
|
+
const DAL_REQUEST_TIMEOUT_MS = 500;
|
|
48
|
+
/**
|
|
49
|
+
* Error names that indicate the request was throttled.
|
|
50
|
+
*
|
|
51
|
+
* Mirrors the AWS SDK's own throttling classification so the telemetry flag agrees with
|
|
52
|
+
* what actually drove adaptive backoff, rather than a hand-maintained subset.
|
|
53
|
+
*/
|
|
54
|
+
const THROTTLING_ERROR_NAMES = new Set([
|
|
55
|
+
'ThrottlingException',
|
|
56
|
+
'ProvisionedThroughputExceededException',
|
|
57
|
+
'RequestLimitExceeded',
|
|
58
|
+
'RequestThrottled',
|
|
59
|
+
'RequestThrottledException',
|
|
60
|
+
'TooManyRequestsException',
|
|
61
|
+
'ThrottledException',
|
|
62
|
+
'Throttling',
|
|
63
|
+
]);
|
|
64
|
+
/**
|
|
65
|
+
* Whether an error represents a throttling response.
|
|
66
|
+
*
|
|
67
|
+
* Checks the SDK's retryable-throttling trait and an HTTP 429 status in addition to the
|
|
68
|
+
* known throttling error names, so throttles surfaced only via metadata are still flagged.
|
|
69
|
+
*/
|
|
70
|
+
function isThrottlingError(error) {
|
|
71
|
+
if (typeof error !== 'object' || error === null) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const candidate = error;
|
|
75
|
+
if (candidate.$retryable?.throttling === true) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (candidate.$metadata?.httpStatusCode === 429) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return typeof candidate.name === 'string' && THROTTLING_ERROR_NAMES.has(candidate.name);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create the DynamoDB client used by the data store, configured for resilient reads under
|
|
85
|
+
* throttling: adaptive retries, bounded attempts, and hard per-attempt timeouts.
|
|
86
|
+
*
|
|
87
|
+
* @returns A configured DynamoDB client
|
|
88
|
+
*/
|
|
89
|
+
export function createDalDynamoDBClient() {
|
|
90
|
+
return new DynamoDBClient({
|
|
91
|
+
region: process.env.AWS_REGION,
|
|
92
|
+
retryMode: DAL_RETRY_MODE,
|
|
93
|
+
maxAttempts: DAL_MAX_ATTEMPTS,
|
|
94
|
+
// Passing a plain object lets the SDK construct its default NodeHttpHandler with these
|
|
95
|
+
// bounds — no direct dependency on the handler package required. `throwOnRequestTimeout`
|
|
96
|
+
// is required for `requestTimeout` to actually abort a hung request: without it the
|
|
97
|
+
// handler only logs a warning and lets the request run on. Safe here because a DAL read
|
|
98
|
+
// is a simple request/response, not a long-lived stream.
|
|
99
|
+
requestHandler: {
|
|
100
|
+
connectionTimeout: DAL_CONNECTION_TIMEOUT_MS,
|
|
101
|
+
requestTimeout: DAL_REQUEST_TIMEOUT_MS,
|
|
102
|
+
throwOnRequestTimeout: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
11
106
|
/**
|
|
12
107
|
* A class for reading entries from the data store.
|
|
13
108
|
*
|
|
@@ -22,6 +117,8 @@ export class DataStore {
|
|
|
22
117
|
static _testDocumentClient = null;
|
|
23
118
|
/** @internal Test hook: inject logMRTError for unit tests */
|
|
24
119
|
static _testLogMRTError = null;
|
|
120
|
+
/** @internal Test hook: inject a deterministic random source (returns [0, 1)) for unit tests */
|
|
121
|
+
static _testRandom = null;
|
|
25
122
|
constructor() {
|
|
26
123
|
// Private constructor for singleton; use DataStore.getDataStore() instead.
|
|
27
124
|
}
|
|
@@ -42,12 +139,34 @@ export class DataStore {
|
|
|
42
139
|
}
|
|
43
140
|
if (!this._ddb) {
|
|
44
141
|
this._tableName = `DataAccessLayer-${process.env.AWS_REGION}`;
|
|
45
|
-
this._ddb = DynamoDBDocumentClient.from(
|
|
46
|
-
region: process.env.AWS_REGION,
|
|
47
|
-
}));
|
|
142
|
+
this._ddb = DynamoDBDocumentClient.from(createDalDynamoDBClient());
|
|
48
143
|
}
|
|
49
144
|
return this._ddb;
|
|
50
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolve the DynamoDB partition key for a read, applying shard selection.
|
|
148
|
+
*
|
|
149
|
+
* Reads the shard count from `MRT_NUM_SHARDS` (default 1) and picks a random
|
|
150
|
+
* shard `i` in `[0, N)`. Shard 0 is the legacy unsuffixed partition key, so
|
|
151
|
+
* when `MRT_NUM_SHARDS` is unset or 1 this is identical to today's behavior.
|
|
152
|
+
* There is no runtime fallback: writers fan out to every shard, so every shard
|
|
153
|
+
* a reader can pick is guaranteed to exist.
|
|
154
|
+
*
|
|
155
|
+
* @private
|
|
156
|
+
* @returns The `projectEnvironment` partition key value to read
|
|
157
|
+
*/
|
|
158
|
+
resolveShardPartitionKey() {
|
|
159
|
+
const base = `${process.env.MOBIFY_PROPERTY_ID} ${process.env.DEPLOY_TARGET}`;
|
|
160
|
+
const parsed = Number(process.env.MRT_NUM_SHARDS);
|
|
161
|
+
const numShards = Number.isInteger(parsed) && parsed > 1 ? parsed : 1;
|
|
162
|
+
if (numShards === 1) {
|
|
163
|
+
return base;
|
|
164
|
+
}
|
|
165
|
+
const random = DataStore._testRandom ?? Math.random;
|
|
166
|
+
const shard = Math.floor(random() * numShards);
|
|
167
|
+
// shard 0 is the legacy unsuffixed partition; shards 1..N-1 carry a suffix.
|
|
168
|
+
return shard === 0 ? base : `${base} ${shard}`;
|
|
169
|
+
}
|
|
51
170
|
/**
|
|
52
171
|
* Get or create the singleton DataStore instance.
|
|
53
172
|
*
|
|
@@ -81,19 +200,22 @@ export class DataStore {
|
|
|
81
200
|
throw new DataStoreUnavailableError('The data store is unavailable.');
|
|
82
201
|
}
|
|
83
202
|
const ddb = this.getClient();
|
|
203
|
+
const projectEnvironment = this.resolveShardPartitionKey();
|
|
84
204
|
let response;
|
|
85
205
|
try {
|
|
86
206
|
response = await ddb.send(new GetCommand({
|
|
87
207
|
TableName: this._tableName,
|
|
88
208
|
Key: {
|
|
89
|
-
projectEnvironment
|
|
209
|
+
projectEnvironment,
|
|
90
210
|
key,
|
|
91
211
|
},
|
|
92
212
|
}));
|
|
93
213
|
}
|
|
94
214
|
catch (error) {
|
|
215
|
+
const errorName = error instanceof Error ? error.name : undefined;
|
|
216
|
+
const throttled = isThrottlingError(error);
|
|
95
217
|
const logFn = DataStore._testLogMRTError ?? logMRTError;
|
|
96
|
-
logFn('data_store', error, { key, tableName: this._tableName });
|
|
218
|
+
logFn('data_store', error, { key, projectEnvironment, tableName: this._tableName, errorName, throttled });
|
|
97
219
|
throw new DataStoreServiceError('Data store request failed.');
|
|
98
220
|
}
|
|
99
221
|
if (!response.Item?.value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"production.js","sourceRoot":"","sources":["../../../src/data-store/production.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAC,sBAAsB,EAAE,UAAU,EAAwB,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAC,sBAAsB,EAAE,qBAAqB,EAAE,yBAAyB,EAAC,MAAM,aAAa,CAAC;AACrG,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAC,sBAAsB,EAAE,qBAAqB,EAAE,yBAAyB,EAAC,MAAM,aAAa,CAAC;AAErG;;;;;GAKG;AACH,MAAM,OAAO,SAAS;IACZ,UAAU,GAAW,EAAE,CAAC;IACxB,IAAI,GAAkC,IAAI,CAAC;IAC3C,MAAM,CAAC,SAAS,GAAqB,IAAI,CAAC;IAElD,mEAAmE;IACnE,MAAM,CAAC,mBAAmB,GAAkC,IAAI,CAAC;IACjE,6DAA6D;IAC7D,MAAM,CAAC,gBAAgB,GAA0F,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"production.js","sourceRoot":"","sources":["../../../src/data-store/production.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/mrt-utilities",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Middleware and utilities to simulate a deployed Managed Runtime environment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Salesforce",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"http-proxy-middleware": "3.0.5",
|
|
103
103
|
"mime-types": "3.0.1",
|
|
104
104
|
"negotiator": "1.0.0",
|
|
105
|
-
"qs": "6.
|
|
105
|
+
"qs": "6.15.2",
|
|
106
106
|
"set-cookie-parser": "2.7.1"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"eslint": "^9",
|
|
125
125
|
"eslint-config-prettier": "^10",
|
|
126
126
|
"eslint-plugin-header": "^3.1.1",
|
|
127
|
-
"eslint-plugin-prettier": "^5.5.
|
|
127
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
128
128
|
"express": "5.1.0",
|
|
129
129
|
"express4": "npm:express@4.21.2",
|
|
130
|
-
"mocha": "^
|
|
131
|
-
"prettier": "^3.
|
|
130
|
+
"mocha": "^11",
|
|
131
|
+
"prettier": "^3.8.3",
|
|
132
132
|
"shx": "^0.3.3",
|
|
133
133
|
"sinon": "^21.0.1",
|
|
134
134
|
"tsx": "^4",
|