totalum-sdk 0.1.0-dev.10

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.
Files changed (53) hide show
  1. package/README.md +18 -0
  2. package/dist/_types/billing.d-BbSZh1wY.d.ts +38 -0
  3. package/dist/_types/coerce.d-C0KIW76L.d.ts +6 -0
  4. package/dist/_types/errors.d-Cu3q_E_r.d.ts +3093 -0
  5. package/dist/_types/integrations.d-BNGV70e3.d.ts +793 -0
  6. package/dist/_types/ops.d-C9375KIG.d.ts +117 -0
  7. package/dist/ai/index.d.ts +424 -0
  8. package/dist/ai/index.js +185 -0
  9. package/dist/analytics/index.d.ts +35 -0
  10. package/dist/analytics/index.js +15 -0
  11. package/dist/browser/index.d.ts +1595 -0
  12. package/dist/browser/index.js +168 -0
  13. package/dist/cron/index.d.ts +180 -0
  14. package/dist/cron/index.js +61 -0
  15. package/dist/d1/errors.js +31 -0
  16. package/dist/d1/https.js +103 -0
  17. package/dist/d1/index.d.ts +107 -0
  18. package/dist/d1/index.js +73 -0
  19. package/dist/d1/lazy.js +112 -0
  20. package/dist/d1/libsql.js +125 -0
  21. package/dist/d1/session.js +38 -0
  22. package/dist/d1/sql.js +87 -0
  23. package/dist/d1/types.js +1 -0
  24. package/dist/email/index.d.ts +31 -0
  25. package/dist/email/index.js +30 -0
  26. package/dist/errors.js +35 -0
  27. package/dist/files/index.d.ts +119 -0
  28. package/dist/files/index.js +63 -0
  29. package/dist/http.js +85 -0
  30. package/dist/index.d.ts +73 -0
  31. package/dist/index.js +68 -0
  32. package/dist/logs/index.d.ts +51 -0
  33. package/dist/logs/index.js +18 -0
  34. package/dist/payments/index.d.ts +47 -0
  35. package/dist/payments/index.js +20 -0
  36. package/dist/pdf/index.d.ts +42 -0
  37. package/dist/pdf/index.js +17 -0
  38. package/dist/react/index.d.ts +91 -0
  39. package/dist/react/index.js +178 -0
  40. package/dist/realtime/index.d.ts +103 -0
  41. package/dist/realtime/index.js +30 -0
  42. package/dist/scan/index.d.ts +46 -0
  43. package/dist/scan/index.js +12 -0
  44. package/dist/seo/index.d.ts +23 -0
  45. package/dist/seo/index.js +12 -0
  46. package/dist/speech/index.d.ts +41 -0
  47. package/dist/speech/index.js +22 -0
  48. package/dist/web/index.d.ts +170 -0
  49. package/dist/web/index.js +31 -0
  50. package/dist/webhooks/index.d.ts +48 -0
  51. package/dist/webhooks/index.js +61 -0
  52. package/package.json +146 -0
  53. package/totalum-sdk.md +1088 -0
@@ -0,0 +1,73 @@
1
+ import { inWorker } from '../errors.js';
2
+ import { apiUrlOf, envOf, readEnv } from '../http.js';
3
+ import { TotalumD1Error } from './errors.js';
4
+ import { httpsExecutor } from './https.js';
5
+ import { lazyD1 } from './lazy.js';
6
+ import { libsqlExecutor } from './libsql.js';
7
+ export { TotalumError, isTotalumError } from '../errors.js';
8
+ export { TotalumD1Error, isTotalumD1Error } from './errors.js';
9
+ export { READ_YOUR_WRITES_COOKIE, withTotalumSession } from './session.js';
10
+ /**
11
+ * A `D1Database` for the project's own database wherever the code runs (A3.4, plan 05 §3): the bound
12
+ * `TOTALUM_DB` inside a Worker, the libsql adapter when the environment moved to Turso, the local D1 that
13
+ * `initOpenNextCloudflareForDev()` binds under `next dev`, and an HTTPS client to SDK-API everywhere else.
14
+ * Detection happens on the first statement, never at call time (§3.1).
15
+ */
16
+ export function totalumD1(options = {}) {
17
+ return lazyD1(() => resolveTarget(options), options.mode === 'migrate');
18
+ }
19
+ async function resolveTarget(options) {
20
+ if (options.binding)
21
+ return options.binding;
22
+ if (options.transport !== 'https') {
23
+ if (inWorker() && readEnv('TOTALUM_DB_PROVIDER') === 'turso')
24
+ return await tursoTarget();
25
+ const binding = await openNextBinding();
26
+ if (binding)
27
+ return binding;
28
+ }
29
+ const key = options.key ?? readEnv('TOTALUM_PROJECT_KEY');
30
+ if (key === undefined) {
31
+ throw TotalumD1Error.client('SDK_NOT_CONFIGURED', 'Set TOTALUM_PROJECT_KEY (server-side) or pass { binding } to totalumD1().');
32
+ }
33
+ return httpsExecutor({
34
+ apiUrl: apiUrlOf(options),
35
+ key,
36
+ env: envOf(options),
37
+ migrate: options.mode === 'migrate',
38
+ destructive: options.destructive === true,
39
+ });
40
+ }
41
+ /**
42
+ * OpenNext exposes the request's bindings, in a Worker and under `next dev` (Miniflare's local D1); absent (`next
43
+ * start`, a script) or outside a request, there is no binding. The executor
44
+ * reads `TOTALUM_DB` again on every statement: it is the current request's database — the plain binding, or the
45
+ * read-replica session `withTotalumSession` put there — never the first request's for the isolate's life.
46
+ */
47
+ async function openNextBinding() {
48
+ let openNext;
49
+ try {
50
+ openNext = (await import('@opennextjs/cloudflare'));
51
+ if (!openNext.getCloudflareContext().env['TOTALUM_DB'])
52
+ return undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ const current = () => openNext.getCloudflareContext().env['TOTALUM_DB'];
58
+ return {
59
+ prepare: (sql) => current().prepare(sql),
60
+ batch: (statements) => current().batch(statements),
61
+ exec: (sql) => current().exec(sql),
62
+ withSession: (c) => current().withSession(c),
63
+ };
64
+ }
65
+ async function tursoTarget() {
66
+ const url = readEnv('TURSO_DATABASE_URL');
67
+ const authToken = readEnv('TURSO_AUTH_TOKEN');
68
+ if (url === undefined || authToken === undefined) {
69
+ throw TotalumD1Error.client('SDK_NOT_CONFIGURED', 'TOTALUM_DB_PROVIDER=turso requires TURSO_DATABASE_URL and TURSO_AUTH_TOKEN');
70
+ }
71
+ const web = (await import('@libsql/client/web'));
72
+ return libsqlExecutor(web.createClient({ url, authToken, intMode: 'bigint' }));
73
+ }
@@ -0,0 +1,112 @@
1
+ import { TotalumD1Error } from './errors.js';
2
+ import { splitStatements } from './sql.js';
3
+ const READ = /^\s*(select|with)\b/i;
4
+ /**
5
+ * `batch()` answers rows as objects keyed by column name (no `raw()` mode), so a select with two columns of one name
6
+ * (`requests.id`, `users.id`) loses one, and Drizzle — which maps a batch row positionally from `Object.keys(row)` —
7
+ * shifts every later field. As a subquery, SQLite names the second `id:1`: every column survives, in order.
8
+ */
9
+ const uniqueColumns = (sql) => READ.test(sql) ? `SELECT * FROM (\n${sql.replace(/[\s;]+$/, '')}\n)` : sql;
10
+ /**
11
+ * The object `totalumD1()` hands out: no I/O and no environment detection until the first statement runs;
12
+ * the target is resolved once and cached (plan 05 §3.1) — in a Worker that target reads the request's own binding on
13
+ * every statement (`index.ts`), so one module-scope instance serves every request with that request's database.
14
+ * Statements remember `sql` + params and are materialised on the target when executed, so `drizzle(totalumD1())` at
15
+ * module scope is legal.
16
+ */
17
+ export function lazyD1(resolve, migrate) {
18
+ let target;
19
+ // a failed resolution is not cached, so a transient failure does not poison the module-scope instance
20
+ const resolved = () => (target ??= resolve().catch((e) => {
21
+ target = undefined;
22
+ throw e;
23
+ }));
24
+ class LazyStatement {
25
+ sql;
26
+ params;
27
+ on;
28
+ constructor(sql, params = [], on = resolved) {
29
+ this.sql = sql;
30
+ this.params = params;
31
+ this.on = on;
32
+ }
33
+ bind(...values) {
34
+ return new LazyStatement(this.sql, values, this.on);
35
+ }
36
+ async real() {
37
+ return (await this.on()).prepare(this.sql).bind(...this.params);
38
+ }
39
+ async first(colName) {
40
+ return (await this.real()).first(colName);
41
+ }
42
+ async run() {
43
+ return (await this.real()).run();
44
+ }
45
+ async all() {
46
+ return (await this.real()).all();
47
+ }
48
+ async raw(options) {
49
+ const s = await this.real();
50
+ return options?.columnNames === true ? s.raw({ columnNames: true }) : s.raw();
51
+ }
52
+ }
53
+ const batchOn = (on) => async (statements) => {
54
+ const t = await on();
55
+ const lazy = statements.map((s) => {
56
+ if (!(s instanceof LazyStatement)) {
57
+ throw TotalumD1Error.client('BATCH_FOREIGN_STATEMENT', 'batch() only accepts statements prepared by this totalumD1() instance');
58
+ }
59
+ return s;
60
+ });
61
+ const run = (sql) => t.batch(lazy.map((s) => t.prepare(sql(s.sql)).bind(...s.params)));
62
+ if (!lazy.some((s) => READ.test(s.sql)))
63
+ return run((s) => s);
64
+ try {
65
+ return await run(uniqueColumns);
66
+ }
67
+ catch (e) {
68
+ // a SQL error is deterministic and rolled the batch back: answer it as the statements were written
69
+ if (e instanceof Error && e.message.endsWith('SQLITE_ERROR'))
70
+ return run((s) => s);
71
+ throw e;
72
+ }
73
+ };
74
+ /**
75
+ * A D1 session (read replication, A3.23): on the native binding the binding's own `withSession()`, created on the
76
+ * first statement, so bookmarks work in hand-written code; on HTTPS and Turso there are no replicas and the session
77
+ * is the database itself, `getBookmark()` → `null`.
78
+ */
79
+ const withSession = (constraintOrBookmark) => {
80
+ let session;
81
+ let real;
82
+ const on = () => (session ??= resolved().then((t) => {
83
+ if (!t.withSession)
84
+ return t;
85
+ real = t.withSession(constraintOrBookmark);
86
+ return real;
87
+ }));
88
+ return {
89
+ prepare: (sql) => new LazyStatement(sql, [], on),
90
+ batch: batchOn(on),
91
+ getBookmark: () => real?.getBookmark() ?? null,
92
+ };
93
+ };
94
+ const exec = async (script) => {
95
+ if (!migrate) {
96
+ throw TotalumD1Error.client('EXEC_REQUIRES_MIGRATE_MODE', 'exec() runs DDL scripts: use totalumD1({ mode: "migrate" }) from scripts/migrate.ts, or db.batch([...]) for data');
97
+ }
98
+ const t = await resolved();
99
+ if (t.exec)
100
+ return t.exec(script);
101
+ const statements = splitStatements(script);
102
+ const results = await t.batch(statements.map((sql) => t.prepare(sql)));
103
+ return { count: statements.length, duration: results.reduce((sum, r) => sum + r.meta.duration, 0) };
104
+ };
105
+ return {
106
+ prepare: (sql) => new LazyStatement(sql),
107
+ batch: batchOn(resolved),
108
+ exec,
109
+ dump: () => Promise.reject(TotalumD1Error.client('NOT_SUPPORTED', 'dump() is not available; use the platform backups')),
110
+ withSession,
111
+ };
112
+ }
@@ -0,0 +1,125 @@
1
+ import { TotalumD1Error } from './errors.js';
2
+ import { assertBindable, pickFirst } from './sql.js';
3
+ /**
4
+ * D1 enforces foreign keys; a Turso database on the new engine (A3.24) starts every connection with them off, and each
5
+ * Hrana request is a new connection. So every request carries the pragma, inside the one transaction libsql opens for
6
+ * it (the new engine applies it there; libSQL has them on already and ignores it inside a transaction).
7
+ */
8
+ const FOREIGN_KEYS_ON = { sql: 'PRAGMA foreign_keys = ON', args: [] };
9
+ /**
10
+ * The Turso path (A3.8, plan 05 §3.10): a `D1Database`-compatible executor over a libsql client created with
11
+ * `intMode: 'bigint'` (integers beyond 2^53 then degrade to the same double D1 returns instead of throwing).
12
+ * `meta` is synthesised from what libsql reports; `size_after` is not reported by libsql and is 0.
13
+ */
14
+ export function libsqlExecutor(client) {
15
+ let lastRowId = 0;
16
+ const toResult = (rs) => {
17
+ if (rs.lastInsertRowid !== undefined && rs.lastInsertRowid !== null)
18
+ lastRowId = Number(rs.lastInsertRowid);
19
+ const rows = rs.rows.map((row) => Array.from(row, fromLibsqlValue));
20
+ const meta = {
21
+ duration: 0,
22
+ changes: rs.rowsAffected,
23
+ last_row_id: lastRowId,
24
+ rows_read: rows.length,
25
+ rows_written: rs.rowsAffected,
26
+ changed_db: rs.rowsAffected > 0,
27
+ size_after: 0,
28
+ };
29
+ const results = rows.map((row) => Object.fromEntries(row.map((cell, i) => [rs.columns[i], cell])));
30
+ return { results, success: true, meta };
31
+ };
32
+ const run = async (f) => {
33
+ try {
34
+ return await f();
35
+ }
36
+ catch (e) {
37
+ throw mapLibsqlError(e);
38
+ }
39
+ };
40
+ class LibsqlD1Statement {
41
+ sql;
42
+ args;
43
+ constructor(sql, args = []) {
44
+ this.sql = sql;
45
+ this.args = args;
46
+ }
47
+ bind(...values) {
48
+ assertBindable(values);
49
+ return new LibsqlD1Statement(this.sql, values);
50
+ }
51
+ async execute() {
52
+ const [, rs] = await run(() => client.batch([FOREIGN_KEYS_ON, { sql: this.sql, args: this.args }], 'deferred'));
53
+ return rs;
54
+ }
55
+ async first(colName) {
56
+ return pickFirst(toResult(await this.execute()).results, colName);
57
+ }
58
+ async run() {
59
+ return toResult(await this.execute());
60
+ }
61
+ all() {
62
+ return this.run();
63
+ }
64
+ async raw(options) {
65
+ const rs = await this.execute();
66
+ toResult(rs);
67
+ const rows = rs.rows.map((row) => Array.from(row, fromLibsqlValue));
68
+ return options?.columnNames === true ? [rs.columns, ...rows] : rows;
69
+ }
70
+ }
71
+ return {
72
+ prepare: (sql) => new LibsqlD1Statement(sql),
73
+ async batch(statements) {
74
+ const stmts = statements.map((s) => ({
75
+ sql: s.sql,
76
+ args: s.args,
77
+ }));
78
+ return (await run(() => client.batch([FOREIGN_KEYS_ON, ...stmts], 'write')))
79
+ .slice(1)
80
+ .map((rs) => toResult(rs));
81
+ },
82
+ };
83
+ }
84
+ function fromLibsqlValue(v) {
85
+ if (typeof v === 'bigint')
86
+ return Number(v);
87
+ if (v instanceof ArrayBuffer)
88
+ return Array.from(new Uint8Array(v));
89
+ return v;
90
+ }
91
+ /**
92
+ * The new engine (A3.24, recorded 2026-09-24) words the same SQLite failures as `Tursodb error: Runtime error: <msg> (19)`
93
+ * or `Tursodb error: Parse error: <msg>`, and a composite key as `t.(a, b)` where SQLite says `t.a, t.b`.
94
+ */
95
+ const TURSODB_PREFIX = /^Tursodb error: [A-Za-z ]+ error: /;
96
+ /**
97
+ * `LibsqlError { code, message: '<CODE>: <sqlite message>' }` → the `D1_ERROR: <sqlite message>: <CODE>` shape. A
98
+ * remote Turso database (recorded 2026-09-23) also prefixes `SQLite error: `, answers `SQLITE_UNKNOWN` where SQLite
99
+ * says `SQLITE_ERROR`, reports name resolution as `SQL_INPUT_ERROR` (`SQLite input error: … (at offset N)`) and its own
100
+ * parser's failures as `SQL_PARSE_ERROR` (wording not SQLite's).
101
+ */
102
+ function mapLibsqlError(e) {
103
+ const err = e;
104
+ if (typeof err.code !== 'string' || typeof err.message !== 'string')
105
+ return e;
106
+ const remote = err.code === 'SQL_PARSE_ERROR' || err.code === 'SQL_INPUT_ERROR';
107
+ if (!remote && !err.code.startsWith('SQLITE_'))
108
+ return e;
109
+ const code = remote || err.code === 'SQLITE_UNKNOWN' ? 'SQLITE_ERROR' : err.code;
110
+ const text = err.message
111
+ .replace(/^((SQLITE_\w+|SQL_PARSE_ERROR|SQL_INPUT_ERROR): )+/, '')
112
+ .replace(/^SQLite (input )?error: /, '')
113
+ .replace(/ \(at offset (\d+)\)$/, ' at offset $1')
114
+ .replace(TURSODB_PREFIX, '')
115
+ .replace(/ \(\d+\)$/, '')
116
+ .replace(/(\w+)\.\(([^)]+)\)/, (_, t, cols) => cols
117
+ .split(', ')
118
+ .map((c) => `${t}.${c}`)
119
+ .join(', '));
120
+ const sqliteMessage = `${text}: ${code}`;
121
+ return new TotalumD1Error('SQL_ERROR', `D1_ERROR: ${sqliteMessage}`, {
122
+ status: 400,
123
+ details: { sqliteMessage },
124
+ });
125
+ }
@@ -0,0 +1,38 @@
1
+ /** Set on every mutating response; while the browser holds it, its reads go to the primary (read-your-writes). */
2
+ export const READ_YOUR_WRITES_COOKIE = '__tlm_d1_w';
3
+ /** Replicas lag the primary by 30–75 ms (Cloudflare); 10 s leaves a wide margin (research 46 §6.3). */
4
+ const WINDOW_SECONDS = 10;
5
+ const READS = new Set(['GET', 'HEAD']);
6
+ /**
7
+ * Runs one request of a Worker with a replicated D1 (A3.23, research 46 §6.3): a `GET`/`HEAD` reads through a
8
+ * `first-unconstrained` session — the nearest replica — unless the browser wrote in the last 10 s; any other method
9
+ * uses the plain binding (writes go to the primary; measured faster than any session) and its response sets the short
10
+ * cookie that sends that browser's next reads to the primary. `run` receives the env to hand to the app, whose
11
+ * `totalumD1()` reads `TOTALUM_DB` from it on every statement. No D1 binding (Turso, a laptop): `run(env)` unchanged.
12
+ */
13
+ export async function withTotalumSession(request, env, run) {
14
+ const db = env.TOTALUM_DB;
15
+ if (typeof db?.withSession !== 'function')
16
+ return run(env);
17
+ const binding = db;
18
+ if (!READS.has(request.method)) {
19
+ const res = await run(env);
20
+ if (res.status === 101)
21
+ return res; // a WebSocket upgrade cannot be re-wrapped
22
+ const out = new Response(res.body, res);
23
+ out.headers.append('set-cookie', `${READ_YOUR_WRITES_COOKIE}=1; Max-Age=${String(WINDOW_SECONDS)}; Path=/; HttpOnly; Secure; SameSite=None; Partitioned`);
24
+ return out;
25
+ }
26
+ const cookies = request.headers.get('cookie') ?? '';
27
+ if (new RegExp(`(?:^|;\\s*)${READ_YOUR_WRITES_COOKIE}=`).test(cookies))
28
+ return run(env);
29
+ const session = binding.withSession('first-unconstrained');
30
+ const replicaDb = {
31
+ prepare: (sql) => session.prepare(sql),
32
+ batch: (statements) => session.batch(statements),
33
+ exec: (sql) => binding.exec(sql),
34
+ dump: () => binding.dump(),
35
+ withSession: (c) => binding.withSession(c),
36
+ };
37
+ return run({ ...env, TOTALUM_DB: replicaDb });
38
+ }
package/dist/d1/sql.js ADDED
@@ -0,0 +1,87 @@
1
+ import { isRecord } from './errors.js';
2
+ /** Native D1 refuses these at `bind()` time with `D1_TYPE_ERROR`; both non-native paths do the same, synchronously. */
3
+ export function assertBindable(values) {
4
+ for (const v of values) {
5
+ const ok = v === null ||
6
+ typeof v === 'string' ||
7
+ typeof v === 'number' ||
8
+ typeof v === 'boolean' ||
9
+ v instanceof ArrayBuffer ||
10
+ ArrayBuffer.isView(v);
11
+ if (!ok)
12
+ throw new Error(`D1_TYPE_ERROR: Type '${typeof v}' not supported for value '${describe(v)}'`);
13
+ }
14
+ }
15
+ /** The value as D1 prints it in `D1_TYPE_ERROR` (`${value}`). */
16
+ function describe(v) {
17
+ switch (typeof v) {
18
+ case 'bigint':
19
+ case 'number':
20
+ case 'boolean':
21
+ case 'string':
22
+ case 'symbol':
23
+ return v.toString();
24
+ case 'undefined':
25
+ return 'undefined';
26
+ case 'function':
27
+ return 'function';
28
+ default:
29
+ return '[object Object]';
30
+ }
31
+ }
32
+ /** `first(col)` as the binding does it: `null` on no rows, `D1_COLUMN_NOTFOUND` on an unknown column. */
33
+ export function pickFirst(results, colName) {
34
+ const row = results[0];
35
+ if (row === undefined)
36
+ return null;
37
+ if (colName === undefined)
38
+ return row;
39
+ if (!isRecord(row) || !(colName in row))
40
+ throw new Error(`D1_COLUMN_NOTFOUND: Column not found (${colName})`);
41
+ return row[colName];
42
+ }
43
+ /**
44
+ * Splits an `exec()` script on statement boundaries — `;` inside string literals, quoted identifiers and
45
+ * comments is not a boundary (plan 05 §3.4). Empty trailing fragments are dropped.
46
+ */
47
+ export function splitStatements(script) {
48
+ const out = [];
49
+ let start = 0;
50
+ let i = 0;
51
+ while (i < script.length) {
52
+ const c = script[i];
53
+ const next = script[i + 1];
54
+ if (c === "'" || c === '"' || c === '`' || c === '[') {
55
+ const close = c === '[' ? ']' : c;
56
+ i = script.indexOf(close, i + 1);
57
+ if (i === -1)
58
+ break;
59
+ // a doubled quote is an escaped quote inside the literal
60
+ while (close !== ']' && script[i + 1] === close)
61
+ i = script.indexOf(close, i + 2);
62
+ if (i === -1)
63
+ break;
64
+ i++;
65
+ }
66
+ else if (c === '-' && next === '-') {
67
+ i = script.indexOf('\n', i);
68
+ if (i === -1)
69
+ break;
70
+ }
71
+ else if (c === '/' && next === '*') {
72
+ i = script.indexOf('*/', i + 2);
73
+ if (i === -1)
74
+ break;
75
+ i += 2;
76
+ }
77
+ else if (c === ';') {
78
+ out.push(script.slice(start, i));
79
+ start = ++i;
80
+ }
81
+ else {
82
+ i++;
83
+ }
84
+ }
85
+ out.push(script.slice(start));
86
+ return out.map((s) => s.trim()).filter((s) => s.length > 0);
87
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { E as EmailSendInput, j as EmailView } from '../_types/integrations.d-BNGV70e3.js';
2
+ export { i as EmailSendOutput } from '../_types/integrations.d-BNGV70e3.js';
3
+ import { T as TotalumClientOptions } from '../_types/errors.d-Cu3q_E_r.js';
4
+ export { a as TotalumError, i as isTotalumError } from '../_types/errors.d-Cu3q_E_r.js';
5
+ import '../_types/coerce.d-C0KIW76L.js';
6
+
7
+ /**
8
+ * `totalum.email` (plan 05 §6.4) over SDK-API `/v1/email/*` (scope `email`): Resend, HTML sent byte for byte, charged
9
+ * per recipient. On dev only allow-listed recipients are accepted (`DEV_RECIPIENT_NOT_ALLOWED`).
10
+ */
11
+ declare function totalumEmail(options?: TotalumClientOptions): {
12
+ /**
13
+ * Sends one e-mail (HTML byte for byte); charged per recipient.
14
+ *
15
+ * @example
16
+ * await email.send({ to: 'ana@example.com', subject: 'Welcome', html: '<p>Hi Ana</p>' });
17
+ */
18
+ send: (msg: EmailSendInput) => Promise<{
19
+ success: true;
20
+ message: "Email sent successfully";
21
+ messageId: string;
22
+ id: string;
23
+ status: "queued" | "sent";
24
+ }>;
25
+ /** `null` when the e-mail is not this project's. */
26
+ get: (id: string) => Promise<EmailView | null>;
27
+ };
28
+ type TotalumEmail = ReturnType<typeof totalumEmail>;
29
+
30
+ export { EmailSendInput, EmailView, TotalumClientOptions, totalumEmail };
31
+ export type { TotalumEmail };
@@ -0,0 +1,30 @@
1
+ import { isTotalumError } from '../errors.js';
2
+ import { jsonClient } from '../http.js';
3
+ export { TotalumError, isTotalumError } from '../errors.js';
4
+ /**
5
+ * `totalum.email` (plan 05 §6.4) over SDK-API `/v1/email/*` (scope `email`): Resend, HTML sent byte for byte, charged
6
+ * per recipient. On dev only allow-listed recipients are accepted (`DEV_RECIPIENT_NOT_ALLOWED`).
7
+ */
8
+ export function totalumEmail(options = {}) {
9
+ const call = jsonClient(options);
10
+ return {
11
+ /**
12
+ * Sends one e-mail (HTML byte for byte); charged per recipient.
13
+ *
14
+ * @example
15
+ * await email.send({ to: 'ana@example.com', subject: 'Welcome', html: '<p>Hi Ana</p>' });
16
+ */
17
+ send: (msg) => call('POST', '/v1/email/send', msg),
18
+ /** `null` when the e-mail is not this project's. */
19
+ get: async (id) => {
20
+ try {
21
+ return await call('GET', `/v1/email/${encodeURIComponent(id)}`);
22
+ }
23
+ catch (e) {
24
+ if (isTotalumError(e) && e.status === 404)
25
+ return null;
26
+ throw e;
27
+ }
28
+ },
29
+ };
30
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The one error every `totalum-sdk` call throws (plan 05 §7.1): the SDK-API envelope's frozen `errorCode`, the HTTP
3
+ * status and `details`. `TotalumD1Error` (`totalum-sdk/d1`) and `TotalumWebhookError` (`totalum-sdk/webhooks`)
4
+ * extend it, so one `isTotalumError(e)` check covers the whole package.
5
+ */
6
+ export class TotalumError extends Error {
7
+ name = 'TotalumError';
8
+ errorCode;
9
+ /** HTTP status; 0 for network errors, 500 for client-side refusals. */
10
+ status;
11
+ details;
12
+ /** SDK-API's request id, for support and the logs explorer. */
13
+ requestId;
14
+ constructor(errorCode, message, status = 500, details, requestId) {
15
+ super(message);
16
+ this.errorCode = errorCode;
17
+ this.status = status;
18
+ this.details = details;
19
+ this.requestId = requestId;
20
+ }
21
+ }
22
+ export function isTotalumError(e) {
23
+ return e instanceof TotalumError;
24
+ }
25
+ /** Inside workerd: the preview and the published app (A7.14). */
26
+ export const inWorker = () => typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers';
27
+ /**
28
+ * Refuses a Node-only method inside a Worker (plan 05 §7.2b): the message says where the app runs, what was called and
29
+ * the HTTPS replacement; `details {method, replacement, docsUrl}` lets a caller render the hint.
30
+ */
31
+ export function assertNode(method, replacement, docsUrl) {
32
+ if (!inWorker())
33
+ return;
34
+ throw new TotalumError('NODE_ONLY', `NODE_ONLY: ${method} needs a Node runtime, and your preview and published app both run on Cloudflare Workers. ${replacement} See ${docsUrl}`, 500, { method, replacement, docsUrl });
35
+ }
@@ -0,0 +1,119 @@
1
+ import { F as FileDescriptor, k as FileUploadUrlInput, m as FilesListQuery } from '../_types/integrations.d-BNGV70e3.js';
2
+ export { l as FileUploadUrlOutput, n as FilesPage } from '../_types/integrations.d-BNGV70e3.js';
3
+ import { TotalumClientOptions } from '../ai/index.js';
4
+ export { a as TotalumError, b as TotalumErrorCode, i as isTotalumError } from '../_types/errors.d-Cu3q_E_r.js';
5
+ import '../_types/coerce.d-C0KIW76L.js';
6
+
7
+ interface TotalumFilesOptions extends TotalumClientOptions {
8
+ /** The public files host; default `TOTALUM_FILES_URL`, else `https://files.totalum-project.com` (A10.1). */
9
+ filesUrl?: string;
10
+ }
11
+ interface UploadOptions {
12
+ name?: string;
13
+ contentType?: string;
14
+ /** `true` stores the file under the private prefix: no public URL, read it through `getSignedUrl` (A10.1). */
15
+ private?: boolean;
16
+ folder?: string;
17
+ }
18
+ /**
19
+ * `totalum.files` (plan 05 §5; SDK-API plan 04 §5.4) over HTTPS with the project key. Every call returns the file
20
+ * descriptor `{key, fileName, fileSize, contentType, fileUrl, visibility, createdAt}` meant to be stored as-is in the
21
+ * row's file column; failures throw `TotalumError` with SDK-API's frozen code.
22
+ */
23
+ declare function totalumFiles(options?: TotalumFilesOptions): {
24
+ /** Up to 25 MB through SDK-API; larger files go through `getUploadUrl` + `complete`. */
25
+ upload(input: Blob | ArrayBuffer | Uint8Array | string, opts?: UploadOptions): Promise<FileDescriptor>;
26
+ /** A presigned PUT (15 min, bound to this type and size) for a direct browser/Node upload; then `complete(key)`. */
27
+ getUploadUrl: (input: FileUploadUrlInput) => Promise<{
28
+ uploadUrl: string;
29
+ key: string;
30
+ headers: Record<string, string>;
31
+ expiresAt: string;
32
+ }>;
33
+ /** Registers a file uploaded with `getUploadUrl` and returns its descriptor. */
34
+ complete: (key: string) => Promise<{
35
+ key: string;
36
+ fileName: string;
37
+ fileSize: number;
38
+ contentType: string;
39
+ fileUrl: string | null;
40
+ visibility: "public-by-link" | "private";
41
+ createdAt: string;
42
+ etag?: string | undefined;
43
+ storageClass?: "Standard" | "InfrequentAccess" | undefined;
44
+ name?: string | undefined;
45
+ url?: string | null | undefined;
46
+ size?: number | undefined;
47
+ id?: string | undefined;
48
+ fileNameId?: string | undefined;
49
+ }>;
50
+ /**
51
+ * One page of the project's files, newest first: filter by `folder`, `name`, `type`, created and size ranges; `cursor`
52
+ * from the previous page's `nextCursor`.
53
+ */
54
+ list: (q?: Partial<Record<keyof FilesListQuery, string | number>>) => Promise<{
55
+ items: {
56
+ key: string;
57
+ fileName: string;
58
+ fileSize: number;
59
+ contentType: string;
60
+ fileUrl: string | null;
61
+ visibility: "public-by-link" | "private";
62
+ createdAt: string;
63
+ etag?: string | undefined;
64
+ storageClass?: "Standard" | "InfrequentAccess" | undefined;
65
+ name?: string | undefined;
66
+ url?: string | null | undefined;
67
+ size?: number | undefined;
68
+ id?: string | undefined;
69
+ fileNameId?: string | undefined;
70
+ }[];
71
+ nextCursor: string | null;
72
+ totalBytes: number;
73
+ fileCount: number;
74
+ }>;
75
+ /** The descriptor of one file; `FILE_NOT_FOUND` (404) when it does not exist. */
76
+ get: (key: string) => Promise<{
77
+ key: string;
78
+ fileName: string;
79
+ fileSize: number;
80
+ contentType: string;
81
+ fileUrl: string | null;
82
+ visibility: "public-by-link" | "private";
83
+ createdAt: string;
84
+ etag?: string | undefined;
85
+ storageClass?: "Standard" | "InfrequentAccess" | undefined;
86
+ name?: string | undefined;
87
+ url?: string | null | undefined;
88
+ size?: number | undefined;
89
+ id?: string | undefined;
90
+ fileNameId?: string | undefined;
91
+ }>;
92
+ /** A presigned GET that R2 verifies: 15 min by default, at most 7 days — the way to hand a private file to a browser. */
93
+ getSignedUrl: (key: string, opts?: {
94
+ expiresInSeconds?: number;
95
+ download?: boolean;
96
+ }) => Promise<string>;
97
+ /** Same as `getSignedUrl`. */
98
+ getDownloadUrl: (key: string, opts?: {
99
+ expiresInSeconds?: number;
100
+ download?: boolean;
101
+ }) => Promise<string>;
102
+ /** Signed GET URLs for many keys at once (`ttl` in seconds, ≤ 7 days). */
103
+ presign(keys: string[], opts?: {
104
+ ttl?: number;
105
+ download?: boolean;
106
+ }): Promise<Record<string, string>>;
107
+ /** Deletes one key, or up to 1000 keys at once. */
108
+ delete(keys: string | string[]): Promise<void>;
109
+ /** The object itself (private files included), with `Range` support; errors throw like every other call. */
110
+ stream: (key: string, opts?: {
111
+ range?: string;
112
+ }) => Promise<Response>;
113
+ /** Sync, no I/O: the public URL of a `p/` key, or the authenticated SDK-API URL of a private `pv/` key. */
114
+ url(key: string): string;
115
+ };
116
+ type TotalumFiles = ReturnType<typeof totalumFiles>;
117
+
118
+ export { FileDescriptor, FileUploadUrlInput, FilesListQuery, totalumFiles };
119
+ export type { TotalumFiles, TotalumFilesOptions, UploadOptions };