@proofkit/better-auth 0.3.0 → 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.
@@ -19,7 +19,7 @@ async function getMetadata(fetch, databaseName) {
19
19
  }
20
20
  async function planMigration(fetch, betterAuthSchema, databaseName) {
21
21
  const metadata = await getMetadata(fetch, databaseName);
22
- let entitySetToType = {};
22
+ const entitySetToType = {};
23
23
  if (metadata) {
24
24
  for (const [key, value] of Object.entries(metadata)) {
25
25
  if (value.$Kind === "EntitySet" && value.$Type) {
@@ -31,13 +31,25 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
31
31
  const existingTables = metadata ? Object.entries(entitySetToType).reduce(
32
32
  (acc, [entitySetName, entityTypeKey]) => {
33
33
  const entityType = metadata[entityTypeKey];
34
- if (!entityType) return acc;
34
+ if (!entityType) {
35
+ return acc;
36
+ }
35
37
  const fields = Object.entries(entityType).filter(
36
- ([fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue
37
- ).map(([fieldKey, fieldValue]) => ({
38
- name: fieldKey,
39
- 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"
40
- }));
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
+ });
41
53
  acc[entitySetName] = fields;
42
54
  return acc;
43
55
  },
@@ -49,34 +61,21 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
49
61
  }));
50
62
  const migrationPlan = [];
51
63
  for (const baTable of baTables) {
52
- const fields = Object.entries(baTable.fields).map(
53
- ([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 {
54
72
  name: field.fieldName ?? key,
55
- type: field.type === "boolean" || field.type.includes("number") ? "numeric" : field.type === "date" ? "timestamp" : "varchar"
56
- })
57
- );
58
- const tableExists = Object.prototype.hasOwnProperty.call(
59
- existingTables,
60
- baTable.modelName
61
- );
62
- if (!tableExists) {
63
- migrationPlan.push({
64
- tableName: baTable.modelName,
65
- operation: "create",
66
- fields: [
67
- {
68
- name: "id",
69
- type: "varchar",
70
- primary: true,
71
- unique: true
72
- },
73
- ...fields
74
- ]
75
- });
76
- } else {
77
- const existingFields = (existingTables[baTable.modelName] || []).map(
78
- (f) => f.name
79
- );
73
+ type
74
+ };
75
+ });
76
+ const tableExists = baTable.modelName in existingTables;
77
+ if (tableExists) {
78
+ const existingFields = (existingTables[baTable.modelName] || []).map((f) => f.name);
80
79
  const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(
81
80
  (acc, f) => {
82
81
  acc[f.name] = f.type;
@@ -84,16 +83,14 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
84
83
  },
85
84
  {}
86
85
  );
87
- fields.forEach((field) => {
86
+ for (const field of fields) {
88
87
  if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {
89
88
  console.warn(
90
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.`
91
90
  );
92
91
  }
93
- });
94
- const fieldsToAdd = fields.filter(
95
- (f) => !existingFields.includes(f.name)
96
- );
92
+ }
93
+ const fieldsToAdd = fields.filter((f) => !existingFields.includes(f.name));
97
94
  if (fieldsToAdd.length > 0) {
98
95
  migrationPlan.push({
99
96
  tableName: baTable.modelName,
@@ -101,6 +98,20 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
101
98
  fields: fieldsToAdd
102
99
  });
103
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
+ });
104
115
  }
105
116
  }
106
117
  return migrationPlan;
@@ -117,10 +128,7 @@ async function executeMigration(fetch, migrationPlan) {
117
128
  }
118
129
  });
119
130
  if (result.error) {
120
- console.error(
121
- `Failed to create table ${step.tableName}:`,
122
- result.error
123
- );
131
+ console.error(`Failed to create table ${step.tableName}:`, result.error);
124
132
  throw new Error(`Migration failed: ${result.error}`);
125
133
  }
126
134
  } else if (step.operation === "update") {
@@ -130,10 +138,7 @@ async function executeMigration(fetch, migrationPlan) {
130
138
  body: { fields: step.fields }
131
139
  });
132
140
  if (result.error) {
133
- console.error(
134
- `Failed to update table ${step.tableName}:`,
135
- result.error
136
- );
141
+ console.error(`Failed to update table ${step.tableName}:`, result.error);
137
142
  throw new Error(`Migration failed: ${result.error}`);
138
143
  }
139
144
  }
@@ -199,8 +204,12 @@ ${emoji} ${step.operation === "create" ? chalk.bold.green("Create table") : chal
199
204
  if (step.fields.length) {
200
205
  for (const field of step.fields) {
201
206
  let fieldDesc = ` - ${field.name} (${field.type}`;
202
- if (field.primary) fieldDesc += ", primary";
203
- if (field.unique) fieldDesc += ", unique";
207
+ if (field.primary) {
208
+ fieldDesc += ", primary";
209
+ }
210
+ if (field.unique) {
211
+ fieldDesc += ", unique";
212
+ }
204
213
  fieldDesc += ")";
205
214
  console.log(fieldDesc);
206
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 { createRawFetch } from \"./odata\";\n\nexport async function getMetadata(\n fetch: ReturnType<typeof createRawFetch>[\"fetch\"],\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 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 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 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(\n `Failed to create table ${step.tableName}:`,\n result.error,\n );\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(\n `Failed to update table ${step.tableName}:`,\n result.error,\n );\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) 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":";;AAMsB,eAAA,YACpB,OACA,cACA;;AACA,UAAQ,IAAI,qBAAqB;AAC3B,QAAA,SAAS,MAAM,MAAM,cAAc;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACtC,QAAQ,EACL,YAAY;AAAA,MACX,UAAU,EAAE,OAAO;AAAA,MACnB,kBAAkB,EAAE,OAAO;AAAA,IAAA,CAC5B,EACA,GAAG,EAAE,MAAM,EACX,MAAM,IAAI;AAAA,EAAA,CACd;AAED,MAAI,OAAO,OAAO;AACR,YAAA,MAAM,2BAA2B,OAAO,KAAK;AAC9C,WAAA;AAAA,EAAA;AAGD,WAAA,YAAO,SAAP,mBAAc,kBAAiB;AACzC;AAEsB,eAAA,cACpB,OACA,kBACA,cACwB;AACxB,QAAM,WAAW,MAAM,YAAY,OAAO,YAAY;AAGtD,MAAI,kBAA0C,CAAC;AAC/C,MAAI,UAAU;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,UAAI,MAAM,UAAU,eAAe,MAAM,OAAO;AAE9C,cAAM,UAAU,MAAM,MAAM,MAAM,GAAG,EAAE,IAAI;AAC3B,wBAAA,GAAG,IAAI,WAAW;AAAA,MAAA;AAAA,IACpC;AAAA,EACF;AAGF,QAAM,iBAAiB,WACnB,OAAO,QAAQ,eAAe,EAAE;AAAA,IAC9B,CAAC,KAAK,CAAC,eAAe,aAAa,MAAM;AACjC,YAAA,aAAa,SAAS,aAAa;AACrC,UAAA,CAAC,WAAmB,QAAA;AACxB,YAAM,SAAS,OAAO,QAAQ,UAAU,EACrC;AAAA,QACC,CAAC,CAAC,UAAU,UAAU,MACpB,OAAO,eAAe,YACtB,eAAe,QACf,WAAW;AAAA,QAEd,IAAI,CAAC,CAAC,UAAU,UAAU,OAAO;AAAA,QAChC,MAAM;AAAA,QACN,MACE,WAAW,UAAU,eACjB,YACA,WAAW,UAAU,uBACnB,cACA,WAAW,UAAU,iBACnB,WAAW,UAAU,eACrB,WAAW,UAAU,cACrB,YACA;AAAA,MAAA,EACV;AACJ,UAAI,aAAa,IAAI;AACd,aAAA;AAAA,IACT;AAAA,IACA,CAAA;AAAA,EAAC,IAEH,CAAC;AAEC,QAAA,WAAW,OAAO,QAAQ,gBAAgB,EAC7C,KAAK,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,EACpD,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,IACtB,GAAG;AAAA,IACH,SAAS;AAAA,EAAA,EACT;AAEJ,QAAM,gBAA+B,CAAC;AAEtC,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAoB,OAAO,QAAQ,QAAQ,MAAM,EAAE;AAAA,MACvD,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,QACjB,MAAM,MAAM,aAAa;AAAA,QACzB,MACE,MAAM,SAAS,aAAa,MAAM,KAAK,SAAS,QAAQ,IACpD,YACA,MAAM,SAAS,SACb,cACA;AAAA,MACV;AAAA,IACF;AAGM,UAAA,cAAc,OAAO,UAAU,eAAe;AAAA,MAClD;AAAA,MACA,QAAQ;AAAA,IACV;AAEA,QAAI,CAAC,aAAa;AAChB,oBAAc,KAAK;AAAA,QACjB,WAAW,QAAQ;AAAA,QACnB,WAAW;AAAA,QACX,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,SAAS;AAAA,YACT,QAAQ;AAAA,UACV;AAAA,UACA,GAAG;AAAA,QAAA;AAAA,MACL,CACD;AAAA,IAAA,OACI;AACL,YAAM,kBAAkB,eAAe,QAAQ,SAAS,KAAK,CAAA,GAAI;AAAA,QAC/D,CAAC,MAAM,EAAE;AAAA,MACX;AACA,YAAM,oBAAoB,eAAe,QAAQ,SAAS,KAAK,CAAA,GAAI;AAAA,QACjE,CAAC,KAAK,MAAM;AACN,cAAA,EAAE,IAAI,IAAI,EAAE;AACT,iBAAA;AAAA,QACT;AAAA,QACA,CAAA;AAAA,MACF;AAEO,aAAA,QAAQ,CAAC,UAAU;AAEtB,YAAA,eAAe,SAAS,MAAM,IAAI,KAClC,iBAAiB,MAAM,IAAI,MAAM,MAAM,MACvC;AACQ,kBAAA;AAAA,YACN,sBAAsB,MAAM,IAAI,eAAe,QAAQ,SAAS,0BAA0B,iBAAiB,MAAM,IAAI,CAAC,gBAAgB,MAAM,IAAI;AAAA,UAClJ;AAAA,QAAA;AAAA,MACF,CACD;AACD,YAAM,cAAc,OAAO;AAAA,QACzB,CAAC,MAAM,CAAC,eAAe,SAAS,EAAE,IAAI;AAAA,MACxC;AACI,UAAA,YAAY,SAAS,GAAG;AAC1B,sBAAc,KAAK;AAAA,UACjB,WAAW,QAAQ;AAAA,UACnB,WAAW;AAAA,UACX,QAAQ;AAAA,QAAA,CACT;AAAA,MAAA;AAAA,IACH;AAAA,EACF;AAGK,SAAA;AACT;AAEsB,eAAA,iBACpB,OACA,eACA;AACA,aAAW,QAAQ,eAAe;AAC5B,QAAA,KAAK,cAAc,UAAU;AACvB,cAAA,IAAI,mBAAmB,KAAK,SAAS;AACvC,YAAA,SAAS,MAAM,MAAM,qBAAqB;AAAA,QAC9C,QAAQ;AAAA,QACR,MAAM;AAAA,UACJ,WAAW,KAAK;AAAA,UAChB,QAAQ,KAAK;AAAA,QAAA;AAAA,MACf,CACD;AAED,UAAI,OAAO,OAAO;AACR,gBAAA;AAAA,UACN,0BAA0B,KAAK,SAAS;AAAA,UACxC,OAAO;AAAA,QACT;AACA,cAAM,IAAI,MAAM,qBAAqB,OAAO,KAAK,EAAE;AAAA,MAAA;AAAA,IACrD,WACS,KAAK,cAAc,UAAU;AAC9B,cAAA,IAAI,2BAA2B,KAAK,SAAS;AACrD,YAAM,SAAS,MAAM,MAAM,qBAAqB,KAAK,SAAS,IAAI;AAAA,QAChE,QAAQ;AAAA,QACR,MAAM,EAAE,QAAQ,KAAK,OAAO;AAAA,MAAA,CAC7B;AAED,UAAI,OAAO,OAAO;AACR,gBAAA;AAAA,UACN,0BAA0B,KAAK,SAAS;AAAA,UACxC,OAAO;AAAA,QACT;AACA,cAAM,IAAI,MAAM,qBAAqB,OAAO,KAAK,EAAE;AAAA,MAAA;AAAA,IACrD;AAAA,EACF;AAEJ;AAEA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC9B,QAAQ,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC7B,QAAQ,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC7B,aAAa,EAAE,OAAO,EAAE,SAAS;AACnC,CAAC;AAED,MAAM,oBAAoB,mBAAmB,OAAO;AAAA,EAClD,MAAM,EAAE,QAAQ,SAAS;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,QAAQ,YAAY,cAAc,CAAC,EAAE,SAAS;AACjE,CAAC;AAED,MAAM,qBAAqB,mBAAmB,OAAO;AAAA,EACnD,MAAM,EAAE,QAAQ,SAAS;AAC3B,CAAC;AAED,MAAM,kBAAkB,mBAAmB,OAAO;AAAA,EAChD,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,SAAS,EAAE,KAAK,CAAC,gBAAgB,SAAS,CAAC,EAAE,SAAS;AACxD,CAAC;AAED,MAAM,kBAAkB,mBAAmB,OAAO;AAAA,EAChD,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,SAAS,EAAE,KAAK,CAAC,gBAAgB,SAAS,CAAC,EAAE,SAAS;AACxD,CAAC;AAED,MAAM,uBAAuB,mBAAmB,OAAO;AAAA,EACrD,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,SAAS,EAAE,KAAK,CAAC,qBAAqB,cAAc,CAAC,EAAE,SAAS;AAClE,CAAC;AAED,MAAM,uBAAuB,mBAAmB,OAAO;AAAA,EACrD,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,oBAAoB,EAAE,OAAO,EAAE,SAAS;AAC1C,CAAC;AAED,MAAM,cAAc,EAAE,mBAAmB,QAAQ;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAI2B,EACzB,OAAO;AAAA,EACN,WAAW,EAAE,OAAO;AAAA,EACpB,WAAW,EAAE,KAAK,CAAC,UAAU,QAAQ,CAAC;AAAA,EACtC,QAAQ,EAAE,MAAM,WAAW;AAC7B,CAAC,EACA,MAAM;AAIF,SAAS,yBAAyB,eAA8B;AACjE,MAAA,CAAC,cAAc,QAAQ;AACzB,YAAQ,IAAI,8CAA8C;AAC1D;AAAA,EAAA;AAEF,UAAQ,IAAI,MAAM,KAAK,MAAM,iBAAiB,CAAC;AAC/C,aAAW,QAAQ,eAAe;AAChC,UAAM,QAAQ,KAAK,cAAc,WAAW,MAAM;AAC1C,YAAA;AAAA,MACN;AAAA,EAAK,KAAK,IAAI,KAAK,cAAc,WAAW,MAAM,KAAK,MAAM,cAAc,IAAI,MAAM,KAAK,OAAO,cAAc,CAAC,KAAK,KAAK,SAAS;AAAA,IACrI;AACI,QAAA,KAAK,OAAO,QAAQ;AACX,iBAAA,SAAS,KAAK,QAAQ;AAC/B,YAAI,YAAY,SAAS,MAAM,IAAI,KAAK,MAAM,IAAI;AAC9C,YAAA,MAAM,QAAsB,cAAA;AAC5B,YAAA,MAAM,OAAqB,cAAA;AAClB,qBAAA;AACb,gBAAQ,IAAI,SAAS;AAAA,MAAA;AAAA,IACvB,OACK;AACL,cAAQ,IAAI,wBAAwB;AAAA,IAAA;AAAA,EACtC;AAEF,UAAQ,IAAI,EAAE;AAChB;"}
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,19 +1,19 @@
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
- };
16
+ }
17
17
  export declare function validateUrl(input: string): Result<URL, unknown>;
18
18
  export declare function createRawFetch(args: FmOdataConfig): {
19
19
  baseURL: string;
@@ -15,7 +15,7 @@ function createRawFetch(args) {
15
15
  }
16
16
  let baseURL = result.value.origin;
17
17
  if ("apiKey" in args.auth) {
18
- baseURL += `/otto`;
18
+ baseURL += "/otto";
19
19
  }
20
20
  baseURL += `/fmi/odata/v4/${args.database}`;
21
21
  const authHeaders = {};
@@ -52,33 +52,20 @@ function createRawFetch(args) {
52
52
  body: processedBody
53
53
  };
54
54
  if (args.logging === "verbose" || args.logging === true) {
55
- logger.info(
56
- "raw-fetch",
57
- `${requestInit.method || "GET"} ${url}`
58
- );
55
+ logger.info("raw-fetch", `${requestInit.method || "GET"} ${url}`);
59
56
  if (requestInit.body) {
60
57
  logger.info("raw-fetch", "Request body:", requestInit.body);
61
58
  }
62
59
  }
63
60
  const response = await fetch(url, requestInit);
64
61
  if (args.logging === "verbose" || args.logging === true) {
65
- logger.info(
66
- "raw-fetch",
67
- `Response status: ${response.status} ${response.statusText}`
68
- );
69
- logger.info(
70
- "raw-fetch",
71
- `Response headers:`,
72
- Object.fromEntries(response.headers.entries())
73
- );
62
+ logger.info("raw-fetch", `Response status: ${response.status} ${response.statusText}`);
63
+ logger.info("raw-fetch", "Response headers:", Object.fromEntries(response.headers.entries()));
74
64
  }
75
65
  if (!response.ok) {
76
66
  const errorText = await response.text().catch(() => "Unknown error");
77
67
  if (args.logging === "verbose" || args.logging === true) {
78
- logger.error(
79
- "raw-fetch",
80
- `HTTP Error ${response.status}: ${errorText}`
81
- );
68
+ logger.error("raw-fetch", `HTTP Error ${response.status}: ${errorText}`);
82
69
  }
83
70
  return {
84
71
  error: `HTTP ${response.status}: ${errorText}`,
@@ -88,48 +75,29 @@ function createRawFetch(args) {
88
75
  let responseData;
89
76
  const contentType = response.headers.get("content-type");
90
77
  if (args.logging === "verbose" || args.logging === true) {
91
- logger.info(
92
- "raw-fetch",
93
- `Response content-type: ${contentType || "none"}`
94
- );
78
+ logger.info("raw-fetch", `Response content-type: ${contentType || "none"}`);
95
79
  }
96
80
  if (contentType == null ? void 0 : contentType.includes("application/json")) {
97
81
  try {
98
82
  const responseText = await response.text();
99
83
  if (args.logging === "verbose" || args.logging === true) {
100
- logger.info(
101
- "raw-fetch",
102
- `Raw response text: "${responseText}"`
103
- );
104
- logger.info(
105
- "raw-fetch",
106
- `Response text length: ${responseText.length}`
107
- );
84
+ logger.info("raw-fetch", `Raw response text: "${responseText}"`);
85
+ logger.info("raw-fetch", `Response text length: ${responseText.length}`);
108
86
  }
109
87
  if (responseText.trim() === "") {
110
88
  if (args.logging === "verbose" || args.logging === true) {
111
- logger.info(
112
- "raw-fetch",
113
- "Empty JSON response, returning null"
114
- );
89
+ logger.info("raw-fetch", "Empty JSON response, returning null");
115
90
  }
116
91
  responseData = null;
117
92
  } else {
118
93
  responseData = JSON.parse(responseText);
119
94
  if (args.logging === "verbose" || args.logging === true) {
120
- logger.info(
121
- "raw-fetch",
122
- "Successfully parsed JSON response"
123
- );
95
+ logger.info("raw-fetch", "Successfully parsed JSON response");
124
96
  }
125
97
  }
126
98
  } catch (parseError) {
127
99
  if (args.logging === "verbose" || args.logging === true) {
128
- logger.error(
129
- "raw-fetch",
130
- "JSON parse error:",
131
- parseError
132
- );
100
+ logger.error("raw-fetch", "JSON parse error:", parseError);
133
101
  }
134
102
  return {
135
103
  error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
@@ -139,27 +107,18 @@ function createRawFetch(args) {
139
107
  } else if (contentType == null ? void 0 : contentType.includes("text/")) {
140
108
  responseData = await response.text();
141
109
  if (args.logging === "verbose" || args.logging === true) {
142
- logger.info(
143
- "raw-fetch",
144
- `Text response: "${responseData}"`
145
- );
110
+ logger.info("raw-fetch", `Text response: "${responseData}"`);
146
111
  }
147
112
  } else {
148
113
  try {
149
114
  responseData = await response.text();
150
115
  if (args.logging === "verbose" || args.logging === true) {
151
- logger.info(
152
- "raw-fetch",
153
- `Unknown content-type response as text: "${responseData}"`
154
- );
116
+ logger.info("raw-fetch", `Unknown content-type response as text: "${responseData}"`);
155
117
  }
156
118
  } catch {
157
119
  responseData = null;
158
120
  if (args.logging === "verbose" || args.logging === true) {
159
- logger.info(
160
- "raw-fetch",
161
- "Could not parse response as text, returning null"
162
- );
121
+ logger.info("raw-fetch", "Could not parse response as text, returning null");
163
122
  }
164
123
  }
165
124
  }
@@ -170,12 +129,11 @@ function createRawFetch(args) {
170
129
  data: validation.data,
171
130
  response
172
131
  };
173
- } else {
174
- return {
175
- error: `Validation failed: ${validation.error.message}`,
176
- response
177
- };
178
132
  }
133
+ return {
134
+ error: `Validation failed: ${validation.error.message}`,
135
+ response
136
+ };
179
137
  }
180
138
  return {
181
139
  data: responseData,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/odata/index.ts"],"sourcesContent":["import { logger as betterAuthLogger } from \"better-auth\";\nimport { err, ok, Result } from \"neverthrow\";\nimport { z } from \"zod/v4\";\n\ntype BasicAuthCredentials = {\n username: string;\n password: string;\n};\ntype OttoAPIKeyAuth = {\n apiKey: string;\n};\ntype ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;\n\nexport type FmOdataConfig = {\n serverUrl: string;\n auth: ODataAuth;\n database: string;\n logging?: true | \"verbose\" | \"none\";\n};\n\nexport function validateUrl(input: string): Result<URL, unknown> {\n try {\n const url = new URL(input);\n return ok(url);\n } catch (error) {\n return err(error);\n }\n}\n\nexport function createRawFetch(args: FmOdataConfig) {\n const result = validateUrl(args.serverUrl);\n\n if (result.isErr()) {\n throw new Error(\"Invalid server URL\");\n }\n\n let baseURL = result.value.origin;\n if (\"apiKey\" in args.auth) {\n baseURL += `/otto`;\n }\n baseURL += `/fmi/odata/v4/${args.database}`;\n\n // Create authentication headers\n const authHeaders: Record<string, string> = {};\n if (\"apiKey\" in args.auth) {\n authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;\n } else {\n const credentials = btoa(`${args.auth.username}:${args.auth.password}`);\n authHeaders.Authorization = `Basic ${credentials}`;\n }\n\n // Enhanced fetch function with body handling, validation, and structured responses\n const wrappedFetch = async <TOutput = any>(\n input: string | URL | Request,\n options?: Omit<RequestInit, \"body\"> & {\n body?: any; // Allow any type for body\n output?: z.ZodSchema<TOutput>; // Optional schema for validation\n },\n ): Promise<{ data?: TOutput; error?: string; response?: Response }> => {\n try {\n let url: string;\n\n // Handle different input types\n if (typeof input === \"string\") {\n // If it's already a full URL, use as-is, otherwise prepend baseURL\n url = input.startsWith(\"http\")\n ? input\n : `${baseURL}${input.startsWith(\"/\") ? input : `/${input}`}`;\n } else if (input instanceof URL) {\n url = input.toString();\n } else if (input instanceof Request) {\n url = input.url;\n } else {\n url = String(input);\n }\n\n // Handle body serialization\n let processedBody = options?.body;\n if (\n processedBody &&\n typeof processedBody === \"object\" &&\n !(processedBody instanceof FormData) &&\n !(processedBody instanceof URLSearchParams) &&\n !(processedBody instanceof ReadableStream)\n ) {\n processedBody = JSON.stringify(processedBody);\n }\n\n // Merge headers\n const headers = {\n \"Content-Type\": \"application/json\",\n ...authHeaders,\n ...(options?.headers || {}),\n };\n\n const requestInit: RequestInit = {\n ...options,\n headers,\n body: processedBody,\n };\n\n // Optional logging\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `${requestInit.method || \"GET\"} ${url}`,\n );\n if (requestInit.body) {\n betterAuthLogger.info(\"raw-fetch\", \"Request body:\", requestInit.body);\n }\n }\n\n const response = await fetch(url, requestInit);\n\n // Optional logging for response details\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response status: ${response.status} ${response.statusText}`,\n );\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response headers:`,\n Object.fromEntries(response.headers.entries()),\n );\n }\n\n // Check if response is ok\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\n \"raw-fetch\",\n `HTTP Error ${response.status}: ${errorText}`,\n );\n }\n return {\n error: `HTTP ${response.status}: ${errorText}`,\n response,\n };\n }\n\n // Parse response based on content type\n let responseData: any;\n const contentType = response.headers.get(\"content-type\");\n\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response content-type: ${contentType || \"none\"}`,\n );\n }\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const responseText = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Raw response text: \"${responseText}\"`,\n );\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response text length: ${responseText.length}`,\n );\n }\n\n // Handle empty responses\n if (responseText.trim() === \"\") {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Empty JSON response, returning null\",\n );\n }\n responseData = null;\n } else {\n responseData = JSON.parse(responseText);\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Successfully parsed JSON response\",\n );\n }\n }\n } catch (parseError) {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\n \"raw-fetch\",\n \"JSON parse error:\",\n parseError,\n );\n }\n return {\n error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : \"Unknown parse error\"}`,\n response,\n };\n }\n } else if (contentType?.includes(\"text/\")) {\n // Handle text responses (text/plain, text/html, etc.)\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Text response: \"${responseData}\"`,\n );\n }\n } else {\n // For other content types, try to get text but don't fail if it's binary\n try {\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Unknown content-type response as text: \"${responseData}\"`,\n );\n }\n } catch {\n // If text parsing fails (e.g., binary data), return null\n responseData = null;\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Could not parse response as text, returning null\",\n );\n }\n }\n }\n\n // Validate output if schema provided\n if (options?.output) {\n const validation = options.output.safeParse(responseData);\n if (validation.success) {\n return {\n data: validation.data,\n response,\n };\n } else {\n return {\n error: `Validation failed: ${validation.error.message}`,\n response,\n };\n }\n }\n\n // Return unvalidated data\n return {\n data: responseData as TOutput,\n response,\n };\n } catch (error) {\n return {\n error:\n error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n };\n\n return {\n baseURL,\n fetch: wrappedFetch,\n };\n}\n"],"names":["betterAuthLogger"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/odata/index.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/noExplicitAny: library code */\nimport { logger as betterAuthLogger } from \"better-auth\";\nimport { err, ok, type Result } from \"neverthrow\";\nimport type { z } from \"zod/v4\";\n\ninterface BasicAuthCredentials {\n username: string;\n password: string;\n}\ninterface OttoAPIKeyAuth {\n apiKey: string;\n}\ntype ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;\n\nexport interface FmOdataConfig {\n serverUrl: string;\n auth: ODataAuth;\n database: string;\n logging?: true | \"verbose\" | \"none\";\n}\n\nexport function validateUrl(input: string): Result<URL, unknown> {\n try {\n const url = new URL(input);\n return ok(url);\n } catch (error) {\n return err(error);\n }\n}\n\nexport function createRawFetch(args: FmOdataConfig) {\n const result = validateUrl(args.serverUrl);\n\n if (result.isErr()) {\n throw new Error(\"Invalid server URL\");\n }\n\n let baseURL = result.value.origin;\n if (\"apiKey\" in args.auth) {\n baseURL += \"/otto\";\n }\n baseURL += `/fmi/odata/v4/${args.database}`;\n\n // Create authentication headers\n const authHeaders: Record<string, string> = {};\n if (\"apiKey\" in args.auth) {\n authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;\n } else {\n const credentials = btoa(`${args.auth.username}:${args.auth.password}`);\n authHeaders.Authorization = `Basic ${credentials}`;\n }\n\n // Enhanced fetch function with body handling, validation, and structured responses\n const wrappedFetch = async <TOutput = any>(\n input: string | URL | Request,\n options?: Omit<RequestInit, \"body\"> & {\n body?: any; // Allow any type for body\n output?: z.ZodSchema<TOutput>; // Optional schema for validation\n },\n ): Promise<{ data?: TOutput; error?: string; response?: Response }> => {\n try {\n let url: string;\n\n // Handle different input types\n if (typeof input === \"string\") {\n // If it's already a full URL, use as-is, otherwise prepend baseURL\n url = input.startsWith(\"http\") ? input : `${baseURL}${input.startsWith(\"/\") ? input : `/${input}`}`;\n } else if (input instanceof URL) {\n url = input.toString();\n } else if (input instanceof Request) {\n url = input.url;\n } else {\n url = String(input);\n }\n\n // Handle body serialization\n let processedBody = options?.body;\n if (\n processedBody &&\n typeof processedBody === \"object\" &&\n !(processedBody instanceof FormData) &&\n !(processedBody instanceof URLSearchParams) &&\n !(processedBody instanceof ReadableStream)\n ) {\n processedBody = JSON.stringify(processedBody);\n }\n\n // Merge headers\n const headers = {\n \"Content-Type\": \"application/json\",\n ...authHeaders,\n ...(options?.headers || {}),\n };\n\n const requestInit: RequestInit = {\n ...options,\n headers,\n body: processedBody,\n };\n\n // Optional logging\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `${requestInit.method || \"GET\"} ${url}`);\n if (requestInit.body) {\n betterAuthLogger.info(\"raw-fetch\", \"Request body:\", requestInit.body);\n }\n }\n\n const response = await fetch(url, requestInit);\n\n // Optional logging for response details\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Response status: ${response.status} ${response.statusText}`);\n betterAuthLogger.info(\"raw-fetch\", \"Response headers:\", Object.fromEntries(response.headers.entries()));\n }\n\n // Check if response is ok\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\"raw-fetch\", `HTTP Error ${response.status}: ${errorText}`);\n }\n return {\n error: `HTTP ${response.status}: ${errorText}`,\n response,\n };\n }\n\n // Parse response based on content type\n let responseData: any;\n const contentType = response.headers.get(\"content-type\");\n\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Response content-type: ${contentType || \"none\"}`);\n }\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const responseText = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Raw response text: \"${responseText}\"`);\n betterAuthLogger.info(\"raw-fetch\", `Response text length: ${responseText.length}`);\n }\n\n // Handle empty responses\n if (responseText.trim() === \"\") {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Empty JSON response, returning null\");\n }\n responseData = null;\n } else {\n responseData = JSON.parse(responseText);\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Successfully parsed JSON response\");\n }\n }\n } catch (parseError) {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\"raw-fetch\", \"JSON parse error:\", parseError);\n }\n return {\n error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : \"Unknown parse error\"}`,\n response,\n };\n }\n } else if (contentType?.includes(\"text/\")) {\n // Handle text responses (text/plain, text/html, etc.)\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Text response: \"${responseData}\"`);\n }\n } else {\n // For other content types, try to get text but don't fail if it's binary\n try {\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Unknown content-type response as text: \"${responseData}\"`);\n }\n } catch {\n // If text parsing fails (e.g., binary data), return null\n responseData = null;\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Could not parse response as text, returning null\");\n }\n }\n }\n\n // Validate output if schema provided\n if (options?.output) {\n const validation = options.output.safeParse(responseData);\n if (validation.success) {\n return {\n data: validation.data,\n response,\n };\n }\n return {\n error: `Validation failed: ${validation.error.message}`,\n response,\n };\n }\n\n // Return unvalidated data\n return {\n data: responseData as TOutput,\n response,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n };\n\n return {\n baseURL,\n fetch: wrappedFetch,\n };\n}\n"],"names":["betterAuthLogger"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofkit/better-auth",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-beta.0",
4
4
  "description": "FileMaker adapter for Better Auth",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -50,7 +50,7 @@
50
50
  "odata-query": "^8.0.4",
51
51
  "prompts": "^2.4.2",
52
52
  "vite": "^6.3.4",
53
- "zod": "3.25.64"
53
+ "zod": "^4.1.13"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/fs-extra": "^11.0.4",
@@ -58,14 +58,16 @@
58
58
  "@vitest/ui": "^3.2.4",
59
59
  "fm-odata-client": "^3.0.1",
60
60
  "publint": "^0.3.12",
61
- "typescript": "^5.9.2",
62
- "vitest": "^3.2.4"
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^4.0.7"
63
63
  },
64
64
  "scripts": {
65
65
  "dev": "pnpm build:watch",
66
66
  "test": "vitest run",
67
67
  "build": "vite build && publint --strict",
68
68
  "build:watch": "vite build --watch",
69
- "ci": "pnpm run build && pnpm run test"
69
+ "ci": "pnpm run build && pnpm run test",
70
+ "lint": "biome check . --write",
71
+ "lint:summary": "biome check . --reporter=summary"
70
72
  }
71
73
  }