@openstax/ts-utils 1.48.2 → 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/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/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
|
@@ -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<{
|
|
@@ -113,7 +113,7 @@ export const dynamoUnversionedDocumentStore = (initializer) => {
|
|
|
113
113
|
if (!id) {
|
|
114
114
|
throw new Error(`Key attribute "${key}" is required for patchItem`);
|
|
115
115
|
}
|
|
116
|
-
const entries = Object.entries(item).filter(([field]) => field !== key);
|
|
116
|
+
const entries = Object.entries(item).filter(([field, value]) => field !== key && value !== undefined);
|
|
117
117
|
if (entries.length === 0) {
|
|
118
118
|
throw new Error('No attributes to update');
|
|
119
119
|
}
|
|
@@ -147,6 +147,68 @@ export const dynamoUnversionedDocumentStore = (initializer) => {
|
|
|
147
147
|
new NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
|
|
148
148
|
});
|
|
149
149
|
},
|
|
150
|
+
/* atomically patches only if the condition fields match; throws ConflictError if condition fails */
|
|
151
|
+
conditionalPatchItem: async (item, condition) => {
|
|
152
|
+
const id = item[hashKey];
|
|
153
|
+
const key = hashKey.toString();
|
|
154
|
+
if (!id) {
|
|
155
|
+
throw new Error(`Key attribute "${key}" is required for conditionalPatchItem`);
|
|
156
|
+
}
|
|
157
|
+
const entries = Object.entries(item).filter(([field, value]) => field !== key && value !== undefined);
|
|
158
|
+
if (entries.length === 0) {
|
|
159
|
+
throw new Error('No attributes to update');
|
|
160
|
+
}
|
|
161
|
+
const conditionEntries = Object.entries(condition);
|
|
162
|
+
if (conditionEntries.length === 0) {
|
|
163
|
+
throw new Error('condition must have at least one attribute');
|
|
164
|
+
}
|
|
165
|
+
const updates = [];
|
|
166
|
+
const expressionAttributeNames = { '#k': key };
|
|
167
|
+
const expressionAttributeValues = {};
|
|
168
|
+
entries.forEach(([field, value], index) => {
|
|
169
|
+
updates.push(`#f${index} = :f${index}`);
|
|
170
|
+
expressionAttributeNames[`#f${index}`] = field;
|
|
171
|
+
expressionAttributeValues[`:f${index}`] = encodeDynamoAttribute(value);
|
|
172
|
+
});
|
|
173
|
+
const conditionClauses = ['attribute_exists(#k)'];
|
|
174
|
+
conditionEntries.forEach(([field, value], index) => {
|
|
175
|
+
expressionAttributeNames[`#c${index}`] = field;
|
|
176
|
+
if (value === undefined) {
|
|
177
|
+
conditionClauses.push(`attribute_not_exists(#c${index})`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
conditionClauses.push(`#c${index} = :c${index}`);
|
|
181
|
+
expressionAttributeValues[`:c${index}`] = encodeDynamoAttribute(value);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const cmd = new UpdateItemCommand({
|
|
185
|
+
Key: { [key]: encodeDynamoAttribute(id) },
|
|
186
|
+
TableName: await tableName(),
|
|
187
|
+
UpdateExpression: `SET ${updates.join(', ')}`,
|
|
188
|
+
ConditionExpression: conditionClauses.join(' AND '),
|
|
189
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
190
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
191
|
+
ReturnValues: 'ALL_NEW',
|
|
192
|
+
ReturnValuesOnConditionCheckFailure: 'ALL_OLD',
|
|
193
|
+
});
|
|
194
|
+
return dynamodb().send(cmd).then(async (result) => {
|
|
195
|
+
var _a;
|
|
196
|
+
if (!result.Attributes) {
|
|
197
|
+
throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
198
|
+
}
|
|
199
|
+
const updatedDoc = decodeDynamoDocument(result.Attributes);
|
|
200
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
|
|
201
|
+
return updatedDoc;
|
|
202
|
+
}).catch((error) => {
|
|
203
|
+
if (error.name === 'ConditionalCheckFailedException') {
|
|
204
|
+
if (!error.Item || Object.keys(error.Item).length === 0) {
|
|
205
|
+
throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
206
|
+
}
|
|
207
|
+
throw new ConflictError(`Condition failed for item with ${key} "${id}"`);
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
});
|
|
211
|
+
},
|
|
150
212
|
/* replaces the entire document with the given data */
|
|
151
213
|
putItem: async (item) => {
|
|
152
214
|
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<{
|
|
@@ -62,6 +62,7 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
62
62
|
return load(hashFilename(id));
|
|
63
63
|
},
|
|
64
64
|
incrementItemAttribute: async (id, attribute) => {
|
|
65
|
+
var _a;
|
|
65
66
|
const filename = hashFilename(id);
|
|
66
67
|
const path = await filePath(filename);
|
|
67
68
|
await mkTableDir;
|
|
@@ -71,19 +72,50 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
71
72
|
}
|
|
72
73
|
const newValue = typeof data[attribute] === 'number' ? data[attribute] + 1 : 1;
|
|
73
74
|
const newItem = { ...data, [hashKey]: id, [attribute]: newValue };
|
|
74
|
-
|
|
75
|
-
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
76
77
|
});
|
|
78
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
79
|
+
return newValue;
|
|
77
80
|
},
|
|
78
81
|
patchItem: async (item) => {
|
|
82
|
+
var _a;
|
|
79
83
|
const id = item[hashKey];
|
|
80
84
|
if (!id) {
|
|
81
85
|
throw new Error(`Key attribute "${hashKey.toString()}" is required for patchItem`);
|
|
82
86
|
}
|
|
83
87
|
// This check is just to make this adapter consistent with the dynamo adapter
|
|
84
|
-
|
|
88
|
+
const definedEntries = Object.entries(item).filter(([k, v]) => k !== hashKey.toString() && v !== undefined);
|
|
89
|
+
if (definedEntries.length === 0) {
|
|
90
|
+
throw new Error('No attributes to update');
|
|
91
|
+
}
|
|
92
|
+
const filename = hashFilename(id);
|
|
93
|
+
const path = await filePath(filename);
|
|
94
|
+
await mkTableDir;
|
|
95
|
+
const data = await load(filename);
|
|
96
|
+
if (!data) {
|
|
97
|
+
throw new NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
|
|
98
|
+
}
|
|
99
|
+
const newItem = { ...data, ...Object.fromEntries(definedEntries) };
|
|
100
|
+
await new Promise((resolve, reject) => {
|
|
101
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
102
|
+
});
|
|
103
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
104
|
+
return newItem;
|
|
105
|
+
},
|
|
106
|
+
conditionalPatchItem: async (item, condition) => {
|
|
107
|
+
var _a;
|
|
108
|
+
const id = item[hashKey];
|
|
109
|
+
if (!id) {
|
|
110
|
+
throw new Error(`Key attribute "${hashKey.toString()}" is required for conditionalPatchItem`);
|
|
111
|
+
}
|
|
112
|
+
const definedEntries = Object.entries(item).filter(([k, v]) => k !== hashKey.toString() && v !== undefined);
|
|
113
|
+
if (definedEntries.length === 0) {
|
|
85
114
|
throw new Error('No attributes to update');
|
|
86
115
|
}
|
|
116
|
+
if (Object.keys(condition).length === 0) {
|
|
117
|
+
throw new Error('condition must have at least one attribute');
|
|
118
|
+
}
|
|
87
119
|
const filename = hashFilename(id);
|
|
88
120
|
const path = await filePath(filename);
|
|
89
121
|
await mkTableDir;
|
|
@@ -91,19 +123,31 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
91
123
|
if (!data) {
|
|
92
124
|
throw new NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
|
|
93
125
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
// The local version of this method currently supports only primitive value conditions
|
|
127
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
128
|
+
if (data[key] !== value) {
|
|
129
|
+
throw new ConflictError(`Condition failed for item with ${hashKey.toString()} "${id}"`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const newItem = { ...data, ...Object.fromEntries(definedEntries) };
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve());
|
|
97
135
|
});
|
|
136
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, newItem));
|
|
137
|
+
return newItem;
|
|
98
138
|
},
|
|
99
139
|
putItem: async (item) => {
|
|
140
|
+
var _a;
|
|
100
141
|
const path = await filePath(hashFilename(item[hashKey]));
|
|
101
142
|
await mkTableDir;
|
|
102
|
-
|
|
103
|
-
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve());
|
|
104
145
|
});
|
|
146
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, item));
|
|
147
|
+
return item;
|
|
105
148
|
},
|
|
106
149
|
createItem: async (item) => {
|
|
150
|
+
var _a;
|
|
107
151
|
const hashed = hashFilename(item[hashKey]);
|
|
108
152
|
const existingItem = await load(hashed);
|
|
109
153
|
if (existingItem) {
|
|
@@ -111,9 +155,11 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
111
155
|
}
|
|
112
156
|
const path = await filePath(hashed);
|
|
113
157
|
await mkTableDir;
|
|
114
|
-
|
|
115
|
-
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve());
|
|
116
160
|
});
|
|
161
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, item));
|
|
162
|
+
return item;
|
|
117
163
|
},
|
|
118
164
|
batchCreateItem: async (items, _concurrency = 1) => {
|
|
119
165
|
var _a;
|
|
@@ -135,11 +181,11 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
135
181
|
const createdItem = await new Promise((resolve, reject) => {
|
|
136
182
|
writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(item));
|
|
137
183
|
});
|
|
138
|
-
successful.push(createdItem);
|
|
139
184
|
// Only call individual afterWrite if batchAfterWrite is not defined
|
|
140
185
|
if (!(options === null || options === void 0 ? void 0 : options.batchAfterWrite)) {
|
|
141
186
|
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, createdItem));
|
|
142
187
|
}
|
|
188
|
+
successful.push(createdItem);
|
|
143
189
|
}
|
|
144
190
|
catch (error) {
|
|
145
191
|
failed.push({ item, error: error });
|