@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): Promise<Array<{
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>): Promise<Map<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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.258",
3
+ "version": "0.0.259",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",