@runappsai/sdk 0.5.1 → 0.6.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/README.md +29 -0
- package/dist/activity-tracker.d.ts +8 -0
- package/dist/activity-tracker.d.ts.map +1 -1
- package/dist/activity-tracker.js +3 -0
- package/dist/activity-tracker.js.map +1 -1
- package/dist/browser-auth.d.ts +7 -0
- package/dist/browser-auth.d.ts.map +1 -1
- package/dist/browser-auth.js +105 -15
- package/dist/browser-auth.js.map +1 -1
- package/dist/client.d.ts +25 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +22 -0
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +20 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/files.d.ts +3 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +4 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/sdk.umd.js +2 -2
- package/dist/sdk.umd.js.map +1 -1
- package/dist/sync/codec.d.ts +39 -0
- package/dist/sync/codec.d.ts.map +1 -0
- package/dist/sync/codec.js +364 -0
- package/dist/sync/codec.js.map +1 -0
- package/dist/sync/engine.d.ts +143 -0
- package/dist/sync/engine.d.ts.map +1 -0
- package/dist/sync/engine.js +727 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hook.d.ts +42 -0
- package/dist/sync/hook.d.ts.map +1 -0
- package/dist/sync/hook.js +426 -0
- package/dist/sync/hook.js.map +1 -0
- package/dist/sync/index.d.ts +6 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +6 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/log.d.ts +41 -0
- package/dist/sync/log.d.ts.map +1 -0
- package/dist/sync/log.js +143 -0
- package/dist/sync/log.js.map +1 -0
- package/dist/sync/remote.d.ts +38 -0
- package/dist/sync/remote.d.ts.map +1 -0
- package/dist/sync/remote.js +61 -0
- package/dist/sync/remote.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +19 -6
- package/dist/transport.js.map +1 -1
- package/package.json +2 -1
- package/src/activity-tracker.ts +5 -0
- package/src/browser-auth.ts +103 -16
- package/src/client.ts +37 -0
- package/src/events.ts +22 -0
- package/src/files.ts +7 -5
- package/src/index.ts +13 -0
- package/src/sync/codec.ts +354 -0
- package/src/sync/engine.ts +818 -0
- package/src/sync/hook.ts +495 -0
- package/src/sync/index.ts +5 -0
- package/src/sync/log.ts +170 -0
- package/src/sync/remote.ts +59 -0
- package/src/transport.ts +20 -8
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// sync/remote.ts — the sync engine's storage, over the files API.
|
|
2
|
+
//
|
|
3
|
+
// Shards are plain JSON files in the user's file space for the app, so
|
|
4
|
+
// they share its quota, billing and deletion with everything else the
|
|
5
|
+
// app stores for that user. Conditional reads (304) and writes (412 /
|
|
6
|
+
// 409) come from the files API, which checks them against the store.
|
|
7
|
+
|
|
8
|
+
import type { FilesService } from "../files.js";
|
|
9
|
+
import type { APIError } from "../errors.js";
|
|
10
|
+
import type { SyncRemote } from "./engine.js";
|
|
11
|
+
|
|
12
|
+
export class FilesRemote implements SyncRemote {
|
|
13
|
+
constructor(private readonly files: FilesService) {}
|
|
14
|
+
|
|
15
|
+
async list(prefix: string): Promise<{ path: string; etag: string }[]> {
|
|
16
|
+
const out: { path: string; etag: string }[] = [];
|
|
17
|
+
let cursor: string | undefined;
|
|
18
|
+
do {
|
|
19
|
+
const page = await this.files.list({ prefix, limit: 1000, ...(cursor ? { cursor } : {}) });
|
|
20
|
+
for (const f of page.files) if (f.etag) out.push({ path: f.path, etag: f.etag });
|
|
21
|
+
cursor = page.next_cursor;
|
|
22
|
+
} while (cursor);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getJSON(path: string, ifNoneMatch?: string) {
|
|
27
|
+
const r = await this.files.readJSON(path, ifNoneMatch ? { ifNoneMatch } : undefined);
|
|
28
|
+
if (r.notModified) return { status: "unchanged" as const };
|
|
29
|
+
if (r.value === undefined) return { status: "missing" as const };
|
|
30
|
+
return { status: "ok" as const, value: r.value, etag: r.etag ?? "" };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async putJSON(path: string, value: unknown, cond: { ifMatch?: string; ifNoneMatch?: boolean }) {
|
|
34
|
+
try {
|
|
35
|
+
const obj = await this.files.put(path, JSON.stringify(value), {
|
|
36
|
+
contentType: "application/json",
|
|
37
|
+
...(cond.ifMatch ? { ifMatch: cond.ifMatch } : {}),
|
|
38
|
+
...(cond.ifNoneMatch ? { ifNoneMatch: true } : {}),
|
|
39
|
+
});
|
|
40
|
+
return { status: "ok" as const, etag: obj.etag ?? "", serverTime: obj.last_modified ? Date.parse(obj.last_modified) : undefined };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
const code = (e as APIError).statusCode;
|
|
43
|
+
if (code === 412 || code === 409) return { status: "conflict" as const };
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async putBlob(path: string, blob: Blob): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
await this.files.put(path, blob, { contentType: blob.type || "application/octet-stream", ifNoneMatch: true });
|
|
51
|
+
} catch (e) {
|
|
52
|
+
if ((e as APIError).statusCode !== 409) throw e; // already stored: same bytes, same name
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getBlob(path: string): Promise<Blob> {
|
|
57
|
+
return this.files.get(path);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/transport.ts
CHANGED
|
@@ -167,20 +167,25 @@ export class Transport {
|
|
|
167
167
|
* and the ETag of those bytes, when the server sends one. */
|
|
168
168
|
async getRaw(
|
|
169
169
|
path: string,
|
|
170
|
-
init?: { signal?: AbortSignal },
|
|
171
|
-
): Promise<{ data: Uint8Array; contentType: string; etag?: string }> {
|
|
172
|
-
const resp = await this.fetchWithAuthRetry(path, async () =>
|
|
173
|
-
|
|
174
|
-
headers
|
|
175
|
-
signal: init?.signal
|
|
176
|
-
})
|
|
170
|
+
init?: { signal?: AbortSignal; ifNoneMatch?: string },
|
|
171
|
+
): Promise<{ data: Uint8Array; contentType: string; etag?: string; notModified?: boolean; serverTime?: number }> {
|
|
172
|
+
const resp = await this.fetchWithAuthRetry(path, async () => {
|
|
173
|
+
const headers: Record<string, string> = await this.authHeaders();
|
|
174
|
+
if (init?.ifNoneMatch) headers["If-None-Match"] = `"${init.ifNoneMatch.replace(/"/g, "")}"`;
|
|
175
|
+
return { method: "GET", headers, signal: init?.signal };
|
|
176
|
+
});
|
|
177
|
+
const serverTime = parseServerDate(resp.headers.get("date"));
|
|
178
|
+
if (resp.status === 304) {
|
|
179
|
+
const etag = (resp.headers.get("etag") ?? "").replace(/"/g, "") || init?.ifNoneMatch;
|
|
180
|
+
return { data: new Uint8Array(0), contentType: "", etag, notModified: true, serverTime };
|
|
181
|
+
}
|
|
177
182
|
if (!resp.ok) {
|
|
178
183
|
throw await this.parseError(resp);
|
|
179
184
|
}
|
|
180
185
|
const buf = new Uint8Array(await resp.arrayBuffer());
|
|
181
186
|
const contentType = resp.headers.get("content-type") ?? "";
|
|
182
187
|
const etag = (resp.headers.get("etag") ?? "").replace(/"/g, "") || undefined;
|
|
183
|
-
return { data: buf, contentType, etag };
|
|
188
|
+
return { data: buf, contentType, etag, serverTime };
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
/** POST a multipart/form-data body. Used by audio.transcribe and image.edit. */
|
|
@@ -308,3 +313,10 @@ export class Transport {
|
|
|
308
313
|
return new APIError(resp.status, type, message);
|
|
309
314
|
}
|
|
310
315
|
}
|
|
316
|
+
|
|
317
|
+
/** The server's clock from a Date header, as ms since epoch. */
|
|
318
|
+
export function parseServerDate(h: string | null): number | undefined {
|
|
319
|
+
if (!h) return undefined;
|
|
320
|
+
const t = Date.parse(h);
|
|
321
|
+
return Number.isNaN(t) ? undefined : t;
|
|
322
|
+
}
|