@proofkit/fmodata
Version:
FileMaker OData API client
290 lines (255 loc) • 8.61 kB
text/typescript
import { type FFetchOptions } from "@fetchkit/ffetch";
import type { StandardSchemaV1 } from "@standard-schema/spec";
export type Auth = { username: string; password: string } | { apiKey: string };
export interface ExecutableBuilder<T> {
execute(): Promise<Result<T>>;
getRequestConfig(): { method: string; url: string; body?: any };
/**
* Convert this builder to a native Request object for batch processing.
* @param baseUrl - The base URL for the OData service
* @returns A native Request object
*/
toRequest(baseUrl: string): Request;
/**
* Process a raw Response object into a typed Result.
* This allows builders to apply their own validation and transformation logic.
* @param response - The native Response object from the batch operation
* @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)
* @returns A typed Result with the builder's expected return type
*/
processResponse(
response: Response,
options?: ExecuteOptions,
): Promise<Result<T>>;
}
export interface ExecutionContext {
_makeRequest<T>(
url: string,
options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
): Promise<Result<T>>;
_setUseEntityIds?(useEntityIds: boolean): void;
_getUseEntityIds?(): boolean;
_getBaseUrl?(): string;
}
export type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {
[K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output>
? Output
: never;
};
export type WithSystemFields<T> =
T extends Record<string, any>
? T & {
ROWID: number;
ROWMODID: number;
}
: never;
// Helper type to exclude system fields from a union of keys
export type ExcludeSystemFields<T extends keyof any> = Exclude<
T,
"ROWID" | "ROWMODID"
>;
// Helper type to omit system fields from an object type
export type OmitSystemFields<T> = Omit<T, "ROWID" | "ROWMODID">;
// OData record metadata fields (present on each record)
export type ODataRecordMetadata = {
"@id": string;
"@editLink": string;
};
// OData response wrapper (top-level, internal use only)
export type ODataListResponse<T> = {
"@context": string;
value: (T & ODataRecordMetadata)[];
};
export type ODataSingleResponse<T> = T &
ODataRecordMetadata & {
"@context": string;
};
// OData response for single field values
export type ODataFieldResponse<T> = {
"@context": string;
value: T;
};
// Result pattern for execute responses
export type Result<T, E = import("./errors").FMODataErrorType> =
| { data: T; error: undefined }
| { data: undefined; error: E };
// Make specific keys required, rest optional
export type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> &
Required<Pick<T, Keys>>;
// Extract keys from schema where validator doesn't allow null/undefined (auto-required fields)
export type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> =
{
[K in keyof Schema]: Extract<
StandardSchemaV1.InferOutput<Schema[K]>,
null | undefined
> extends never
? K
: never;
}[keyof Schema];
// Helper type to compute excluded fields (readOnly fields + idField)
export type ExcludedFields<
IdField extends keyof any | undefined,
ReadOnly extends readonly any[],
> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];
// Helper type for InsertData computation
type ComputeInsertData<
Schema extends Record<string, StandardSchemaV1>,
IdField extends keyof Schema | undefined,
Required extends readonly any[],
ReadOnly extends readonly any[],
> = [Required[number]] extends [keyof InferSchemaType<Schema>]
? Required extends readonly (keyof InferSchemaType<Schema>)[]
? MakeFieldsRequired<
Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,
Exclude<
AutoRequiredKeys<Schema> | Required[number],
ExcludedFields<IdField, ReadOnly>
>
>
: MakeFieldsRequired<
Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,
Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>
>
: MakeFieldsRequired<
Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,
Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>
>;
// Extract insert data type from BaseTable
// Auto-infers required fields from validator nullability + user-specified required fields
// Excludes readOnly fields and idField
export type InsertData<BT> = BT extends import("./client/base-table").BaseTable<
any,
any,
any,
any
>
? BT extends {
schema: infer Schema;
idField?: infer IdField;
required?: infer Required;
readOnly?: infer ReadOnly;
}
? Schema extends Record<string, StandardSchemaV1>
? IdField extends keyof Schema | undefined
? Required extends readonly any[]
? ReadOnly extends readonly any[]
? ComputeInsertData<
Schema,
Extract<IdField, keyof Schema | undefined>,
Required,
ReadOnly
>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>;
// Extract update data type from BaseTable
// All fields are optional for updates, excludes readOnly fields and idField
export type UpdateData<BT> = BT extends import("./client/base-table").BaseTable<
any,
any,
any,
any
>
? BT extends {
schema: infer Schema;
idField?: infer IdField;
readOnly?: infer ReadOnly;
}
? Schema extends Record<string, StandardSchemaV1>
? IdField extends keyof Schema | undefined
? ReadOnly extends readonly any[]
? Partial<
Omit<
InferSchemaType<Schema>,
ExcludedFields<
Extract<IdField, keyof Schema | undefined>,
ReadOnly
>
>
>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>
: Partial<Record<string, any>>;
export type ExecuteOptions = {
includeODataAnnotations?: boolean;
skipValidation?: boolean;
/**
* Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY
*/
useEntityIds?: boolean;
};
export type ConditionallyWithODataAnnotations<
T,
IncludeODataAnnotations extends boolean,
> = IncludeODataAnnotations extends true
? T & {
"@id": string;
"@editLink": string;
}
: T;
// Helper type to extract schema from a TableOccurrence
export type ExtractSchemaFromOccurrence<Occ> = Occ extends {
baseTable: { schema: infer S };
}
? S extends Record<string, StandardSchemaV1>
? S
: Record<string, StandardSchemaV1>
: Record<string, StandardSchemaV1>;
export type GenericFieldMetadata = {
$Nullable?: boolean;
"@Index"?: boolean;
"@Calculation"?: boolean;
"@Summary"?: boolean;
"@Global"?: boolean;
"@Org.OData.Core.V1.Permissions"?: "Org.OData.Core.V1.Permission@Read";
};
export type StringFieldMetadata = GenericFieldMetadata & {
$Type: "Edm.String";
$DefaultValue?: "USER" | "USERNAME" | "CURRENT_USER";
$MaxLength?: number;
};
export type DecimalFieldMetadata = GenericFieldMetadata & {
$Type: "Edm.Decimal";
"@AutoGenerated"?: boolean;
};
export type DateFieldMetadata = GenericFieldMetadata & {
$Type: "Edm.Date";
$DefaultValue?: "CURDATE" | "CURRENT_DATE";
};
export type TimeOfDayFieldMetadata = GenericFieldMetadata & {
$Type: "Edm.TimeOfDay";
$DefaultValue?: "CURTIME" | "CURRENT_TIME";
};
export type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {
$Type: "Edm.Date";
$DefaultValue?: "CURTIMESTAMP" | "CURRENT_TIMESTAMP";
"@VersionId"?: boolean;
};
export type StreamFieldMetadata = {
$Type: "Edm.Stream";
$Nullable?: boolean;
"@EnclosedPath": string;
"@ExternalOpenPath": string;
"@ExternalSecurePath"?: string;
};
export type FieldMetadata =
| StringFieldMetadata
| DecimalFieldMetadata
| DateFieldMetadata
| TimeOfDayFieldMetadata
| DateTimeOffsetFieldMetadata
| StreamFieldMetadata;
export type EntityType = {
$Kind: "EntityType";
$Key: string[];
} & Record<string, FieldMetadata>;
export type EntitySet = {
$Kind: "EntitySet";
$Type: string;
};
export type Metadata = Record<string, EntityType | EntitySet>;