airtable-ts
Advanced tools
Comparing version 1.3.0 to 1.3.1
import { AirtableTypeString, FromAirtableTypeString, FromTsTypeString, TsTypeString } from './typeUtils'; | ||
type Mapper = { | ||
[T in TsTypeString]?: { | ||
[A in AirtableTypeString]?: { | ||
[A in AirtableTypeString | 'unknown']?: { | ||
toAirtable: (value: FromTsTypeString<T>) => FromAirtableTypeString<A>; | ||
@@ -6,0 +6,0 @@ fromAirtable: (value: FromAirtableTypeString<A> | null | undefined) => FromTsTypeString<T>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.fieldMappers = void 0; | ||
const required = (value) => { | ||
if (value === null || value === undefined) { | ||
throw new Error('[airtable-ts] Missing required value'); | ||
} | ||
return value; | ||
}; | ||
const typeUtils_1 = require("./typeUtils"); | ||
const fallbackMapperPair = (toFallback, fromFallback) => ({ | ||
@@ -14,147 +9,47 @@ toAirtable: (value) => value ?? toFallback, | ||
}); | ||
const requiredMapperPair = { | ||
toAirtable: (value) => required(value), | ||
fromAirtable: (value) => required(value), | ||
const dateTimeMapperPair = { | ||
// Number assumed to be unix time in seconds | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(typeof value === 'number' ? value * 1000 : value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}; | ||
exports.fieldMappers = { | ||
string: { | ||
url: fallbackMapperPair('', ''), | ||
email: fallbackMapperPair('', ''), | ||
phoneNumber: fallbackMapperPair('', ''), | ||
singleLineText: fallbackMapperPair('', ''), | ||
multilineText: fallbackMapperPair('', ''), | ||
richText: fallbackMapperPair('', ''), | ||
singleSelect: fallbackMapperPair('', ''), | ||
externalSyncSource: { | ||
toAirtable: () => { throw new Error('[airtable-ts] externalSyncSource type field is readonly'); }, | ||
fromAirtable: (value) => value ?? '', | ||
}, | ||
multipleSelects: { | ||
toAirtable: (value) => { | ||
return [value]; | ||
}, | ||
fromAirtable: (value) => { | ||
if (!value) { | ||
throw new Error('[airtable-ts] Failed to coerce multipleSelects type field to a single string, as it was blank'); | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce multipleSelects to a single string, as there were ${value?.length} entries`); | ||
} | ||
return value[0]; | ||
}, | ||
}, | ||
multipleRecordLinks: { | ||
toAirtable: (value) => { | ||
return [value]; | ||
}, | ||
fromAirtable: (value) => { | ||
if (!value) { | ||
throw new Error('[airtable-ts] Failed to coerce multipleRecordLinks type field to a single string, as it was blank'); | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce multipleRecordLinks to a single string, as there were ${value?.length} entries`); | ||
} | ||
return value[0]; | ||
}, | ||
}, | ||
date: { | ||
toAirtable: (value) => { | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date string'); | ||
} | ||
return date.toJSON().slice(0, 10); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}, | ||
dateTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}, | ||
createdTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}, | ||
lastModifiedTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime string'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}, | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value) { | ||
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single string, as it was blank'); | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single string, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'string') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single string, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
}, | ||
rollup: { | ||
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'string') | ||
return value; | ||
if (value === undefined || value === null) | ||
return ''; | ||
throw new Error(`[airtable-ts] Can't coerce rollup to a string, as it was of type ${typeof value}`); | ||
}, | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'string') | ||
return value; | ||
if (value === undefined || value === null) | ||
return ''; | ||
throw new Error(`[airtable-ts] Can't coerce formula to a string, as it was of type ${typeof value}`); | ||
}, | ||
}, | ||
}, | ||
const readonly = (airtableType) => () => { throw new Error(`[airtable-ts] ${airtableType} type field is readonly`); }; | ||
const coerce = (airtableType, tsType) => (value) => { | ||
const parsedType = (0, typeUtils_1.parseType)(tsType); | ||
if (!parsedType.array && typeof value === parsedType.single) { | ||
return value; | ||
} | ||
if (parsedType.array && Array.isArray(value) && value.every((v) => typeof v === parsedType.single)) { | ||
return value; | ||
} | ||
if (parsedType.nullable && (value === undefined || value === null || (Array.isArray(value) && value.length === 0))) { | ||
return null; | ||
} | ||
if (parsedType.array && typeof value === parsedType.single) { | ||
return [value]; | ||
} | ||
if (!parsedType.array && Array.isArray(value) && value.length === 1 && typeof value[0] === parsedType.single) { | ||
return value[0]; | ||
} | ||
if (!parsedType.array && Array.isArray(value) && value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce ${airtableType} to a ${tsType}, as there were ${value.length} array entries`); | ||
} | ||
throw new Error(`[airtable-ts] Can't coerce ${airtableType} to a ${tsType}, as it was of type ${typeof value}`); | ||
}; | ||
const stringOrNull = { | ||
'string | null': { | ||
@@ -168,298 +63,53 @@ url: fallbackMapperPair(null, null), | ||
singleSelect: fallbackMapperPair(null, null), | ||
externalSyncSource: { | ||
toAirtable: () => { throw new Error('[airtable-ts] externalSyncSource type field is readonly'); }, | ||
fromAirtable: (value) => value ?? null, | ||
}, | ||
multipleSelects: { | ||
toAirtable: (value) => { | ||
return value ? [value] : []; | ||
}, | ||
fromAirtable: (value) => { | ||
if (!value || value.length === 0) { | ||
return null; | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce multipleSelects to a single string, as there were ${value?.length} entries`); | ||
} | ||
return value[0]; | ||
}, | ||
toAirtable: (value) => (value ? [value] : []), | ||
fromAirtable: coerce('multipleSelects', 'string | null'), | ||
}, | ||
multipleRecordLinks: { | ||
toAirtable: (value) => { | ||
return value ? [value] : []; | ||
}, | ||
fromAirtable: (value) => { | ||
if (!value || value.length === 0) { | ||
return null; | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce multipleRecordLinks to a single string, as there were ${value?.length} entries`); | ||
} | ||
return value[0]; | ||
}, | ||
toAirtable: (value) => (value ? [value] : []), | ||
fromAirtable: coerce('multipleRecordLinks', 'string | null'), | ||
}, | ||
date: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
return date.toJSON().slice(0, 10); | ||
}, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
toAirtable: (value) => dateTimeMapperPair.toAirtable(value)?.slice(0, 10) ?? null, | ||
fromAirtable: dateTimeMapperPair.fromAirtable, | ||
}, | ||
dateTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
dateTime: dateTimeMapperPair, | ||
createdTime: dateTimeMapperPair, | ||
lastModifiedTime: dateTimeMapperPair, | ||
multipleLookupValues: { | ||
toAirtable: readonly('multipleLookupValues'), | ||
fromAirtable: coerce('multipleLookupValues', 'string | null'), | ||
}, | ||
createdTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
externalSyncSource: { | ||
toAirtable: readonly('externalSyncSource'), | ||
fromAirtable: coerce('externalSyncSource', 'string | null'), | ||
}, | ||
lastModifiedTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
return null; | ||
const date = new Date(value); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
}, | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value || value.length === 0) { | ||
return null; | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single string, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'string') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single string, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
}, | ||
rollup: { | ||
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'string') | ||
return value; | ||
if (value === undefined || value === null) | ||
return null; | ||
throw new Error(`[airtable-ts] Can't coerce rollup to a string, as it was of type ${typeof value}`); | ||
}, | ||
toAirtable: readonly('rollup'), | ||
fromAirtable: coerce('rollup', 'string | null'), | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'string') | ||
return value; | ||
if (value === undefined || value === null) | ||
return null; | ||
throw new Error(`[airtable-ts] Can't coerce formula to a string, as it was of type ${typeof value}`); | ||
}, | ||
toAirtable: readonly('formula'), | ||
fromAirtable: coerce('formula', 'string | null'), | ||
}, | ||
}, | ||
boolean: { | ||
checkbox: fallbackMapperPair(false, false), | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value) { | ||
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single boolean, as it was blank'); | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single boolean, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'boolean') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single boolean, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
unknown: { | ||
toAirtable: (value) => value, | ||
fromAirtable: coerce('unknown', 'string | null'), | ||
}, | ||
}, | ||
}; | ||
const booleanOrNull = { | ||
'boolean | null': { | ||
checkbox: fallbackMapperPair(null, null), | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value || value.length === 0) { | ||
return null; | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single boolean, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'boolean') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single boolean, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
toAirtable: readonly('multipleLookupValues'), | ||
fromAirtable: coerce('multipleLookupValues', 'boolean | null'), | ||
}, | ||
}, | ||
number: { | ||
number: requiredMapperPair, | ||
rating: requiredMapperPair, | ||
duration: requiredMapperPair, | ||
currency: requiredMapperPair, | ||
percent: requiredMapperPair, | ||
count: { | ||
toAirtable: () => { throw new Error('[airtable-ts] count type field is readonly'); }, | ||
fromAirtable: (value) => required(value), | ||
unknown: { | ||
toAirtable: (value) => value, | ||
fromAirtable: coerce('unknown', 'boolean | null'), | ||
}, | ||
autoNumber: { | ||
toAirtable: () => { throw new Error('[airtable-ts] autoNumber type field is readonly'); }, | ||
fromAirtable: (value) => required(value), | ||
}, | ||
// Number assumed to be unix time in seconds | ||
date: { | ||
toAirtable: (value) => { | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
return date.toJSON().slice(0, 10); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
return Math.floor(date.getTime() / 1000); | ||
}, | ||
}, | ||
// Number assumed to be unix time in seconds | ||
dateTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return Math.floor(date.getTime() / 1000); | ||
}, | ||
}, | ||
createdTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return Math.floor(date.getTime() / 1000); | ||
}, | ||
}, | ||
lastModifiedTime: { | ||
toAirtable: (value) => { | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
fromAirtable: (value) => { | ||
const date = new Date(value ?? ''); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return Math.floor(date.getTime() / 1000); | ||
}, | ||
}, | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value) { | ||
throw new Error('[airtable-ts] Failed to coerce lookup type field to a single number, as it was blank'); | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single number, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'number') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single number, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
}, | ||
rollup: { | ||
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'number') | ||
return value; | ||
throw new Error(`[airtable-ts] Can't coerce rollup to a number, as it was of type ${typeof value}`); | ||
}, | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'number') | ||
return value; | ||
throw new Error(`[airtable-ts] Can't coerce formula to a number, as it was of type ${typeof value}`); | ||
}, | ||
}, | ||
}, | ||
}; | ||
const numberOrNull = { | ||
'number | null': { | ||
@@ -473,23 +123,15 @@ number: fallbackMapperPair(null, null), | ||
fromAirtable: (value) => value ?? null, | ||
toAirtable: () => { throw new Error('[airtable-ts] count type field is readonly'); }, | ||
toAirtable: readonly('count'), | ||
}, | ||
autoNumber: { | ||
fromAirtable: (value) => value ?? null, | ||
toAirtable: () => { throw new Error('[airtable-ts] autoNumber field is readonly'); }, | ||
toAirtable: readonly('autoNumber'), | ||
}, | ||
// Number assumed to be unix time in seconds | ||
date: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
return date.toJSON().slice(0, 10); | ||
}, | ||
toAirtable: (value) => dateTimeMapperPair.toAirtable(value)?.slice(0, 10) ?? null, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
const nullableValue = dateTimeMapperPair.fromAirtable(value); | ||
if (nullableValue === null) | ||
return null; | ||
const date = new Date(value); | ||
const date = new Date(nullableValue); | ||
if (Number.isNaN(date.getTime())) { | ||
@@ -501,19 +143,11 @@ throw new Error('[airtable-ts] Invalid date'); | ||
}, | ||
// Number assumed to be unix time in seconds | ||
dateTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
toAirtable: dateTimeMapperPair.toAirtable, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
const nullableValue = dateTimeMapperPair.fromAirtable(value); | ||
if (nullableValue === null) | ||
return null; | ||
const date = new Date(value); | ||
const date = new Date(nullableValue); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
@@ -524,17 +158,10 @@ return Math.floor(date.getTime() / 1000); | ||
createdTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
toAirtable: dateTimeMapperPair.toAirtable, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
const nullableValue = dateTimeMapperPair.fromAirtable(value); | ||
if (nullableValue === null) | ||
return null; | ||
const date = new Date(value); | ||
const date = new Date(nullableValue); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
@@ -545,17 +172,10 @@ return Math.floor(date.getTime() / 1000); | ||
lastModifiedTime: { | ||
toAirtable: (value) => { | ||
if (value === null) | ||
return null; | ||
const date = new Date(value * 1000); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
} | ||
return date.toJSON(); | ||
}, | ||
toAirtable: dateTimeMapperPair.toAirtable, | ||
fromAirtable: (value) => { | ||
if (value === null || value === undefined) | ||
const nullableValue = dateTimeMapperPair.fromAirtable(value); | ||
if (nullableValue === null) | ||
return null; | ||
const date = new Date(value); | ||
const date = new Date(nullableValue); | ||
if (Number.isNaN(date.getTime())) { | ||
throw new Error('[airtable-ts] Invalid dateTime'); | ||
throw new Error('[airtable-ts] Invalid date'); | ||
} | ||
@@ -566,38 +186,21 @@ return Math.floor(date.getTime() / 1000); | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value || value.length === 0) { | ||
return null; | ||
} | ||
if (value.length !== 1) { | ||
throw new Error(`[airtable-ts] Can't coerce lookup to a single number, as there were ${value?.length} entries`); | ||
} | ||
if (typeof value[0] !== 'number') { | ||
throw new Error(`[airtable-ts] Can't coerce singular lookup to a single number, as it was of type ${typeof value[0]}`); | ||
} | ||
return value[0]; | ||
}, | ||
toAirtable: readonly('multipleLookupValues'), | ||
fromAirtable: coerce('multipleLookupValues', 'number | null'), | ||
}, | ||
rollup: { | ||
toAirtable: () => { throw new Error('[airtable-ts] rollup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'number') | ||
return value; | ||
if (value === null || value === undefined) | ||
return null; | ||
throw new Error(`[airtable-ts] Can't coerce rollup to a number, as it was of type ${typeof value}`); | ||
}, | ||
toAirtable: readonly('rollup'), | ||
fromAirtable: coerce('rollup', 'number | null'), | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (typeof value === 'number') | ||
return value; | ||
if (value === null || value === undefined) | ||
return null; | ||
throw new Error(`[airtable-ts] Can't coerce formula to a number, as it was of type ${typeof value}`); | ||
}, | ||
toAirtable: readonly('formula'), | ||
fromAirtable: coerce('formula', 'number | null'), | ||
}, | ||
unknown: { | ||
toAirtable: (value) => value, | ||
fromAirtable: coerce('unknown', 'number | null'), | ||
}, | ||
}, | ||
'string[]': { | ||
}; | ||
const stringArrayOrNull = { | ||
'string[] | null': { | ||
multipleSelects: fallbackMapperPair([], []), | ||
@@ -607,59 +210,63 @@ multipleRecordLinks: fallbackMapperPair([], []), | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!Array.isArray(value)) { | ||
throw new Error('[airtable-ts] Failed to coerce lookup type field to a string array, as it was not an array'); | ||
} | ||
if (value.some((v) => typeof v !== 'string')) { | ||
throw new Error('[airtable-ts] Can\'t coerce lookup to a string array, as it had non string type'); | ||
} | ||
return value; | ||
}, | ||
fromAirtable: coerce('multipleLookupValues', 'string[] | null'), | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!Array.isArray(value)) { | ||
throw new Error('[airtable-ts] Failed to coerce formula type field to a string array, as it was not an array'); | ||
} | ||
if (value.some((v) => typeof v !== 'string')) { | ||
throw new Error('[airtable-ts] Can\'t coerce formula to a string array, as it had non string type'); | ||
} | ||
return value; | ||
}, | ||
fromAirtable: coerce('multipleLookupValues', 'string[] | null'), | ||
}, | ||
}, | ||
'string[] | null': { | ||
multipleSelects: fallbackMapperPair(null, null), | ||
multipleRecordLinks: fallbackMapperPair(null, null), | ||
multipleLookupValues: { | ||
toAirtable: () => { throw new Error('[airtable-ts] lookup type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value && !Array.isArray(value)) { | ||
return null; | ||
} | ||
if (!Array.isArray(value)) { | ||
throw new Error('[airtable-ts] Failed to coerce lookup type field to a string array, as it was not an array'); | ||
} | ||
if (value.some((v) => typeof v !== 'string')) { | ||
throw new Error('[airtable-ts] Can\'t coerce lookup to a string array, as it had non string type'); | ||
} | ||
return value; | ||
}, | ||
unknown: { | ||
toAirtable: (value) => value, | ||
fromAirtable: coerce('unknown', 'string[] | null'), | ||
}, | ||
formula: { | ||
toAirtable: () => { throw new Error('[airtable-ts] formula type field is readonly'); }, | ||
fromAirtable: (value) => { | ||
if (!value && !Array.isArray(value)) { | ||
return null; | ||
} | ||
if (!Array.isArray(value)) { | ||
throw new Error('[airtable-ts] Failed to coerce formula type field to a string array, as it was not an array'); | ||
} | ||
if (value.some((v) => typeof v !== 'string')) { | ||
throw new Error('[airtable-ts] Can\'t coerce formula to a string array, as it had non string type'); | ||
} | ||
return value; | ||
}, | ||
}, | ||
}, | ||
}; | ||
exports.fieldMappers = { | ||
...stringOrNull, | ||
string: { | ||
...Object.fromEntries(Object.entries(stringOrNull['string | null']).map(([airtableType, nullablePair]) => { | ||
return [airtableType, { | ||
toAirtable: nullablePair.toAirtable, | ||
fromAirtable: (value) => { | ||
const nullableValue = nullablePair.fromAirtable(value); | ||
if (nullableValue === null && ['multipleRecordLinks', 'dateTime', 'createdTime', 'lastModifiedTime'].includes(airtableType)) { | ||
throw new Error(`[airtable-ts] Expected non-null or non-empty value to map to string for field type ${airtableType}`); | ||
} | ||
return nullableValue ?? ''; | ||
}, | ||
}]; | ||
})), | ||
}, | ||
...booleanOrNull, | ||
boolean: { | ||
...Object.fromEntries(Object.entries(booleanOrNull['boolean | null']).map(([airtableType, nullablePair]) => { | ||
return [airtableType, { | ||
toAirtable: nullablePair.toAirtable, | ||
fromAirtable: (value) => nullablePair.fromAirtable(value) ?? false, | ||
}]; | ||
})), | ||
}, | ||
...numberOrNull, | ||
number: { | ||
...Object.fromEntries(Object.entries(numberOrNull['number | null']).map(([airtableType, nullablePair]) => { | ||
return [airtableType, { | ||
toAirtable: nullablePair.toAirtable, | ||
fromAirtable: (value) => { | ||
const nullableValue = nullablePair.fromAirtable(value); | ||
if (nullableValue === null) { | ||
throw new Error(`[airtable-ts] Expected non-null or non-empty value to map to number for field type ${airtableType}`); | ||
} | ||
return nullableValue; | ||
}, | ||
}]; | ||
})), | ||
}, | ||
...stringArrayOrNull, | ||
'string[]': { | ||
...Object.fromEntries(Object.entries(stringArrayOrNull['string[] | null']).map(([airtableType, nullablePair]) => { | ||
return [airtableType, { | ||
toAirtable: nullablePair.toAirtable, | ||
fromAirtable: (value) => nullablePair.fromAirtable(value) ?? [], | ||
}]; | ||
})), | ||
}, | ||
}; |
@@ -7,2 +7,16 @@ "use strict"; | ||
const typeUtils_1 = require("./typeUtils"); | ||
const getMapper = (tsType, airtableType) => { | ||
const tsMapper = fieldMappers_1.fieldMappers[tsType]; | ||
if (!tsMapper) { | ||
throw new Error(`[airtable-ts] No mappers for ts type ${tsType}`); | ||
} | ||
if (tsMapper[airtableType]) { | ||
return tsMapper[airtableType]; | ||
} | ||
if (tsMapper.unknown) { | ||
console.warn(`[airtable-ts] Unknown airtable type ${airtableType}. This is not fully supported and exact mapping behaviour may change in a future release.`); | ||
return tsMapper.unknown; | ||
} | ||
throw new Error(`[airtable-ts] Expected to be able to map to ts type ${tsType}, but got airtable type ${airtableType} which can't.`); | ||
}; | ||
/** | ||
@@ -32,14 +46,6 @@ * This function coerces an Airtable record to a TypeScript object, given an | ||
const value = record.fields[fieldDefinition.name]; | ||
const tsMapper = fieldMappers_1.fieldMappers[tsType]; | ||
if (!tsMapper) { | ||
throw new Error(`[airtable-ts] No mappers for ts type ${tsType}`); | ||
} | ||
const specificMapper = tsMapper[fieldDefinition.type]?.fromAirtable; | ||
if (!specificMapper) { | ||
// eslint-disable-next-line no-underscore-dangle | ||
throw new Error(`[airtable-ts] Expected field ${record._table.name}.${fieldNameOrId} to be able to map to ts type ${tsType}, but got airtable type ${fieldDefinition.type} which can't.`); | ||
} | ||
try { | ||
const { fromAirtable } = getMapper(tsType, fieldDefinition.type); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
item[fieldNameOrId] = specificMapper(value); | ||
item[fieldNameOrId] = fromAirtable(value); | ||
} | ||
@@ -93,12 +99,16 @@ catch (error) { | ||
} | ||
const tsMapper = fieldMappers_1.fieldMappers[tsType]; | ||
if (!tsMapper) { | ||
throw new Error(`[airtable-ts] No mappers for ts type ${tsType}`); | ||
try { | ||
const { toAirtable } = getMapper(tsType, fieldDefinition.type); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
item[fieldNameOrId] = toAirtable(value); | ||
} | ||
const specificMapper = tsMapper[fieldDefinition.type]?.toAirtable; | ||
if (!specificMapper) { | ||
throw new Error(`[airtable-ts] Expected field ${airtableTable.name}.${fieldNameOrId} to be able to map to airtable type \`${fieldDefinition.type}\`, but got ts type \`${tsType}\` which can't.`); | ||
catch (error) { | ||
if (error instanceof Error) { | ||
// eslint-disable-next-line no-underscore-dangle | ||
error.message = `Failed to map field ${airtableTable.name}.${fieldNameOrId}: ${error.message}`; | ||
// eslint-disable-next-line no-underscore-dangle | ||
error.stack = `Error: Failed to map field ${airtableTable.name}.${fieldNameOrId}: ${error.stack?.startsWith('Error: ') ? error.stack.slice('Error: '.length) : error.stack}`; | ||
} | ||
throw error; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
item[fieldNameOrId] = specificMapper(value); | ||
}); | ||
@@ -105,0 +115,0 @@ return Object.assign(item, { id: tsRecord.id }); |
@@ -6,3 +6,9 @@ export type TsTypeString = NonNullToString<any> | ToTsTypeString<any>; | ||
export type AirtableTypeString = 'aiText' | 'autoNumber' | 'barcode' | 'button' | 'checkbox' | 'count' | 'createdBy' | 'createdTime' | 'currency' | 'date' | 'dateTime' | 'duration' | 'email' | 'externalSyncSource' | 'formula' | 'lastModifiedBy' | 'lastModifiedTime' | 'lookup' | 'multipleLookupValues' | 'multilineText' | 'multipleAttachments' | 'multipleCollaborators' | 'multipleRecordLinks' | 'multipleSelects' | 'number' | 'percent' | 'phoneNumber' | 'rating' | 'richText' | 'rollup' | 'singleCollaborator' | 'singleLineText' | 'singleSelect' | 'url'; | ||
export type FromAirtableTypeString<T extends AirtableTypeString> = null | (T extends 'url' | 'email' | 'phoneNumber' | 'singleLineText' | 'multilineText' | 'richText' | 'singleSelect' | 'externalSyncSource' | 'date' | 'dateTime' | 'createdTime' | 'lastModifiedTime' ? string : T extends 'multipleRecordLinks' | 'multipleSelects' ? string[] : T extends 'number' | 'rating' | 'duration' | 'currency' | 'percent' | 'count' | 'autoNumber' ? number : T extends 'checkbox' ? boolean : T extends 'lookup' | 'multipleLookupValues' | 'rollup' | 'formula' ? FromAirtableTypeString<any>[] : never); | ||
export type FromAirtableTypeString<T extends AirtableTypeString | 'unknown'> = null | (T extends 'url' | 'email' | 'phoneNumber' | 'singleLineText' | 'multilineText' | 'richText' | 'singleSelect' | 'externalSyncSource' | 'date' | 'dateTime' | 'createdTime' | 'lastModifiedTime' ? string : T extends 'multipleRecordLinks' | 'multipleSelects' ? string[] : T extends 'number' | 'rating' | 'duration' | 'currency' | 'percent' | 'count' | 'autoNumber' ? number : T extends 'checkbox' ? boolean : T extends 'lookup' | 'multipleLookupValues' | 'rollup' | 'formula' ? FromAirtableTypeString<any>[] : T extends 'unknown' ? unknown : never); | ||
interface TypeDef { | ||
single: 'string' | 'number' | 'boolean'; | ||
array: boolean; | ||
nullable: boolean; | ||
} | ||
export declare const parseType: (t: TsTypeString) => TypeDef; | ||
/** | ||
@@ -9,0 +15,0 @@ * Verifies whether the given value is assignable to the given type |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.airtableFieldNameTsTypes = exports.matchesType = void 0; | ||
exports.airtableFieldNameTsTypes = exports.matchesType = exports.parseType = void 0; | ||
const parseType = (t) => { | ||
@@ -32,2 +32,3 @@ if (t.endsWith('[] | null')) { | ||
}; | ||
exports.parseType = parseType; | ||
/** | ||
@@ -46,3 +47,3 @@ * Verifies whether the given value is assignable to the given type | ||
const matchesType = (value, tsType) => { | ||
const expectedType = parseType(tsType); | ||
const expectedType = (0, exports.parseType)(tsType); | ||
if (expectedType.nullable && value === null) { | ||
@@ -49,0 +50,0 @@ return true; |
{ | ||
"name": "airtable-ts", | ||
"version": "1.3.0", | ||
"version": "1.3.1", | ||
"description": "A type-safe Airtable SDK", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50702
996