@shushed/helpers 0.0.209 → 0.0.210

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.
@@ -42,71 +42,107 @@ class AirtableHelper extends runtime_1.default {
42
42
  }
43
43
  return existingRecord;
44
44
  }
45
- async updateMultiple(payload, options = {}, callIdx = 0, collectedResult = {
46
- updatedRecords: [],
47
- createdRecords: [],
48
- records: []
49
- }) {
45
+ async updateMultiple(payload, options = {}) {
50
46
  let response = null;
51
47
  const tableUrl = `https://api.airtable.com/v0/${this.baseId}/${this.tableId}`;
52
- const currentBatch = payload.slice(callIdx * 10, (callIdx + 1) * 10);
53
- try {
54
- response = await fetch(`${tableUrl}`, {
55
- method: "PATCH",
56
- headers: {
57
- Authorization: `Bearer ${this.apiKey}`,
58
- "Content-Type": "application/json",
59
- },
60
- body: JSON.stringify({
61
- performUpsert: {
62
- fieldsToMergeOn: (options.fieldsToMergeOn ?? [this.primaryKeyFieldName]).map(x => this.dictionary[x] || x),
63
- },
64
- returnFieldsByFieldId: true,
65
- typecast: options.typecast || false,
66
- records: currentBatch.map(x => {
67
- const recordId = x.$recordId;
68
- const fieldsWithoutRecordId = { ...x };
69
- delete fieldsWithoutRecordId.$recordId;
70
- const record = {
71
- fields: AirtableHelper.convertToDictionary(this.dictionary, this.primaryKeyWritable === false
72
- ? AirtableHelper.removePrimaryKey(fieldsWithoutRecordId, this.primaryKeyFieldName)
73
- : fieldsWithoutRecordId),
74
- };
75
- if (recordId) {
76
- record.id = recordId;
77
- }
78
- return record;
79
- })
80
- }),
48
+ const batchSize = 10;
49
+ let collectedResult = {
50
+ updatedRecords: [],
51
+ createdRecords: [],
52
+ records: []
53
+ };
54
+ const fieldsToMergeOn = (options.fieldsToMergeOn || [this.primaryKeyFieldName]);
55
+ const fieldsToMergeOnIds = fieldsToMergeOn.map(x => this.dictionary[x] || x);
56
+ let callIdx = 0;
57
+ const payloadDeduplicated = payload.filter((x, idx, self) => idx === self.findIndex((t) => fieldsToMergeOn.every(field => (0, lodash_isequal_1.default)(t[field], x[field]))));
58
+ const maxCallIdx = Math.ceil(payloadDeduplicated.length / batchSize);
59
+ while (callIdx < maxCallIdx) {
60
+ const currentBatch = payloadDeduplicated.slice(callIdx * batchSize, (callIdx + 1) * batchSize);
61
+ const recordsInBatch = currentBatch.map(x => {
62
+ const recordId = x.$recordId;
63
+ const fieldsWithoutRecordId = { ...x };
64
+ delete fieldsWithoutRecordId.$recordId;
65
+ const record = {
66
+ fields: AirtableHelper.convertToDictionary(this.dictionary, this.primaryKeyWritable === false
67
+ ? AirtableHelper.removePrimaryKey(fieldsWithoutRecordId, this.primaryKeyFieldName)
68
+ : fieldsWithoutRecordId),
69
+ };
70
+ if (recordId) {
71
+ record.id = recordId;
72
+ }
73
+ return record;
81
74
  });
82
- if (!response.ok && response) {
83
- const text = await response.text().catch(() => `${response?.status || 'unknown'}`);
84
- throw new Error(text);
75
+ try {
76
+ response = await fetch(`${tableUrl}`, {
77
+ method: "PATCH",
78
+ headers: {
79
+ Authorization: `Bearer ${this.apiKey}`,
80
+ "Content-Type": "application/json",
81
+ },
82
+ body: JSON.stringify({
83
+ performUpsert: {
84
+ fieldsToMergeOn: fieldsToMergeOnIds,
85
+ },
86
+ returnFieldsByFieldId: true,
87
+ typecast: options.typecast || false,
88
+ records: recordsInBatch
89
+ }),
90
+ });
91
+ if (!response.ok && response) {
92
+ const text = await response.text().catch(() => `${response?.status || 'unknown'}`);
93
+ throw new Error(text);
94
+ }
95
+ const resp = (await response.json());
96
+ collectedResult = {
97
+ updatedRecords: collectedResult.updatedRecords.concat(resp.updatedRecords),
98
+ createdRecords: collectedResult.createdRecords.concat(resp.createdRecords),
99
+ records: collectedResult.records.concat(resp.records.map(x => AirtableHelper.translateFields(this.dictionary, x)))
100
+ };
85
101
  }
86
- const resp = (await response.json());
87
- const nextCollectedResult = {
88
- updatedRecords: collectedResult.updatedRecords.concat(resp.updatedRecords),
89
- createdRecords: collectedResult.createdRecords.concat(resp.createdRecords),
90
- records: collectedResult.records.concat(resp.records.map(x => AirtableHelper.translateFields(this.dictionary, x)))
91
- };
92
- if (payload.length > (callIdx + 1) * 10) {
93
- return this.updateMultiple(payload, options, callIdx + 1, nextCollectedResult);
102
+ catch (err) {
103
+ const errorMessage = `Failed to update records in ${this.tableId} table (baseId: ${this.baseId}). Status: ${response?.status || 'unknown'}. Error: ${err.message}`;
104
+ const batchErrors = currentBatch.map(() => new Error(errorMessage));
105
+ collectedResult = {
106
+ updatedRecords: collectedResult.updatedRecords,
107
+ createdRecords: collectedResult.createdRecords,
108
+ records: collectedResult.records.concat(batchErrors)
109
+ };
110
+ }
111
+ finally {
112
+ callIdx += 1;
94
113
  }
95
- return nextCollectedResult;
96
114
  }
97
- catch (err) {
98
- const errorMessage = `Failed to update records in ${this.tableId} table (baseId: ${this.baseId}). Status: ${response?.status || 'unknown'}. Error: ${err.message}`;
99
- const batchErrors = currentBatch.map(() => new Error(errorMessage));
100
- const nextCollectedResult = {
101
- updatedRecords: collectedResult.updatedRecords,
102
- createdRecords: collectedResult.createdRecords,
103
- records: collectedResult.records.concat(batchErrors)
104
- };
105
- if (payload.length > (callIdx + 1) * 10) {
106
- return this.updateMultiple(payload, options, callIdx + 1, nextCollectedResult);
115
+ const resultInOrder = [];
116
+ for (let i = 0; i < payload.length; i++) {
117
+ let j = 0;
118
+ let foundMatchingRecord = null;
119
+ while (j < collectedResult.records.length && foundMatchingRecord === null) {
120
+ let isMatching = true;
121
+ let k = 0;
122
+ while (k < fieldsToMergeOn.length && isMatching) {
123
+ const field = fieldsToMergeOn[k];
124
+ if (!(0, lodash_isequal_1.default)(collectedResult.records[j].fields[field], payload[i][field])) {
125
+ isMatching = false;
126
+ }
127
+ k += 1;
128
+ }
129
+ if (isMatching) {
130
+ foundMatchingRecord = j;
131
+ }
132
+ j += 1;
133
+ }
134
+ if (foundMatchingRecord !== null) {
135
+ resultInOrder.push(collectedResult.records[foundMatchingRecord]);
136
+ }
137
+ else {
138
+ resultInOrder.push(new Error(`Record ${payload[i][this.primaryKeyFieldName]} does not match any record in the response`));
107
139
  }
108
- return nextCollectedResult;
109
140
  }
141
+ return {
142
+ updatedRecords: collectedResult.updatedRecords,
143
+ createdRecords: collectedResult.createdRecords,
144
+ records: resultInOrder
145
+ };
110
146
  }
111
147
  async upsert(payload) {
112
148
  const existingRecord = await this.getExistingRecord(payload);
@@ -258,9 +294,10 @@ class AirtableHelper extends runtime_1.default {
258
294
  const escapeFormulaValue = (value) => {
259
295
  return value.replace(/"/g, '\\"');
260
296
  };
297
+ const dedupedKeys = keys.filter((x, idx, self) => self.indexOf(x) === idx);
261
298
  const batchSize = 50;
262
- for (let i = 0; i < keys.length; i += batchSize) {
263
- const batch = keys.slice(i, i + batchSize);
299
+ for (let i = 0; i < dedupedKeys.length; i += batchSize) {
300
+ const batch = dedupedKeys.slice(i, i + batchSize);
264
301
  const orConditions = batch.map(key => `${this.dictionary[this.primaryKeyFieldName]} = "${escapeFormulaValue(key)}"`).join(', ');
265
302
  const formula = `OR(${orConditions})`;
266
303
  const records = await this.getExistingRecords(formula);
@@ -45,15 +45,6 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
45
45
  fieldsToMergeOn?: Array<keyof T>;
46
46
  primaryKeyWritable?: boolean;
47
47
  typecast?: boolean;
48
- }, callIdx?: number, collectedResult?: {
49
- updatedRecords: Array<string>;
50
- createdRecords: Array<string>;
51
- records: Array<{
52
- id: string;
53
- fields: {
54
- [key in T[keyof T]]: any;
55
- };
56
- } | Error>;
57
48
  }): Promise<{
58
49
  updatedRecords: Array<string>;
59
50
  createdRecords: Array<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.209",
3
+ "version": "0.0.210",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",