@proofkit/better-auth 0.3.0 → 0.3.1-beta.1
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.
- package/dist/esm/adapter.d.ts +6 -7
- package/dist/esm/adapter.js +48 -25
- package/dist/esm/adapter.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js +4 -12
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/get-config.js +12 -21
- package/dist/esm/better-auth-cli/utils/get-config.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js +4 -11
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js.map +1 -1
- package/dist/esm/cli/index.js +11 -25
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/migrate.d.ts +7 -3
- package/dist/esm/migrate.js +70 -50
- package/dist/esm/migrate.js.map +1 -1
- package/dist/esm/odata/index.d.ts +6 -6
- package/dist/esm/odata/index.js +18 -60
- package/dist/esm/odata/index.js.map +1 -1
- package/package.json +21 -17
- package/src/adapter.ts +62 -44
- package/src/better-auth-cli/utils/add-svelte-kit-env-modules.ts +68 -80
- package/src/better-auth-cli/utils/get-config.ts +16 -30
- package/src/better-auth-cli/utils/get-tsconfig-info.ts +12 -20
- package/src/cli/index.ts +12 -31
- package/src/index.ts +1 -0
- package/src/migrate.ts +87 -79
- package/src/odata/index.ts +29 -73
package/dist/esm/migrate.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import z from "zod/v4";
|
|
3
|
+
function normalizeBetterAuthFieldType(fieldType) {
|
|
4
|
+
if (typeof fieldType === "string") {
|
|
5
|
+
return fieldType;
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(fieldType)) {
|
|
8
|
+
return fieldType.map(String).join("|");
|
|
9
|
+
}
|
|
10
|
+
return String(fieldType);
|
|
11
|
+
}
|
|
3
12
|
async function getMetadata(fetch, databaseName) {
|
|
4
13
|
var _a;
|
|
5
14
|
console.log("getting metadata...");
|
|
@@ -19,7 +28,7 @@ async function getMetadata(fetch, databaseName) {
|
|
|
19
28
|
}
|
|
20
29
|
async function planMigration(fetch, betterAuthSchema, databaseName) {
|
|
21
30
|
const metadata = await getMetadata(fetch, databaseName);
|
|
22
|
-
|
|
31
|
+
const entitySetToType = {};
|
|
23
32
|
if (metadata) {
|
|
24
33
|
for (const [key, value] of Object.entries(metadata)) {
|
|
25
34
|
if (value.$Kind === "EntitySet" && value.$Type) {
|
|
@@ -31,13 +40,25 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
|
|
|
31
40
|
const existingTables = metadata ? Object.entries(entitySetToType).reduce(
|
|
32
41
|
(acc, [entitySetName, entityTypeKey]) => {
|
|
33
42
|
const entityType = metadata[entityTypeKey];
|
|
34
|
-
if (!entityType)
|
|
43
|
+
if (!entityType) {
|
|
44
|
+
return acc;
|
|
45
|
+
}
|
|
35
46
|
const fields = Object.entries(entityType).filter(
|
|
36
|
-
([
|
|
37
|
-
).map(([fieldKey, fieldValue]) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
([_fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue
|
|
48
|
+
).map(([fieldKey, fieldValue]) => {
|
|
49
|
+
let type = "varchar";
|
|
50
|
+
if (fieldValue.$Type === "Edm.String") {
|
|
51
|
+
type = "varchar";
|
|
52
|
+
} else if (fieldValue.$Type === "Edm.DateTimeOffset") {
|
|
53
|
+
type = "timestamp";
|
|
54
|
+
} else if (fieldValue.$Type === "Edm.Decimal" || fieldValue.$Type === "Edm.Int32" || fieldValue.$Type === "Edm.Int64") {
|
|
55
|
+
type = "numeric";
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: fieldKey,
|
|
59
|
+
type
|
|
60
|
+
};
|
|
61
|
+
});
|
|
41
62
|
acc[entitySetName] = fields;
|
|
42
63
|
return acc;
|
|
43
64
|
},
|
|
@@ -45,38 +66,27 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
|
|
|
45
66
|
) : {};
|
|
46
67
|
const baTables = Object.entries(betterAuthSchema).sort((a, b) => (a[1].order ?? 0) - (b[1].order ?? 0)).map(([key, value]) => ({
|
|
47
68
|
...value,
|
|
48
|
-
|
|
69
|
+
modelName: key
|
|
70
|
+
// Use the key as modelName since getSchema uses table names as keys
|
|
49
71
|
}));
|
|
50
72
|
const migrationPlan = [];
|
|
51
73
|
for (const baTable of baTables) {
|
|
52
|
-
const fields = Object.entries(baTable.fields).map(
|
|
53
|
-
|
|
74
|
+
const fields = Object.entries(baTable.fields).map(([key, field]) => {
|
|
75
|
+
const t = normalizeBetterAuthFieldType(field.type);
|
|
76
|
+
let type = "varchar";
|
|
77
|
+
if (t.includes("boolean") || t.includes("number")) {
|
|
78
|
+
type = "numeric";
|
|
79
|
+
} else if (t.includes("date")) {
|
|
80
|
+
type = "timestamp";
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
54
83
|
name: field.fieldName ?? key,
|
|
55
|
-
type
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
const tableExists =
|
|
59
|
-
|
|
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
|
-
);
|
|
84
|
+
type
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
const tableExists = baTable.modelName in existingTables;
|
|
88
|
+
if (tableExists) {
|
|
89
|
+
const existingFields = (existingTables[baTable.modelName] || []).map((f) => f.name);
|
|
80
90
|
const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(
|
|
81
91
|
(acc, f) => {
|
|
82
92
|
acc[f.name] = f.type;
|
|
@@ -84,16 +94,14 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
|
|
|
84
94
|
},
|
|
85
95
|
{}
|
|
86
96
|
);
|
|
87
|
-
|
|
97
|
+
for (const field of fields) {
|
|
88
98
|
if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {
|
|
89
99
|
console.warn(
|
|
90
100
|
`⚠️ 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
101
|
);
|
|
92
102
|
}
|
|
93
|
-
}
|
|
94
|
-
const fieldsToAdd = fields.filter(
|
|
95
|
-
(f) => !existingFields.includes(f.name)
|
|
96
|
-
);
|
|
103
|
+
}
|
|
104
|
+
const fieldsToAdd = fields.filter((f) => !existingFields.includes(f.name));
|
|
97
105
|
if (fieldsToAdd.length > 0) {
|
|
98
106
|
migrationPlan.push({
|
|
99
107
|
tableName: baTable.modelName,
|
|
@@ -101,6 +109,20 @@ async function planMigration(fetch, betterAuthSchema, databaseName) {
|
|
|
101
109
|
fields: fieldsToAdd
|
|
102
110
|
});
|
|
103
111
|
}
|
|
112
|
+
} else {
|
|
113
|
+
migrationPlan.push({
|
|
114
|
+
tableName: baTable.modelName,
|
|
115
|
+
operation: "create",
|
|
116
|
+
fields: [
|
|
117
|
+
{
|
|
118
|
+
name: "id",
|
|
119
|
+
type: "varchar",
|
|
120
|
+
primary: true,
|
|
121
|
+
unique: true
|
|
122
|
+
},
|
|
123
|
+
...fields
|
|
124
|
+
]
|
|
125
|
+
});
|
|
104
126
|
}
|
|
105
127
|
}
|
|
106
128
|
return migrationPlan;
|
|
@@ -117,10 +139,7 @@ async function executeMigration(fetch, migrationPlan) {
|
|
|
117
139
|
}
|
|
118
140
|
});
|
|
119
141
|
if (result.error) {
|
|
120
|
-
console.error(
|
|
121
|
-
`Failed to create table ${step.tableName}:`,
|
|
122
|
-
result.error
|
|
123
|
-
);
|
|
142
|
+
console.error(`Failed to create table ${step.tableName}:`, result.error);
|
|
124
143
|
throw new Error(`Migration failed: ${result.error}`);
|
|
125
144
|
}
|
|
126
145
|
} else if (step.operation === "update") {
|
|
@@ -130,10 +149,7 @@ async function executeMigration(fetch, migrationPlan) {
|
|
|
130
149
|
body: { fields: step.fields }
|
|
131
150
|
});
|
|
132
151
|
if (result.error) {
|
|
133
|
-
console.error(
|
|
134
|
-
`Failed to update table ${step.tableName}:`,
|
|
135
|
-
result.error
|
|
136
|
-
);
|
|
152
|
+
console.error(`Failed to update table ${step.tableName}:`, result.error);
|
|
137
153
|
throw new Error(`Migration failed: ${result.error}`);
|
|
138
154
|
}
|
|
139
155
|
}
|
|
@@ -199,8 +215,12 @@ ${emoji} ${step.operation === "create" ? chalk.bold.green("Create table") : chal
|
|
|
199
215
|
if (step.fields.length) {
|
|
200
216
|
for (const field of step.fields) {
|
|
201
217
|
let fieldDesc = ` - ${field.name} (${field.type}`;
|
|
202
|
-
if (field.primary)
|
|
203
|
-
|
|
218
|
+
if (field.primary) {
|
|
219
|
+
fieldDesc += ", primary";
|
|
220
|
+
}
|
|
221
|
+
if (field.unique) {
|
|
222
|
+
fieldDesc += ", unique";
|
|
223
|
+
}
|
|
204
224
|
fieldDesc += ")";
|
|
205
225
|
console.log(fieldDesc);
|
|
206
226
|
}
|
package/dist/esm/migrate.js.map
CHANGED
|
@@ -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":"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"}
|
|
1
|
+
{"version":3,"file":"migrate.js","sources":["../../src/migrate.ts"],"sourcesContent":["import type { DBFieldAttribute } 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\n/** Schema type returned by better-auth's getSchema function */\ntype BetterAuthSchema = Record<string, { fields: Record<string, DBFieldAttribute>; order: number }>;\n\nfunction normalizeBetterAuthFieldType(fieldType: unknown): string {\n if (typeof fieldType === \"string\") {\n return fieldType;\n }\n if (Array.isArray(fieldType)) {\n return fieldType.map(String).join(\"|\");\n }\n return String(fieldType);\n}\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: BetterAuthSchema,\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 modelName: key, // Use the key as modelName since getSchema uses table names as keys\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 // Better Auth's FieldType can be a string literal union or arrays.\n // Normalize it to a string so our FM mapping logic remains stable.\n // Use .includes() for all checks to handle array types like [\"boolean\", \"null\"] → \"boolean|null\"\n const t = normalizeBetterAuthFieldType(field.type);\n let type: \"varchar\" | \"numeric\" | \"timestamp\" = \"varchar\";\n if (t.includes(\"boolean\") || t.includes(\"number\")) {\n type = \"numeric\";\n } else if (t.includes(\"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":";;AASA,SAAS,6BAA6B,WAA4B;AAChE,MAAI,OAAO,cAAc,UAAU;AACjC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,SAAS,GAAG;AAC5B,WAAO,UAAU,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,EACvC;AACA,SAAO,OAAO,SAAS;AACzB;AAEA,eAAsB,YAAY,OAAmD,cAAsB;;AACzG,UAAQ,IAAI,qBAAqB;AACjC,QAAM,SAAS,MAAM,MAAM,cAAc;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS,EAAE,QAAQ,mBAAA;AAAA,IACnB,QAAQ,EACL,YAAY;AAAA,MACX,UAAU,EAAE,OAAA;AAAA,MACZ,kBAAkB,EAAE,OAAA;AAAA,IAAO,CAC5B,EACA,GAAG,EAAE,MAAM,EACX,MAAM,IAAI;AAAA,EAAA,CACd;AAED,MAAI,OAAO,OAAO;AAChB,YAAQ,MAAM,2BAA2B,OAAO,KAAK;AACrD,WAAO;AAAA,EACT;AAEA,WAAQ,YAAO,SAAP,mBAAc,kBAAiB;AACzC;AAEA,eAAsB,cACpB,OACA,kBACA,cACwB;AACxB,QAAM,WAAW,MAAM,YAAY,OAAO,YAAY;AAGtD,QAAM,kBAA0C,CAAA;AAChD,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,IAAA;AACvC,wBAAgB,GAAG,IAAI,WAAW;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,iBAAiB,WACnB,OAAO,QAAQ,eAAe,EAAE;AAAA,IAC9B,CAAC,KAAK,CAAC,eAAe,aAAa,MAAM;AACvC,YAAM,aAAa,SAAS,aAAa;AACzC,UAAI,CAAC,YAAY;AACf,eAAO;AAAA,MACT;AACA,YAAM,SAAS,OAAO,QAAQ,UAAU,EACrC;AAAA,QACC,CAAC,CAAC,WAAW,UAAU,MACrB,OAAO,eAAe,YAAY,eAAe,QAAQ,WAAW;AAAA,MAAA,EAEvE,IAAI,CAAC,CAAC,UAAU,UAAU,MAAM;AAC/B,YAAI,OAAO;AACX,YAAI,WAAW,UAAU,cAAc;AACrC,iBAAO;AAAA,QACT,WAAW,WAAW,UAAU,sBAAsB;AACpD,iBAAO;AAAA,QACT,WACE,WAAW,UAAU,iBACrB,WAAW,UAAU,eACrB,WAAW,UAAU,aACrB;AACA,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,UACL,MAAM;AAAA,UACN;AAAA,QAAA;AAAA,MAEJ,CAAC;AACH,UAAI,aAAa,IAAI;AACrB,aAAO;AAAA,IACT;AAAA,IACA,CAAA;AAAA,EAAC,IAEH,CAAA;AAEJ,QAAM,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,WAAW;AAAA;AAAA,EAAA,EACX;AAEJ,QAAM,gBAA+B,CAAA;AAErC,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAoB,OAAO,QAAQ,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAI7E,YAAM,IAAI,6BAA6B,MAAM,IAAI;AACjD,UAAI,OAA4C;AAChD,UAAI,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,QAAQ,GAAG;AACjD,eAAO;AAAA,MACT,WAAW,EAAE,SAAS,MAAM,GAAG;AAC7B,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,MAAM,MAAM,aAAa;AAAA,QACzB;AAAA,MAAA;AAAA,IAEJ,CAAC;AAGD,UAAM,cAAc,QAAQ,aAAa;AAEzC,QAAI,aAAa;AACf,YAAM,kBAAkB,eAAe,QAAQ,SAAS,KAAK,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,IAAI;AAClF,YAAM,oBAAoB,eAAe,QAAQ,SAAS,KAAK,CAAA,GAAI;AAAA,QACjE,CAAC,KAAK,MAAM;AACV,cAAI,EAAE,IAAI,IAAI,EAAE;AAChB,iBAAO;AAAA,QACT;AAAA,QACA,CAAA;AAAA,MAAC;AAGH,iBAAW,SAAS,QAAQ;AAC1B,YAAI,eAAe,SAAS,MAAM,IAAI,KAAK,iBAAiB,MAAM,IAAI,MAAM,MAAM,MAAM;AACtF,kBAAQ;AAAA,YACN,sBAAsB,MAAM,IAAI,eAAe,QAAQ,SAAS,0BAA0B,iBAAiB,MAAM,IAAI,CAAC,gBAAgB,MAAM,IAAI;AAAA,UAAA;AAAA,QAEpJ;AAAA,MACF;AACA,YAAM,cAAc,OAAO,OAAO,CAAC,MAAM,CAAC,eAAe,SAAS,EAAE,IAAI,CAAC;AACzE,UAAI,YAAY,SAAS,GAAG;AAC1B,sBAAc,KAAK;AAAA,UACjB,WAAW,QAAQ;AAAA,UACnB,WAAW;AAAA,UACX,QAAQ;AAAA,QAAA,CACT;AAAA,MACH;AAAA,IACF,OAAO;AACL,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,UAAA;AAAA,UAEV,GAAG;AAAA,QAAA;AAAA,MACL,CACD;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,iBACpB,OACA,eACA;AACA,aAAW,QAAQ,eAAe;AAChC,QAAI,KAAK,cAAc,UAAU;AAC/B,cAAQ,IAAI,mBAAmB,KAAK,SAAS;AAC7C,YAAM,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;AAChB,gBAAQ,MAAM,0BAA0B,KAAK,SAAS,KAAK,OAAO,KAAK;AACvE,cAAM,IAAI,MAAM,qBAAqB,OAAO,KAAK,EAAE;AAAA,MACrD;AAAA,IACF,WAAW,KAAK,cAAc,UAAU;AACtC,cAAQ,IAAI,2BAA2B,KAAK,SAAS;AACrD,YAAM,SAAS,MAAM,MAAM,qBAAqB,KAAK,SAAS,IAAI;AAAA,QAChE,QAAQ;AAAA,QACR,MAAM,EAAE,QAAQ,KAAK,OAAA;AAAA,MAAO,CAC7B;AAED,UAAI,OAAO,OAAO;AAChB,gBAAQ,MAAM,0BAA0B,KAAK,SAAS,KAAK,OAAO,KAAK;AACvE,cAAM,IAAI,MAAM,qBAAqB,OAAO,KAAK,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,MAAM,EAAE,OAAA;AAAA,EACR,UAAU,EAAE,QAAA,EAAU,SAAA;AAAA,EACtB,SAAS,EAAE,QAAA,EAAU,SAAA;AAAA,EACrB,QAAQ,EAAE,QAAA,EAAU,SAAA;AAAA,EACpB,QAAQ,EAAE,QAAA,EAAU,SAAA;AAAA,EACpB,aAAa,EAAE,OAAA,EAAS,SAAA;AAC1B,CAAC;AAED,MAAM,oBAAoB,mBAAmB,OAAO;AAAA,EAClD,MAAM,EAAE,QAAQ,SAAS;AAAA,EACzB,WAAW,EAAE,OAAA,EAAS,SAAA;AAAA,EACtB,SAAS,EAAE,KAAK,CAAC,QAAQ,YAAY,cAAc,CAAC,EAAE,SAAA;AACxD,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,SAAA;AAC/C,CAAC;AAED,MAAM,kBAAkB,mBAAmB,OAAO;AAAA,EAChD,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,SAAS,EAAE,KAAK,CAAC,gBAAgB,SAAS,CAAC,EAAE,SAAA;AAC/C,CAAC;AAED,MAAM,uBAAuB,mBAAmB,OAAO;AAAA,EACrD,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,SAAS,EAAE,KAAK,CAAC,qBAAqB,cAAc,CAAC,EAAE,SAAA;AACzD,CAAC;AAED,MAAM,uBAAuB,mBAAmB,OAAO;AAAA,EACrD,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,oBAAoB,EAAE,OAAA,EAAS,SAAA;AACjC,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,OAAA;AAAA,EACb,WAAW,EAAE,KAAK,CAAC,UAAU,QAAQ,CAAC;AAAA,EACtC,QAAQ,EAAE,MAAM,WAAW;AAC7B,CAAC,EACA,MAAA;AAII,SAAS,yBAAyB,eAA8B;AACrE,MAAI,CAAC,cAAc,QAAQ;AACzB,YAAQ,IAAI,8CAA8C;AAC1D;AAAA,EACF;AACA,UAAQ,IAAI,MAAM,KAAK,MAAM,iBAAiB,CAAC;AAC/C,aAAW,QAAQ,eAAe;AAChC,UAAM,QAAQ,KAAK,cAAc,WAAW,MAAM;AAClD,YAAQ;AAAA,MACN;AAAA,EAAK,KAAK,IAAI,KAAK,cAAc,WAAW,MAAM,KAAK,MAAM,cAAc,IAAI,MAAM,KAAK,OAAO,cAAc,CAAC,KAAK,KAAK,SAAS;AAAA,IAAA;AAErI,QAAI,KAAK,OAAO,QAAQ;AACtB,iBAAW,SAAS,KAAK,QAAQ;AAC/B,YAAI,YAAY,SAAS,MAAM,IAAI,KAAK,MAAM,IAAI;AAClD,YAAI,MAAM,SAAS;AACjB,uBAAa;AAAA,QACf;AACA,YAAI,MAAM,QAAQ;AAChB,uBAAa;AAAA,QACf;AACA,qBAAa;AACb,gBAAQ,IAAI,SAAS;AAAA,MACvB;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,wBAAwB;AAAA,IACtC;AAAA,EACF;AACA,UAAQ,IAAI,EAAE;AAChB;"}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Result } from 'neverthrow';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
|
-
|
|
3
|
+
interface BasicAuthCredentials {
|
|
4
4
|
username: string;
|
|
5
5
|
password: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
6
|
+
}
|
|
7
|
+
interface OttoAPIKeyAuth {
|
|
8
8
|
apiKey: string;
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
10
|
type ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;
|
|
11
|
-
export
|
|
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;
|
package/dist/esm/odata/index.js
CHANGED
|
@@ -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 +=
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.1-beta.1",
|
|
4
4
|
"description": "FileMaker adapter for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
@@ -36,36 +36,40 @@
|
|
|
36
36
|
"url": "git+https://github.com/proofgeist/proofkit.git"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@babel/preset-react": "^7.
|
|
40
|
-
"@babel/preset-typescript": "^7.
|
|
39
|
+
"@babel/preset-react": "^7.28.5",
|
|
40
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
41
41
|
"@commander-js/extra-typings": "^14.0.0",
|
|
42
|
-
"@tanstack/vite-config": "^0.2.
|
|
43
|
-
"better-auth": "^1.
|
|
44
|
-
"c12": "^3.
|
|
42
|
+
"@tanstack/vite-config": "^0.2.1",
|
|
43
|
+
"better-auth": "^1.4.11",
|
|
44
|
+
"c12": "^3.3.3",
|
|
45
45
|
"chalk": "5.4.1",
|
|
46
|
-
"commander": "^14.0.
|
|
47
|
-
"dotenv": "^16.
|
|
48
|
-
"fs-extra": "^11.3.
|
|
46
|
+
"commander": "^14.0.2",
|
|
47
|
+
"dotenv": "^16.6.1",
|
|
48
|
+
"fs-extra": "^11.3.3",
|
|
49
49
|
"neverthrow": "^8.2.0",
|
|
50
|
-
"odata-query": "^8.0.
|
|
50
|
+
"odata-query": "^8.0.7",
|
|
51
51
|
"prompts": "^2.4.2",
|
|
52
|
-
"vite": "^6.
|
|
53
|
-
"zod": "3.
|
|
52
|
+
"vite": "^6.4.1",
|
|
53
|
+
"zod": "^4.3.5"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/fs-extra": "^11.0.4",
|
|
57
57
|
"@types/prompts": "^2.4.9",
|
|
58
58
|
"@vitest/ui": "^3.2.4",
|
|
59
|
-
"fm-odata-client": "^3.0.
|
|
60
|
-
"publint": "^0.3.
|
|
61
|
-
"typescript": "^5.9.
|
|
62
|
-
"vitest": "^
|
|
59
|
+
"fm-odata-client": "^3.0.2",
|
|
60
|
+
"publint": "^0.3.16",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"vitest": "^4.0.17"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"dev": "pnpm build:watch",
|
|
66
66
|
"test": "vitest run",
|
|
67
|
+
"test:e2e": "doppler run -c test_betterauth -- vitest run tests/e2e",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
67
69
|
"build": "vite build && publint --strict",
|
|
68
70
|
"build:watch": "vite build --watch",
|
|
69
|
-
"ci": "pnpm run build && pnpm run test"
|
|
71
|
+
"ci": "pnpm run build && pnpm run test",
|
|
72
|
+
"lint": "biome check . --write",
|
|
73
|
+
"lint:summary": "biome check . --reporter=summary"
|
|
70
74
|
}
|
|
71
75
|
}
|