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.
- package/README.md +18 -0
- package/dist/_types/billing.d-BbSZh1wY.d.ts +38 -0
- package/dist/_types/coerce.d-C0KIW76L.d.ts +6 -0
- package/dist/_types/errors.d-Cu3q_E_r.d.ts +3093 -0
- package/dist/_types/integrations.d-BNGV70e3.d.ts +793 -0
- package/dist/_types/ops.d-C9375KIG.d.ts +117 -0
- package/dist/ai/index.d.ts +424 -0
- package/dist/ai/index.js +185 -0
- package/dist/analytics/index.d.ts +35 -0
- package/dist/analytics/index.js +15 -0
- package/dist/browser/index.d.ts +1595 -0
- package/dist/browser/index.js +168 -0
- package/dist/cron/index.d.ts +180 -0
- package/dist/cron/index.js +61 -0
- package/dist/d1/errors.js +31 -0
- package/dist/d1/https.js +103 -0
- package/dist/d1/index.d.ts +107 -0
- package/dist/d1/index.js +73 -0
- package/dist/d1/lazy.js +112 -0
- package/dist/d1/libsql.js +125 -0
- package/dist/d1/session.js +38 -0
- package/dist/d1/sql.js +87 -0
- package/dist/d1/types.js +1 -0
- package/dist/email/index.d.ts +31 -0
- package/dist/email/index.js +30 -0
- package/dist/errors.js +35 -0
- package/dist/files/index.d.ts +119 -0
- package/dist/files/index.js +63 -0
- package/dist/http.js +85 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +68 -0
- package/dist/logs/index.d.ts +51 -0
- package/dist/logs/index.js +18 -0
- package/dist/payments/index.d.ts +47 -0
- package/dist/payments/index.js +20 -0
- package/dist/pdf/index.d.ts +42 -0
- package/dist/pdf/index.js +17 -0
- package/dist/react/index.d.ts +91 -0
- package/dist/react/index.js +178 -0
- package/dist/realtime/index.d.ts +103 -0
- package/dist/realtime/index.js +30 -0
- package/dist/scan/index.d.ts +46 -0
- package/dist/scan/index.js +12 -0
- package/dist/seo/index.d.ts +23 -0
- package/dist/seo/index.js +12 -0
- package/dist/speech/index.d.ts +41 -0
- package/dist/speech/index.js +22 -0
- package/dist/web/index.d.ts +170 -0
- package/dist/web/index.js +31 -0
- package/dist/webhooks/index.d.ts +48 -0
- package/dist/webhooks/index.js +61 -0
- package/package.json +146 -0
- package/totalum-sdk.md +1088 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { TotalumError } from '../errors.js';
|
|
2
|
+
import { jsonClient } from '../http.js';
|
|
3
|
+
export { TotalumError, isTotalumError } from '../errors.js';
|
|
4
|
+
/** The frozen 67 (contracts `BROWSER_COMMANDS`, plan 11 §3.4) as a literal, so nothing is imported at runtime. */
|
|
5
|
+
const BROWSER_COMMANDS = [
|
|
6
|
+
...[
|
|
7
|
+
'goto',
|
|
8
|
+
'back',
|
|
9
|
+
'forward',
|
|
10
|
+
'reload',
|
|
11
|
+
'pageInfo',
|
|
12
|
+
'click',
|
|
13
|
+
'dblclick',
|
|
14
|
+
'type',
|
|
15
|
+
'press',
|
|
16
|
+
'hover',
|
|
17
|
+
],
|
|
18
|
+
...[
|
|
19
|
+
'scroll',
|
|
20
|
+
'dragAndDrop',
|
|
21
|
+
'select',
|
|
22
|
+
'upload',
|
|
23
|
+
'frames.list',
|
|
24
|
+
'frames.use',
|
|
25
|
+
'tabs.list',
|
|
26
|
+
'tabs.new',
|
|
27
|
+
],
|
|
28
|
+
...['tabs.use', 'tabs.close', 'waitFor', 'evaluate', 'content', 'screenshot', 'pdf', 'extract'],
|
|
29
|
+
...[
|
|
30
|
+
'cookies.get',
|
|
31
|
+
'cookies.set',
|
|
32
|
+
'cookies.clear',
|
|
33
|
+
'storage.get',
|
|
34
|
+
'storage.set',
|
|
35
|
+
'storage.clear',
|
|
36
|
+
],
|
|
37
|
+
...[
|
|
38
|
+
'context.save',
|
|
39
|
+
'downloads.list',
|
|
40
|
+
'downloads.get',
|
|
41
|
+
'network.capture',
|
|
42
|
+
'network.log',
|
|
43
|
+
'network.har',
|
|
44
|
+
],
|
|
45
|
+
...[
|
|
46
|
+
'network.intercept',
|
|
47
|
+
'network.setHeaders',
|
|
48
|
+
'emulate.viewport',
|
|
49
|
+
'emulate.device',
|
|
50
|
+
'emulate.geolocation',
|
|
51
|
+
],
|
|
52
|
+
...[
|
|
53
|
+
'emulate.timezone',
|
|
54
|
+
'emulate.locale',
|
|
55
|
+
'emulate.userAgent',
|
|
56
|
+
'captcha.solve',
|
|
57
|
+
'captcha.events',
|
|
58
|
+
],
|
|
59
|
+
...[
|
|
60
|
+
'proxy.usage',
|
|
61
|
+
'stealth.get',
|
|
62
|
+
'session.status',
|
|
63
|
+
'session.liveView',
|
|
64
|
+
'session.recording',
|
|
65
|
+
'dialog.handle',
|
|
66
|
+
],
|
|
67
|
+
...[
|
|
68
|
+
'page.addInitScript',
|
|
69
|
+
'page.exposeFunction',
|
|
70
|
+
'emulate.media',
|
|
71
|
+
'emulate.network',
|
|
72
|
+
'emulate.cpu',
|
|
73
|
+
],
|
|
74
|
+
...[
|
|
75
|
+
'permissions.grant',
|
|
76
|
+
'clipboard.read',
|
|
77
|
+
'clipboard.write',
|
|
78
|
+
'act',
|
|
79
|
+
'observe',
|
|
80
|
+
'session.release',
|
|
81
|
+
],
|
|
82
|
+
...['network.subscribe', 'accessibility.snapshot'],
|
|
83
|
+
];
|
|
84
|
+
/** The leading positional arguments of a command method; the last argument is always the rest of its parameters. */
|
|
85
|
+
const POSITIONAL = {
|
|
86
|
+
goto: ['url'],
|
|
87
|
+
click: ['target'],
|
|
88
|
+
dblclick: ['target'],
|
|
89
|
+
type: ['target', 'text'],
|
|
90
|
+
press: ['key'],
|
|
91
|
+
hover: ['target'],
|
|
92
|
+
select: ['target', 'value'],
|
|
93
|
+
upload: ['target', 'files'],
|
|
94
|
+
dragAndDrop: ['from', 'to'],
|
|
95
|
+
evaluate: ['expression'],
|
|
96
|
+
'cookies.set': ['cookies'],
|
|
97
|
+
act: ['instruction'],
|
|
98
|
+
observe: ['instruction'],
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* `totalum.browser` (plan 05 §6.3) over SDK-API `/v1/browser/*` (scope `browser`): Browserbase sessions driven entirely
|
|
102
|
+
* over HTTPS, so every command works from a Worker (A6.29). A command's refusal throws `TotalumError` with its frozen
|
|
103
|
+
* code (`ACTION_TIMEOUT`, `ACTION_FAILED`, …); `actions()` returns every result instead.
|
|
104
|
+
*/
|
|
105
|
+
export function totalumBrowser(options = {}) {
|
|
106
|
+
const call = jsonClient(options);
|
|
107
|
+
const path = (id) => `/v1/browser/sessions/${encodeURIComponent(id)}`;
|
|
108
|
+
function handle(created) {
|
|
109
|
+
const run = async (command) => {
|
|
110
|
+
const { results } = await call('POST', `${path(created.id)}/actions`, {
|
|
111
|
+
action: command,
|
|
112
|
+
});
|
|
113
|
+
const r = results[0];
|
|
114
|
+
if (!r?.ok)
|
|
115
|
+
throw new TotalumError(r?.error?.code ?? 'ACTION_FAILED', r?.error?.message ?? 'Browser command failed', 400);
|
|
116
|
+
return r.data;
|
|
117
|
+
};
|
|
118
|
+
const methods = {};
|
|
119
|
+
for (const type of BROWSER_COMMANDS) {
|
|
120
|
+
const names = POSITIONAL[type] ?? [];
|
|
121
|
+
const method = (...args) => {
|
|
122
|
+
const params = Object.fromEntries(names.map((n, i) => [n, args[i]]));
|
|
123
|
+
return run({ ...args[names.length], ...params, type });
|
|
124
|
+
};
|
|
125
|
+
const [group, name] = type.split('.');
|
|
126
|
+
if (name && group)
|
|
127
|
+
(methods[group] ??= {})[name] = method;
|
|
128
|
+
else
|
|
129
|
+
methods[type] = method;
|
|
130
|
+
}
|
|
131
|
+
return Object.assign(methods, {
|
|
132
|
+
id: created.id,
|
|
133
|
+
liveViewUrl: created.liveViewUrl ?? '',
|
|
134
|
+
expiresAt: created.expiresAt ?? '',
|
|
135
|
+
actions: async (batch, opts = {}) => (await call('POST', `${path(created.id)}/actions`, { actions: batch, ...opts }))
|
|
136
|
+
.results,
|
|
137
|
+
program: (program) => call('POST', `${path(created.id)}/program`, program),
|
|
138
|
+
status: () => call('GET', path(created.id)),
|
|
139
|
+
close: () => call('DELETE', path(created.id)),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
/** Opens a session with the vendor options and the Totalum-synthesised ones. */
|
|
144
|
+
session: async (opts = {}) => handle(await call('POST', '/v1/browser/sessions', opts)),
|
|
145
|
+
sessions: {
|
|
146
|
+
/** The project's sessions, filtered by `status` or `metadata`. */
|
|
147
|
+
list: (q = {}) => call('GET', `/v1/browser/sessions?${Object.entries(q)
|
|
148
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
149
|
+
.join('&')}`),
|
|
150
|
+
/** A handle on an existing session (no call is made). */
|
|
151
|
+
get: (id) => handle({ id }),
|
|
152
|
+
},
|
|
153
|
+
programs: {
|
|
154
|
+
/** A program's status and, once finished, its results. */
|
|
155
|
+
get: (programId) => call('GET', `/v1/browser/programs/${encodeURIComponent(programId)}`),
|
|
156
|
+
/** Polls until the program leaves `pending`/`running` (the `browser.program.completed` webhook is the push twin). */
|
|
157
|
+
wait: async (programId, opts = {}) => {
|
|
158
|
+
const until = Date.now() + (opts.timeoutMs ?? 960_000);
|
|
159
|
+
for (;;) {
|
|
160
|
+
const s = await call('GET', `/v1/browser/programs/${encodeURIComponent(programId)}`);
|
|
161
|
+
if ((s.status !== 'pending' && s.status !== 'running') || Date.now() > until)
|
|
162
|
+
return s;
|
|
163
|
+
await new Promise((r) => setTimeout(r, opts.intervalMs ?? 2000));
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { c as CronJobInput, b as CronJob, d as CronJobPatch } from '../_types/integrations.d-BNGV70e3.js';
|
|
2
|
+
export { e as CronRun, f as CronRunsPage } 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.cron` (plan 05 §6.9) over SDK-API `/v1/cron/*` (scope `cron`): HTTP jobs against the project's own host,
|
|
9
|
+
* fired by Totalum's Scheduler; limits per plan (`429 CRON_LIMIT_REACHED {limit, plan}`, `400 INVALID_SCHEDULE
|
|
10
|
+
* {minIntervalSeconds}`). Executions cost nothing.
|
|
11
|
+
*/
|
|
12
|
+
declare function totalumCron(options?: TotalumClientOptions): {
|
|
13
|
+
/**
|
|
14
|
+
* A job that calls a URL of the project's own host on a cron `schedule` (or `{ everyMinutes }`).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* await cron.create({
|
|
18
|
+
* name: 'daily-report',
|
|
19
|
+
* schedule: '0 8 * * *',
|
|
20
|
+
* request: { method: 'POST', url: 'https://my-app.totalum-project.com/api/cron/report' },
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
create: (input: CronJobInput) => Promise<{
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
schedule: string | {
|
|
27
|
+
everyMinutes: number;
|
|
28
|
+
};
|
|
29
|
+
request: {
|
|
30
|
+
method: "GET" | "POST";
|
|
31
|
+
url: string;
|
|
32
|
+
headers?: Record<string, string> | undefined;
|
|
33
|
+
body?: string | undefined;
|
|
34
|
+
timeoutMs?: number | undefined;
|
|
35
|
+
};
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
status: "active" | "paused" | "paused_failures";
|
|
38
|
+
nextRunAt: string | null;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
env: "dev" | "live";
|
|
41
|
+
lastRun?: {
|
|
42
|
+
at: string;
|
|
43
|
+
status: number | "error" | "timeout" | "skipped";
|
|
44
|
+
latencyMs: number;
|
|
45
|
+
} | undefined;
|
|
46
|
+
description?: string | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
/** Every job of the project. */
|
|
49
|
+
list: () => Promise<{
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
schedule: string | {
|
|
53
|
+
everyMinutes: number;
|
|
54
|
+
};
|
|
55
|
+
request: {
|
|
56
|
+
method: "GET" | "POST";
|
|
57
|
+
url: string;
|
|
58
|
+
headers?: Record<string, string> | undefined;
|
|
59
|
+
body?: string | undefined;
|
|
60
|
+
timeoutMs?: number | undefined;
|
|
61
|
+
};
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
status: "active" | "paused" | "paused_failures";
|
|
64
|
+
nextRunAt: string | null;
|
|
65
|
+
createdAt: string;
|
|
66
|
+
env: "dev" | "live";
|
|
67
|
+
lastRun?: {
|
|
68
|
+
at: string;
|
|
69
|
+
status: number | "error" | "timeout" | "skipped";
|
|
70
|
+
latencyMs: number;
|
|
71
|
+
} | undefined;
|
|
72
|
+
description?: string | undefined;
|
|
73
|
+
}[]>;
|
|
74
|
+
/** `null` when the job is not this project's. */
|
|
75
|
+
get: (id: string) => Promise<CronJob | null>;
|
|
76
|
+
/** Changes any field of a job (`schedule`, `request`, `enabled`, …). */
|
|
77
|
+
update: (id: string, patch: CronJobPatch) => Promise<{
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
schedule: string | {
|
|
81
|
+
everyMinutes: number;
|
|
82
|
+
};
|
|
83
|
+
request: {
|
|
84
|
+
method: "GET" | "POST";
|
|
85
|
+
url: string;
|
|
86
|
+
headers?: Record<string, string> | undefined;
|
|
87
|
+
body?: string | undefined;
|
|
88
|
+
timeoutMs?: number | undefined;
|
|
89
|
+
};
|
|
90
|
+
enabled: boolean;
|
|
91
|
+
status: "active" | "paused" | "paused_failures";
|
|
92
|
+
nextRunAt: string | null;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
env: "dev" | "live";
|
|
95
|
+
lastRun?: {
|
|
96
|
+
at: string;
|
|
97
|
+
status: number | "error" | "timeout" | "skipped";
|
|
98
|
+
latencyMs: number;
|
|
99
|
+
} | undefined;
|
|
100
|
+
description?: string | undefined;
|
|
101
|
+
}>;
|
|
102
|
+
/** Deletes a job. */
|
|
103
|
+
delete: (id: string) => Promise<void>;
|
|
104
|
+
/** Stops a job from firing until `resume`. */
|
|
105
|
+
pause: (id: string) => Promise<{
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
schedule: string | {
|
|
109
|
+
everyMinutes: number;
|
|
110
|
+
};
|
|
111
|
+
request: {
|
|
112
|
+
method: "GET" | "POST";
|
|
113
|
+
url: string;
|
|
114
|
+
headers?: Record<string, string> | undefined;
|
|
115
|
+
body?: string | undefined;
|
|
116
|
+
timeoutMs?: number | undefined;
|
|
117
|
+
};
|
|
118
|
+
enabled: boolean;
|
|
119
|
+
status: "active" | "paused" | "paused_failures";
|
|
120
|
+
nextRunAt: string | null;
|
|
121
|
+
createdAt: string;
|
|
122
|
+
env: "dev" | "live";
|
|
123
|
+
lastRun?: {
|
|
124
|
+
at: string;
|
|
125
|
+
status: number | "error" | "timeout" | "skipped";
|
|
126
|
+
latencyMs: number;
|
|
127
|
+
} | undefined;
|
|
128
|
+
description?: string | undefined;
|
|
129
|
+
}>;
|
|
130
|
+
/** Lets a paused job fire again. */
|
|
131
|
+
resume: (id: string) => Promise<{
|
|
132
|
+
id: string;
|
|
133
|
+
name: string;
|
|
134
|
+
schedule: string | {
|
|
135
|
+
everyMinutes: number;
|
|
136
|
+
};
|
|
137
|
+
request: {
|
|
138
|
+
method: "GET" | "POST";
|
|
139
|
+
url: string;
|
|
140
|
+
headers?: Record<string, string> | undefined;
|
|
141
|
+
body?: string | undefined;
|
|
142
|
+
timeoutMs?: number | undefined;
|
|
143
|
+
};
|
|
144
|
+
enabled: boolean;
|
|
145
|
+
status: "active" | "paused" | "paused_failures";
|
|
146
|
+
nextRunAt: string | null;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
env: "dev" | "live";
|
|
149
|
+
lastRun?: {
|
|
150
|
+
at: string;
|
|
151
|
+
status: number | "error" | "timeout" | "skipped";
|
|
152
|
+
latencyMs: number;
|
|
153
|
+
} | undefined;
|
|
154
|
+
description?: string | undefined;
|
|
155
|
+
}>;
|
|
156
|
+
/** A job's executions, newest first (30 days). */
|
|
157
|
+
runs: (id: string, opts?: {
|
|
158
|
+
limit?: number;
|
|
159
|
+
cursor?: string;
|
|
160
|
+
}) => Promise<{
|
|
161
|
+
items: {
|
|
162
|
+
id: string;
|
|
163
|
+
scheduledFor: string;
|
|
164
|
+
startedAt: string | null;
|
|
165
|
+
status: number | "error" | "timeout" | "skipped";
|
|
166
|
+
latencyMs: number | null;
|
|
167
|
+
error?: string | undefined;
|
|
168
|
+
responseSnippet?: string | undefined;
|
|
169
|
+
}[];
|
|
170
|
+
nextCursor: string | null;
|
|
171
|
+
}>;
|
|
172
|
+
/** Fires a job now, outside its schedule. */
|
|
173
|
+
trigger: (id: string) => Promise<{
|
|
174
|
+
runId: string;
|
|
175
|
+
}>;
|
|
176
|
+
};
|
|
177
|
+
type TotalumCron = ReturnType<typeof totalumCron>;
|
|
178
|
+
|
|
179
|
+
export { CronJob, CronJobInput, CronJobPatch, TotalumClientOptions, totalumCron };
|
|
180
|
+
export type { TotalumCron };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isTotalumError } from '../errors.js';
|
|
2
|
+
import { jsonClient } from '../http.js';
|
|
3
|
+
export { TotalumError, isTotalumError } from '../errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* `totalum.cron` (plan 05 §6.9) over SDK-API `/v1/cron/*` (scope `cron`): HTTP jobs against the project's own host,
|
|
6
|
+
* fired by Totalum's Scheduler; limits per plan (`429 CRON_LIMIT_REACHED {limit, plan}`, `400 INVALID_SCHEDULE
|
|
7
|
+
* {minIntervalSeconds}`). Executions cost nothing.
|
|
8
|
+
*/
|
|
9
|
+
export function totalumCron(options = {}) {
|
|
10
|
+
const call = jsonClient(options);
|
|
11
|
+
const job = (id) => `/v1/cron/jobs/${encodeURIComponent(id)}`;
|
|
12
|
+
const update = (id, patch) => call('PATCH', job(id), patch);
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* A job that calls a URL of the project's own host on a cron `schedule` (or `{ everyMinutes }`).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* await cron.create({
|
|
19
|
+
* name: 'daily-report',
|
|
20
|
+
* schedule: '0 8 * * *',
|
|
21
|
+
* request: { method: 'POST', url: 'https://my-app.totalum-project.com/api/cron/report' },
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
create: (input) => call('POST', '/v1/cron/jobs', input),
|
|
25
|
+
/** Every job of the project. */
|
|
26
|
+
list: async () => (await call('GET', '/v1/cron/jobs')).items,
|
|
27
|
+
/** `null` when the job is not this project's. */
|
|
28
|
+
get: async (id) => {
|
|
29
|
+
try {
|
|
30
|
+
return await call('GET', job(id));
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (isTotalumError(e) && e.errorCode === 'CRON_JOB_NOT_FOUND')
|
|
34
|
+
return null;
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
/** Changes any field of a job (`schedule`, `request`, `enabled`, …). */
|
|
39
|
+
update,
|
|
40
|
+
/** Deletes a job. */
|
|
41
|
+
delete: async (id) => {
|
|
42
|
+
await call('DELETE', job(id));
|
|
43
|
+
},
|
|
44
|
+
/** Stops a job from firing until `resume`. */
|
|
45
|
+
pause: (id) => update(id, { enabled: false }),
|
|
46
|
+
/** Lets a paused job fire again. */
|
|
47
|
+
resume: (id) => update(id, { enabled: true }),
|
|
48
|
+
/** A job's executions, newest first (30 days). */
|
|
49
|
+
runs: (id, opts = {}) => {
|
|
50
|
+
const q = new URLSearchParams();
|
|
51
|
+
if (opts.limit !== undefined)
|
|
52
|
+
q.set('limit', String(opts.limit));
|
|
53
|
+
if (opts.cursor !== undefined)
|
|
54
|
+
q.set('cursor', opts.cursor);
|
|
55
|
+
const qs = q.toString();
|
|
56
|
+
return call('GET', `${job(id)}/runs${qs ? `?${qs}` : ''}`);
|
|
57
|
+
},
|
|
58
|
+
/** Fires a job now, outside its schedule. */
|
|
59
|
+
trigger: (id) => call('POST', `${job(id)}/trigger`),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TotalumError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The database `TotalumError` (plan 05 §3.5): `message` is `D1_ERROR: …`-shaped so it reads like a native failure —
|
|
4
|
+
* byte-identical to the binding's message when the failure is SQLite's.
|
|
5
|
+
*/
|
|
6
|
+
export class TotalumD1Error extends TotalumError {
|
|
7
|
+
name = 'TotalumD1Error';
|
|
8
|
+
constructor(errorCode, message, options = {}) {
|
|
9
|
+
super(errorCode, message, options.status ?? 500, options.details, options.requestId);
|
|
10
|
+
}
|
|
11
|
+
/** A client-side refusal (`D1_ERROR: <code>: <message>`). */
|
|
12
|
+
static client(errorCode, message) {
|
|
13
|
+
return new TotalumD1Error(errorCode, `D1_ERROR: ${errorCode}: ${message}`);
|
|
14
|
+
}
|
|
15
|
+
/** The SDK-API error envelope → typed error; `SQL_ERROR` rebuilds the native message from `sqliteMessage`. */
|
|
16
|
+
static fromWire(errors, status, requestId) {
|
|
17
|
+
const details = isRecord(errors.errorDetails) ? errors.errorDetails : undefined;
|
|
18
|
+
const sqlite = details?.['sqliteMessage'];
|
|
19
|
+
const message = errors.errorCode === 'SQL_ERROR' && typeof sqlite === 'string'
|
|
20
|
+
? `D1_ERROR: ${sqlite}`
|
|
21
|
+
: `D1_ERROR: ${errors.errorCode}: ${errors.errorMessage}`;
|
|
22
|
+
const init = { status, ...(details && { details }), ...(requestId !== undefined && { requestId }) };
|
|
23
|
+
return new TotalumD1Error(errors.errorCode, message, init);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function isTotalumD1Error(e) {
|
|
27
|
+
return e instanceof TotalumD1Error;
|
|
28
|
+
}
|
|
29
|
+
export function isRecord(v) {
|
|
30
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
31
|
+
}
|
package/dist/d1/https.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { TotalumD1Error } from './errors.js';
|
|
2
|
+
import { assertBindable, pickFirst } from './sql.js';
|
|
3
|
+
/**
|
|
4
|
+
* The wire headers the driver sends, as literals so the published package has no runtime dependency (plan 05
|
|
5
|
+
* §1); `satisfies` over contracts' `as const` object fails the build if either side ever drifts.
|
|
6
|
+
*/
|
|
7
|
+
const HEADERS = {
|
|
8
|
+
env: 'x-totalum-env',
|
|
9
|
+
migrate: 'x-totalum-migrate',
|
|
10
|
+
migrateDestructive: 'x-totalum-migrate-destructive',
|
|
11
|
+
};
|
|
12
|
+
/** The `D1Database` half that speaks `POST /v1/db/query|batch` (plan 04 §3.2; contracts `db.ts`). */
|
|
13
|
+
export function httpsExecutor(config) {
|
|
14
|
+
const headers = {
|
|
15
|
+
authorization: `Bearer ${config.key}`,
|
|
16
|
+
'content-type': 'application/json',
|
|
17
|
+
[HEADERS.env]: config.env,
|
|
18
|
+
};
|
|
19
|
+
if (config.migrate)
|
|
20
|
+
headers[HEADERS.migrate] = '1';
|
|
21
|
+
if (config.migrate && config.destructive)
|
|
22
|
+
headers[HEADERS.migrateDestructive] = '1';
|
|
23
|
+
async function call(path, body) {
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await fetch(config.apiUrl + path, { method: 'POST', headers, body: JSON.stringify(body) });
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
30
|
+
throw new TotalumD1Error('NETWORK_ERROR', `D1_ERROR: NETWORK_ERROR: ${reason}`, { status: 0 });
|
|
31
|
+
}
|
|
32
|
+
let payload;
|
|
33
|
+
try {
|
|
34
|
+
payload = await response.json();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new TotalumD1Error('RESPONSE_PARSE_ERROR', `D1_ERROR: RESPONSE_PARSE_ERROR: HTTP ${String(response.status)} without a JSON envelope`, {
|
|
38
|
+
status: response.status,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const envelope = payload;
|
|
42
|
+
if (envelope.errors !== null) {
|
|
43
|
+
throw TotalumD1Error.fromWire(envelope.errors, response.status, envelope.metadata?.requestId);
|
|
44
|
+
}
|
|
45
|
+
return envelope.data;
|
|
46
|
+
}
|
|
47
|
+
class HttpsStatement {
|
|
48
|
+
sql;
|
|
49
|
+
params;
|
|
50
|
+
constructor(sql, params = []) {
|
|
51
|
+
this.sql = sql;
|
|
52
|
+
this.params = params;
|
|
53
|
+
}
|
|
54
|
+
bind(...values) {
|
|
55
|
+
assertBindable(values);
|
|
56
|
+
return new HttpsStatement(this.sql, values.map(encodeParam));
|
|
57
|
+
}
|
|
58
|
+
query(mode, columnNames) {
|
|
59
|
+
const statement = { sql: this.sql, params: this.params, mode };
|
|
60
|
+
if (columnNames)
|
|
61
|
+
statement.columnNames = true;
|
|
62
|
+
return call('/v1/db/query', statement);
|
|
63
|
+
}
|
|
64
|
+
async first(colName) {
|
|
65
|
+
return pickFirst((await this.query('first')).results, colName);
|
|
66
|
+
}
|
|
67
|
+
run() {
|
|
68
|
+
return this.query('run');
|
|
69
|
+
}
|
|
70
|
+
all() {
|
|
71
|
+
return this.query('all');
|
|
72
|
+
}
|
|
73
|
+
async raw(options) {
|
|
74
|
+
return (await this.query('raw', options?.columnNames === true ? true : undefined)).results;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
prepare: (sql) => new HttpsStatement(sql),
|
|
79
|
+
async batch(statements) {
|
|
80
|
+
const body = {
|
|
81
|
+
statements: statements.map((s) => ({
|
|
82
|
+
sql: s.sql,
|
|
83
|
+
params: s.params,
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
return (await call('/v1/db/batch', body)).results;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Bytes travel as `{ $bytes: base64 }` (plan 04 §3.2); every other bindable value is JSON already. */
|
|
91
|
+
function encodeParam(value) {
|
|
92
|
+
if (value instanceof ArrayBuffer)
|
|
93
|
+
return { $bytes: toBase64(new Uint8Array(value)) };
|
|
94
|
+
if (ArrayBuffer.isView(value))
|
|
95
|
+
return { $bytes: toBase64(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)) };
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function toBase64(bytes) {
|
|
99
|
+
let binary = '';
|
|
100
|
+
for (const b of bytes)
|
|
101
|
+
binary += String.fromCharCode(b);
|
|
102
|
+
return btoa(binary);
|
|
103
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { S as SdkClientErrorCode, a as TotalumError, b as TotalumErrorCode, E as ErrorBody } from '../_types/errors.d-Cu3q_E_r.js';
|
|
2
|
+
import { TotalumClientOptions } from '../ai/index.js';
|
|
3
|
+
export { i as isTotalumError } from '../_types/errors.d-Cu3q_E_r.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cloudflare's `D1Database` surface, re-declared structurally so an app needs no Cloudflare types to compile
|
|
7
|
+
* and `drizzle-orm/d1` accepts the object (plan 05 §1, §3.1). `D1Meta` is Cloudflare's own interface
|
|
8
|
+
* (`size_after` required); the contracts wire shape is looser and every value here satisfies it (type-tested).
|
|
9
|
+
*/
|
|
10
|
+
type D1Meta = {
|
|
11
|
+
duration: number;
|
|
12
|
+
size_after: number;
|
|
13
|
+
rows_read: number;
|
|
14
|
+
rows_written: number;
|
|
15
|
+
last_row_id: number;
|
|
16
|
+
changed_db: boolean;
|
|
17
|
+
changes: number;
|
|
18
|
+
served_by_region?: string;
|
|
19
|
+
served_by_primary?: boolean;
|
|
20
|
+
};
|
|
21
|
+
interface D1Result<T = Record<string, unknown>> {
|
|
22
|
+
results: T[];
|
|
23
|
+
success: true;
|
|
24
|
+
meta: D1Meta;
|
|
25
|
+
}
|
|
26
|
+
interface D1ExecResult {
|
|
27
|
+
count: number;
|
|
28
|
+
duration: number;
|
|
29
|
+
}
|
|
30
|
+
interface D1PreparedStatement {
|
|
31
|
+
bind(...values: unknown[]): D1PreparedStatement;
|
|
32
|
+
first<T = Record<string, unknown>>(colName?: string): Promise<T | null>;
|
|
33
|
+
run<T = Record<string, unknown>>(): Promise<D1Result<T>>;
|
|
34
|
+
all<T = Record<string, unknown>>(): Promise<D1Result<T>>;
|
|
35
|
+
raw<T = unknown[]>(options: {
|
|
36
|
+
columnNames: true;
|
|
37
|
+
}): Promise<[string[], ...T[]]>;
|
|
38
|
+
raw<T = unknown[]>(options?: {
|
|
39
|
+
columnNames?: false;
|
|
40
|
+
}): Promise<T[]>;
|
|
41
|
+
}
|
|
42
|
+
interface D1DatabaseSession {
|
|
43
|
+
prepare(sql: string): D1PreparedStatement;
|
|
44
|
+
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
|
|
45
|
+
getBookmark(): string | null;
|
|
46
|
+
}
|
|
47
|
+
interface D1Database {
|
|
48
|
+
prepare(sql: string): D1PreparedStatement;
|
|
49
|
+
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
|
|
50
|
+
exec(sql: string): Promise<D1ExecResult>;
|
|
51
|
+
dump(): Promise<ArrayBuffer>;
|
|
52
|
+
withSession(constraintOrBookmark?: string): D1DatabaseSession;
|
|
53
|
+
}
|
|
54
|
+
/** `key`, `apiUrl` and `env` (HTTPS path only) resolve exactly as in every other namespace. */
|
|
55
|
+
interface TotalumD1Options extends TotalumClientOptions {
|
|
56
|
+
/** `'migrate'` (scripts/migrate.ts only) sends `x-totalum-migrate: 1` and enables `exec()`; default `'query'`. */
|
|
57
|
+
mode?: 'query' | 'migrate';
|
|
58
|
+
/** Migrate mode only: sends `x-totalum-migrate-destructive: 1` (A10.4). */
|
|
59
|
+
destructive?: boolean;
|
|
60
|
+
/** An explicit binding wins over detection (plain Workers: `env.TOTALUM_DB`; tests: a Miniflare D1). */
|
|
61
|
+
binding?: D1Database;
|
|
62
|
+
/** `'https'` forces the HTTPS path even inside a Worker (the conformance corpus); default `'auto'`. */
|
|
63
|
+
transport?: 'auto' | 'https';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The client-side codes the driver throws (plan 05 §7.2), from contracts' `SDK_CLIENT_ERROR_CODES`. */
|
|
67
|
+
type TotalumD1ClientCode = Extract<SdkClientErrorCode, 'SDK_NOT_CONFIGURED' | 'NOT_SUPPORTED' | 'BATCH_FOREIGN_STATEMENT' | 'NETWORK_ERROR' | 'RESPONSE_PARSE_ERROR'>;
|
|
68
|
+
type TotalumD1ErrorCode = TotalumErrorCode;
|
|
69
|
+
/**
|
|
70
|
+
* The database `TotalumError` (plan 05 §3.5): `message` is `D1_ERROR: …`-shaped so it reads like a native failure —
|
|
71
|
+
* byte-identical to the binding's message when the failure is SQLite's.
|
|
72
|
+
*/
|
|
73
|
+
declare class TotalumD1Error extends TotalumError {
|
|
74
|
+
name: string;
|
|
75
|
+
constructor(errorCode: TotalumD1ErrorCode, message: string, options?: {
|
|
76
|
+
status?: number;
|
|
77
|
+
details?: Record<string, unknown>;
|
|
78
|
+
requestId?: string;
|
|
79
|
+
});
|
|
80
|
+
/** A client-side refusal (`D1_ERROR: <code>: <message>`). */
|
|
81
|
+
static client(errorCode: TotalumD1ClientCode | 'EXEC_REQUIRES_MIGRATE_MODE', message: string): TotalumD1Error;
|
|
82
|
+
/** The SDK-API error envelope → typed error; `SQL_ERROR` rebuilds the native message from `sqliteMessage`. */
|
|
83
|
+
static fromWire(errors: ErrorBody, status: number, requestId: string | undefined): TotalumD1Error;
|
|
84
|
+
}
|
|
85
|
+
declare function isTotalumD1Error(e: unknown): e is TotalumD1Error;
|
|
86
|
+
|
|
87
|
+
/** Set on every mutating response; while the browser holds it, its reads go to the primary (read-your-writes). */
|
|
88
|
+
declare const READ_YOUR_WRITES_COOKIE = "__tlm_d1_w";
|
|
89
|
+
/**
|
|
90
|
+
* Runs one request of a Worker with a replicated D1 (A3.23, research 46 §6.3): a `GET`/`HEAD` reads through a
|
|
91
|
+
* `first-unconstrained` session — the nearest replica — unless the browser wrote in the last 10 s; any other method
|
|
92
|
+
* uses the plain binding (writes go to the primary; measured faster than any session) and its response sets the short
|
|
93
|
+
* cookie that sends that browser's next reads to the primary. `run` receives the env to hand to the app, whose
|
|
94
|
+
* `totalumD1()` reads `TOTALUM_DB` from it on every statement. No D1 binding (Turso, a laptop): `run(env)` unchanged.
|
|
95
|
+
*/
|
|
96
|
+
declare function withTotalumSession<E extends object>(request: Request, env: E, run: (env: E) => Promise<Response>): Promise<Response>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A `D1Database` for the project's own database wherever the code runs (A3.4, plan 05 §3): the bound
|
|
100
|
+
* `TOTALUM_DB` inside a Worker, the libsql adapter when the environment moved to Turso, the local D1 that
|
|
101
|
+
* `initOpenNextCloudflareForDev()` binds under `next dev`, and an HTTPS client to SDK-API everywhere else.
|
|
102
|
+
* Detection happens on the first statement, never at call time (§3.1).
|
|
103
|
+
*/
|
|
104
|
+
declare function totalumD1(options?: TotalumD1Options): D1Database;
|
|
105
|
+
|
|
106
|
+
export { READ_YOUR_WRITES_COOKIE, TotalumD1Error, TotalumError, TotalumErrorCode, isTotalumD1Error, totalumD1, withTotalumSession };
|
|
107
|
+
export type { D1Database, D1DatabaseSession, D1ExecResult, D1PreparedStatement, D1Result, TotalumD1ClientCode, TotalumD1ErrorCode, TotalumD1Options };
|