@flarelink/client 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,501 @@
1
+ // src/errors.ts
2
+ var FlarelinkError = class extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status, code) {
6
+ super(message);
7
+ this.name = "FlarelinkError";
8
+ this.status = status;
9
+ this.code = code;
10
+ }
11
+ };
12
+ var AuthError = class extends FlarelinkError {
13
+ constructor(message, status, code) {
14
+ super(message, status, code);
15
+ this.name = "AuthError";
16
+ }
17
+ };
18
+ var StorageError = class extends FlarelinkError {
19
+ constructor(message, status, code) {
20
+ super(message, status, code);
21
+ this.name = "StorageError";
22
+ }
23
+ };
24
+ var DatabaseError = class extends FlarelinkError {
25
+ constructor(message, status, code) {
26
+ super(message, status, code);
27
+ this.name = "DatabaseError";
28
+ }
29
+ };
30
+ var MissingServiceKeyError = class extends FlarelinkError {
31
+ constructor(api) {
32
+ super(
33
+ `flarelink.${api} requires a service key. Pass it to createFlarelink({ serviceKey }) \u2014 only do this on the server (never in the browser). Mint a key from your project's dashboard.`,
34
+ 400,
35
+ "MISSING_SERVICE_KEY"
36
+ );
37
+ this.name = "MissingServiceKeyError";
38
+ }
39
+ };
40
+
41
+ // src/auth.ts
42
+ function createAuth(base, f) {
43
+ async function call(path, init = {}) {
44
+ const method = (init.method ?? "GET").toUpperCase();
45
+ const bodyBearing = method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
46
+ const headers = {
47
+ ...init.headers ?? {}
48
+ };
49
+ let body = init.body;
50
+ if (bodyBearing) {
51
+ if (body === void 0 || body === null) body = "{}";
52
+ if (headers["Content-Type"] === void 0) headers["Content-Type"] = "application/json";
53
+ }
54
+ const res = await f(`${base}${path}`, {
55
+ ...init,
56
+ credentials: "include",
57
+ headers,
58
+ body
59
+ });
60
+ if (!res.ok) {
61
+ const data = await res.json().catch(() => ({}));
62
+ throw new AuthError(
63
+ data.message ?? data.error ?? res.statusText,
64
+ res.status,
65
+ data.code
66
+ );
67
+ }
68
+ return await res.json();
69
+ }
70
+ const fetchMe = async () => {
71
+ try {
72
+ return await call("/api/me");
73
+ } catch (err) {
74
+ if (err instanceof AuthError && err.status === 401) return null;
75
+ throw err;
76
+ }
77
+ };
78
+ const browserDefault = () => typeof location !== "undefined" ? location.href : void 0;
79
+ return {
80
+ signUp: (input) => call("/api/auth/sign-up/email", {
81
+ method: "POST",
82
+ body: JSON.stringify({
83
+ ...input,
84
+ callbackURL: input.callbackURL ?? browserDefault()
85
+ })
86
+ }),
87
+ signIn: (input) => call("/api/auth/sign-in/email", {
88
+ method: "POST",
89
+ body: JSON.stringify(input)
90
+ }),
91
+ signInWithSocial: async (provider, opts = {}) => {
92
+ const callbackURL = opts.callbackURL ?? browserDefault();
93
+ const r = await call("/api/auth/sign-in/social", {
94
+ method: "POST",
95
+ body: JSON.stringify({ provider, callbackURL })
96
+ });
97
+ if (!opts.noRedirect && typeof location !== "undefined") {
98
+ location.href = r.url;
99
+ }
100
+ return r;
101
+ },
102
+ signInWithMagicLink: (email, opts = {}) => {
103
+ const callbackURL = opts.callbackURL ?? browserDefault();
104
+ return call("/api/auth/sign-in/magic-link", {
105
+ method: "POST",
106
+ body: JSON.stringify({ email, callbackURL })
107
+ });
108
+ },
109
+ signOut: async () => {
110
+ await call("/api/auth/sign-out", { method: "POST" });
111
+ },
112
+ requestPasswordReset: (input) => call("/api/auth/request-password-reset", {
113
+ method: "POST",
114
+ body: JSON.stringify(input)
115
+ }),
116
+ resetPassword: (input) => call("/api/auth/reset-password", {
117
+ method: "POST",
118
+ body: JSON.stringify(input)
119
+ }),
120
+ sendVerificationEmail: (input) => call("/api/auth/send-verification-email", {
121
+ method: "POST",
122
+ body: JSON.stringify({
123
+ ...input,
124
+ callbackURL: input.callbackURL ?? browserDefault()
125
+ })
126
+ }),
127
+ getMe: async () => (await fetchMe())?.user ?? null,
128
+ getSession: async () => (await fetchMe())?.session ?? null
129
+ };
130
+ }
131
+
132
+ // src/sql-builder.ts
133
+ var IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
134
+ function assertIdent(name, role) {
135
+ if (typeof name !== "string" || !IDENT_RE.test(name)) {
136
+ throw new DatabaseError(
137
+ `Invalid ${role} name: ${JSON.stringify(
138
+ name
139
+ )}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/ \u2014 use flarelink.sql\`...\` for anything more dynamic.`,
140
+ 400,
141
+ "INVALID_IDENTIFIER"
142
+ );
143
+ }
144
+ }
145
+ function composeWhere(filter, paramOffset = 0) {
146
+ const parts = [];
147
+ const params = [];
148
+ for (const [col, value] of Object.entries(filter)) {
149
+ assertIdent(col, "column");
150
+ if (value === null) {
151
+ parts.push(`"${col}" IS NULL`);
152
+ continue;
153
+ }
154
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
155
+ params.push(value);
156
+ parts.push(`"${col}" = ?${paramOffset + params.length}`);
157
+ continue;
158
+ }
159
+ throw new DatabaseError(
160
+ `Unsupported filter value for column ${JSON.stringify(col)} (${typeof value}). where() takes equality on primitives or NULL \u2014 use flarelink.sql\`...\` for IN / ranges / OR / etc.`,
161
+ 400,
162
+ "UNSUPPORTED_FILTER"
163
+ );
164
+ }
165
+ return { sql: parts.length > 0 ? parts.join(" AND ") : "1=1", params };
166
+ }
167
+ function composeSelect(opts) {
168
+ assertIdent(opts.table, "table");
169
+ const cols = opts.columns === "*" ? "*" : opts.columns.map((c) => {
170
+ assertIdent(c, "column");
171
+ return `"${c}"`;
172
+ }).join(", ");
173
+ let sql = `SELECT ${cols} FROM "${opts.table}"`;
174
+ let params = [];
175
+ if (opts.where) {
176
+ const w = composeWhere(opts.where);
177
+ sql += ` WHERE ${w.sql}`;
178
+ params = params.concat(w.params);
179
+ }
180
+ if (opts.orderBy) {
181
+ assertIdent(opts.orderBy.column, "column");
182
+ const dir = opts.orderBy.direction === "desc" ? "DESC" : "ASC";
183
+ sql += ` ORDER BY "${opts.orderBy.column}" ${dir}`;
184
+ }
185
+ if (typeof opts.limit === "number") {
186
+ if (!Number.isInteger(opts.limit) || opts.limit < 0) {
187
+ throw new DatabaseError("limit must be a non-negative integer", 400, "INVALID_LIMIT");
188
+ }
189
+ sql += ` LIMIT ${opts.limit}`;
190
+ }
191
+ if (typeof opts.offset === "number") {
192
+ if (!Number.isInteger(opts.offset) || opts.offset < 0) {
193
+ throw new DatabaseError("offset must be a non-negative integer", 400, "INVALID_OFFSET");
194
+ }
195
+ sql += ` OFFSET ${opts.offset}`;
196
+ }
197
+ return { sql, params };
198
+ }
199
+ function composeInsert(opts) {
200
+ assertIdent(opts.table, "table");
201
+ if (opts.rows.length === 0) {
202
+ throw new DatabaseError("insert(): no rows provided", 400, "EMPTY_INSERT");
203
+ }
204
+ const cols = Object.keys(opts.rows[0]);
205
+ if (cols.length === 0) {
206
+ throw new DatabaseError(
207
+ "insert(): row has no columns. Pass at least one column to insert a row.",
208
+ 400,
209
+ "EMPTY_ROW"
210
+ );
211
+ }
212
+ for (const c of cols) assertIdent(c, "column");
213
+ const colList = cols.map((c) => `"${c}"`).join(", ");
214
+ const params = [];
215
+ const valueGroups = [];
216
+ for (const row of opts.rows) {
217
+ const placeholders = [];
218
+ for (const c of cols) {
219
+ const v = row[c];
220
+ params.push(v === void 0 ? null : v);
221
+ placeholders.push(`?${params.length}`);
222
+ }
223
+ valueGroups.push(`(${placeholders.join(", ")})`);
224
+ }
225
+ let sql = `INSERT INTO "${opts.table}" (${colList}) VALUES ${valueGroups.join(", ")}`;
226
+ if (opts.returning) {
227
+ sql += ` RETURNING ${composeReturning(opts.returning)}`;
228
+ }
229
+ return { sql, params };
230
+ }
231
+ function composeUpdate(opts) {
232
+ assertIdent(opts.table, "table");
233
+ const cols = Object.keys(opts.patch);
234
+ if (cols.length === 0) {
235
+ throw new DatabaseError(
236
+ "update(): patch is empty. Pass at least one column to update.",
237
+ 400,
238
+ "EMPTY_PATCH"
239
+ );
240
+ }
241
+ const params = [];
242
+ const setParts = [];
243
+ for (const c of cols) {
244
+ assertIdent(c, "column");
245
+ const v = opts.patch[c];
246
+ params.push(v);
247
+ setParts.push(`"${c}" = ?${params.length}`);
248
+ }
249
+ let sql = `UPDATE "${opts.table}" SET ${setParts.join(", ")}`;
250
+ if (opts.where) {
251
+ const w = composeWhere(opts.where, params.length);
252
+ sql += ` WHERE ${w.sql}`;
253
+ params.push(...w.params);
254
+ }
255
+ if (opts.returning) {
256
+ sql += ` RETURNING ${composeReturning(opts.returning)}`;
257
+ }
258
+ return { sql, params };
259
+ }
260
+ function composeDelete(opts) {
261
+ assertIdent(opts.table, "table");
262
+ let sql = `DELETE FROM "${opts.table}"`;
263
+ const params = [];
264
+ if (opts.where) {
265
+ const w = composeWhere(opts.where);
266
+ sql += ` WHERE ${w.sql}`;
267
+ params.push(...w.params);
268
+ }
269
+ return { sql, params };
270
+ }
271
+ function composeReturning(returning) {
272
+ if (returning === "*") return "*";
273
+ return returning.map((c) => {
274
+ assertIdent(c, "column");
275
+ return `"${c}"`;
276
+ }).join(", ");
277
+ }
278
+ function composeTaggedSql(strings, values) {
279
+ let sql = "";
280
+ const params = [];
281
+ for (let i = 0; i < strings.length; i++) {
282
+ sql += strings[i] ?? "";
283
+ if (i < values.length) {
284
+ params.push(values[i]);
285
+ sql += `?${params.length}`;
286
+ }
287
+ }
288
+ return { sql, params };
289
+ }
290
+
291
+ // src/db.ts
292
+ async function postQuery(base, serviceKey, f, sql, params) {
293
+ const res = await f(`${base}/api/db/query`, {
294
+ method: "POST",
295
+ headers: {
296
+ Authorization: `Bearer ${serviceKey}`,
297
+ "Content-Type": "application/json"
298
+ },
299
+ body: JSON.stringify({ sql, params })
300
+ });
301
+ if (!res.ok) {
302
+ const body = await res.json().catch(() => ({}));
303
+ throw new DatabaseError(body.error ?? res.statusText, res.status, body.code);
304
+ }
305
+ const data = await res.json();
306
+ return { rows: data.results, meta: data.meta };
307
+ }
308
+ function createDatabase(base, serviceKey, f) {
309
+ const requireKey = () => {
310
+ if (!serviceKey) throw new MissingServiceKeyError("database");
311
+ return serviceKey;
312
+ };
313
+ function makeSelect(state) {
314
+ const exec = async () => {
315
+ const key = requireKey();
316
+ const { sql, params } = composeSelect(state);
317
+ return await postQuery(base, key, f, sql, params);
318
+ };
319
+ return {
320
+ select: (columns) => makeSelect({ ...state, columns: normalizeCols(columns) }),
321
+ where: (filter) => makeSelect({ ...state, where: filter }),
322
+ orderBy: (column, direction = "asc") => makeSelect({
323
+ ...state,
324
+ orderBy: { column, direction }
325
+ }),
326
+ limit: (n) => makeSelect({ ...state, limit: n }),
327
+ offset: (n) => makeSelect({ ...state, offset: n }),
328
+ then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected)
329
+ };
330
+ }
331
+ function makeInsert(table, rows, returning) {
332
+ const exec = async () => {
333
+ const key = requireKey();
334
+ const { sql, params } = composeInsert({ table, rows, returning });
335
+ return await postQuery(base, key, f, sql, params);
336
+ };
337
+ return {
338
+ returning: (cols) => makeInsert(table, rows, normalizeCols(cols ?? "*")),
339
+ then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected)
340
+ };
341
+ }
342
+ function makeUpdate(table, patch, where, returning) {
343
+ const exec = async () => {
344
+ const key = requireKey();
345
+ const { sql, params } = composeUpdate({ table, patch, where, returning });
346
+ return await postQuery(base, key, f, sql, params);
347
+ };
348
+ return {
349
+ where: (filter) => makeUpdate(table, patch, filter, returning),
350
+ returning: (cols) => makeUpdate(table, patch, where, normalizeCols(cols ?? "*")),
351
+ then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected)
352
+ };
353
+ }
354
+ function makeDelete(table, where) {
355
+ const exec = async () => {
356
+ const key = requireKey();
357
+ const { sql, params } = composeDelete({ table, where });
358
+ return await postQuery(base, key, f, sql, params);
359
+ };
360
+ return {
361
+ where: (filter) => makeDelete(table, filter),
362
+ then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected)
363
+ };
364
+ }
365
+ return {
366
+ from: (table) => {
367
+ assertIdent(table, "table");
368
+ const select = makeSelect({ table, columns: "*" });
369
+ const tableQuery = select;
370
+ tableQuery.insert = (row) => {
371
+ const rows = Array.isArray(row) ? row : [row];
372
+ return makeInsert(table, rows);
373
+ };
374
+ tableQuery.update = (patch) => makeUpdate(table, patch);
375
+ tableQuery.delete = () => makeDelete(table);
376
+ return tableQuery;
377
+ },
378
+ sql: async (strings, ...values) => {
379
+ const key = requireKey();
380
+ const { sql, params } = composeTaggedSql(strings, values);
381
+ return await postQuery(base, key, f, sql, params);
382
+ }
383
+ };
384
+ }
385
+ function normalizeCols(cols) {
386
+ if (cols === "*") return "*";
387
+ return [...cols];
388
+ }
389
+
390
+ // src/storage.ts
391
+ function createStorage(base, serviceKey, f) {
392
+ const requireKey = () => {
393
+ if (!serviceKey) throw new MissingServiceKeyError("storage");
394
+ return serviceKey;
395
+ };
396
+ async function call(path, init = {}) {
397
+ const key = requireKey();
398
+ const headers = {
399
+ Authorization: `Bearer ${key}`,
400
+ ...init.headers ?? {}
401
+ };
402
+ const method = (init.method ?? "GET").toUpperCase();
403
+ if (method !== "GET" && method !== "HEAD" && headers["Content-Type"] === void 0) {
404
+ headers["Content-Type"] = "application/json";
405
+ }
406
+ const url = new URL(`${base}${path}`);
407
+ if (init.query) {
408
+ for (const [k, v] of Object.entries(init.query)) {
409
+ if (v !== void 0 && v !== "") url.searchParams.set(k, v);
410
+ }
411
+ }
412
+ const res = await f(url.toString(), { ...init, headers });
413
+ if (!res.ok) {
414
+ const body = await res.json().catch(() => ({}));
415
+ throw new StorageError(
416
+ body.error ?? res.statusText,
417
+ res.status,
418
+ body.code
419
+ );
420
+ }
421
+ return await res.json();
422
+ }
423
+ return {
424
+ listBuckets: async () => {
425
+ const r = await call("/api/storage/buckets");
426
+ return r.buckets;
427
+ },
428
+ from: (bucket) => ({
429
+ createSignedUploadUrl: async (key, opts = {}) => {
430
+ const r = await call(
431
+ "/api/storage/presign",
432
+ {
433
+ method: "POST",
434
+ body: JSON.stringify({
435
+ bucket,
436
+ key,
437
+ op: "put",
438
+ contentType: opts.contentType,
439
+ expiresIn: opts.expiresIn
440
+ })
441
+ }
442
+ );
443
+ return { url: r.url, signedHeaders: r.signedHeaders };
444
+ },
445
+ createSignedDownloadUrl: async (key, opts = {}) => {
446
+ const r = await call("/api/storage/presign", {
447
+ method: "POST",
448
+ body: JSON.stringify({
449
+ bucket,
450
+ key,
451
+ op: "get",
452
+ expiresIn: opts.expiresIn
453
+ })
454
+ });
455
+ return { url: r.url };
456
+ },
457
+ remove: async (keys) => {
458
+ for (const k of keys) {
459
+ await call("/api/storage/object", {
460
+ method: "DELETE",
461
+ body: JSON.stringify({ bucket, key: k })
462
+ });
463
+ }
464
+ },
465
+ list: async (opts = {}) => {
466
+ return call("/api/storage/list", {
467
+ method: "GET",
468
+ query: {
469
+ bucket,
470
+ ...opts.prefix !== void 0 && { prefix: opts.prefix },
471
+ ...opts.cursor !== void 0 && { cursor: opts.cursor }
472
+ }
473
+ });
474
+ }
475
+ })
476
+ };
477
+ }
478
+
479
+ // src/index.ts
480
+ function createFlarelink(config) {
481
+ if (!config?.url) {
482
+ throw new Error(
483
+ `createFlarelink({ url }) is required. The URL is your project's auth Worker, e.g. "https://myapp-auth.your-subdomain.workers.dev" \u2014 find it in the Flarelink dashboard.`
484
+ );
485
+ }
486
+ const base = config.url.replace(/\/$/, "");
487
+ const f = config.fetch ?? fetch;
488
+ const auth = createAuth(base, f);
489
+ const storage = createStorage(base, config.serviceKey, f);
490
+ const db = createDatabase(base, config.serviceKey, f);
491
+ return {
492
+ auth,
493
+ storage,
494
+ from: (table) => db.from(table),
495
+ sql: (strings, ...values) => db.sql(strings, ...values)
496
+ };
497
+ }
498
+
499
+ export { AuthError, DatabaseError, FlarelinkError, MissingServiceKeyError, StorageError, createFlarelink };
500
+ //# sourceMappingURL=index.js.map
501
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/auth.ts","../src/sql-builder.ts","../src/db.ts","../src/storage.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Shared error types. Callers get a concrete class to instanceof against,\n// plus the BetterAuth machine-readable `code` so they can branch on\n// \"INVALID_PASSWORD\" / \"TOO_MANY_REQUESTS\" / etc. without string-matching.\n\nexport class FlarelinkError extends Error {\n readonly status: number;\n readonly code: string | undefined;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'FlarelinkError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class AuthError extends FlarelinkError {\n constructor(message: string, status: number, code?: string) {\n super(message, status, code);\n this.name = 'AuthError';\n }\n}\n\nexport class StorageError extends FlarelinkError {\n constructor(message: string, status: number, code?: string) {\n super(message, status, code);\n this.name = 'StorageError';\n }\n}\n\nexport class DatabaseError extends FlarelinkError {\n constructor(message: string, status: number, code?: string) {\n super(message, status, code);\n this.name = 'DatabaseError';\n }\n}\n\n/** Thrown when a server-only API is called without a service key. */\nexport class MissingServiceKeyError extends FlarelinkError {\n constructor(api: 'storage' | 'database') {\n super(\n `flarelink.${api} requires a service key. Pass it to createFlarelink({ serviceKey }) — only do this on the server (never in the browser). Mint a key from your project's dashboard.`,\n 400,\n 'MISSING_SERVICE_KEY',\n );\n this.name = 'MissingServiceKeyError';\n }\n}\n","// Auth surface. Ported verbatim from auth-module/client/flarelinkAuth.ts —\n// every method sends `credentials: 'include'` so the browser carries the\n// session cookie automatically. Caller's app origin must be in the\n// deployment's trustedOrigins list (Authentication page in the dashboard),\n// otherwise the Worker rejects with 403.\n\nimport { AuthError } from './errors.js';\nimport type {\n RequestPasswordResetInput,\n ResetPasswordInput,\n SendVerificationEmailInput,\n Session,\n SignInInput,\n SignInWithMagicLinkOptions,\n SignInWithSocialOptions,\n SignUpInput,\n SocialProvider,\n User,\n} from './types.js';\n\nexport type Auth = {\n signUp(input: SignUpInput): Promise<{ user: User }>;\n signIn(input: SignInInput): Promise<{ user: User }>;\n signInWithSocial(\n provider: SocialProvider,\n opts?: SignInWithSocialOptions,\n ): Promise<{ url: string }>;\n signInWithMagicLink(\n email: string,\n opts?: SignInWithMagicLinkOptions,\n ): Promise<{ status: boolean }>;\n signOut(): Promise<void>;\n /**\n * Triggers a password-reset email. Always resolves with `{ status: true }`\n * even when the email is unknown — that's deliberate, BetterAuth doesn't\n * leak account existence on this endpoint.\n */\n requestPasswordReset(input: RequestPasswordResetInput): Promise<{ status: boolean }>;\n /**\n * Completes the reset using the token from the email link. Your reset\n * page reads `?token=` from the URL and passes it here alongside the\n * new password.\n */\n resetPassword(input: ResetPasswordInput): Promise<{ status: boolean }>;\n /**\n * Sends a verification email to the address. The link in the email lands\n * the user on `callbackURL` after the email is marked verified.\n */\n sendVerificationEmail(input: SendVerificationEmailInput): Promise<{ status: boolean }>;\n /** Current user, or null when not signed in. */\n getMe(): Promise<User | null>;\n /** Active session, or null when not signed in. */\n getSession(): Promise<Session | null>;\n};\n\nexport function createAuth(base: string, f: typeof fetch): Auth {\n async function call<T>(path: string, init: RequestInit = {}): Promise<T> {\n // BetterAuth is strict on two fronts: state-changing requests must declare\n // Content-Type: application/json (else 415), AND if Content-Type is JSON\n // the body has to actually parse as JSON (empty body throws). For POSTs\n // like sign-out that semantically have nothing to send, we default the\n // body to \"{}\" so both checks pass.\n const method = (init.method ?? 'GET').toUpperCase();\n const bodyBearing = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';\n const headers: Record<string, string> = {\n ...((init.headers as Record<string, string> | undefined) ?? {}),\n };\n let body = init.body;\n if (bodyBearing) {\n if (body === undefined || body === null) body = '{}';\n if (headers['Content-Type'] === undefined) headers['Content-Type'] = 'application/json';\n }\n const res = await f(`${base}${path}`, {\n ...init,\n credentials: 'include',\n headers,\n body,\n });\n if (!res.ok) {\n const data = (await res.json().catch(() => ({}))) as {\n message?: string;\n error?: string;\n code?: string;\n };\n throw new AuthError(\n data.message ?? data.error ?? res.statusText,\n res.status,\n data.code,\n );\n }\n return (await res.json()) as T;\n }\n\n const fetchMe = async (): Promise<{ user: User; session: Session } | null> => {\n try {\n return await call<{ user: User; session: Session }>('/api/me');\n } catch (err) {\n if (err instanceof AuthError && err.status === 401) return null;\n throw err;\n }\n };\n\n // Default a missing browser-side callbackURL to the current page so the\n // auto-send-on-signup verification email lands the user back where they\n // started. Without this, BetterAuth defaults to `/` which resolves against\n // the auth Worker's hostname → 404. Non-browser callers (SSR, tests) pass\n // explicit values or accept the Worker's first-trusted-origin fallback.\n const browserDefault = (): string | undefined =>\n typeof location !== 'undefined' ? location.href : undefined;\n\n return {\n signUp: (input) =>\n call<{ user: User }>('/api/auth/sign-up/email', {\n method: 'POST',\n body: JSON.stringify({\n ...input,\n callbackURL: input.callbackURL ?? browserDefault(),\n }),\n }),\n signIn: (input) =>\n call<{ user: User }>('/api/auth/sign-in/email', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n signInWithSocial: async (provider, opts = {}) => {\n const callbackURL = opts.callbackURL ?? browserDefault();\n const r = await call<{ url: string }>('/api/auth/sign-in/social', {\n method: 'POST',\n body: JSON.stringify({ provider, callbackURL }),\n });\n if (!opts.noRedirect && typeof location !== 'undefined') {\n location.href = r.url;\n }\n return r;\n },\n signInWithMagicLink: (email, opts = {}) => {\n const callbackURL = opts.callbackURL ?? browserDefault();\n return call<{ status: boolean }>('/api/auth/sign-in/magic-link', {\n method: 'POST',\n body: JSON.stringify({ email, callbackURL }),\n });\n },\n signOut: async () => {\n await call<{ success: true }>('/api/auth/sign-out', { method: 'POST' });\n },\n requestPasswordReset: (input) =>\n call<{ status: boolean }>('/api/auth/request-password-reset', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n resetPassword: (input) =>\n call<{ status: boolean }>('/api/auth/reset-password', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n sendVerificationEmail: (input) =>\n call<{ status: boolean }>('/api/auth/send-verification-email', {\n method: 'POST',\n body: JSON.stringify({\n ...input,\n callbackURL: input.callbackURL ?? browserDefault(),\n }),\n }),\n getMe: async () => (await fetchMe())?.user ?? null,\n getSession: async () => (await fetchMe())?.session ?? null,\n };\n}\n","// Internal SQL composer. Customer-facing API is in db.ts; this module is\n// the engine. Every identifier (table + column names) is validated against\n// IDENT_RE before composition — if a string doesn't match, we throw rather\n// than letting it reach the SQL.\n//\n// Values always go through bind parameters. There is no path from a\n// customer-supplied value into a SQL string. Try to find one in code review\n// before adding any.\n\nimport { DatabaseError } from './errors.js';\n\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport type Equality = string | number | boolean | null;\n\nexport function assertIdent(name: string, role: 'table' | 'column'): void {\n if (typeof name !== 'string' || !IDENT_RE.test(name)) {\n throw new DatabaseError(\n `Invalid ${role} name: ${JSON.stringify(\n name,\n )}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/ — use flarelink.sql\\`...\\` for anything more dynamic.`,\n 400,\n 'INVALID_IDENTIFIER',\n );\n }\n}\n\n// Build a WHERE clause from a filter object. Equality only, AND-chained.\n// NULL becomes `IS NULL` (not `= NULL`, which is always false in SQL).\n// Arrays + objects in the filter throw with a message pointing at\n// flarelink.sql — keeps the builder surface honestly minimal.\nexport type WhereFilter = Record<string, Equality>;\n\nexport function composeWhere(\n filter: WhereFilter,\n paramOffset = 0,\n): { sql: string; params: Equality[] } {\n const parts: string[] = [];\n const params: Equality[] = [];\n for (const [col, value] of Object.entries(filter)) {\n assertIdent(col, 'column');\n if (value === null) {\n parts.push(`\"${col}\" IS NULL`);\n continue;\n }\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n params.push(value);\n parts.push(`\"${col}\" = ?${paramOffset + params.length}`);\n continue;\n }\n throw new DatabaseError(\n `Unsupported filter value for column ${JSON.stringify(col)} (${typeof value}). ` +\n `where() takes equality on primitives or NULL — use flarelink.sql\\`...\\` for IN / ranges / OR / etc.`,\n 400,\n 'UNSUPPORTED_FILTER',\n );\n }\n return { sql: parts.length > 0 ? parts.join(' AND ') : '1=1', params };\n}\n\n// Build a SELECT query.\nexport function composeSelect(opts: {\n table: string;\n columns: '*' | string[];\n where?: WhereFilter;\n orderBy?: { column: string; direction: 'asc' | 'desc' };\n limit?: number;\n offset?: number;\n}): { sql: string; params: Equality[] } {\n assertIdent(opts.table, 'table');\n const cols =\n opts.columns === '*'\n ? '*'\n : opts.columns\n .map((c) => {\n assertIdent(c, 'column');\n return `\"${c}\"`;\n })\n .join(', ');\n let sql = `SELECT ${cols} FROM \"${opts.table}\"`;\n let params: Equality[] = [];\n if (opts.where) {\n const w = composeWhere(opts.where);\n sql += ` WHERE ${w.sql}`;\n params = params.concat(w.params);\n }\n if (opts.orderBy) {\n assertIdent(opts.orderBy.column, 'column');\n const dir = opts.orderBy.direction === 'desc' ? 'DESC' : 'ASC';\n sql += ` ORDER BY \"${opts.orderBy.column}\" ${dir}`;\n }\n if (typeof opts.limit === 'number') {\n if (!Number.isInteger(opts.limit) || opts.limit < 0) {\n throw new DatabaseError('limit must be a non-negative integer', 400, 'INVALID_LIMIT');\n }\n sql += ` LIMIT ${opts.limit}`;\n }\n if (typeof opts.offset === 'number') {\n if (!Number.isInteger(opts.offset) || opts.offset < 0) {\n throw new DatabaseError('offset must be a non-negative integer', 400, 'INVALID_OFFSET');\n }\n sql += ` OFFSET ${opts.offset}`;\n }\n return { sql, params };\n}\n\n// Build INSERT — accepts a single row or an array of rows. All rows must\n// have the same column shape; we use the first row's keys as the schema.\nexport function composeInsert(opts: {\n table: string;\n rows: Record<string, Equality>[];\n returning?: '*' | string[];\n}): { sql: string; params: Equality[] } {\n assertIdent(opts.table, 'table');\n if (opts.rows.length === 0) {\n throw new DatabaseError('insert(): no rows provided', 400, 'EMPTY_INSERT');\n }\n const cols = Object.keys(opts.rows[0]!);\n if (cols.length === 0) {\n throw new DatabaseError(\n 'insert(): row has no columns. Pass at least one column to insert a row.',\n 400,\n 'EMPTY_ROW',\n );\n }\n for (const c of cols) assertIdent(c, 'column');\n const colList = cols.map((c) => `\"${c}\"`).join(', ');\n const params: Equality[] = [];\n const valueGroups: string[] = [];\n for (const row of opts.rows) {\n const placeholders: string[] = [];\n for (const c of cols) {\n const v = row[c];\n // Allow undefined as null on insert — most ORMs default missing\n // columns to NULL; matches user expectation.\n params.push(v === undefined ? null : (v as Equality));\n placeholders.push(`?${params.length}`);\n }\n valueGroups.push(`(${placeholders.join(', ')})`);\n }\n let sql = `INSERT INTO \"${opts.table}\" (${colList}) VALUES ${valueGroups.join(', ')}`;\n if (opts.returning) {\n sql += ` RETURNING ${composeReturning(opts.returning)}`;\n }\n return { sql, params };\n}\n\nexport function composeUpdate(opts: {\n table: string;\n patch: Record<string, Equality>;\n where?: WhereFilter;\n returning?: '*' | string[];\n}): { sql: string; params: Equality[] } {\n assertIdent(opts.table, 'table');\n const cols = Object.keys(opts.patch);\n if (cols.length === 0) {\n throw new DatabaseError(\n 'update(): patch is empty. Pass at least one column to update.',\n 400,\n 'EMPTY_PATCH',\n );\n }\n const params: Equality[] = [];\n const setParts: string[] = [];\n for (const c of cols) {\n assertIdent(c, 'column');\n const v = opts.patch[c]!;\n params.push(v);\n setParts.push(`\"${c}\" = ?${params.length}`);\n }\n let sql = `UPDATE \"${opts.table}\" SET ${setParts.join(', ')}`;\n if (opts.where) {\n const w = composeWhere(opts.where, params.length);\n sql += ` WHERE ${w.sql}`;\n params.push(...w.params);\n }\n if (opts.returning) {\n sql += ` RETURNING ${composeReturning(opts.returning)}`;\n }\n return { sql, params };\n}\n\nexport function composeDelete(opts: {\n table: string;\n where?: WhereFilter;\n}): { sql: string; params: Equality[] } {\n assertIdent(opts.table, 'table');\n let sql = `DELETE FROM \"${opts.table}\"`;\n const params: Equality[] = [];\n if (opts.where) {\n const w = composeWhere(opts.where);\n sql += ` WHERE ${w.sql}`;\n params.push(...w.params);\n }\n return { sql, params };\n}\n\nfunction composeReturning(returning: '*' | string[]): string {\n if (returning === '*') return '*';\n return returning\n .map((c) => {\n assertIdent(c, 'column');\n return `\"${c}\"`;\n })\n .join(', ');\n}\n\n// Tagged-template SQL: turn `SELECT * WHERE id = ${userId}` into\n// sql: \"SELECT * WHERE id = ?1\"\n// params: [userId]\n// Every interpolated value becomes a bind param. There is no path to inject\n// raw SQL via this entry point — interpolation is the only way values get\n// in, and the result strings are concatenations of the developer-owned\n// template parts.\nexport function composeTaggedSql(\n strings: TemplateStringsArray,\n values: unknown[],\n): { sql: string; params: unknown[] } {\n let sql = '';\n const params: unknown[] = [];\n for (let i = 0; i < strings.length; i++) {\n sql += strings[i] ?? '';\n if (i < values.length) {\n params.push(values[i]);\n sql += `?${params.length}`;\n }\n }\n return { sql, params };\n}\n","// Database surface — server-only query builder over the project's D1.\n//\n// Wire shape:\n// POST {url}/api/db/query { sql, params? } → { results, meta }\n// POST {url}/api/db/batch { statements } → { responses: [{ results, meta }] }\n//\n// Server-only because the service key is the only auth: anyone with the\n// key has full DB access. Use server env (process.env, CF env binding,\n// SvelteKit `$env/static/private`, etc.). Never bundle the service key\n// into client-side code.\n//\n// The chainable builder is a thin facade over composeSelect/Insert/Update/\n// Delete in sql-builder.ts. Every method returns the same chainable object\n// so calls compose into one terminal request. Awaiting (via the .then on\n// the chainable) sends the HTTP request.\n\nimport { DatabaseError, MissingServiceKeyError } from './errors.js';\nimport {\n assertIdent,\n composeDelete,\n composeInsert,\n composeSelect,\n composeTaggedSql,\n composeUpdate,\n type Equality,\n} from './sql-builder.js';\n\nexport type QueryResult<T = Record<string, unknown>> = {\n rows: T[];\n meta: {\n duration: number;\n rows_read?: number;\n rows_written?: number;\n last_row_id?: number;\n changes?: number;\n };\n};\n\nexport type { Equality };\n\nexport type QueryBuilder<T = Record<string, unknown>> = PromiseLike<QueryResult<T>> & {\n /** `*` is the default — call this only when you want to narrow. */\n select(columns: '*' | (keyof T & string)[] | string[]): QueryBuilder<T>;\n /** Equality filter, AND-chained. NULL becomes `IS NULL`. */\n where(filter: Partial<Record<keyof T & string, Equality>>): QueryBuilder<T>;\n orderBy(column: keyof T & string, direction?: 'asc' | 'desc'): QueryBuilder<T>;\n limit(n: number): QueryBuilder<T>;\n offset(n: number): QueryBuilder<T>;\n};\n\nexport type InsertBuilder<T = Record<string, unknown>> = PromiseLike<QueryResult<T>> & {\n /** Return the inserted row(s). Without this, the promise resolves with `rows: []`. */\n returning(columns?: '*' | (keyof T & string)[] | string[]): InsertBuilder<T>;\n};\n\nexport type UpdateBuilder<T = Record<string, unknown>> = PromiseLike<QueryResult<T>> & {\n where(filter: Partial<Record<keyof T & string, Equality>>): UpdateBuilder<T>;\n returning(columns?: '*' | (keyof T & string)[] | string[]): UpdateBuilder<T>;\n};\n\nexport type DeleteBuilder<T = Record<string, unknown>> = PromiseLike<QueryResult<T>> & {\n where(filter: Partial<Record<keyof T & string, Equality>>): DeleteBuilder<T>;\n};\n\nexport type TableQuery<T = Record<string, unknown>> = QueryBuilder<T> & {\n insert(row: Partial<T> | Partial<T>[]): InsertBuilder<T>;\n update(patch: Partial<T>): UpdateBuilder<T>;\n delete(): DeleteBuilder<T>;\n};\n\nexport type Database = {\n from<T extends Record<string, unknown> = Record<string, unknown>>(\n table: string,\n ): TableQuery<T>;\n\n /**\n * Raw SQL escape hatch. Tagged-template syntax interpolates values as\n * bind params — there's no way for an interpolated value to inject SQL:\n * await flarelink.sql`SELECT * FROM users WHERE id = ${userId}`\n */\n sql<T extends Record<string, unknown> = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<QueryResult<T>>;\n};\n\n// Internal HTTP transport for a single query.\nasync function postQuery(\n base: string,\n serviceKey: string,\n f: typeof fetch,\n sql: string,\n params: unknown[],\n): Promise<QueryResult> {\n const res = await f(`${base}/api/db/query`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${serviceKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ sql, params }),\n });\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };\n throw new DatabaseError(body.error ?? res.statusText, res.status, body.code);\n }\n const data = (await res.json()) as {\n results: Record<string, unknown>[];\n meta: QueryResult['meta'];\n };\n return { rows: data.results, meta: data.meta };\n}\n\nexport function createDatabase(\n base: string,\n serviceKey: string | undefined,\n f: typeof fetch,\n): Database {\n const requireKey = (): string => {\n if (!serviceKey) throw new MissingServiceKeyError('database');\n return serviceKey;\n };\n\n // SELECT builder. State accumulates via clone-and-return so a builder\n // can't be mutated mid-chain (each call returns a fresh object that\n // shares immutable state).\n type SelectState = {\n table: string;\n columns: '*' | string[];\n where?: Record<string, Equality>;\n orderBy?: { column: string; direction: 'asc' | 'desc' };\n limit?: number;\n offset?: number;\n };\n function makeSelect<T extends Record<string, unknown>>(\n state: SelectState,\n ): QueryBuilder<T> {\n const exec = async (): Promise<QueryResult<T>> => {\n const key = requireKey();\n const { sql, params } = composeSelect(state);\n return (await postQuery(base, key, f, sql, params)) as QueryResult<T>;\n };\n return {\n select: (columns) => makeSelect<T>({ ...state, columns: normalizeCols(columns) }),\n where: (filter) =>\n makeSelect<T>({ ...state, where: filter as Record<string, Equality> }),\n orderBy: (column, direction = 'asc') =>\n makeSelect<T>({\n ...state,\n orderBy: { column: column as string, direction },\n }),\n limit: (n) => makeSelect<T>({ ...state, limit: n }),\n offset: (n) => makeSelect<T>({ ...state, offset: n }),\n then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected),\n };\n }\n\n function makeInsert<T extends Record<string, unknown>>(\n table: string,\n rows: Record<string, Equality>[],\n returning?: '*' | string[],\n ): InsertBuilder<T> {\n const exec = async (): Promise<QueryResult<T>> => {\n const key = requireKey();\n const { sql, params } = composeInsert({ table, rows, returning });\n return (await postQuery(base, key, f, sql, params)) as QueryResult<T>;\n };\n return {\n returning: (cols) => makeInsert<T>(table, rows, normalizeCols(cols ?? '*')),\n then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected),\n };\n }\n\n function makeUpdate<T extends Record<string, unknown>>(\n table: string,\n patch: Record<string, Equality>,\n where?: Record<string, Equality>,\n returning?: '*' | string[],\n ): UpdateBuilder<T> {\n const exec = async (): Promise<QueryResult<T>> => {\n const key = requireKey();\n const { sql, params } = composeUpdate({ table, patch, where, returning });\n return (await postQuery(base, key, f, sql, params)) as QueryResult<T>;\n };\n return {\n where: (filter) =>\n makeUpdate<T>(table, patch, filter as Record<string, Equality>, returning),\n returning: (cols) => makeUpdate<T>(table, patch, where, normalizeCols(cols ?? '*')),\n then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected),\n };\n }\n\n function makeDelete<T extends Record<string, unknown>>(\n table: string,\n where?: Record<string, Equality>,\n ): DeleteBuilder<T> {\n const exec = async (): Promise<QueryResult<T>> => {\n const key = requireKey();\n const { sql, params } = composeDelete({ table, where });\n return (await postQuery(base, key, f, sql, params)) as QueryResult<T>;\n };\n return {\n where: (filter) => makeDelete<T>(table, filter as Record<string, Equality>),\n then: (onfulfilled, onrejected) => exec().then(onfulfilled, onrejected),\n };\n }\n\n return {\n from: <T extends Record<string, unknown>>(table: string) => {\n // Defer key check until execution — instantiating a builder without a\n // key shouldn't throw (lets callers compose builders for codegen,\n // tests, etc.). Identifier validation IS eager because that's a\n // programmer error caught early.\n assertIdent(table, 'table');\n const select = makeSelect<T>({ table, columns: '*' });\n const tableQuery = select as TableQuery<T>;\n // Augment with the write methods. TS already knows the shape via the\n // intersection — these casts are just glue.\n (tableQuery as TableQuery<T>).insert = (row) => {\n // Partial<T> permits undefined per-field; the composer turns those\n // into NULL on insert. Cast at the boundary — runtime handles it.\n const rows = (Array.isArray(row) ? row : [row]) as Record<string, Equality>[];\n return makeInsert<T>(table, rows) as InsertBuilder<T>;\n };\n (tableQuery as TableQuery<T>).update = (patch) =>\n makeUpdate<T>(table, patch as Record<string, Equality>);\n (tableQuery as TableQuery<T>).delete = () => makeDelete<T>(table);\n return tableQuery;\n },\n sql: async <T extends Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ) => {\n const key = requireKey();\n const { sql, params } = composeTaggedSql(strings, values);\n return (await postQuery(base, key, f, sql, params)) as QueryResult<T>;\n },\n };\n}\n\nfunction normalizeCols(cols: '*' | string[] | readonly string[]): '*' | string[] {\n if (cols === '*') return '*';\n return [...cols];\n}\n","// Storage surface — Supabase-shaped facade over the project's R2 buckets.\n//\n// Wire shape:\n// POST {url}/api/storage/presign { bucket, key, op, contentType?, expiresIn? }\n// DELETE {url}/api/storage/object { bucket, key }\n// GET {url}/api/storage/list?bucket&prefix&cursor\n// GET {url}/api/storage/buckets\n//\n// All gated by `Authorization: Bearer <serviceKey>`. Browser PUT/GET hits R2\n// directly via presigned URL — the Worker is only in the control plane.\n//\n// Service key is required for every call. Without it (or if it's invalid),\n// the Worker returns 401 / 412 and we surface a clear error rather than a\n// raw fetch failure.\n\nimport { MissingServiceKeyError, StorageError } from './errors.js';\n\nexport type StorageBucket = {\n name: string;\n createdAt: string;\n};\n\nexport type StorageObject = {\n key: string;\n size: number;\n lastModified: string;\n etag: string;\n};\n\nexport type StorageListResponse = {\n objects: StorageObject[];\n prefixes: string[];\n nextCursor?: string;\n};\n\nexport type PresignOptions = {\n /** Default 300s (5 min). Server clamps to [60s, 3600s]. */\n expiresIn?: number;\n contentType?: string;\n};\n\nexport type StorageBucketAPI = {\n /**\n * Mint a presigned PUT URL. Use it with a plain `fetch` (or XHR for\n * progress) — bytes go direct to R2, the Worker never sees them.\n *\n * @returns `url` to PUT to, and `signedHeaders` you must send on the PUT\n * request (currently `content-type` when supplied). Don't add extra\n * headers — they'll break the signature.\n */\n createSignedUploadUrl(\n key: string,\n opts?: PresignOptions,\n ): Promise<{ url: string; signedHeaders: Record<string, string> }>;\n\n /**\n * Mint a presigned GET URL. Open it in the browser, embed it as an\n * `<img src>`, or fetch it server-side — same URL works anywhere until\n * it expires.\n */\n createSignedDownloadUrl(\n key: string,\n opts?: PresignOptions,\n ): Promise<{ url: string }>;\n\n /** Delete an object. */\n remove(keys: string[]): Promise<void>;\n\n /**\n * List objects under an optional prefix. Returns up to 1000 per call;\n * pass `cursor` from the previous response to page further.\n */\n list(opts?: { prefix?: string; cursor?: string }): Promise<StorageListResponse>;\n};\n\nexport type Storage = {\n /** List buckets attached to the project's R2 account. */\n listBuckets(): Promise<StorageBucket[]>;\n /** Scope all operations to a single bucket. */\n from(bucket: string): StorageBucketAPI;\n};\n\nexport function createStorage(\n base: string,\n serviceKey: string | undefined,\n f: typeof fetch,\n): Storage {\n const requireKey = (): string => {\n if (!serviceKey) throw new MissingServiceKeyError('storage');\n return serviceKey;\n };\n\n async function call<T>(\n path: string,\n init: RequestInit & { query?: Record<string, string> } = {},\n ): Promise<T> {\n const key = requireKey();\n const headers: Record<string, string> = {\n Authorization: `Bearer ${key}`,\n ...((init.headers as Record<string, string> | undefined) ?? {}),\n };\n const method = (init.method ?? 'GET').toUpperCase();\n if (method !== 'GET' && method !== 'HEAD' && headers['Content-Type'] === undefined) {\n headers['Content-Type'] = 'application/json';\n }\n const url = new URL(`${base}${path}`);\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v !== undefined && v !== '') url.searchParams.set(k, v);\n }\n }\n const res = await f(url.toString(), { ...init, headers });\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };\n throw new StorageError(\n body.error ?? res.statusText,\n res.status,\n body.code,\n );\n }\n return (await res.json()) as T;\n }\n\n return {\n listBuckets: async () => {\n const r = await call<{ buckets: StorageBucket[] }>('/api/storage/buckets');\n return r.buckets;\n },\n from: (bucket) => ({\n createSignedUploadUrl: async (key, opts = {}) => {\n const r = await call<{ url: string; signedHeaders: Record<string, string> }>(\n '/api/storage/presign',\n {\n method: 'POST',\n body: JSON.stringify({\n bucket,\n key,\n op: 'put',\n contentType: opts.contentType,\n expiresIn: opts.expiresIn,\n }),\n },\n );\n return { url: r.url, signedHeaders: r.signedHeaders };\n },\n createSignedDownloadUrl: async (key, opts = {}) => {\n const r = await call<{ url: string }>('/api/storage/presign', {\n method: 'POST',\n body: JSON.stringify({\n bucket,\n key,\n op: 'get',\n expiresIn: opts.expiresIn,\n }),\n });\n return { url: r.url };\n },\n remove: async (keys) => {\n // Each delete is its own request — R2's S3-compatible API supports\n // batch delete but the wire format (signed XML body) is materially\n // more code on the Worker. Sequential deletes are fine for typical\n // app use; revisit if customer needs bulk delete UX.\n for (const k of keys) {\n await call<{ ok: true }>('/api/storage/object', {\n method: 'DELETE',\n body: JSON.stringify({ bucket, key: k }),\n });\n }\n },\n list: async (opts = {}) => {\n return call<StorageListResponse>('/api/storage/list', {\n method: 'GET',\n query: {\n bucket,\n ...(opts.prefix !== undefined && { prefix: opts.prefix }),\n ...(opts.cursor !== undefined && { cursor: opts.cursor }),\n },\n });\n },\n }),\n };\n}\n","// @flarelink/client — typed client SDK for a Flarelink-provisioned project.\n//\n// import { createFlarelink } from '@flarelink/client';\n// const flarelink = createFlarelink({ url: 'https://myapp-auth.workers.dev' });\n//\n// Auth works everywhere (browser + server). Storage + database require a\n// per-project service key passed via `createFlarelink({ serviceKey })` and only\n// work on the server — never include the service key in client bundles.\n\nimport { createAuth, type Auth } from './auth.js';\nimport { createDatabase, type Database, type QueryResult, type TableQuery } from './db.js';\nimport { createStorage, type Storage } from './storage.js';\nimport type { FlarelinkConfig } from './types.js';\n\nexport type Flarelink = {\n /** Auth surface — browser + server safe. */\n readonly auth: Auth;\n\n /** File storage (R2). Server-only — requires `serviceKey`. */\n readonly storage: Storage;\n\n /**\n * Build a query against a D1 table.\n * Server-only — requires `serviceKey`.\n */\n from<T extends Record<string, unknown> = Record<string, unknown>>(\n table: string,\n ): TableQuery<T>;\n\n /**\n * Raw SQL escape hatch. Tagged-template syntax interpolates values as\n * bind params:\n * await flarelink.sql`SELECT * FROM users WHERE id = ${userId}`\n *\n * Server-only — requires `serviceKey`.\n */\n sql<T extends Record<string, unknown> = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<QueryResult<T>>;\n};\n\nexport function createFlarelink(config: FlarelinkConfig): Flarelink {\n if (!config?.url) {\n throw new Error(\n 'createFlarelink({ url }) is required. The URL is your project\\'s auth Worker, e.g. \"https://myapp-auth.your-subdomain.workers.dev\" — find it in the Flarelink dashboard.',\n );\n }\n const base = config.url.replace(/\\/$/, '');\n const f = config.fetch ?? fetch;\n\n const auth = createAuth(base, f);\n const storage = createStorage(base, config.serviceKey, f);\n const db = createDatabase(base, config.serviceKey, f);\n\n return {\n auth,\n storage,\n from: (table) => db.from(table),\n sql: (strings, ...values) => db.sql(strings, ...values),\n };\n}\n\n// Re-export everything callers might want to instanceof / annotate / catch.\nexport {\n AuthError,\n FlarelinkError,\n DatabaseError,\n MissingServiceKeyError,\n StorageError,\n} from './errors.js';\n\nexport type { Auth } from './auth.js';\nexport type {\n Database,\n DeleteBuilder,\n Equality,\n InsertBuilder,\n QueryBuilder,\n QueryResult,\n TableQuery,\n UpdateBuilder,\n} from './db.js';\nexport type {\n PresignOptions,\n Storage,\n StorageBucket,\n StorageBucketAPI,\n StorageListResponse,\n StorageObject,\n} from './storage.js';\nexport type {\n FlarelinkConfig,\n RequestPasswordResetInput,\n ResetPasswordInput,\n SendVerificationEmailInput,\n Session,\n SignInInput,\n SignInWithMagicLinkOptions,\n SignInWithSocialOptions,\n SignUpInput,\n SocialProvider,\n User,\n} from './types.js';\n"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@flarelink/client",
3
+ "version": "0.2.0",
4
+ "description": "Client SDK for Flarelink — auth + storage + database for the Cloudflare developer stack.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "keywords": [
28
+ "flarelink",
29
+ "cloudflare",
30
+ "auth",
31
+ "d1",
32
+ "r2",
33
+ "workers"
34
+ ],
35
+ "devDependencies": {
36
+ "tsup": "^8.3.0",
37
+ "typescript": "^5.6.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "sideEffects": false
43
+ }