@openstax/ts-utils 1.48.1 → 1.50.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/config/index.d.ts +0 -2
- package/dist/cjs/config/index.js +0 -2
- package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +1 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.js +63 -1
- package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +1 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.js +57 -11
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/config/index.d.ts +0 -2
- package/dist/esm/config/index.js +0 -2
- package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +1 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.js +63 -1
- package/dist/esm/services/documentStore/unversioned/file-system.d.ts +1 -0
- package/dist/esm/services/documentStore/unversioned/file-system.js +57 -11
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -44,5 +44,3 @@ export * from './resolveConfigValue.js';
|
|
|
44
44
|
export declare const stubConfig: <V extends ConfigValue>(configValue: V) => ConfigValueProvider<V>;
|
|
45
45
|
export * from './envConfig.js';
|
|
46
46
|
export * from './replaceConfig.js';
|
|
47
|
-
export * from './awsParameterConfig.js';
|
|
48
|
-
export * from './lambdaParameterConfig.js';
|
package/dist/cjs/config/index.js
CHANGED
|
@@ -31,5 +31,3 @@ const stubConfig = (configValue) => configValue;
|
|
|
31
31
|
exports.stubConfig = stubConfig;
|
|
32
32
|
__exportStar(require("./envConfig.js"), exports);
|
|
33
33
|
__exportStar(require("./replaceConfig.js"), exports);
|
|
34
|
-
__exportStar(require("./awsParameterConfig.js"), exports);
|
|
35
|
-
__exportStar(require("./lambdaParameterConfig.js"), exports);
|
|
@@ -18,6 +18,7 @@ export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamo
|
|
|
18
18
|
getItem: (id: T[K]) => Promise<T | undefined>;
|
|
19
19
|
incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
|
|
20
20
|
patchItem: (item: Partial<T>) => Promise<T>;
|
|
21
|
+
conditionalPatchItem: (item: Partial<T>, condition: Partial<T>) => Promise<T>;
|
|
21
22
|
putItem: (item: T) => Promise<T>;
|
|
22
23
|
createItem: (item: T) => Promise<T>;
|
|
23
24
|
batchCreateItem: (items: T[], concurrency?: number) => Promise<{
|
|
@@ -119,7 +119,7 @@ const dynamoUnversionedDocumentStore = (initializer) => {
|
|
|
119
119
|
if (!id) {
|
|
120
120
|
throw new Error(`Key attribute "${key}" is required for patchItem`);
|
|
121
121
|
}
|
|
122
|
-
const entries = Object.entries(item).filter(([field]) => field !== key);
|
|
122
|
+
const entries = Object.entries(item).filter(([field, value]) => field !== key && value !== undefined);
|
|
123
123
|
if (entries.length === 0) {
|
|
124
124
|
throw new Error('No attributes to update');
|
|
125
125
|
}
|
|
@@ -153,6 +153,68 @@ const dynamoUnversionedDocumentStore = (initializer) => {
|
|
|
153
153
|
new index_js_2.NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
|
|
154
154
|
});
|
|
155
155
|
},
|
|
156
|
+
/* atomically patches only if the condition fields match; throws ConflictError if condition fails */
|
|
157
|
+
conditionalPatchItem: async (item, condition) => {
|
|
158
|
+
const id = item[hashKey];
|
|
159
|
+
const key = hashKey.toString();
|
|
160
|
+
if (!id) {
|
|
161
|
+
throw new Error(`Key attribute "${key}" is required for conditionalPatchItem`);
|
|
162
|
+
}
|
|
163
|
+
const entries = Object.entries(item).filter(([field, value]) => field !== key && value !== undefined);
|
|
164
|
+
if (entries.length === 0) {
|
|
165
|
+
throw new Error('No attributes to update');
|
|
166
|
+
}
|
|
167
|
+
const conditionEntries = Object.entries(condition);
|
|
168
|
+
if (conditionEntries.length === 0) {
|
|
169
|
+
throw new Error('condition must have at least one attribute');
|
|
170
|
+
}
|
|
171
|
+
const updates = [];
|
|
172
|
+
const expressionAttributeNames = { '#k': key };
|
|
173
|
+
const expressionAttributeValues = {};
|
|
174
|
+
entries.forEach(([field, value], index) => {
|
|
175
|
+
updates.push(`#f${index} = :f${index}`);
|
|
176
|
+
expressionAttributeNames[`#f${index}`] = field;
|
|
177
|
+
expressionAttributeValues[`:f${index}`] = (0, dynamoEncoding_js_1.encodeDynamoAttribute)(value);
|
|
178
|
+
});
|
|
179
|
+
const conditionClauses = ['attribute_exists(#k)'];
|
|
180
|
+
conditionEntries.forEach(([field, value], index) => {
|
|
181
|
+
expressionAttributeNames[`#c${index}`] = field;
|
|
182
|
+
if (value === undefined) {
|
|
183
|
+
conditionClauses.push(`attribute_not_exists(#c${index})`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
conditionClauses.push(`#c${index} = :c${index}`);
|
|
187
|
+
expressionAttributeValues[`:c${index}`] = (0, dynamoEncoding_js_1.encodeDynamoAttribute)(value);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const cmd = new client_dynamodb_1.UpdateItemCommand({
|
|
191
|
+
Key: { [key]: (0, dynamoEncoding_js_1.encodeDynamoAttribute)(id) },
|
|
192
|
+
TableName: await tableName(),
|
|
193
|
+
UpdateExpression: `SET ${updates.join(', ')}`,
|
|
194
|
+
ConditionExpression: conditionClauses.join(' AND '),
|
|
195
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
196
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
197
|
+
ReturnValues: 'ALL_NEW',
|
|
198
|
+
ReturnValuesOnConditionCheckFailure: 'ALL_OLD',
|
|
199
|
+
});
|
|
200
|
+
return dynamodb().send(cmd).then(async (result) => {
|
|
201
|
+
var _a;
|
|
202
|
+
if (!result.Attributes) {
|
|
203
|
+
throw new index_js_2.NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
204
|
+
}
|
|
205
|
+
const updatedDoc = (0, dynamoEncoding_js_1.decodeDynamoDocument)(result.Attributes);
|
|
206
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
|
|
207
|
+
return updatedDoc;
|
|
208
|
+
}).catch((error) => {
|
|
209
|
+
if (error.name === 'ConditionalCheckFailedException') {
|
|
210
|
+
if (!error.Item || Object.keys(error.Item).length === 0) {
|
|
211
|
+
throw new index_js_2.NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
212
|
+
}
|
|
213
|
+
throw new index_js_2.ConflictError(`Condition failed for item with ${key} "${id}"`);
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
});
|
|
217
|
+
},
|
|
156
218
|
/* replaces the entire document with the given data */
|
|
157
219
|
putItem: async (item) => {
|
|
158
220
|
var _a;
|
|
@@ -18,6 +18,7 @@ export declare const fileSystemUnversionedDocumentStore: <C extends string = "fi
|
|
|
18
18
|
getItem: (id: T[K]) => Promise<T | undefined>;
|
|
19
19
|
incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
|
|
20
20
|
patchItem: (item: Partial<T>) => Promise<T>;
|
|
21
|
+
conditionalPatchItem: (item: Partial<T>, condition: Partial<T>) => Promise<T>;
|
|
21
22
|
putItem: (item: T) => Promise<T>;
|
|
22
23
|
createItem: (item: T) => Promise<T>;
|
|
23
24
|
batchCreateItem: (items: T[], _concurrency?: number) => Promise<{
|
|
@@ -101,6 +101,7 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
101
101
|
return load(hashFilename(id));
|
|
102
102
|
},
|
|
103
103
|
incrementItemAttribute: async (id, attribute) => {
|
|
104
|
+
var _a;
|
|
104
105
|
const filename = hashFilename(id);
|
|
105
106
|
const path = await filePath(filename);
|
|
106
107
|
await mkTableDir;
|
|
@@ -110,19 +111,50 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
110
111
|
}
|
|
111
112
|
const newValue = typeof data[attribute] === 'number' ? data[attribute] + 1 : 1;
|
|
112
113
|
const newItem = { ...data, [hashKey]: id, [attribute]: newValue };
|
|
113
|
-
|
|
114
|
-
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
115
116
|
});
|
|
117
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
118
|
+
return newValue;
|
|
116
119
|
},
|
|
117
120
|
patchItem: async (item) => {
|
|
121
|
+
var _a;
|
|
118
122
|
const id = item[hashKey];
|
|
119
123
|
if (!id) {
|
|
120
124
|
throw new Error(`Key attribute "${hashKey.toString()}" is required for patchItem`);
|
|
121
125
|
}
|
|
122
126
|
// This check is just to make this adapter consistent with the dynamo adapter
|
|
123
|
-
|
|
127
|
+
const definedEntries = Object.entries(item).filter(([k, v]) => k !== hashKey.toString() && v !== undefined);
|
|
128
|
+
if (definedEntries.length === 0) {
|
|
129
|
+
throw new Error('No attributes to update');
|
|
130
|
+
}
|
|
131
|
+
const filename = hashFilename(id);
|
|
132
|
+
const path = await filePath(filename);
|
|
133
|
+
await mkTableDir;
|
|
134
|
+
const data = await load(filename);
|
|
135
|
+
if (!data) {
|
|
136
|
+
throw new index_js_2.NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
|
|
137
|
+
}
|
|
138
|
+
const newItem = { ...data, ...Object.fromEntries(definedEntries) };
|
|
139
|
+
await new Promise((resolve, reject) => {
|
|
140
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
141
|
+
});
|
|
142
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
143
|
+
return newItem;
|
|
144
|
+
},
|
|
145
|
+
conditionalPatchItem: async (item, condition) => {
|
|
146
|
+
var _a;
|
|
147
|
+
const id = item[hashKey];
|
|
148
|
+
if (!id) {
|
|
149
|
+
throw new Error(`Key attribute "${hashKey.toString()}" is required for conditionalPatchItem`);
|
|
150
|
+
}
|
|
151
|
+
const definedEntries = Object.entries(item).filter(([k, v]) => k !== hashKey.toString() && v !== undefined);
|
|
152
|
+
if (definedEntries.length === 0) {
|
|
124
153
|
throw new Error('No attributes to update');
|
|
125
154
|
}
|
|
155
|
+
if (Object.keys(condition).length === 0) {
|
|
156
|
+
throw new Error('condition must have at least one attribute');
|
|
157
|
+
}
|
|
126
158
|
const filename = hashFilename(id);
|
|
127
159
|
const path = await filePath(filename);
|
|
128
160
|
await mkTableDir;
|
|
@@ -130,19 +162,31 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
130
162
|
if (!data) {
|
|
131
163
|
throw new index_js_2.NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
|
|
132
164
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
165
|
+
// The local version of this method currently supports only primitive value conditions
|
|
166
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
167
|
+
if (data[key] !== value) {
|
|
168
|
+
throw new index_js_2.ConflictError(`Condition failed for item with ${hashKey.toString()} "${id}"`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const newItem = { ...data, ...Object.fromEntries(definedEntries) };
|
|
172
|
+
await new Promise((resolve, reject) => {
|
|
173
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
136
174
|
});
|
|
175
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
176
|
+
return newItem;
|
|
137
177
|
},
|
|
138
178
|
putItem: async (item) => {
|
|
179
|
+
var _a;
|
|
139
180
|
const path = await filePath(hashFilename(item[hashKey]));
|
|
140
181
|
await mkTableDir;
|
|
141
|
-
|
|
142
|
-
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(
|
|
182
|
+
await new Promise((resolve, reject) => {
|
|
183
|
+
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve());
|
|
143
184
|
});
|
|
185
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, item));
|
|
186
|
+
return item;
|
|
144
187
|
},
|
|
145
188
|
createItem: async (item) => {
|
|
189
|
+
var _a;
|
|
146
190
|
const hashed = hashFilename(item[hashKey]);
|
|
147
191
|
const existingItem = await load(hashed);
|
|
148
192
|
if (existingItem) {
|
|
@@ -150,9 +194,11 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
150
194
|
}
|
|
151
195
|
const path = await filePath(hashed);
|
|
152
196
|
await mkTableDir;
|
|
153
|
-
|
|
154
|
-
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(
|
|
197
|
+
await new Promise((resolve, reject) => {
|
|
198
|
+
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve());
|
|
155
199
|
});
|
|
200
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, item));
|
|
201
|
+
return item;
|
|
156
202
|
},
|
|
157
203
|
batchCreateItem: async (items, _concurrency = 1) => {
|
|
158
204
|
var _a;
|
|
@@ -174,11 +220,11 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
174
220
|
const createdItem = await new Promise((resolve, reject) => {
|
|
175
221
|
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(item));
|
|
176
222
|
});
|
|
177
|
-
successful.push(createdItem);
|
|
178
223
|
// Only call individual afterWrite if batchAfterWrite is not defined
|
|
179
224
|
if (!(options === null || options === void 0 ? void 0 : options.batchAfterWrite)) {
|
|
180
225
|
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, createdItem));
|
|
181
226
|
}
|
|
227
|
+
successful.push(createdItem);
|
|
182
228
|
}
|
|
183
229
|
catch (error) {
|
|
184
230
|
failed.push({ item, error: error });
|