@proofkit/fmodata
Version:
FileMaker OData API client
294 lines (293 loc) • 9.57 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
const FILEMAKER_LIST_DELIMITER = "\r";
const FILEMAKER_NEWLINE_REGEX = /\r\n|\n/g;
function normalizeFileMakerNewlines(value) {
return value.replace(FILEMAKER_NEWLINE_REGEX, FILEMAKER_LIST_DELIMITER);
}
function splitFileMakerList(value) {
const normalized = normalizeFileMakerNewlines(value);
if (normalized === "") {
return [];
}
return normalized.split(FILEMAKER_LIST_DELIMITER);
}
function issue(message) {
return { message };
}
function validateItemsWithSchema(items, itemValidator) {
const validations = items.map((item) => itemValidator["~standard"].validate(item));
const hasAsyncValidation = validations.some((result) => result instanceof Promise);
const finalize = (results) => {
const transformed = [];
const issues = [];
results.forEach((result, index) => {
if ("issues" in result && result.issues) {
for (const validationIssue of result.issues) {
issues.push({
...validationIssue,
path: validationIssue.path ? [index, ...validationIssue.path] : [index]
});
}
return;
}
if ("value" in result) {
transformed.push(result.value);
}
});
if (issues.length > 0) {
return { issues };
}
return { value: transformed };
};
if (hasAsyncValidation) {
return Promise.all(validations).then((results) => finalize(results));
}
return finalize(validations);
}
class FieldBuilder {
constructor(fieldType) {
__publicField(this, "_primaryKey", false);
__publicField(this, "_notNull", false);
__publicField(this, "_readOnly", false);
__publicField(this, "_entityId");
// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
__publicField(this, "_outputValidator");
// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
__publicField(this, "_inputValidator");
__publicField(this, "_fieldType");
__publicField(this, "_comment");
this._fieldType = fieldType;
}
/**
* Mark this field as the primary key for the table.
* Primary keys are automatically read-only and non-nullable.
*/
primaryKey() {
const builder = this._clone();
builder._primaryKey = true;
builder._notNull = true;
builder._readOnly = true;
return builder;
}
/**
* Mark this field as non-nullable.
* Updates the type to exclude null/undefined.
*/
notNull() {
const builder = this._clone();
builder._notNull = true;
return builder;
}
/**
* Mark this field as read-only.
* Read-only fields are excluded from insert and update operations.
*/
readOnly() {
const builder = this._clone();
builder._readOnly = true;
return builder;
}
/**
* Assign a FileMaker field ID (FMFID) to this field.
* When useEntityIds is enabled, this ID will be used in API requests instead of the field name.
*/
entityId(id) {
const builder = this._clone();
builder._entityId = id;
return builder;
}
/**
* Set a validator for the output (reading from database).
* The output validator transforms/validates data coming FROM the database in list or get operations.
*
* @example
* numberField().readValidator(z.coerce.boolean())
* // FileMaker returns 0/1, you get true/false
*/
readValidator(validator) {
const builder = this._clone();
builder._outputValidator = validator;
return builder;
}
/**
* Set a validator for the input (writing to database).
* The input validator transforms/validates data going TO the database in insert, update, and filter operations.
*
* @example
* numberField().writeValidator(z.boolean().transform(v => v ? 1 : 0))
* // You pass true/false, FileMaker gets 1/0
*/
writeValidator(validator) {
const builder = this._clone();
builder._inputValidator = validator;
return builder;
}
/**
* Add a comment to this field for metadata purposes.
* This helps future developers understand the purpose of the field.
*
* @example
* textField().comment("Account name of the user who last modified each record")
*/
comment(comment) {
const builder = this._clone();
builder._comment = comment;
return builder;
}
/**
* Get the metadata configuration for this field.
* @internal Used by fmTableOccurrence to extract field configuration
*/
_getConfig() {
return {
fieldType: this._fieldType,
primaryKey: this._primaryKey,
notNull: this._notNull,
readOnly: this._readOnly,
entityId: this._entityId,
outputValidator: this._outputValidator,
inputValidator: this._inputValidator,
comment: this._comment
};
}
/**
* Clone this builder to allow immutable chaining.
* @private
*/
_clone() {
const builder = new FieldBuilder(this._fieldType);
builder._primaryKey = this._primaryKey;
builder._notNull = this._notNull;
builder._readOnly = this._readOnly;
builder._entityId = this._entityId;
builder._outputValidator = this._outputValidator;
builder._inputValidator = this._inputValidator;
builder._comment = this._comment;
return builder;
}
}
function textField() {
return new FieldBuilder("text");
}
function listField(options) {
const allowNull = (options == null ? void 0 : options.allowNull) ?? false;
const itemValidator = options == null ? void 0 : options.itemValidator;
const readListSchema = {
"~standard": {
version: 1,
vendor: "proofkit",
validate(input) {
if (input === null || input === void 0 || input === "") {
return { value: allowNull ? null : [] };
}
if (typeof input !== "string") {
return { issues: [issue("Expected a FileMaker list string or null")] };
}
const items = splitFileMakerList(input);
if (!itemValidator) {
return { value: items };
}
const validatedItems = validateItemsWithSchema(items, itemValidator);
if (validatedItems instanceof Promise) {
return validatedItems.then((result) => {
if ("issues" in result) {
return result;
}
return { value: result.value };
});
}
if ("issues" in validatedItems) {
return validatedItems;
}
return { value: validatedItems.value };
}
}
};
const writeListSchema = {
"~standard": {
version: 1,
vendor: "proofkit",
validate(input) {
if (input === null || input === void 0) {
return { value: allowNull ? null : "" };
}
if (!Array.isArray(input)) {
return { issues: [issue("Expected an array for FileMaker list field input")] };
}
if (!itemValidator) {
const hasNonStringItem = input.some((item) => typeof item !== "string");
if (hasNonStringItem) {
return { issues: [issue("Expected all list items to be strings without an itemValidator")] };
}
const serialized = input.map((item) => normalizeFileMakerNewlines(item)).join(FILEMAKER_LIST_DELIMITER);
return { value: serialized };
}
const validateInputItems = input.map((item) => itemValidator["~standard"].validate(item));
const hasAsyncValidation = validateInputItems.some((result) => result instanceof Promise);
const serializeValidated = (results) => {
const validatedItems = [];
const issues = [];
results.forEach((result, index) => {
if ("issues" in result && result.issues) {
for (const validationIssue of result.issues) {
issues.push({
...validationIssue,
path: validationIssue.path ? [index, ...validationIssue.path] : [index]
});
}
return;
}
if ("value" in result) {
validatedItems.push(result.value);
}
});
if (issues.length > 0) {
return { issues };
}
const serialized = validatedItems.map((item) => normalizeFileMakerNewlines(typeof item === "string" ? item : String(item))).join(FILEMAKER_LIST_DELIMITER);
return { value: serialized };
};
if (hasAsyncValidation) {
return Promise.all(validateInputItems).then((results) => serializeValidated(results));
}
return serializeValidated(
validateInputItems
);
}
}
};
return textField().readValidator(readListSchema).writeValidator(writeListSchema);
}
function numberField() {
return new FieldBuilder("number");
}
function dateField() {
return new FieldBuilder("date");
}
function timeField() {
return new FieldBuilder("time");
}
function timestampField() {
return new FieldBuilder("timestamp");
}
function containerField() {
return new FieldBuilder("container");
}
function calcField() {
const builder = new FieldBuilder("calculated");
return builder.readOnly();
}
export {
FieldBuilder,
calcField,
containerField,
dateField,
listField,
numberField,
textField,
timeField,
timestampField
};
//# sourceMappingURL=field-builders.js.map