@proofkit/better-auth 0.2.4 → 0.3.1-beta.0

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.
@@ -1,21 +1,17 @@
1
1
  #!/usr/bin/env node --no-warnings
2
2
  import { Command } from "@commander-js/extra-typings";
3
- import fs from "fs-extra";
4
- import { planMigration, prettyPrintMigrationPlan, executeMigration } from "../migrate.js";
5
- import { getAdapter, getAuthTables } from "better-auth/db";
6
- import { getConfig } from "../better-auth-cli/utils/get-config.js";
7
3
  import { logger } from "better-auth";
8
- import prompts from "prompts";
4
+ import { getAdapter, getAuthTables } from "better-auth/db";
9
5
  import chalk from "chalk";
10
- import { createFmOdataFetch } from "../odata/index.js";
6
+ import fs from "fs-extra";
7
+ import prompts from "prompts";
8
+ import { getConfig } from "../better-auth-cli/utils/get-config.js";
9
+ import { planMigration, prettyPrintMigrationPlan, executeMigration } from "../migrate.js";
10
+ import { createRawFetch } from "../odata/index.js";
11
11
  import "dotenv/config";
12
12
  async function main() {
13
13
  const program = new Command();
14
- program.command("migrate", { isDefault: true }).option(
15
- "--cwd <path>",
16
- "Path to the current working directory",
17
- process.cwd()
18
- ).option("--config <path>", "Path to the config file").option("-u, --username <username>", "Full Access Username").option("-p, --password <password>", "Full Access Password").option("-y, --yes", "Skip confirmation", false).action(async (options) => {
14
+ program.command("migrate", { isDefault: true }).option("--cwd <path>", "Path to the current working directory", process.cwd()).option("--config <path>", "Path to the config file").option("-u, --username <username>", "Full Access Username").option("-p, --password <password>", "Full Access Password").option("-y, --yes", "Skip confirmation", false).action(async (options) => {
19
15
  const cwd = options.cwd;
20
16
  if (!fs.existsSync(cwd)) {
21
17
  logger.error(`The directory "${cwd}" does not exist.`);
@@ -36,14 +32,12 @@ async function main() {
36
32
  process.exit(1);
37
33
  });
38
34
  if (adapter.id !== "filemaker") {
39
- logger.error(
40
- "This generator is only compatible with the FileMaker adapter."
41
- );
35
+ logger.error("This generator is only compatible with the FileMaker adapter.");
42
36
  return;
43
37
  }
44
38
  const betterAuthSchema = getAuthTables(config);
45
39
  const adapterConfig = adapter.options.config;
46
- const fetch = createFmOdataFetch({
40
+ const { fetch } = createRawFetch({
47
41
  ...adapterConfig.odata,
48
42
  auth: (
49
43
  // If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.
@@ -51,13 +45,11 @@ async function main() {
51
45
  username: options.username,
52
46
  password: options.password
53
47
  } : adapterConfig.odata.auth
54
- )
48
+ ),
49
+ logging: "verbose"
50
+ // Enable logging for CLI operations
55
51
  });
56
- const migrationPlan = await planMigration(
57
- fetch,
58
- betterAuthSchema,
59
- adapterConfig.odata.database
60
- );
52
+ const migrationPlan = await planMigration(fetch, betterAuthSchema, adapterConfig.odata.database);
61
53
  if (migrationPlan.length === 0) {
62
54
  logger.info("No changes to apply. Database is up to date.");
63
55
  return;
@@ -65,11 +57,7 @@ async function main() {
65
57
  if (!options.yes) {
66
58
  prettyPrintMigrationPlan(migrationPlan);
67
59
  if (migrationPlan.length > 0) {
68
- console.log(
69
- chalk.gray(
70
- "💡 Tip: You can use the --yes flag to skip this confirmation."
71
- )
72
- );
60
+ console.log(chalk.gray("💡 Tip: You can use the --yes flag to skip this confirmation."));
73
61
  }
74
62
  const { confirm } = await prompts({
75
63
  type: "confirm",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node --no-warnings\nimport { Command } from \"@commander-js/extra-typings\";\nimport fs from \"fs-extra\";\n\nimport {\n executeMigration,\n planMigration,\n prettyPrintMigrationPlan,\n} from \"../migrate\";\nimport { getAdapter, getAuthTables } from \"better-auth/db\";\nimport { getConfig } from \"../better-auth-cli/utils/get-config\";\nimport { logger } from \"better-auth\";\nimport prompts from \"prompts\";\nimport chalk from \"chalk\";\nimport { AdapterOptions } from \"../adapter\";\nimport { createFmOdataFetch } from \"../odata\";\nimport \"dotenv/config\";\n\nasync function main() {\n const program = new Command();\n\n program\n .command(\"migrate\", { isDefault: true })\n .option(\n \"--cwd <path>\",\n \"Path to the current working directory\",\n process.cwd(),\n )\n .option(\"--config <path>\", \"Path to the config file\")\n .option(\"-u, --username <username>\", \"Full Access Username\")\n .option(\"-p, --password <password>\", \"Full Access Password\")\n .option(\"-y, --yes\", \"Skip confirmation\", false)\n\n .action(async (options) => {\n const cwd = options.cwd;\n if (!fs.existsSync(cwd)) {\n logger.error(`The directory \"${cwd}\" does not exist.`);\n process.exit(1);\n }\n\n const config = await getConfig({\n cwd,\n configPath: options.config,\n });\n if (!config) {\n logger.error(\n \"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.\",\n );\n return;\n }\n\n const adapter = await getAdapter(config).catch((e) => {\n logger.error(e.message);\n process.exit(1);\n });\n\n if (adapter.id !== \"filemaker\") {\n logger.error(\n \"This generator is only compatible with the FileMaker adapter.\",\n );\n return;\n }\n\n const betterAuthSchema = getAuthTables(config);\n\n const adapterConfig = (adapter.options as AdapterOptions).config;\n const fetch = createFmOdataFetch({\n ...adapterConfig.odata,\n auth:\n // If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.\n options.username && options.password\n ? {\n username: options.username,\n password: options.password,\n }\n : adapterConfig.odata.auth,\n });\n\n const migrationPlan = await planMigration(\n fetch,\n betterAuthSchema,\n adapterConfig.odata.database,\n );\n\n if (migrationPlan.length === 0) {\n logger.info(\"No changes to apply. Database is up to date.\");\n return;\n }\n\n if (!options.yes) {\n prettyPrintMigrationPlan(migrationPlan);\n\n if (migrationPlan.length > 0) {\n console.log(\n chalk.gray(\n \"💡 Tip: You can use the --yes flag to skip this confirmation.\",\n ),\n );\n }\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Apply the above changes to your database?\",\n });\n if (!confirm) {\n logger.error(\"Schema changes not applied.\");\n return;\n }\n }\n\n await executeMigration(fetch, migrationPlan);\n\n logger.info(\"Migration applied successfully.\");\n });\n await program.parseAsync(process.argv);\n process.exit(0);\n}\n\nmain().catch(console.error);\n"],"names":[],"mappings":";;;;;;;;;;;AAkBA,eAAe,OAAO;AACd,QAAA,UAAU,IAAI,QAAQ;AAE5B,UACG,QAAQ,WAAW,EAAE,WAAW,KAAM,CAAA,EACtC;AAAA,IACC;AAAA,IACA;AAAA,IACA,QAAQ,IAAI;AAAA,EAAA,EAEb,OAAO,mBAAmB,yBAAyB,EACnD,OAAO,6BAA6B,sBAAsB,EAC1D,OAAO,6BAA6B,sBAAsB,EAC1D,OAAO,aAAa,qBAAqB,KAAK,EAE9C,OAAO,OAAO,YAAY;AACzB,UAAM,MAAM,QAAQ;AACpB,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AAChB,aAAA,MAAM,kBAAkB,GAAG,mBAAmB;AACrD,cAAQ,KAAK,CAAC;AAAA,IAAA;AAGV,UAAA,SAAS,MAAM,UAAU;AAAA,MAC7B;AAAA,MACA,YAAY,QAAQ;AAAA,IAAA,CACrB;AACD,QAAI,CAAC,QAAQ;AACJ,aAAA;AAAA,QACL;AAAA,MACF;AACA;AAAA,IAAA;AAGF,UAAM,UAAU,MAAM,WAAW,MAAM,EAAE,MAAM,CAAC,MAAM;AAC7C,aAAA,MAAM,EAAE,OAAO;AACtB,cAAQ,KAAK,CAAC;AAAA,IAAA,CACf;AAEG,QAAA,QAAQ,OAAO,aAAa;AACvB,aAAA;AAAA,QACL;AAAA,MACF;AACA;AAAA,IAAA;AAGI,UAAA,mBAAmB,cAAc,MAAM;AAEvC,UAAA,gBAAiB,QAAQ,QAA2B;AAC1D,UAAM,QAAQ,mBAAmB;AAAA,MAC/B,GAAG,cAAc;AAAA,MACjB;AAAA;AAAA,QAEE,QAAQ,YAAY,QAAQ,WACxB;AAAA,UACE,UAAU,QAAQ;AAAA,UAClB,UAAU,QAAQ;AAAA,QAAA,IAEpB,cAAc,MAAM;AAAA;AAAA,IAAA,CAC3B;AAED,UAAM,gBAAgB,MAAM;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,cAAc,MAAM;AAAA,IACtB;AAEI,QAAA,cAAc,WAAW,GAAG;AAC9B,aAAO,KAAK,8CAA8C;AAC1D;AAAA,IAAA;AAGE,QAAA,CAAC,QAAQ,KAAK;AAChB,+BAAyB,aAAa;AAElC,UAAA,cAAc,SAAS,GAAG;AACpB,gBAAA;AAAA,UACN,MAAM;AAAA,YACJ;AAAA,UAAA;AAAA,QAEJ;AAAA,MAAA;AAGF,YAAM,EAAE,YAAY,MAAM,QAAQ;AAAA,QAChC,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AACD,UAAI,CAAC,SAAS;AACZ,eAAO,MAAM,6BAA6B;AAC1C;AAAA,MAAA;AAAA,IACF;AAGI,UAAA,iBAAiB,OAAO,aAAa;AAE3C,WAAO,KAAK,iCAAiC;AAAA,EAAA,CAC9C;AACG,QAAA,QAAQ,WAAW,QAAQ,IAAI;AACrC,UAAQ,KAAK,CAAC;AAChB;AAEA,OAAO,MAAM,QAAQ,KAAK;"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node --no-warnings\nimport { Command } from \"@commander-js/extra-typings\";\nimport { logger } from \"better-auth\";\nimport { getAdapter, getAuthTables } from \"better-auth/db\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport type { AdapterOptions } from \"../adapter\";\nimport { getConfig } from \"../better-auth-cli/utils/get-config\";\nimport { executeMigration, planMigration, prettyPrintMigrationPlan } from \"../migrate\";\nimport { createRawFetch } from \"../odata\";\nimport \"dotenv/config\";\n\nasync function main() {\n const program = new Command();\n\n program\n .command(\"migrate\", { isDefault: true })\n .option(\"--cwd <path>\", \"Path to the current working directory\", process.cwd())\n .option(\"--config <path>\", \"Path to the config file\")\n .option(\"-u, --username <username>\", \"Full Access Username\")\n .option(\"-p, --password <password>\", \"Full Access Password\")\n .option(\"-y, --yes\", \"Skip confirmation\", false)\n\n .action(async (options) => {\n const cwd = options.cwd;\n if (!fs.existsSync(cwd)) {\n logger.error(`The directory \"${cwd}\" does not exist.`);\n process.exit(1);\n }\n\n const config = await getConfig({\n cwd,\n configPath: options.config,\n });\n if (!config) {\n logger.error(\n \"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.\",\n );\n return;\n }\n\n const adapter = await getAdapter(config).catch((e) => {\n logger.error(e.message);\n process.exit(1);\n });\n\n if (adapter.id !== \"filemaker\") {\n logger.error(\"This generator is only compatible with the FileMaker adapter.\");\n return;\n }\n\n const betterAuthSchema = getAuthTables(config);\n\n const adapterConfig = (adapter.options as AdapterOptions).config;\n const { fetch } = createRawFetch({\n ...adapterConfig.odata,\n auth:\n // If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.\n options.username && options.password\n ? {\n username: options.username,\n password: options.password,\n }\n : adapterConfig.odata.auth,\n logging: \"verbose\", // Enable logging for CLI operations\n });\n\n const migrationPlan = await planMigration(fetch, betterAuthSchema, adapterConfig.odata.database);\n\n if (migrationPlan.length === 0) {\n logger.info(\"No changes to apply. Database is up to date.\");\n return;\n }\n\n if (!options.yes) {\n prettyPrintMigrationPlan(migrationPlan);\n\n if (migrationPlan.length > 0) {\n console.log(chalk.gray(\"💡 Tip: You can use the --yes flag to skip this confirmation.\"));\n }\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Apply the above changes to your database?\",\n });\n if (!confirm) {\n logger.error(\"Schema changes not applied.\");\n return;\n }\n }\n\n await executeMigration(fetch, migrationPlan);\n\n logger.info(\"Migration applied successfully.\");\n });\n await program.parseAsync(process.argv);\n process.exit(0);\n}\n\nmain().catch(console.error);\n"],"names":[],"mappings":";;;;;;;;;;;AAaA,eAAe,OAAO;AACd,QAAA,UAAU,IAAI,QAAQ;AAE5B,UACG,QAAQ,WAAW,EAAE,WAAW,MAAM,EACtC,OAAO,gBAAgB,yCAAyC,QAAQ,IAAI,CAAC,EAC7E,OAAO,mBAAmB,yBAAyB,EACnD,OAAO,6BAA6B,sBAAsB,EAC1D,OAAO,6BAA6B,sBAAsB,EAC1D,OAAO,aAAa,qBAAqB,KAAK,EAE9C,OAAO,OAAO,YAAY;AACzB,UAAM,MAAM,QAAQ;AACpB,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AAChB,aAAA,MAAM,kBAAkB,GAAG,mBAAmB;AACrD,cAAQ,KAAK,CAAC;AAAA,IAAA;AAGV,UAAA,SAAS,MAAM,UAAU;AAAA,MAC7B;AAAA,MACA,YAAY,QAAQ;AAAA,IAAA,CACrB;AACD,QAAI,CAAC,QAAQ;AACJ,aAAA;AAAA,QACL;AAAA,MACF;AACA;AAAA,IAAA;AAGF,UAAM,UAAU,MAAM,WAAW,MAAM,EAAE,MAAM,CAAC,MAAM;AAC7C,aAAA,MAAM,EAAE,OAAO;AACtB,cAAQ,KAAK,CAAC;AAAA,IAAA,CACf;AAEG,QAAA,QAAQ,OAAO,aAAa;AAC9B,aAAO,MAAM,+DAA+D;AAC5E;AAAA,IAAA;AAGI,UAAA,mBAAmB,cAAc,MAAM;AAEvC,UAAA,gBAAiB,QAAQ,QAA2B;AACpD,UAAA,EAAE,MAAM,IAAI,eAAe;AAAA,MAC/B,GAAG,cAAc;AAAA,MACjB;AAAA;AAAA,QAEE,QAAQ,YAAY,QAAQ,WACxB;AAAA,UACE,UAAU,QAAQ;AAAA,UAClB,UAAU,QAAQ;AAAA,QAAA,IAEpB,cAAc,MAAM;AAAA;AAAA,MAC1B,SAAS;AAAA;AAAA,IAAA,CACV;AAED,UAAM,gBAAgB,MAAM,cAAc,OAAO,kBAAkB,cAAc,MAAM,QAAQ;AAE3F,QAAA,cAAc,WAAW,GAAG;AAC9B,aAAO,KAAK,8CAA8C;AAC1D;AAAA,IAAA;AAGE,QAAA,CAAC,QAAQ,KAAK;AAChB,+BAAyB,aAAa;AAElC,UAAA,cAAc,SAAS,GAAG;AAC5B,gBAAQ,IAAI,MAAM,KAAK,+DAA+D,CAAC;AAAA,MAAA;AAGzF,YAAM,EAAE,YAAY,MAAM,QAAQ;AAAA,QAChC,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AACD,UAAI,CAAC,SAAS;AACZ,eAAO,MAAM,6BAA6B;AAC1C;AAAA,MAAA;AAAA,IACF;AAGI,UAAA,iBAAiB,OAAO,aAAa;AAE3C,WAAO,KAAK,iCAAiC;AAAA,EAAA,CAC9C;AACG,QAAA,QAAQ,WAAW,QAAQ,IAAI;AACrC,UAAQ,KAAK,CAAC;AAChB;AAEA,OAAO,MAAM,QAAQ,KAAK;"}
@@ -1,15 +1,15 @@
1
1
  import { BetterAuthDbSchema } from 'better-auth/db';
2
2
  import { Metadata } from 'fm-odata-client';
3
3
  import { default as z } from 'zod/v4';
4
- import { createFmOdataFetch } from './odata.js';
5
- export declare function getMetadata(fetch: ReturnType<typeof createFmOdataFetch>, databaseName: string): Promise<Metadata | null>;
6
- export declare function planMigration(fetch: ReturnType<typeof createFmOdataFetch>, betterAuthSchema: BetterAuthDbSchema, databaseName: string): Promise<MigrationPlan>;
7
- export declare function executeMigration(fetch: ReturnType<typeof createFmOdataFetch>, migrationPlan: MigrationPlan): Promise<void>;
4
+ import { createRawFetch } from './odata.js';
5
+ export declare function getMetadata(fetch: ReturnType<typeof createRawFetch>["fetch"], databaseName: string): Promise<Metadata | null>;
6
+ export declare function planMigration(fetch: ReturnType<typeof createRawFetch>["fetch"], betterAuthSchema: BetterAuthDbSchema, databaseName: string): Promise<MigrationPlan>;
7
+ export declare function executeMigration(fetch: ReturnType<typeof createRawFetch>["fetch"], migrationPlan: MigrationPlan): Promise<void>;
8
8
  declare const migrationPlanSchema: z.ZodArray<z.ZodObject<{
9
9
  tableName: z.ZodString;
10
10
  operation: z.ZodEnum<{
11
- create: "create";
12
11
  update: "update";
12
+ create: "create";
13
13
  }>;
14
14
  fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
15
15
  name: z.ZodString;
@@ -78,7 +78,7 @@ declare const migrationPlanSchema: z.ZodArray<z.ZodObject<{
78
78
  repetitions: z.ZodOptional<z.ZodNumber>;
79
79
  type: z.ZodLiteral<"container">;
80
80
  externalSecurePath: z.ZodOptional<z.ZodString>;
81
- }, z.core.$strip>]>>;
81
+ }, z.core.$strip>], "type">>;
82
82
  }, z.core.$strip>>;
83
83
  export type MigrationPlan = z.infer<typeof migrationPlanSchema>;
84
84
  export declare function prettyPrintMigrationPlan(migrationPlan: MigrationPlan): void;
@@ -11,11 +11,15 @@ async function getMetadata(fetch, databaseName) {
11
11
  "@ServerVersion": z.string()
12
12
  }).or(z.null()).catch(null)
13
13
  });
14
+ if (result.error) {
15
+ console.error("Failed to get metadata:", result.error);
16
+ return null;
17
+ }
14
18
  return ((_a = result.data) == null ? void 0 : _a[databaseName]) ?? null;
15
19
  }
16
20
  async function planMigration(fetch, betterAuthSchema, databaseName) {
17
21
  const metadata = await getMetadata(fetch, databaseName);
18
- let entitySetToType = {};
22
+ const entitySetToType = {};
19
23
  if (metadata) {
20
24
  for (const [key, value] of Object.entries(metadata)) {
21
25
  if (value.$Kind === "EntitySet" && value.$Type) {
@@ -27,13 +31,25 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
27
31
  const existingTables = metadata ? Object.entries(entitySetToType).reduce(
28
32
  (acc, [entitySetName, entityTypeKey]) => {
29
33
  const entityType = metadata[entityTypeKey];
30
- if (!entityType) return acc;
34
+ if (!entityType) {
35
+ return acc;
36
+ }
31
37
  const fields = Object.entries(entityType).filter(
32
- ([fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue
33
- ).map(([fieldKey, fieldValue]) => ({
34
- name: fieldKey,
35
- type: fieldValue.$Type === "Edm.String" ? "varchar" : fieldValue.$Type === "Edm.DateTimeOffset" ? "timestamp" : fieldValue.$Type === "Edm.Decimal" || fieldValue.$Type === "Edm.Int32" || fieldValue.$Type === "Edm.Int64" ? "numeric" : "varchar"
36
- }));
38
+ ([_fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue
39
+ ).map(([fieldKey, fieldValue]) => {
40
+ let type = "varchar";
41
+ if (fieldValue.$Type === "Edm.String") {
42
+ type = "varchar";
43
+ } else if (fieldValue.$Type === "Edm.DateTimeOffset") {
44
+ type = "timestamp";
45
+ } else if (fieldValue.$Type === "Edm.Decimal" || fieldValue.$Type === "Edm.Int32" || fieldValue.$Type === "Edm.Int64") {
46
+ type = "numeric";
47
+ }
48
+ return {
49
+ name: fieldKey,
50
+ type
51
+ };
52
+ });
37
53
  acc[entitySetName] = fields;
38
54
  return acc;
39
55
  },
@@ -45,34 +61,21 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
45
61
  }));
46
62
  const migrationPlan = [];
47
63
  for (const baTable of baTables) {
48
- const fields = Object.entries(baTable.fields).map(
49
- ([key, field]) => ({
64
+ const fields = Object.entries(baTable.fields).map(([key, field]) => {
65
+ let type = "varchar";
66
+ if (field.type === "boolean" || field.type.includes("number")) {
67
+ type = "numeric";
68
+ } else if (field.type === "date") {
69
+ type = "timestamp";
70
+ }
71
+ return {
50
72
  name: field.fieldName ?? key,
51
- type: field.type === "boolean" || field.type.includes("number") ? "numeric" : field.type === "date" ? "timestamp" : "varchar"
52
- })
53
- );
54
- const tableExists = Object.prototype.hasOwnProperty.call(
55
- existingTables,
56
- baTable.modelName
57
- );
58
- if (!tableExists) {
59
- migrationPlan.push({
60
- tableName: baTable.modelName,
61
- operation: "create",
62
- fields: [
63
- {
64
- name: "id",
65
- type: "varchar",
66
- primary: true,
67
- unique: true
68
- },
69
- ...fields
70
- ]
71
- });
72
- } else {
73
- const existingFields = (existingTables[baTable.modelName] || []).map(
74
- (f) => f.name
75
- );
73
+ type
74
+ };
75
+ });
76
+ const tableExists = baTable.modelName in existingTables;
77
+ if (tableExists) {
78
+ const existingFields = (existingTables[baTable.modelName] || []).map((f) => f.name);
76
79
  const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(
77
80
  (acc, f) => {
78
81
  acc[f.name] = f.type;
@@ -80,16 +83,14 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
80
83
  },
81
84
  {}
82
85
  );
83
- fields.forEach((field) => {
86
+ for (const field of fields) {
84
87
  if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {
85
88
  console.warn(
86
89
  `⚠️ WARNING: Field '${field.name}' in table '${baTable.modelName}' exists but has type '${existingFieldMap[field.name]}' (expected '${field.type}'). Change the field type in FileMaker to avoid potential errors.`
87
90
  );
88
91
  }
89
- });
90
- const fieldsToAdd = fields.filter(
91
- (f) => !existingFields.includes(f.name)
92
- );
92
+ }
93
+ const fieldsToAdd = fields.filter((f) => !existingFields.includes(f.name));
93
94
  if (fieldsToAdd.length > 0) {
94
95
  migrationPlan.push({
95
96
  tableName: baTable.modelName,
@@ -97,6 +98,20 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
97
98
  fields: fieldsToAdd
98
99
  });
99
100
  }
101
+ } else {
102
+ migrationPlan.push({
103
+ tableName: baTable.modelName,
104
+ operation: "create",
105
+ fields: [
106
+ {
107
+ name: "id",
108
+ type: "varchar",
109
+ primary: true,
110
+ unique: true
111
+ },
112
+ ...fields
113
+ ]
114
+ });
100
115
  }
101
116
  }
102
117
  return migrationPlan;
@@ -105,18 +120,27 @@ async function executeMigration(fetch, migrationPlan) {
105
120
  for (const step of migrationPlan) {
106
121
  if (step.operation === "create") {
107
122
  console.log("Creating table:", step.tableName);
108
- await fetch("@post/FileMaker_Tables", {
123
+ const result = await fetch("/FileMaker_Tables", {
124
+ method: "POST",
109
125
  body: {
110
126
  tableName: step.tableName,
111
127
  fields: step.fields
112
128
  }
113
129
  });
130
+ if (result.error) {
131
+ console.error(`Failed to create table ${step.tableName}:`, result.error);
132
+ throw new Error(`Migration failed: ${result.error}`);
133
+ }
114
134
  } else if (step.operation === "update") {
115
135
  console.log("Adding fields to table:", step.tableName);
116
- await fetch("@post/FileMaker_Tables/:tableName", {
117
- params: { tableName: step.tableName },
136
+ const result = await fetch(`/FileMaker_Tables/${step.tableName}`, {
137
+ method: "PATCH",
118
138
  body: { fields: step.fields }
119
139
  });
140
+ if (result.error) {
141
+ console.error(`Failed to update table ${step.tableName}:`, result.error);
142
+ throw new Error(`Migration failed: ${result.error}`);
143
+ }
120
144
  }
121
145
  }
122
146
  }
@@ -180,8 +204,12 @@ ${emoji} ${step.operation === "create" ? chalk.bold.green("Create table") : chal
180
204
  if (step.fields.length) {
181
205
  for (const field of step.fields) {
182
206
  let fieldDesc = ` - ${field.name} (${field.type}`;
183
- if (field.primary) fieldDesc += ", primary";
184
- if (field.unique) fieldDesc += ", unique";
207
+ if (field.primary) {
208
+ fieldDesc += ", primary";
209
+ }
210
+ if (field.unique) {
211
+ fieldDesc += ", unique";
212
+ }
185
213
  fieldDesc += ")";
186
214
  console.log(fieldDesc);
187
215
  }
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.js","sources":["../../src/migrate.ts"],"sourcesContent":["import { type BetterAuthDbSchema } from \"better-auth/db\";\nimport { type Metadata } from \"fm-odata-client\";\nimport chalk from \"chalk\";\nimport z from \"zod/v4\";\nimport { createFmOdataFetch } from \"./odata\";\n\nexport async function getMetadata(\n fetch: ReturnType<typeof createFmOdataFetch>,\n databaseName: string,\n) {\n console.log(\"getting metadata...\");\n const result = await fetch(\"/$metadata\", {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n output: z\n .looseObject({\n $Version: z.string(),\n \"@ServerVersion\": z.string(),\n })\n .or(z.null())\n .catch(null),\n });\n\n return (result.data?.[databaseName] ?? null) as Metadata | null;\n}\n\nexport async function planMigration(\n fetch: ReturnType<typeof createFmOdataFetch>,\n betterAuthSchema: BetterAuthDbSchema,\n databaseName: string,\n): Promise<MigrationPlan> {\n const metadata = await getMetadata(fetch, databaseName);\n\n // Build a map from entity set name to entity type key\n let entitySetToType: Record<string, string> = {};\n if (metadata) {\n for (const [key, value] of Object.entries(metadata)) {\n if (value.$Kind === \"EntitySet\" && value.$Type) {\n // $Type is like 'betterauth_test.fmp12.proofkit_user_'\n const typeKey = value.$Type.split(\".\").pop(); // e.g., 'proofkit_user_'\n entitySetToType[key] = typeKey || key;\n }\n }\n }\n\n const existingTables = metadata\n ? Object.entries(entitySetToType).reduce(\n (acc, [entitySetName, entityTypeKey]) => {\n const entityType = metadata[entityTypeKey];\n if (!entityType) return acc;\n const fields = Object.entries(entityType)\n .filter(\n ([fieldKey, fieldValue]) =>\n typeof fieldValue === \"object\" &&\n fieldValue !== null &&\n \"$Type\" in fieldValue,\n )\n .map(([fieldKey, fieldValue]) => ({\n name: fieldKey,\n type:\n fieldValue.$Type === \"Edm.String\"\n ? \"varchar\"\n : fieldValue.$Type === \"Edm.DateTimeOffset\"\n ? \"timestamp\"\n : fieldValue.$Type === \"Edm.Decimal\" ||\n fieldValue.$Type === \"Edm.Int32\" ||\n fieldValue.$Type === \"Edm.Int64\"\n ? \"numeric\"\n : \"varchar\",\n }));\n acc[entitySetName] = fields;\n return acc;\n },\n {} as Record<string, { name: string; type: string }[]>,\n )\n : {};\n\n const baTables = Object.entries(betterAuthSchema)\n .sort((a, b) => (a[1].order ?? 0) - (b[1].order ?? 0))\n .map(([key, value]) => ({\n ...value,\n keyName: key,\n }));\n\n const migrationPlan: MigrationPlan = [];\n\n for (const baTable of baTables) {\n const fields: FmField[] = Object.entries(baTable.fields).map(\n ([key, field]) => ({\n name: field.fieldName ?? key,\n type:\n field.type === \"boolean\" || field.type.includes(\"number\")\n ? \"numeric\"\n : field.type === \"date\"\n ? \"timestamp\"\n : \"varchar\",\n }),\n );\n\n // get existing table or create it\n const tableExists = Object.prototype.hasOwnProperty.call(\n existingTables,\n baTable.modelName,\n );\n\n if (!tableExists) {\n migrationPlan.push({\n tableName: baTable.modelName,\n operation: \"create\",\n fields: [\n {\n name: \"id\",\n type: \"varchar\",\n primary: true,\n unique: true,\n },\n ...fields,\n ],\n });\n } else {\n const existingFields = (existingTables[baTable.modelName] || []).map(\n (f) => f.name,\n );\n const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(\n (acc, f) => {\n acc[f.name] = f.type;\n return acc;\n },\n {} as Record<string, string>,\n );\n // Warn about type mismatches (optional, not in plan)\n fields.forEach((field) => {\n if (\n existingFields.includes(field.name) &&\n existingFieldMap[field.name] !== field.type\n ) {\n console.warn(\n `⚠️ WARNING: Field '${field.name}' in table '${baTable.modelName}' exists but has type '${existingFieldMap[field.name]}' (expected '${field.type}'). Change the field type in FileMaker to avoid potential errors.`,\n );\n }\n });\n const fieldsToAdd = fields.filter(\n (f) => !existingFields.includes(f.name),\n );\n if (fieldsToAdd.length > 0) {\n migrationPlan.push({\n tableName: baTable.modelName,\n operation: \"update\",\n fields: fieldsToAdd,\n });\n }\n }\n }\n\n return migrationPlan;\n}\n\nexport async function executeMigration(\n fetch: ReturnType<typeof createFmOdataFetch>,\n migrationPlan: MigrationPlan,\n) {\n for (const step of migrationPlan) {\n if (step.operation === \"create\") {\n console.log(\"Creating table:\", step.tableName);\n await fetch(\"@post/FileMaker_Tables\", {\n body: {\n tableName: step.tableName,\n fields: step.fields,\n },\n });\n } else if (step.operation === \"update\") {\n console.log(\"Adding fields to table:\", step.tableName);\n await fetch(\"@post/FileMaker_Tables/:tableName\", {\n params: { tableName: step.tableName },\n body: { fields: step.fields },\n });\n }\n }\n}\n\nconst genericFieldSchema = z.object({\n name: z.string(),\n nullable: z.boolean().optional(),\n primary: z.boolean().optional(),\n unique: z.boolean().optional(),\n global: z.boolean().optional(),\n repetitions: z.number().optional(),\n});\n\nconst stringFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"varchar\"),\n maxLength: z.number().optional(),\n default: z.enum([\"USER\", \"USERNAME\", \"CURRENT_USER\"]).optional(),\n});\n\nconst numericFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"numeric\"),\n});\n\nconst dateFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"date\"),\n default: z.enum([\"CURRENT_DATE\", \"CURDATE\"]).optional(),\n});\n\nconst timeFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"time\"),\n default: z.enum([\"CURRENT_TIME\", \"CURTIME\"]).optional(),\n});\n\nconst timestampFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"timestamp\"),\n default: z.enum([\"CURRENT_TIMESTAMP\", \"CURTIMESTAMP\"]).optional(),\n});\n\nconst containerFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"container\"),\n externalSecurePath: z.string().optional(),\n});\n\nconst fieldSchema = z.discriminatedUnion(\"type\", [\n stringFieldSchema,\n numericFieldSchema,\n dateFieldSchema,\n timeFieldSchema,\n timestampFieldSchema,\n containerFieldSchema,\n]);\n\ntype FmField = z.infer<typeof fieldSchema>;\n\nconst migrationPlanSchema = z\n .object({\n tableName: z.string(),\n operation: z.enum([\"create\", \"update\"]),\n fields: z.array(fieldSchema),\n })\n .array();\n\nexport type MigrationPlan = z.infer<typeof migrationPlanSchema>;\n\nexport function prettyPrintMigrationPlan(migrationPlan: MigrationPlan) {\n if (!migrationPlan.length) {\n console.log(\"No changes to apply. Database is up to date.\");\n return;\n }\n console.log(chalk.bold.green(\"Migration plan:\"));\n for (const step of migrationPlan) {\n const emoji = step.operation === \"create\" ? \"✅\" : \"✏️\";\n console.log(\n `\\n${emoji} ${step.operation === \"create\" ? chalk.bold.green(\"Create table\") : chalk.bold.yellow(\"Update table\")}: ${step.tableName}`,\n );\n if (step.fields.length) {\n for (const field of step.fields) {\n let fieldDesc = ` - ${field.name} (${field.type}`;\n if (field.primary) fieldDesc += \", primary\";\n if (field.unique) fieldDesc += \", unique\";\n fieldDesc += \")\";\n console.log(fieldDesc);\n }\n } else {\n console.log(\" (No fields to add)\");\n }\n }\n console.log(\"\");\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrate.js","sources":["../../src/migrate.ts"],"sourcesContent":["import type { BetterAuthDbSchema } from \"better-auth/db\";\nimport chalk from \"chalk\";\nimport type { Metadata } from \"fm-odata-client\";\nimport z from \"zod/v4\";\nimport type { createRawFetch } from \"./odata\";\n\nexport async function getMetadata(fetch: ReturnType<typeof createRawFetch>[\"fetch\"], databaseName: string) {\n console.log(\"getting metadata...\");\n const result = await fetch(\"/$metadata\", {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n output: z\n .looseObject({\n $Version: z.string(),\n \"@ServerVersion\": z.string(),\n })\n .or(z.null())\n .catch(null),\n });\n\n if (result.error) {\n console.error(\"Failed to get metadata:\", result.error);\n return null;\n }\n\n return (result.data?.[databaseName] ?? null) as Metadata | null;\n}\n\nexport async function planMigration(\n fetch: ReturnType<typeof createRawFetch>[\"fetch\"],\n betterAuthSchema: BetterAuthDbSchema,\n databaseName: string,\n): Promise<MigrationPlan> {\n const metadata = await getMetadata(fetch, databaseName);\n\n // Build a map from entity set name to entity type key\n const entitySetToType: Record<string, string> = {};\n if (metadata) {\n for (const [key, value] of Object.entries(metadata)) {\n if (value.$Kind === \"EntitySet\" && value.$Type) {\n // $Type is like 'betterauth_test.fmp12.proofkit_user_'\n const typeKey = value.$Type.split(\".\").pop(); // e.g., 'proofkit_user_'\n entitySetToType[key] = typeKey || key;\n }\n }\n }\n\n const existingTables = metadata\n ? Object.entries(entitySetToType).reduce(\n (acc, [entitySetName, entityTypeKey]) => {\n const entityType = metadata[entityTypeKey];\n if (!entityType) {\n return acc;\n }\n const fields = Object.entries(entityType)\n .filter(\n ([_fieldKey, fieldValue]) =>\n typeof fieldValue === \"object\" && fieldValue !== null && \"$Type\" in fieldValue,\n )\n .map(([fieldKey, fieldValue]) => {\n let type = \"varchar\";\n if (fieldValue.$Type === \"Edm.String\") {\n type = \"varchar\";\n } else if (fieldValue.$Type === \"Edm.DateTimeOffset\") {\n type = \"timestamp\";\n } else if (\n fieldValue.$Type === \"Edm.Decimal\" ||\n fieldValue.$Type === \"Edm.Int32\" ||\n fieldValue.$Type === \"Edm.Int64\"\n ) {\n type = \"numeric\";\n }\n return {\n name: fieldKey,\n type,\n };\n });\n acc[entitySetName] = fields;\n return acc;\n },\n {} as Record<string, { name: string; type: string }[]>,\n )\n : {};\n\n const baTables = Object.entries(betterAuthSchema)\n .sort((a, b) => (a[1].order ?? 0) - (b[1].order ?? 0))\n .map(([key, value]) => ({\n ...value,\n keyName: key,\n }));\n\n const migrationPlan: MigrationPlan = [];\n\n for (const baTable of baTables) {\n const fields: FmField[] = Object.entries(baTable.fields).map(([key, field]) => {\n let type: \"varchar\" | \"numeric\" | \"timestamp\" = \"varchar\";\n if (field.type === \"boolean\" || field.type.includes(\"number\")) {\n type = \"numeric\";\n } else if (field.type === \"date\") {\n type = \"timestamp\";\n }\n return {\n name: field.fieldName ?? key,\n type,\n };\n });\n\n // get existing table or create it\n const tableExists = baTable.modelName in existingTables;\n\n if (tableExists) {\n const existingFields = (existingTables[baTable.modelName] || []).map((f) => f.name);\n const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(\n (acc, f) => {\n acc[f.name] = f.type;\n return acc;\n },\n {} as Record<string, string>,\n );\n // Warn about type mismatches (optional, not in plan)\n for (const field of fields) {\n if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {\n console.warn(\n `⚠️ WARNING: Field '${field.name}' in table '${baTable.modelName}' exists but has type '${existingFieldMap[field.name]}' (expected '${field.type}'). Change the field type in FileMaker to avoid potential errors.`,\n );\n }\n }\n const fieldsToAdd = fields.filter((f) => !existingFields.includes(f.name));\n if (fieldsToAdd.length > 0) {\n migrationPlan.push({\n tableName: baTable.modelName,\n operation: \"update\",\n fields: fieldsToAdd,\n });\n }\n } else {\n migrationPlan.push({\n tableName: baTable.modelName,\n operation: \"create\",\n fields: [\n {\n name: \"id\",\n type: \"varchar\",\n primary: true,\n unique: true,\n },\n ...fields,\n ],\n });\n }\n }\n\n return migrationPlan;\n}\n\nexport async function executeMigration(\n fetch: ReturnType<typeof createRawFetch>[\"fetch\"],\n migrationPlan: MigrationPlan,\n) {\n for (const step of migrationPlan) {\n if (step.operation === \"create\") {\n console.log(\"Creating table:\", step.tableName);\n const result = await fetch(\"/FileMaker_Tables\", {\n method: \"POST\",\n body: {\n tableName: step.tableName,\n fields: step.fields,\n },\n });\n\n if (result.error) {\n console.error(`Failed to create table ${step.tableName}:`, result.error);\n throw new Error(`Migration failed: ${result.error}`);\n }\n } else if (step.operation === \"update\") {\n console.log(\"Adding fields to table:\", step.tableName);\n const result = await fetch(`/FileMaker_Tables/${step.tableName}`, {\n method: \"PATCH\",\n body: { fields: step.fields },\n });\n\n if (result.error) {\n console.error(`Failed to update table ${step.tableName}:`, result.error);\n throw new Error(`Migration failed: ${result.error}`);\n }\n }\n }\n}\n\nconst genericFieldSchema = z.object({\n name: z.string(),\n nullable: z.boolean().optional(),\n primary: z.boolean().optional(),\n unique: z.boolean().optional(),\n global: z.boolean().optional(),\n repetitions: z.number().optional(),\n});\n\nconst stringFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"varchar\"),\n maxLength: z.number().optional(),\n default: z.enum([\"USER\", \"USERNAME\", \"CURRENT_USER\"]).optional(),\n});\n\nconst numericFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"numeric\"),\n});\n\nconst dateFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"date\"),\n default: z.enum([\"CURRENT_DATE\", \"CURDATE\"]).optional(),\n});\n\nconst timeFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"time\"),\n default: z.enum([\"CURRENT_TIME\", \"CURTIME\"]).optional(),\n});\n\nconst timestampFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"timestamp\"),\n default: z.enum([\"CURRENT_TIMESTAMP\", \"CURTIMESTAMP\"]).optional(),\n});\n\nconst containerFieldSchema = genericFieldSchema.extend({\n type: z.literal(\"container\"),\n externalSecurePath: z.string().optional(),\n});\n\nconst fieldSchema = z.discriminatedUnion(\"type\", [\n stringFieldSchema,\n numericFieldSchema,\n dateFieldSchema,\n timeFieldSchema,\n timestampFieldSchema,\n containerFieldSchema,\n]);\n\ntype FmField = z.infer<typeof fieldSchema>;\n\nconst migrationPlanSchema = z\n .object({\n tableName: z.string(),\n operation: z.enum([\"create\", \"update\"]),\n fields: z.array(fieldSchema),\n })\n .array();\n\nexport type MigrationPlan = z.infer<typeof migrationPlanSchema>;\n\nexport function prettyPrintMigrationPlan(migrationPlan: MigrationPlan) {\n if (!migrationPlan.length) {\n console.log(\"No changes to apply. Database is up to date.\");\n return;\n }\n console.log(chalk.bold.green(\"Migration plan:\"));\n for (const step of migrationPlan) {\n const emoji = step.operation === \"create\" ? \"✅\" : \"✏️\";\n console.log(\n `\\n${emoji} ${step.operation === \"create\" ? chalk.bold.green(\"Create table\") : chalk.bold.yellow(\"Update table\")}: ${step.tableName}`,\n );\n if (step.fields.length) {\n for (const field of step.fields) {\n let fieldDesc = ` - ${field.name} (${field.type}`;\n if (field.primary) {\n fieldDesc += \", primary\";\n }\n if (field.unique) {\n fieldDesc += \", unique\";\n }\n fieldDesc += \")\";\n console.log(fieldDesc);\n }\n } else {\n console.log(\" (No fields to add)\");\n }\n }\n console.log(\"\");\n}\n"],"names":[],"mappings":"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"}
@@ -1,103 +1,29 @@
1
1
  import { Result } from 'neverthrow';
2
2
  import { z } from 'zod/v4';
3
- type BasicAuthCredentials = {
3
+ interface BasicAuthCredentials {
4
4
  username: string;
5
5
  password: string;
6
- };
7
- type OttoAPIKeyAuth = {
6
+ }
7
+ interface OttoAPIKeyAuth {
8
8
  apiKey: string;
9
- };
9
+ }
10
10
  type ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;
11
- export type FmOdataConfig = {
11
+ export interface FmOdataConfig {
12
12
  serverUrl: string;
13
13
  auth: ODataAuth;
14
14
  database: string;
15
15
  logging?: true | "verbose" | "none";
16
- };
17
- export declare function createFmOdataFetch(args: FmOdataConfig): import('@better-fetch/fetch').BetterFetch<{
18
- baseURL: string;
19
- auth: {
20
- type: "Bearer";
21
- token: string;
22
- username?: undefined;
23
- password?: undefined;
24
- } | {
25
- type: "Basic";
26
- username: string;
27
- password: string;
28
- token?: undefined;
29
- };
30
- onError: (error: import('@better-fetch/fetch').ErrorContext) => void;
31
- schema: {
32
- schema: {
33
- "@post/FileMaker_Tables": {
34
- input: z.ZodObject<{
35
- tableName: z.ZodString;
36
- fields: z.ZodArray<z.ZodAny>;
37
- }, z.core.$strip>;
38
- };
39
- "@patch/FileMaker_Tables/:tableName": {
40
- params: z.ZodObject<{
41
- tableName: z.ZodString;
42
- }, z.core.$strip>;
43
- input: z.ZodObject<{
44
- fields: z.ZodArray<z.ZodAny>;
45
- }, z.core.$strip>;
46
- };
47
- "@delete/FileMaker_Tables/:tableName": {
48
- params: z.ZodObject<{
49
- tableName: z.ZodString;
50
- }, z.core.$strip>;
51
- };
52
- "@delete/FileMaker_Tables/:tableName/:fieldName": {
53
- params: z.ZodObject<{
54
- tableName: z.ZodString;
55
- fieldName: z.ZodString;
56
- }, z.core.$strip>;
57
- };
58
- };
59
- config: import('@better-fetch/fetch').SchemaConfig;
60
- };
61
- plugins: {
62
- id: string;
63
- name: string;
64
- version: string;
65
- hooks: {
66
- onRequest<T extends Record<string, any>>(context: import('@better-fetch/fetch').RequestContext<T>): void;
67
- onSuccess(context: import('@better-fetch/fetch').SuccessContext<any>): Promise<void>;
68
- onRetry(response: import('@better-fetch/fetch').ResponseContext): void;
69
- onError(context: import('@better-fetch/fetch').ErrorContext): Promise<void>;
70
- };
71
- }[];
72
- }, unknown, unknown, {
73
- schema: {
74
- "@post/FileMaker_Tables": {
75
- input: z.ZodObject<{
76
- tableName: z.ZodString;
77
- fields: z.ZodArray<z.ZodAny>;
78
- }, z.core.$strip>;
79
- };
80
- "@patch/FileMaker_Tables/:tableName": {
81
- params: z.ZodObject<{
82
- tableName: z.ZodString;
83
- }, z.core.$strip>;
84
- input: z.ZodObject<{
85
- fields: z.ZodArray<z.ZodAny>;
86
- }, z.core.$strip>;
87
- };
88
- "@delete/FileMaker_Tables/:tableName": {
89
- params: z.ZodObject<{
90
- tableName: z.ZodString;
91
- }, z.core.$strip>;
92
- };
93
- "@delete/FileMaker_Tables/:tableName/:fieldName": {
94
- params: z.ZodObject<{
95
- tableName: z.ZodString;
96
- fieldName: z.ZodString;
97
- }, z.core.$strip>;
98
- };
99
- };
100
- config: import('@better-fetch/fetch').SchemaConfig;
101
- }>;
16
+ }
102
17
  export declare function validateUrl(input: string): Result<URL, unknown>;
18
+ export declare function createRawFetch(args: FmOdataConfig): {
19
+ baseURL: string;
20
+ fetch: <TOutput = any>(input: string | URL | Request, options?: Omit<RequestInit, "body"> & {
21
+ body?: any;
22
+ output?: z.ZodSchema<TOutput>;
23
+ }) => Promise<{
24
+ data?: TOutput;
25
+ error?: string;
26
+ response?: Response;
27
+ }>;
28
+ };
103
29
  export {};