@adukilabs/sdk 0.0.0-stage → 0.1.2
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/LICENSE +201 -0
- package/README.md +184 -2
- package/dist/api/api.d.ts +226 -0
- package/dist/api/api.js +365 -0
- package/dist/api/id.d.ts +75 -0
- package/dist/api/id.js +184 -0
- package/dist/api/index.d.ts +23 -0
- package/dist/api/index.js +19 -0
- package/dist/api/key.d.ts +16 -0
- package/dist/api/key.js +16 -0
- package/dist/api/model.d.ts +476 -0
- package/dist/api/space.d.ts +147 -0
- package/dist/api/space.js +218 -0
- package/dist/api/store.d.ts +181 -0
- package/dist/api/store.js +352 -0
- package/dist/api/tenant.d.ts +319 -0
- package/dist/api/tenant.js +429 -0
- package/dist/base64.d.ts +11 -0
- package/dist/base64.js +40 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.js +22 -0
- package/dist/dpop.d.ts +82 -0
- package/dist/dpop.js +156 -0
- package/dist/envelope.d.ts +59 -0
- package/dist/envelope.js +114 -0
- package/dist/errors.d.ts +165 -0
- package/dist/errors.js +206 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +22 -0
- package/dist/jmap.d.ts +100 -0
- package/dist/jmap.js +180 -0
- package/dist/space/model.d.ts +147 -0
- package/dist/store/blake3.d.ts +4 -0
- package/dist/store/blake3.js +130 -0
- package/dist/store/cdc.d.ts +12 -0
- package/dist/store/cdc.js +127 -0
- package/dist/store/chunks.d.ts +23 -0
- package/dist/store/chunks.js +63 -0
- package/dist/store/model.d.ts +202 -0
- package/dist/store/model.js +38 -0
- package/package.json +45 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Aduki Space for a program of the workspace (machine access), with the
|
|
2
|
+
// DPoP-bound service-client token of `IdClient` (audience `space`). The same
|
|
3
|
+
// transport rules as the web Space module: the envelope, keyset lists with an
|
|
4
|
+
// opaque cursor, `Idempotency-Key` on creation, `ETag` / `If-Match` on edits.
|
|
5
|
+
//
|
|
6
|
+
// What Space gives a service client (its docs, "Programs of the workspace"):
|
|
7
|
+
// every route that is neither strict nor money, within the bits its grant
|
|
8
|
+
// gives (`space.use` and the route's bit, for example `tasks.write`,
|
|
9
|
+
// `tickets.write`), seeing the rows a person with those bits and no team
|
|
10
|
+
// would see. Refused with `403 access.denied`, reason `client`
|
|
11
|
+
// (`ClientRefusedError`): refunds, charges, payouts, rails and gateways,
|
|
12
|
+
// staff management and payroll, node management, settings changes, hooks,
|
|
13
|
+
// endpoints and exports. The activity feed marks what a program did with
|
|
14
|
+
// `kind: "client"`.
|
|
15
|
+
import { parse, rejection } from '../envelope.js';
|
|
16
|
+
import { ProtocolError } from '../errors.js';
|
|
17
|
+
import { url } from '../http.js';
|
|
18
|
+
/** The Space REST root. */
|
|
19
|
+
export const SPACE = 'https://space.aduki.pro/v1';
|
|
20
|
+
const HEX = /^[0-9a-f]{16}$/;
|
|
21
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
|
+
/** Aduki Space for a program (see the module notes for what it may not do). */
|
|
23
|
+
export class SpaceApi {
|
|
24
|
+
root;
|
|
25
|
+
#fetch;
|
|
26
|
+
constructor(session, options = {}) {
|
|
27
|
+
this.#fetch = (input, init) => session.fetch('space', input, init);
|
|
28
|
+
this.root = (options.root ?? SPACE).replace(/\/+$/, '');
|
|
29
|
+
}
|
|
30
|
+
// ---- transport -------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* One request to `path` (under `/v1`, segments already encoded): the
|
|
33
|
+
* envelope's `data`, the status and the `ETag`. A refusal throws the typed
|
|
34
|
+
* error of `errors.refusal`: `ClientRefusedError` on a strict or money
|
|
35
|
+
* route, `NetworkError 403 access.denied` for a bit the client lacks,
|
|
36
|
+
* `403 app.disabled` for a read-only workspace, `404` for a row it may not
|
|
37
|
+
* see, `RateLimitedError` with `retryAfter`.
|
|
38
|
+
*/
|
|
39
|
+
async call(method, path, options = {}) {
|
|
40
|
+
const params = new URLSearchParams();
|
|
41
|
+
for (const [k, v] of Object.entries(options.query ?? {}))
|
|
42
|
+
if (v !== undefined && v !== null && v !== false)
|
|
43
|
+
params.set(k, String(v));
|
|
44
|
+
const q = params.toString();
|
|
45
|
+
const clean = path.replace(/^\/+/, '');
|
|
46
|
+
const headers = { Accept: 'application/json' };
|
|
47
|
+
const init = { method, headers };
|
|
48
|
+
if (options.body !== undefined) {
|
|
49
|
+
headers['Content-Type'] = 'application/json';
|
|
50
|
+
init.body = JSON.stringify(options.body);
|
|
51
|
+
}
|
|
52
|
+
if (options.key !== undefined) {
|
|
53
|
+
if (!UUID.test(options.key))
|
|
54
|
+
throw new TypeError('An Idempotency-Key is a UUID');
|
|
55
|
+
headers['Idempotency-Key'] = options.key;
|
|
56
|
+
}
|
|
57
|
+
if (options.version !== undefined)
|
|
58
|
+
headers['If-Match'] = options.version === '*' || options.version.startsWith('"') ? options.version : `"${options.version}"`;
|
|
59
|
+
if (options.signal)
|
|
60
|
+
init.signal = options.signal;
|
|
61
|
+
const response = await this.#fetch(`${url(this.root, clean)}${q ? `?${q}` : ''}`, init);
|
|
62
|
+
const text = await response.text();
|
|
63
|
+
const what = `${method} /v1/${clean.split('?')[0]}`;
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
throw rejection(response, text, what);
|
|
66
|
+
const envelope = parse(text);
|
|
67
|
+
if (!envelope || envelope.success !== true)
|
|
68
|
+
throw new ProtocolError(`${what}: the answer is not a success envelope`);
|
|
69
|
+
const tag = response.headers.get('ETag');
|
|
70
|
+
return { data: (envelope.data ?? null), status: response.status, etag: tag ? tag.replace(/^W\//, '').replace(/^"|"$/g, '') : null };
|
|
71
|
+
}
|
|
72
|
+
/** One page of a list route (`items`, `next`). */
|
|
73
|
+
async list(path, query = {}, page = {}) {
|
|
74
|
+
if (page.limit !== undefined && !(Number.isInteger(page.limit) && page.limit >= 1 && page.limit <= 100))
|
|
75
|
+
throw new TypeError('limit is 1 to 100');
|
|
76
|
+
const { data } = await this.call('GET', path, { query: { ...query, limit: page.limit, after: page.after } });
|
|
77
|
+
if (!data || !Array.isArray(data.items))
|
|
78
|
+
throw new ProtocolError(`${path}: a list without items`);
|
|
79
|
+
return { items: data.items, next: typeof data.next === 'string' && data.next ? data.next : null };
|
|
80
|
+
}
|
|
81
|
+
/** Every item of a list route, page by page (`size` per page). */
|
|
82
|
+
async *all(path, query = {}, size = 100) {
|
|
83
|
+
let after;
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
for (;;) {
|
|
86
|
+
const page = await this.list(path, query, after ? { limit: size, after } : { limit: size });
|
|
87
|
+
yield* page.items;
|
|
88
|
+
if (!page.next)
|
|
89
|
+
return;
|
|
90
|
+
if (seen.has(page.next))
|
|
91
|
+
throw new ProtocolError(`${path}: cursor repeated`);
|
|
92
|
+
seen.add(page.next);
|
|
93
|
+
after = page.next;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ---- workspace -------------------------------------------------------
|
|
97
|
+
/** The workspace settings, read only (`GET /v1/settings`); changing them is strict and refused to programs. */
|
|
98
|
+
settings() {
|
|
99
|
+
return this.call('GET', 'settings');
|
|
100
|
+
}
|
|
101
|
+
/** The workspace's teams (`GET /v1/teams`). */
|
|
102
|
+
teams(page = {}) {
|
|
103
|
+
return this.list('teams', {}, page);
|
|
104
|
+
}
|
|
105
|
+
/** The members of the workspace: name, handle and teams (`GET /v1/staff/directory`). */
|
|
106
|
+
directory(page = {}) {
|
|
107
|
+
return this.list('staff/directory', {}, page);
|
|
108
|
+
}
|
|
109
|
+
/** The activity feed (`GET /v1/activity`); entries a program made carry `kind: "client"`. */
|
|
110
|
+
activity(filters = {}) {
|
|
111
|
+
const { limit, after, ...query } = filters;
|
|
112
|
+
if (query.task)
|
|
113
|
+
hex(query.task, 'task');
|
|
114
|
+
return this.list('activity', query, paging(limit, after));
|
|
115
|
+
}
|
|
116
|
+
// ---- tasks and check-ins -----------------------------------------
|
|
117
|
+
/** Tasks the client may see (`GET /v1/tasks`), filtered. */
|
|
118
|
+
tasks(filters = {}) {
|
|
119
|
+
const { limit, after, ...query } = filters;
|
|
120
|
+
if (query.client)
|
|
121
|
+
hex(query.client, 'client');
|
|
122
|
+
if (query.team)
|
|
123
|
+
hex(query.team, 'team');
|
|
124
|
+
if (query.q === '')
|
|
125
|
+
delete query.q;
|
|
126
|
+
return this.list('tasks', query, paging(limit, after));
|
|
127
|
+
}
|
|
128
|
+
/** A task and its version (`GET /v1/tasks/{id}`). */
|
|
129
|
+
task(task) {
|
|
130
|
+
return this.call('GET', `tasks/${hex(task, 'task')}`);
|
|
131
|
+
}
|
|
132
|
+
/** Creates a task (`POST /v1/tasks`, `tasks.write`); a new Idempotency-Key unless `key` is given. */
|
|
133
|
+
createTask(input, options = {}) {
|
|
134
|
+
return this.call('POST', 'tasks', { body: input, key: options.key ?? crypto.randomUUID() });
|
|
135
|
+
}
|
|
136
|
+
/** Changes a task (`PATCH /v1/tasks/{id}`); `version` guards against a lost update. */
|
|
137
|
+
updateTask(task, changes, options = {}) {
|
|
138
|
+
return this.call('PATCH', `tasks/${hex(task, 'task')}`, versioned({ body: changes }, options.version));
|
|
139
|
+
}
|
|
140
|
+
/** The check-ins of a task (`GET /v1/tasks/{id}/checkins`). */
|
|
141
|
+
checkins(task, page = {}) {
|
|
142
|
+
return this.list(`tasks/${hex(task, 'task')}/checkins`, {}, page);
|
|
143
|
+
}
|
|
144
|
+
/** Reports work on a task (`POST /v1/tasks/{id}/checkins`, `tasks.work`): `minutes` and an optional `proof` link. */
|
|
145
|
+
checkin(task, input, options = {}) {
|
|
146
|
+
const minutes = input.minutes ?? 0;
|
|
147
|
+
if (!Number.isInteger(minutes) || minutes < 0)
|
|
148
|
+
throw new TypeError('minutes is a whole number, not negative');
|
|
149
|
+
const body = { body: input.body, minutes };
|
|
150
|
+
if (input.proof !== undefined)
|
|
151
|
+
body.proof = input.proof;
|
|
152
|
+
return this.call('POST', `tasks/${hex(task, 'task')}/checkins`, { body, key: options.key ?? crypto.randomUUID() });
|
|
153
|
+
}
|
|
154
|
+
// ---- tickets -------------------------------------------------------
|
|
155
|
+
/** Tickets the client may see (`GET /v1/tickets`, `tickets.read`), filtered. */
|
|
156
|
+
tickets(filters = {}) {
|
|
157
|
+
const { limit, after, ...query } = filters;
|
|
158
|
+
if (query.client)
|
|
159
|
+
hex(query.client, 'client');
|
|
160
|
+
if (query.assignee)
|
|
161
|
+
hex(query.assignee, 'assignee');
|
|
162
|
+
if (query.q === '')
|
|
163
|
+
delete query.q;
|
|
164
|
+
return this.list('tickets', query, paging(limit, after));
|
|
165
|
+
}
|
|
166
|
+
/** A ticket and its version (`GET /v1/tickets/{id}`). */
|
|
167
|
+
ticket(ticket) {
|
|
168
|
+
return this.call('GET', `tickets/${hex(ticket, 'ticket')}`);
|
|
169
|
+
}
|
|
170
|
+
/** Opens a ticket (`POST /v1/tickets`, `tickets.write`). */
|
|
171
|
+
createTicket(input, options = {}) {
|
|
172
|
+
return this.call('POST', 'tickets', { body: input, key: options.key ?? crypto.randomUUID() });
|
|
173
|
+
}
|
|
174
|
+
/** Changes a ticket (`PATCH /v1/tickets/{id}`): title, priority, status, assignee, service, node. */
|
|
175
|
+
updateTicket(ticket, changes, options = {}) {
|
|
176
|
+
return this.call('PATCH', `tickets/${hex(ticket, 'ticket')}`, versioned({ body: changes }, options.version));
|
|
177
|
+
}
|
|
178
|
+
/** The thread of a ticket, oldest first (`GET /v1/tickets/{id}/messages`). */
|
|
179
|
+
messages(ticket, page = {}) {
|
|
180
|
+
return this.list(`tickets/${hex(ticket, 'ticket')}/messages`, {}, page);
|
|
181
|
+
}
|
|
182
|
+
/** Replies on a ticket (`POST /v1/tickets/{id}/messages`); `internal` makes it a staff note the client never sees. */
|
|
183
|
+
reply(ticket, body, options = {}) {
|
|
184
|
+
return this.call('POST', `tickets/${hex(ticket, 'ticket')}/messages`, {
|
|
185
|
+
body: { body, internal: options.internal ?? false },
|
|
186
|
+
key: options.key ?? crypto.randomUUID(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// ---- search ----------------------------------------------------------
|
|
190
|
+
/** Ids by query (`GET /v1/search`): `q` 1 to 200 characters, `entity` one collection, `limit` 1 to 50. */
|
|
191
|
+
async search(q, options = {}) {
|
|
192
|
+
const text = q.trim();
|
|
193
|
+
if (text.length < 1 || text.length > 200)
|
|
194
|
+
throw new TypeError('q is 1 to 200 characters');
|
|
195
|
+
if (options.limit !== undefined && !(Number.isInteger(options.limit) && options.limit >= 1 && options.limit <= 50))
|
|
196
|
+
throw new TypeError('limit is 1 to 50');
|
|
197
|
+
const { data } = await this.call('GET', 'search', { query: { q: text, entity: options.entity, limit: options.limit } });
|
|
198
|
+
return { items: data?.items ?? [], source: String(data?.source ?? ''), stale: Boolean(data?.stale) };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function hex(value, what) {
|
|
202
|
+
if (!HEX.test(value))
|
|
203
|
+
throw new TypeError(`${what} is not an id (16 lowercase hex digits)`);
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
function paging(limit, after) {
|
|
207
|
+
const out = {};
|
|
208
|
+
if (limit !== undefined)
|
|
209
|
+
out.limit = limit;
|
|
210
|
+
if (after !== undefined)
|
|
211
|
+
out.after = after;
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function versioned(options, version) {
|
|
215
|
+
if (version !== undefined)
|
|
216
|
+
options.version = version;
|
|
217
|
+
return options;
|
|
218
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Content, Manifest } from '../store/chunks.js';
|
|
2
|
+
import type { Begun, Derivative, FileEntry, Folder, Listing, Quota, Version, VersionStatus } from '../store/model.js';
|
|
3
|
+
/** Store's metadata API. */
|
|
4
|
+
export declare const STORE = "https://store.aduki.pro/v1";
|
|
5
|
+
/** Store's content API (chunks, downloads). */
|
|
6
|
+
export declare const FILES = "https://files.aduki.pro/v1";
|
|
7
|
+
/** An authenticated fetch for one audience (`IdClient`). */
|
|
8
|
+
export interface AudienceFetch {
|
|
9
|
+
fetch(audience: string, input: Request | string | URL, init?: RequestInit): Promise<Response>;
|
|
10
|
+
}
|
|
11
|
+
export interface StoreApiOptions {
|
|
12
|
+
/** the metadata root with `/v1` (default `https://store.aduki.pro/v1`) */
|
|
13
|
+
root?: string | undefined;
|
|
14
|
+
/** the content root with `/v1` (default `https://files.aduki.pro/v1`) */
|
|
15
|
+
files?: string | undefined;
|
|
16
|
+
/** waits `ms` milliseconds before retrying a busy chunk (default a timer; tests pass a fake) */
|
|
17
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** What `updateFolder` changes: `parent: null` moves to the drive root. */
|
|
20
|
+
export interface FolderChange {
|
|
21
|
+
name?: string;
|
|
22
|
+
parent?: string | null;
|
|
23
|
+
trashed?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** What `updateFile` changes: `folder: null` moves to the drive root. */
|
|
26
|
+
export interface FileChange {
|
|
27
|
+
name?: string;
|
|
28
|
+
folder?: string | null;
|
|
29
|
+
trashed?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Client metadata of a version: modification time (a Date or milliseconds) and the executable bit. */
|
|
32
|
+
export interface VersionAttrs {
|
|
33
|
+
mtime: Date | number;
|
|
34
|
+
exec: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Progress of an upload: hashing the content, then sending the missing chunks. */
|
|
37
|
+
export interface UploadProgress {
|
|
38
|
+
phase: 'hash' | 'send';
|
|
39
|
+
done: number;
|
|
40
|
+
total: number;
|
|
41
|
+
}
|
|
42
|
+
export interface StoreUploadOptions {
|
|
43
|
+
/** an existing file to add a version to; without it a new file is created */
|
|
44
|
+
file?: string;
|
|
45
|
+
/** the new file's name (required without `file`; default the name of a `File`) */
|
|
46
|
+
name?: string;
|
|
47
|
+
/** the new file's folder (default the drive root) */
|
|
48
|
+
folder?: string | null;
|
|
49
|
+
/** the new file's type (default the Blob's type) */
|
|
50
|
+
mime?: string;
|
|
51
|
+
/** the version this edit started from (default the file's current version, when adding to `file`) */
|
|
52
|
+
base?: string;
|
|
53
|
+
/** a UUID: the same key (and content) resumes the same upload (default a new one) */
|
|
54
|
+
key?: string;
|
|
55
|
+
attrs?: VersionAttrs;
|
|
56
|
+
/** a manifest computed before (`manifest(content)`), to skip hashing again */
|
|
57
|
+
manifest?: Manifest;
|
|
58
|
+
progress?: (progress: UploadProgress) => void;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
/** What an upload made: the file, the version and its state (`pending` while scanned, or `ready`). */
|
|
62
|
+
export interface StoreUploaded {
|
|
63
|
+
file: FileEntry;
|
|
64
|
+
version: string;
|
|
65
|
+
state: string;
|
|
66
|
+
/** bytes sent (0 when the server already held every chunk) */
|
|
67
|
+
sent: number;
|
|
68
|
+
}
|
|
69
|
+
/** A download: the current version or `version`; `range` asks for bytes `start` to `end` (inclusive); `etag` for a `304`. */
|
|
70
|
+
export interface StoreDownload {
|
|
71
|
+
version?: string;
|
|
72
|
+
range?: {
|
|
73
|
+
start: number;
|
|
74
|
+
end?: number;
|
|
75
|
+
};
|
|
76
|
+
etag?: string;
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
}
|
|
79
|
+
type Query = Record<string, string | number | boolean | undefined>;
|
|
80
|
+
/** The workspace drive (see the module notes for what a program may not do). */
|
|
81
|
+
export declare class StoreApi {
|
|
82
|
+
#private;
|
|
83
|
+
readonly root: string;
|
|
84
|
+
readonly files: string;
|
|
85
|
+
constructor(session: AudienceFetch, options?: StoreApiOptions);
|
|
86
|
+
/** The workspace drive's numbers: `owner` (the workspace), `cap`, `per`, `used` (`GET /v1/quota`). */
|
|
87
|
+
quota(): Promise<Quota>;
|
|
88
|
+
/** The live children of folder `parent`, or of the drive root; with `trashed`, the trash (`GET /v1/folders`). */
|
|
89
|
+
list(options?: {
|
|
90
|
+
parent?: string;
|
|
91
|
+
trashed?: boolean;
|
|
92
|
+
}): Promise<Listing>;
|
|
93
|
+
/** Folder `folder` (`GET /v1/folders/{id}`); one outside the workspace drive is `404`. */
|
|
94
|
+
folder(folder: string): Promise<Folder>;
|
|
95
|
+
/** File `file` (`GET /v1/files/{id}`). */
|
|
96
|
+
file(file: string): Promise<FileEntry>;
|
|
97
|
+
/** Creates a folder in `parent` (default the drive root) (`POST /v1/folders`, `store.write`); a name in use is 409. */
|
|
98
|
+
createFolder(name: string, options?: {
|
|
99
|
+
parent?: string | null;
|
|
100
|
+
}): Promise<Folder>;
|
|
101
|
+
/** Creates an empty file to upload a version into (`POST /v1/files`). `upload` does this for you. */
|
|
102
|
+
createFile(name: string, options?: {
|
|
103
|
+
folder?: string | null;
|
|
104
|
+
mime?: string;
|
|
105
|
+
}): Promise<FileEntry>;
|
|
106
|
+
/** Renames, moves (`parent: null` is the drive root), trashes or restores a folder (`PATCH /v1/folders/{id}`). */
|
|
107
|
+
updateFolder(folder: string, change: FolderChange): Promise<Folder>;
|
|
108
|
+
/** Renames, moves (`folder: null` is the drive root), trashes or restores a file (`PATCH /v1/files/{id}`). */
|
|
109
|
+
updateFile(file: string, change: FileChange): Promise<FileEntry>;
|
|
110
|
+
/** Moves a folder and everything in it to the trash (`DELETE /v1/folders/{id}`). */
|
|
111
|
+
trashFolder(folder: string): Promise<Folder>;
|
|
112
|
+
/** Moves a file to the trash (`DELETE /v1/files/{id}`). */
|
|
113
|
+
trashFile(file: string): Promise<FileEntry>;
|
|
114
|
+
/** Takes a folder out of the trash. */
|
|
115
|
+
restoreFolder(folder: string): Promise<Folder>;
|
|
116
|
+
/** Takes a file out of the trash. */
|
|
117
|
+
restoreFile(file: string): Promise<FileEntry>;
|
|
118
|
+
/** The versions of a file, newest first (`GET /v1/files/{id}/versions`). */
|
|
119
|
+
versions(file: string): Promise<Version[]>;
|
|
120
|
+
/** A version and the chunk positions still missing (`GET /v1/versions/{id}`). */
|
|
121
|
+
version(version: string): Promise<VersionStatus>;
|
|
122
|
+
/** Begins a version of `file` with its manifest (`POST /v1/files/{id}/versions`); `key` (a UUID) makes it idempotent. */
|
|
123
|
+
begin(file: string, plan: Manifest, options?: {
|
|
124
|
+
base?: string;
|
|
125
|
+
key?: string;
|
|
126
|
+
attrs?: VersionAttrs;
|
|
127
|
+
}): Promise<Begun>;
|
|
128
|
+
/**
|
|
129
|
+
* Sends one chunk of an open version (`PUT /v1/chunks/{owner}/{hash}` on
|
|
130
|
+
* the content host): true when stored, false when the drive already held
|
|
131
|
+
* it. A chunk being reclaimed (`409 chunk.busy`) is retried after the
|
|
132
|
+
* server's wait, up to three times.
|
|
133
|
+
*/
|
|
134
|
+
putChunk(owner: string, version: string, hash: string, bytes: Uint8Array | Blob, signal?: AbortSignal): Promise<boolean>;
|
|
135
|
+
/** Commits a version (`POST /v1/files/{id}/commits`); chunks still missing are `409` with `details.missing`. */
|
|
136
|
+
commit(file: string, version: string): Promise<{
|
|
137
|
+
version: string;
|
|
138
|
+
state: string;
|
|
139
|
+
}>;
|
|
140
|
+
/** Aborts an uncommitted version and frees its reservation (`DELETE /v1/files/{id}/versions/{version}`). */
|
|
141
|
+
abort(file: string, version: string): Promise<{
|
|
142
|
+
released: number;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Uploads `content` as a new file (`name`, `folder`) or as a new version of
|
|
146
|
+
* `file`: hashes it, begins the version, sends the missing chunks and
|
|
147
|
+
* commits. Run again with the same `key` to resume: only the chunks the
|
|
148
|
+
* server still lacks are sent. The version is `pending` until scanned.
|
|
149
|
+
*/
|
|
150
|
+
upload(content: Content, options?: StoreUploadOptions): Promise<StoreUploaded>;
|
|
151
|
+
/**
|
|
152
|
+
* The content of a file as a Response (`GET /v1/files/{id}/content` on the
|
|
153
|
+
* content host): `200`, `206` for a range, `304` for an unchanged `etag`;
|
|
154
|
+
* `ETag`, `Content-Type`, `Content-Range` headers. A failure throws (`416`
|
|
155
|
+
* for a range past the end).
|
|
156
|
+
*/
|
|
157
|
+
content(file: string, options?: StoreDownload): Promise<Response>;
|
|
158
|
+
/** The content of a file as a Blob (the whole file, or `range`). */
|
|
159
|
+
download(file: string, options?: StoreDownload): Promise<Blob>;
|
|
160
|
+
/** The renditions (thumbnails, posters, previews) of a version (`GET /v1/versions/{id}/media`). */
|
|
161
|
+
media(version: string): Promise<Derivative[]>;
|
|
162
|
+
/** The bytes of a ready rendition (`GET /v1/derivatives/{id}/content` on the content host), or a `range` of them. */
|
|
163
|
+
derivative(derivative: string, options?: {
|
|
164
|
+
range?: {
|
|
165
|
+
start: number;
|
|
166
|
+
end?: number;
|
|
167
|
+
};
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}): Promise<Response>;
|
|
170
|
+
/**
|
|
171
|
+
* Any metadata route under `/v1` (`path` without it): returns the
|
|
172
|
+
* envelope's `data`; a refusal throws the typed error of `errors.refusal`
|
|
173
|
+
* (`ClientRefusedError` for a route that is not for programs).
|
|
174
|
+
*/
|
|
175
|
+
call<T = any>(method: string, path: string, options?: {
|
|
176
|
+
query?: Query;
|
|
177
|
+
body?: unknown;
|
|
178
|
+
signal?: AbortSignal;
|
|
179
|
+
}): Promise<T>;
|
|
180
|
+
}
|
|
181
|
+
export {};
|