@shushed/helpers 0.0.258 → 0.0.259
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.
|
@@ -39,6 +39,23 @@ class AirtableHelper extends runtime_1.default {
|
|
|
39
39
|
});
|
|
40
40
|
return nextObj;
|
|
41
41
|
}
|
|
42
|
+
static translateFieldsAll(dictionary, y) {
|
|
43
|
+
const mappedFieldIds = new Set(Object.values(dictionary));
|
|
44
|
+
const nextFields = {};
|
|
45
|
+
for (const k in dictionary) {
|
|
46
|
+
const v = dictionary[k];
|
|
47
|
+
nextFields[k] = y.fields[v];
|
|
48
|
+
}
|
|
49
|
+
for (const fieldId in y.fields) {
|
|
50
|
+
if (!mappedFieldIds.has(fieldId)) {
|
|
51
|
+
nextFields[fieldId] = y.fields[fieldId];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const nextObj = Object.assign({}, y, {
|
|
55
|
+
fields: nextFields
|
|
56
|
+
});
|
|
57
|
+
return nextObj;
|
|
58
|
+
}
|
|
42
59
|
async createIfNotExist(payload) {
|
|
43
60
|
const existingRecord = await this.getExistingRecord(payload);
|
|
44
61
|
if (!existingRecord) {
|
|
@@ -257,18 +274,21 @@ class AirtableHelper extends runtime_1.default {
|
|
|
257
274
|
throw new Error(`Failed to obtain records with the id: ${id} from the ${this.tableId} table and the baseId ${this.baseId}. Status Code: ${responseRecords?.status || 'unknown'}. Error: ${err.message}`);
|
|
258
275
|
}
|
|
259
276
|
}
|
|
260
|
-
async getExistingRecords(formula
|
|
277
|
+
async getExistingRecords(formula, options) {
|
|
261
278
|
const allRecords = [];
|
|
262
279
|
let offset = undefined;
|
|
263
280
|
const tableUrl = `https://api.airtable.com/v0/${this.baseId}/${this.tableId}/listRecords`;
|
|
281
|
+
const fetchAllFields = options?.fetchAllFields === true;
|
|
264
282
|
do {
|
|
265
283
|
let responseRecords = null;
|
|
266
284
|
const params = {
|
|
267
|
-
filterByFormula: formula,
|
|
285
|
+
filterByFormula: formula || '',
|
|
268
286
|
pageSize: 100,
|
|
269
287
|
returnFieldsByFieldId: true,
|
|
270
|
-
fields: Object.values(this.dictionary).filter((x, idx, arr) => arr.indexOf(x) === idx),
|
|
271
288
|
};
|
|
289
|
+
if (!fetchAllFields) {
|
|
290
|
+
params.fields = Object.values(this.dictionary).filter((x, idx, arr) => arr.indexOf(x) === idx);
|
|
291
|
+
}
|
|
272
292
|
if (offset) {
|
|
273
293
|
params.offset = `${offset}`;
|
|
274
294
|
}
|
|
@@ -295,9 +315,12 @@ class AirtableHelper extends runtime_1.default {
|
|
|
295
315
|
throw new Error(`Failed to obtain records ${formulaMsg} from the ${this.tableId} table and the baseId ${this.baseId}. Status Code: ${responseRecords?.status || 'unknown'}. Error: ${err.message}`);
|
|
296
316
|
}
|
|
297
317
|
} while (offset);
|
|
318
|
+
if (fetchAllFields) {
|
|
319
|
+
return allRecords.map(x => AirtableHelper.translateFieldsAll(this.dictionary, x));
|
|
320
|
+
}
|
|
298
321
|
return allRecords.map(x => AirtableHelper.translateFields(this.dictionary, x));
|
|
299
322
|
}
|
|
300
|
-
async getExistingRecordsByKeys(keys) {
|
|
323
|
+
async getExistingRecordsByKeys(keys, options) {
|
|
301
324
|
const result = new Map();
|
|
302
325
|
if (keys.length === 0) {
|
|
303
326
|
return result;
|
|
@@ -311,7 +334,7 @@ class AirtableHelper extends runtime_1.default {
|
|
|
311
334
|
const batch = dedupedKeys.slice(i, i + batchSize);
|
|
312
335
|
const orConditions = batch.map(key => `${this.dictionary[this.primaryKeyFieldName]} = "${escapeFormulaValue(key)}"`).join(', ');
|
|
313
336
|
const formula = `OR(${orConditions})`;
|
|
314
|
-
const records = await this.getExistingRecords(formula);
|
|
337
|
+
const records = await this.getExistingRecords(formula, options);
|
|
315
338
|
for (const record of records) {
|
|
316
339
|
const key = record.fields[this.primaryKeyFieldName];
|
|
317
340
|
result.set(key, record);
|
|
@@ -685,5 +708,129 @@ class AirtableHelper extends runtime_1.default {
|
|
|
685
708
|
const notSameImage = ([key, value]) => !(value && Array.isArray(value) && value.every(x => x && typeof x === 'object' && Object.keys(x).length === 1 && x.url && y.fields[key]?.some((y) => y.url === x.url)));
|
|
686
709
|
return Object.fromEntries(Object.entries(x).filter(([key, value]) => notUndefinedAndNotClearsUndefined([key, value]) && notEqual([key, value]) && notSameImage([key, value])));
|
|
687
710
|
}
|
|
711
|
+
async getTableFields(tableId) {
|
|
712
|
+
const targetTableId = tableId || this.tableId;
|
|
713
|
+
const fields = await this.getTableFieldsFull(targetTableId);
|
|
714
|
+
const result = {};
|
|
715
|
+
for (const field of fields) {
|
|
716
|
+
result[field.id] = field.name;
|
|
717
|
+
}
|
|
718
|
+
return result;
|
|
719
|
+
}
|
|
720
|
+
async getTableFieldsFull(tableId) {
|
|
721
|
+
let response = null;
|
|
722
|
+
const url = `https://api.airtable.com/v0/meta/bases/${this.baseId}/tables`;
|
|
723
|
+
try {
|
|
724
|
+
response = await fetch(url, {
|
|
725
|
+
method: 'GET',
|
|
726
|
+
headers: {
|
|
727
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
if (!response.ok) {
|
|
731
|
+
const text = await response.text().catch(() => `${response?.status || 'unknown'}`);
|
|
732
|
+
throw new Error(text);
|
|
733
|
+
}
|
|
734
|
+
const data = await response.json();
|
|
735
|
+
const table = data.tables.find(t => t.id === tableId);
|
|
736
|
+
if (!table) {
|
|
737
|
+
throw new Error(`Table ${tableId} not found in base ${this.baseId}`);
|
|
738
|
+
}
|
|
739
|
+
return table.fields;
|
|
740
|
+
}
|
|
741
|
+
catch (err) {
|
|
742
|
+
throw new Error(`Failed to get table fields for ${tableId} in base ${this.baseId}. Status Code: ${response?.status || 'unknown'}. Error: ${err.message}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async upsertFields(fields, options) {
|
|
746
|
+
const defaultLimitter = { run: (x) => x() };
|
|
747
|
+
const limitter = options?.limitter || defaultLimitter;
|
|
748
|
+
const targetTableId = this.tableId;
|
|
749
|
+
const existingFields = await limitter.run(() => this.getTableFieldsFull(targetTableId));
|
|
750
|
+
const existingByName = new Map();
|
|
751
|
+
for (const field of existingFields) {
|
|
752
|
+
existingByName.set(field.name, field);
|
|
753
|
+
}
|
|
754
|
+
const result = {};
|
|
755
|
+
for (const field of fields) {
|
|
756
|
+
const existing = existingByName.get(field.name);
|
|
757
|
+
if (existing) {
|
|
758
|
+
let hasChanged = false;
|
|
759
|
+
for (const key of Object.keys(field)) {
|
|
760
|
+
if (field[key] !== undefined && !(0, lodash_isequal_1.default)(field[key], existing[key])) {
|
|
761
|
+
hasChanged = true;
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (hasChanged) {
|
|
766
|
+
const updated = await limitter.run(() => this.updateField(targetTableId, existing.id, field));
|
|
767
|
+
result[updated.name] = updated.id;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
result[existing.name] = existing.id;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
if (!field.type) {
|
|
775
|
+
throw new Error(`Cannot create field "${field.name}": type is required for new fields`);
|
|
776
|
+
}
|
|
777
|
+
const created = await limitter.run(() => this.createField(targetTableId, field));
|
|
778
|
+
result[created.name] = created.id;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
async createField(tableId, field) {
|
|
784
|
+
let response = null;
|
|
785
|
+
const url = `https://api.airtable.com/v0/meta/bases/${this.baseId}/tables/${tableId}/fields`;
|
|
786
|
+
try {
|
|
787
|
+
response = await fetch(url, {
|
|
788
|
+
method: 'POST',
|
|
789
|
+
headers: {
|
|
790
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
791
|
+
'Content-Type': 'application/json',
|
|
792
|
+
},
|
|
793
|
+
body: JSON.stringify(field),
|
|
794
|
+
});
|
|
795
|
+
if (!response.ok) {
|
|
796
|
+
const text = await response.text().catch(() => `${response?.status || 'unknown'}`);
|
|
797
|
+
throw new Error(text);
|
|
798
|
+
}
|
|
799
|
+
return await response.json();
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
throw new Error(`Failed to create field "${field.name}" in table ${tableId}, base ${this.baseId}. Status Code: ${response?.status || 'unknown'}. Error: ${err.message}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async updateField(tableId, fieldId, field) {
|
|
806
|
+
let response = null;
|
|
807
|
+
const url = `https://api.airtable.com/v0/meta/bases/${this.baseId}/tables/${tableId}/fields/${fieldId}`;
|
|
808
|
+
const payload = {};
|
|
809
|
+
if (field.name !== undefined)
|
|
810
|
+
payload.name = field.name;
|
|
811
|
+
if (field.description !== undefined)
|
|
812
|
+
payload.description = field.description;
|
|
813
|
+
if (Object.keys(payload).length === 0) {
|
|
814
|
+
return { id: fieldId, name: field.name || '', type: '' };
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
response = await fetch(url, {
|
|
818
|
+
method: 'PATCH',
|
|
819
|
+
headers: {
|
|
820
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
821
|
+
'Content-Type': 'application/json',
|
|
822
|
+
},
|
|
823
|
+
body: JSON.stringify(payload),
|
|
824
|
+
});
|
|
825
|
+
if (!response.ok) {
|
|
826
|
+
const text = await response.text().catch(() => `${response?.status || 'unknown'}`);
|
|
827
|
+
throw new Error(text);
|
|
828
|
+
}
|
|
829
|
+
return await response.json();
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
throw new Error(`Failed to update field "${fieldId}" in table ${tableId}, base ${this.baseId}. Status Code: ${response?.status || 'unknown'}. Error: ${err.message}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
688
835
|
}
|
|
689
836
|
exports.default = AirtableHelper;
|
|
@@ -29,6 +29,13 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
|
|
|
29
29
|
[key in keyof T]: any;
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
+
static translateFieldsAll<T extends Record<string, string>, S extends {
|
|
33
|
+
fields: Record<string, any>;
|
|
34
|
+
}>(dictionary: T, y: S): S & {
|
|
35
|
+
fields: {
|
|
36
|
+
[key in keyof T]: any;
|
|
37
|
+
} & Record<string, any>;
|
|
38
|
+
};
|
|
32
39
|
createIfNotExist(payload: Partial<{
|
|
33
40
|
[key in keyof T]: any;
|
|
34
41
|
}> & {
|
|
@@ -101,17 +108,21 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
|
|
|
101
108
|
[key in keyof T]: any;
|
|
102
109
|
};
|
|
103
110
|
}>;
|
|
104
|
-
getExistingRecords(formula?: string
|
|
111
|
+
getExistingRecords(formula?: string, options?: {
|
|
112
|
+
fetchAllFields?: boolean;
|
|
113
|
+
}): Promise<Array<{
|
|
105
114
|
id: string;
|
|
106
115
|
fields: {
|
|
107
116
|
[key in keyof T]: any;
|
|
108
|
-
};
|
|
117
|
+
} & (Record<string, any>);
|
|
109
118
|
}>>;
|
|
110
|
-
getExistingRecordsByKeys(keys: Array<string
|
|
119
|
+
getExistingRecordsByKeys(keys: Array<string>, options?: {
|
|
120
|
+
fetchAllFields?: boolean;
|
|
121
|
+
}): Promise<Map<string, {
|
|
111
122
|
id: string;
|
|
112
123
|
fields: {
|
|
113
124
|
[key in keyof T]: any;
|
|
114
|
-
};
|
|
125
|
+
} & (Record<string, any>);
|
|
115
126
|
}>>;
|
|
116
127
|
pingWebhook(webhook: {
|
|
117
128
|
webhook: {
|
|
@@ -180,6 +191,20 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
|
|
|
180
191
|
}>(x: T1, y: T2 | null | undefined): {
|
|
181
192
|
[k: string]: any;
|
|
182
193
|
};
|
|
194
|
+
getTableFields(tableId?: string): Promise<Record<string, string>>;
|
|
195
|
+
private getTableFieldsFull;
|
|
196
|
+
upsertFields(fields: Array<{
|
|
197
|
+
name: string;
|
|
198
|
+
type?: string;
|
|
199
|
+
description?: string;
|
|
200
|
+
options?: Record<string, any>;
|
|
201
|
+
}>, options?: {
|
|
202
|
+
limitter?: {
|
|
203
|
+
run: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
204
|
+
};
|
|
205
|
+
}): Promise<Record<string, string>>;
|
|
206
|
+
private createField;
|
|
207
|
+
private updateField;
|
|
183
208
|
}
|
|
184
209
|
export default AirtableHelper;
|
|
185
210
|
type AirtableWebhook = {
|