@brass-build/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +255 -0
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +836 -0
- package/dist/api-fetch.d.ts +16 -0
- package/dist/api-fetch.d.ts.map +1 -0
- package/dist/api-fetch.js +97 -0
- package/dist/api-fetch.js.map +1 -0
- package/dist/api-headers.d.ts +3 -0
- package/dist/api-headers.d.ts.map +1 -0
- package/dist/api-headers.js +25 -0
- package/dist/api-headers.js.map +1 -0
- package/dist/auth-flow.d.ts +23 -0
- package/dist/auth-flow.d.ts.map +1 -0
- package/dist/auth-flow.js +76 -0
- package/dist/auth-flow.js.map +1 -0
- package/dist/client.d.ts +106 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1278 -0
- package/dist/client.js.map +1 -0
- package/dist/doc-url-sync.d.ts +19 -0
- package/dist/doc-url-sync.d.ts.map +1 -0
- package/dist/doc-url-sync.js +181 -0
- package/dist/doc-url-sync.js.map +1 -0
- package/dist/document-branches.d.ts +32 -0
- package/dist/document-branches.d.ts.map +1 -0
- package/dist/document-branches.js +89 -0
- package/dist/document-branches.js.map +1 -0
- package/dist/document-checkpoints.d.ts +24 -0
- package/dist/document-checkpoints.d.ts.map +1 -0
- package/dist/document-checkpoints.js +115 -0
- package/dist/document-checkpoints.js.map +1 -0
- package/dist/document-commands.d.ts +93 -0
- package/dist/document-commands.d.ts.map +1 -0
- package/dist/document-commands.js +423 -0
- package/dist/document-commands.js.map +1 -0
- package/dist/document-signals.d.ts +53 -0
- package/dist/document-signals.d.ts.map +1 -0
- package/dist/document-signals.js +114 -0
- package/dist/document-signals.js.map +1 -0
- package/dist/document-streams.d.ts +22 -0
- package/dist/document-streams.d.ts.map +1 -0
- package/dist/document-streams.js +98 -0
- package/dist/document-streams.js.map +1 -0
- package/dist/document-thumbnail-capture.d.ts +27 -0
- package/dist/document-thumbnail-capture.d.ts.map +1 -0
- package/dist/document-thumbnail-capture.js +160 -0
- package/dist/document-thumbnail-capture.js.map +1 -0
- package/dist/documents-namespace.d.ts +134 -0
- package/dist/documents-namespace.d.ts.map +1 -0
- package/dist/documents-namespace.js +190 -0
- package/dist/documents-namespace.js.map +1 -0
- package/dist/documents.d.ts +146 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +2045 -0
- package/dist/documents.js.map +1 -0
- package/dist/emitter.d.ts +15 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +90 -0
- package/dist/emitter.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/export-guest.d.ts +9 -0
- package/dist/export-guest.d.ts.map +1 -0
- package/dist/export-guest.js +225 -0
- package/dist/export-guest.js.map +1 -0
- package/dist/fetch-retry.d.ts +16 -0
- package/dist/fetch-retry.d.ts.map +1 -0
- package/dist/fetch-retry.js +90 -0
- package/dist/fetch-retry.js.map +1 -0
- package/dist/guest-link.d.ts +3 -0
- package/dist/guest-link.d.ts.map +1 -0
- package/dist/guest-link.js +13 -0
- package/dist/guest-link.js.map +1 -0
- package/dist/handles.d.ts +57 -0
- package/dist/handles.d.ts.map +1 -0
- package/dist/handles.js +1375 -0
- package/dist/handles.js.map +1 -0
- package/dist/hosted-runtime.d.ts +33 -0
- package/dist/hosted-runtime.d.ts.map +1 -0
- package/dist/hosted-runtime.js +99 -0
- package/dist/hosted-runtime.js.map +1 -0
- package/dist/hosted-ui.d.ts +84 -0
- package/dist/hosted-ui.d.ts.map +1 -0
- package/dist/hosted-ui.js +673 -0
- package/dist/hosted-ui.js.map +1 -0
- package/dist/importer.d.ts +22 -0
- package/dist/importer.d.ts.map +1 -0
- package/dist/importer.js +54 -0
- package/dist/importer.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/init-keys.d.ts +3 -0
- package/dist/init-keys.d.ts.map +1 -0
- package/dist/init-keys.js +13 -0
- package/dist/init-keys.js.map +1 -0
- package/dist/internal.d.ts +6 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +17 -0
- package/dist/internal.js.map +1 -0
- package/dist/jwt.d.ts +3 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +22 -0
- package/dist/jwt.js.map +1 -0
- package/dist/launch-context.d.ts +4 -0
- package/dist/launch-context.d.ts.map +1 -0
- package/dist/launch-context.js +44 -0
- package/dist/launch-context.js.map +1 -0
- package/dist/login-hint.d.ts +4 -0
- package/dist/login-hint.d.ts.map +1 -0
- package/dist/login-hint.js +63 -0
- package/dist/login-hint.js.map +1 -0
- package/dist/mutate-batching.d.ts +14 -0
- package/dist/mutate-batching.d.ts.map +1 -0
- package/dist/mutate-batching.js +65 -0
- package/dist/mutate-batching.js.map +1 -0
- package/dist/open-document.d.ts +127 -0
- package/dist/open-document.d.ts.map +1 -0
- package/dist/open-document.js +2121 -0
- package/dist/open-document.js.map +1 -0
- package/dist/page-title.d.ts +3 -0
- package/dist/page-title.d.ts.map +1 -0
- package/dist/page-title.js +32 -0
- package/dist/page-title.js.map +1 -0
- package/dist/perf.d.ts +9 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +39 -0
- package/dist/perf.js.map +1 -0
- package/dist/presence.d.ts +62 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +331 -0
- package/dist/presence.js.map +1 -0
- package/dist/record-paging.d.ts +99 -0
- package/dist/record-paging.d.ts.map +1 -0
- package/dist/record-paging.js +220 -0
- package/dist/record-paging.js.map +1 -0
- package/dist/schema-conflict-dialog.d.ts +10 -0
- package/dist/schema-conflict-dialog.d.ts.map +1 -0
- package/dist/schema-conflict-dialog.js +164 -0
- package/dist/schema-conflict-dialog.js.map +1 -0
- package/dist/selection-persistence.d.ts +5 -0
- package/dist/selection-persistence.d.ts.map +1 -0
- package/dist/selection-persistence.js +90 -0
- package/dist/selection-persistence.js.map +1 -0
- package/dist/serializer.d.ts +3 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +2 -0
- package/dist/serializer.js.map +1 -0
- package/dist/session-persistence.d.ts +4 -0
- package/dist/session-persistence.d.ts.map +1 -0
- package/dist/session-persistence.js +68 -0
- package/dist/session-persistence.js.map +1 -0
- package/dist/session-toolbar.d.ts +62 -0
- package/dist/session-toolbar.d.ts.map +1 -0
- package/dist/session-toolbar.js +1205 -0
- package/dist/session-toolbar.js.map +1 -0
- package/dist/session.d.ts +69 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +926 -0
- package/dist/session.js.map +1 -0
- package/dist/tail-ops.d.ts +28 -0
- package/dist/tail-ops.d.ts.map +1 -0
- package/dist/tail-ops.js +46 -0
- package/dist/tail-ops.js.map +1 -0
- package/dist/topic-lifecycle.d.ts +41 -0
- package/dist/topic-lifecycle.d.ts.map +1 -0
- package/dist/topic-lifecycle.js +169 -0
- package/dist/topic-lifecycle.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/undo.d.ts +18 -0
- package/dist/undo.d.ts.map +1 -0
- package/dist/undo.js +110 -0
- package/dist/undo.js.map +1 -0
- package/dist/version-bar.d.ts +22 -0
- package/dist/version-bar.d.ts.map +1 -0
- package/dist/version-bar.js +169 -0
- package/dist/version-bar.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/dist/vite.d.ts +15 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +36 -0
- package/dist/vite.js.map +1 -0
- package/dist/ws-client.d.ts +53 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +604 -0
- package/dist/ws-client.js.map +1 -0
- package/dist/ws-frame-limit.d.ts +21 -0
- package/dist/ws-frame-limit.d.ts.map +1 -0
- package/dist/ws-frame-limit.js +40 -0
- package/dist/ws-frame-limit.js.map +1 -0
- package/docs/app-manifest.md +65 -0
- package/docs/custom-chrome.md +147 -0
- package/docs/hosted-ui-advanced.md +77 -0
- package/docs/imported-documents.md +261 -0
- package/docs/presence.md +129 -0
- package/docs/schemas.md +135 -0
- package/docs/streams.md +248 -0
- package/package.json +60 -0
- package/scripts/prepare.mjs +50 -0
- package/src/api-fetch.ts +127 -0
- package/src/api-headers.ts +28 -0
- package/src/auth-flow.ts +125 -0
- package/src/client.ts +1457 -0
- package/src/doc-url-sync.ts +183 -0
- package/src/document-branches.ts +124 -0
- package/src/document-checkpoints.ts +135 -0
- package/src/document-commands.ts +651 -0
- package/src/document-signals.ts +234 -0
- package/src/document-streams.ts +132 -0
- package/src/document-thumbnail-capture.ts +191 -0
- package/src/documents-namespace.ts +516 -0
- package/src/documents.ts +2426 -0
- package/src/emitter.ts +129 -0
- package/src/errors.ts +45 -0
- package/src/export-guest.ts +282 -0
- package/src/fetch-retry.ts +119 -0
- package/src/guest-link.ts +14 -0
- package/src/handles.ts +1731 -0
- package/src/hosted-runtime.ts +109 -0
- package/src/hosted-ui.ts +1020 -0
- package/src/importer.ts +89 -0
- package/src/index.ts +267 -0
- package/src/init-keys.ts +15 -0
- package/src/internal.ts +23 -0
- package/src/jwt.ts +18 -0
- package/src/launch-context.ts +43 -0
- package/src/login-hint.ts +60 -0
- package/src/mutate-batching.ts +63 -0
- package/src/open-document.ts +2869 -0
- package/src/page-title.ts +33 -0
- package/src/perf.ts +46 -0
- package/src/presence.ts +471 -0
- package/src/record-paging.ts +270 -0
- package/src/schema-conflict-dialog.ts +176 -0
- package/src/selection-persistence.ts +98 -0
- package/src/serializer.ts +15 -0
- package/src/session-persistence.ts +69 -0
- package/src/session-toolbar.ts +1457 -0
- package/src/session.ts +1262 -0
- package/src/tail-ops.ts +63 -0
- package/src/topic-lifecycle.ts +217 -0
- package/src/types.ts +75 -0
- package/src/undo.ts +125 -0
- package/src/version-bar.ts +221 -0
- package/src/version.ts +8 -0
- package/src/vite.ts +49 -0
- package/src/ws-client.ts +678 -0
- package/src/ws-frame-limit.ts +52 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
// SDK surface: `Brass.documents.*`. The metadata surface: get /
|
|
2
|
+
// thumbnails / branches. The editable surface (snapshot + ops +
|
|
3
|
+
// mutate) is the `Document` runtime class via `Brass.openDocumentById`
|
|
4
|
+
// / `Brass.createDocument`. Access-granting is hosted-only (the share
|
|
5
|
+
// dialog), not a programmatic method here.
|
|
6
|
+
|
|
7
|
+
import type { Document } from './documents.js';
|
|
8
|
+
import type { ImportClaimResponse } from './importer.js';
|
|
9
|
+
import { createApiFetch } from './api-fetch.js';
|
|
10
|
+
import { BrassError } from './errors.js';
|
|
11
|
+
|
|
12
|
+
export type DocumentRole = 'owner' | 'editor' | 'viewer';
|
|
13
|
+
|
|
14
|
+
export interface DocumentMember {
|
|
15
|
+
sub: string;
|
|
16
|
+
role: DocumentRole;
|
|
17
|
+
granted_at: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// An app-supplied preview image, anchored to a `(branch, version)`.
|
|
21
|
+
// `url` is a short-lived presigned GET valid until `expires_at`;
|
|
22
|
+
// `width` / `height` are `null` when the uploading app didn't report
|
|
23
|
+
// them. Brass can't render an app's document body, so a thumbnail
|
|
24
|
+
// exists only when the app uploaded one; treat its absence as normal.
|
|
25
|
+
export interface DocumentThumbnail {
|
|
26
|
+
url: string;
|
|
27
|
+
version: number;
|
|
28
|
+
branch_id: string;
|
|
29
|
+
content_type: string;
|
|
30
|
+
width: number | null;
|
|
31
|
+
height: number | null;
|
|
32
|
+
created_at: string;
|
|
33
|
+
expires_at: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Wire shape returned by the document HTTP routes. `project_id` is
|
|
37
|
+
// `null` for loose documents.
|
|
38
|
+
export interface DocumentSummary {
|
|
39
|
+
doc_id: string;
|
|
40
|
+
organization_id: string;
|
|
41
|
+
project_id: string | null;
|
|
42
|
+
name: string;
|
|
43
|
+
created_at: string;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
owner_sub: string;
|
|
46
|
+
deleted_at?: string;
|
|
47
|
+
// The document's current card thumbnail, or absent when no app has
|
|
48
|
+
// uploaded one.
|
|
49
|
+
thumbnail?: DocumentThumbnail;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// One byte stream of the original file behind an opaque-blob document. A
|
|
53
|
+
// source file is a set of one or more streams: the primary stream (what
|
|
54
|
+
// the file's own name names) plus any sidecar streams uploaded with it (a
|
|
55
|
+
// glTF's `.bin` buffer, a shapefile's `.dbf`). Each stream carries its own
|
|
56
|
+
// short-lived download URL, so an app fetches exactly the streams it
|
|
57
|
+
// needs, in parallel.
|
|
58
|
+
export interface DocumentSourceStream {
|
|
59
|
+
// The stream's name within the file set (the primary stream's is the
|
|
60
|
+
// file's own name).
|
|
61
|
+
name: string;
|
|
62
|
+
url: string;
|
|
63
|
+
content_type: string;
|
|
64
|
+
byte_size: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The original file behind an opaque-blob document, returned by
|
|
68
|
+
// `doc.sourceBlob()`. `url` is a short-lived presigned GET the app
|
|
69
|
+
// fetches the raw bytes from (the bytes are not inlined: a real source
|
|
70
|
+
// file routinely exceeds the API response cap); the descriptor fields let
|
|
71
|
+
// the app decide whether it handles this format before downloading.
|
|
72
|
+
// `streams` lists every stream of the file set, primary first (its entry
|
|
73
|
+
// mirrors the top-level fields): a single-stream file has exactly one
|
|
74
|
+
// entry, so an app that iterates `streams` handles single- and
|
|
75
|
+
// multi-stream files with one code path.
|
|
76
|
+
export interface DocumentSourceBlob {
|
|
77
|
+
url: string;
|
|
78
|
+
filename: string;
|
|
79
|
+
content_type: string;
|
|
80
|
+
byte_size: number;
|
|
81
|
+
expires_in: number;
|
|
82
|
+
streams: DocumentSourceStream[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DocumentRecord extends DocumentSummary {
|
|
86
|
+
// The caller's effective role on this document, resolved via the
|
|
87
|
+
// scope-loop on the server.
|
|
88
|
+
role: DocumentRole;
|
|
89
|
+
members: DocumentMember[];
|
|
90
|
+
app_metadata?: Record<string, unknown>;
|
|
91
|
+
// Server-authored, opaque label for where the document lives, rendered
|
|
92
|
+
// verbatim by the toolbar's location chip (the project's name when filed,
|
|
93
|
+
// null when loose). The SDK never composes location from `project_id` /
|
|
94
|
+
// `organization_id`, so the hierarchy above the document can evolve
|
|
95
|
+
// (folders / teams) without a client change.
|
|
96
|
+
location_label: string | null;
|
|
97
|
+
// Whether someone can produce a file from this document: a platform
|
|
98
|
+
// codec, an opaque blob's stored bytes, or an app installed on the
|
|
99
|
+
// document's organization that declares an output for it. The server
|
|
100
|
+
// resolves it, so a newly exportable format needs no client change.
|
|
101
|
+
downloadable: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// One stream of a document: a named, independently-loadable CRDT unit
|
|
105
|
+
// (`doc.stream(name)` opens exactly the streams an app touches, so open
|
|
106
|
+
// cost scales with what is used, not the whole document). `line_id`
|
|
107
|
+
// identifies the stream's underlying CRDT line; apps only need `name`.
|
|
108
|
+
export interface DocumentStreamInfo {
|
|
109
|
+
name: string;
|
|
110
|
+
// The stream's format. `doc.stream()` opens `'crdt'` entries, the
|
|
111
|
+
// editable collaborative unit, and rejects any other value loudly, so a
|
|
112
|
+
// format the platform adds later cannot be half-opened by this SDK.
|
|
113
|
+
format: string;
|
|
114
|
+
line_id: string;
|
|
115
|
+
created_at: string;
|
|
116
|
+
created_by: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// One branch of a document. A document has N branches: alternate
|
|
120
|
+
// states of the same design (the AEC "design option" used for
|
|
121
|
+
// whole-scheme versioning). Each branch is edited independently;
|
|
122
|
+
// `Brass.openDocumentById(docId, { branch })` opens a specific one.
|
|
123
|
+
export interface BranchSummary {
|
|
124
|
+
branch_id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
created_at: string;
|
|
127
|
+
created_by: string;
|
|
128
|
+
// True for the branch a cold open (no `?branch=`) lands on.
|
|
129
|
+
is_default: boolean;
|
|
130
|
+
// Lineage. Null for the doc's original branch; set on forked ones.
|
|
131
|
+
created_from: { branch_id: string; version: number } | null;
|
|
132
|
+
// The branch's current live head seq.
|
|
133
|
+
current_seq: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Input to `documents.setThumbnail`. `image` is the raw image bytes
|
|
137
|
+
// (a canvas `toBlob` result, a fetched `ArrayBuffer`, a `Uint8Array`);
|
|
138
|
+
// the SDK base64-encodes them onto the JSON upload body. `contentType`
|
|
139
|
+
// is required unless `image` is a `Blob` that already carries a
|
|
140
|
+
// `type` (canvas `toBlob` does). Allowed types: image/png,
|
|
141
|
+
// image/jpeg, image/webp. The version the thumbnail depicts and the
|
|
142
|
+
// branch it belongs to are read from the `Document` passed to
|
|
143
|
+
// `setThumbnail`, not supplied here, so the stored anchor always
|
|
144
|
+
// matches the state the app rendered.
|
|
145
|
+
export interface SetThumbnailInput {
|
|
146
|
+
image: Blob | ArrayBuffer | Uint8Array;
|
|
147
|
+
// Image MIME type. Optional only when `image` is a typed `Blob`.
|
|
148
|
+
contentType?: string;
|
|
149
|
+
// Optional intrinsic pixel dimensions, round-tripped so a renderer
|
|
150
|
+
// can reserve layout space before the image loads.
|
|
151
|
+
width?: number;
|
|
152
|
+
height?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Options for `documents.getThumbnail`. With no options the call
|
|
156
|
+
// returns the document's current card thumbnail; pass `version` to
|
|
157
|
+
// fetch the thumbnail for a point in history (the newest one at or
|
|
158
|
+
// below that version).
|
|
159
|
+
export interface GetThumbnailOptions {
|
|
160
|
+
version?: number;
|
|
161
|
+
branch?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Wire shape returned by `POST /documents/:docId/checkpoints` and
|
|
165
|
+
// mirrored on `listDocumentVersions` markers with `kind: 'checkpoint'`.
|
|
166
|
+
// `created_by` / `created_at` are always present.
|
|
167
|
+
export interface DocumentCheckpoint {
|
|
168
|
+
version: number;
|
|
169
|
+
branch_id: string;
|
|
170
|
+
created_by: string;
|
|
171
|
+
created_at: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Input to `documents.pushToDrive`. The optional `message` rides as
|
|
175
|
+
// the new drive version's commit message (and surfaces on the
|
|
176
|
+
// `drive.push.success` audit so operators investigating "what
|
|
177
|
+
// changed" can read it). Omitted means no message.
|
|
178
|
+
export interface PushToDriveInput {
|
|
179
|
+
message?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Outcome of a successful push.
|
|
183
|
+
export interface PushToDriveResult {
|
|
184
|
+
// The new external ref (the drive's version id) the push produced.
|
|
185
|
+
// Apps that surface "published as version X" use this. Empty when
|
|
186
|
+
// `pending` is true (the importer has not produced the version yet).
|
|
187
|
+
new_ref: string;
|
|
188
|
+
// True when the drive's write is asynchronous: the bytes were
|
|
189
|
+
// submitted and the drive's importer was queued, but the resulting
|
|
190
|
+
// Version (and the re-synced baseline) is not known yet. This is the
|
|
191
|
+
// file-roundtrip path (a doc imported from a drive file). The doc stays
|
|
192
|
+
// `ahead` until the drive's change-event webhook confirms the import,
|
|
193
|
+
// so an app should say "submitted, waiting for the importer" rather
|
|
194
|
+
// than "published", and expect `driveStatus` to stay `canPush` until
|
|
195
|
+
// the confirmation lands. Absent / false means the push was
|
|
196
|
+
// synchronous and the baseline already advanced (the doc is `synced`).
|
|
197
|
+
pending?: boolean;
|
|
198
|
+
// Entity count the push uploaded.
|
|
199
|
+
entity_count: number;
|
|
200
|
+
// ISO timestamp of the push.
|
|
201
|
+
pushed_at: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface CreateBranchRequest {
|
|
205
|
+
// The version on the source branch to fork from. Omit it to fork
|
|
206
|
+
// from the branch's live head, which is the common case ("branch
|
|
207
|
+
// the current state") and the only race-free one: the server
|
|
208
|
+
// resolves the head atomically, so you never have to read and
|
|
209
|
+
// pass a `current_seq` that is already stale by the time the
|
|
210
|
+
// create lands. Provide a value only to fork from a specific
|
|
211
|
+
// historical version.
|
|
212
|
+
from_version?: number;
|
|
213
|
+
// The branch to fork from. Omit to fork from the doc's default.
|
|
214
|
+
from_branch_id?: string;
|
|
215
|
+
// Optional display name. Defaults to "<source> copy" server-side.
|
|
216
|
+
name?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface DocumentsNamespace {
|
|
220
|
+
// Metadata read. Returns organization_id, project_id (or null), role,
|
|
221
|
+
// members, name. Closes the cold-URL-restore gap (an app whose URL
|
|
222
|
+
// carries a doc_id can resolve project + organization + role from
|
|
223
|
+
// this one call before opening the editable Document via
|
|
224
|
+
// `Brass.openDocumentById`). Creating / moving / listing documents is
|
|
225
|
+
// done through the hosted pickers + document-settings dialog, not
|
|
226
|
+
// this namespace; the runtime create-and-open path is the top-level
|
|
227
|
+
// `Brass.createDocument` / `Brass.openDocumentById`.
|
|
228
|
+
get(docId: string): Promise<DocumentRecord>;
|
|
229
|
+
// Upload a preview image for a document. Brass cannot render an
|
|
230
|
+
// app's document body, so an app that wants a thumbnail on its
|
|
231
|
+
// document cards renders one itself (e.g. `canvas.toBlob`) and
|
|
232
|
+
// uploads it here. Pass the live `Document` the image was rendered
|
|
233
|
+
// from: the SDK anchors the thumbnail to that document's exact
|
|
234
|
+
// current version and branch, so the stored preview always matches
|
|
235
|
+
// the state it depicts. The most recent upload becomes the
|
|
236
|
+
// document's card thumbnail. Returns the stored thumbnail (with a
|
|
237
|
+
// presigned `url`).
|
|
238
|
+
setThumbnail(
|
|
239
|
+
doc: Document,
|
|
240
|
+
input: SetThumbnailInput,
|
|
241
|
+
): Promise<DocumentThumbnail>;
|
|
242
|
+
// Read a document's thumbnail. With no options, returns the
|
|
243
|
+
// document's current card thumbnail; pass `version` for the
|
|
244
|
+
// thumbnail at a point in history. Resolves `null` when no
|
|
245
|
+
// matching thumbnail exists.
|
|
246
|
+
getThumbnail(
|
|
247
|
+
docId: string,
|
|
248
|
+
options?: GetThumbnailOptions,
|
|
249
|
+
): Promise<DocumentThumbnail | null>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Plumbing behind the per-`Document` surface (`doc.pushToDrive` /
|
|
253
|
+
// `doc.createCheckpoint`), the internal branch controller (list / create,
|
|
254
|
+
// which the toolbar branch command + hosted branch dialog drive), and the
|
|
255
|
+
// hosted history modal; not exposed on `Brass.documents`.
|
|
256
|
+
export interface DocumentsInternalNamespace {
|
|
257
|
+
createCheckpoint(doc: Document): Promise<DocumentCheckpoint>;
|
|
258
|
+
listBranches(
|
|
259
|
+
docId: string,
|
|
260
|
+
): Promise<{ default_branch_id: string; branches: BranchSummary[] }>;
|
|
261
|
+
// Streams plumbing behind `doc.stream()` /
|
|
262
|
+
// `doc.listStreams()`. A stream's `line_id` is what the SDK joins
|
|
263
|
+
// (`?branch=<line_id>`) and subscribes (`doc:<docId>:<line_id>`).
|
|
264
|
+
listStreams(
|
|
265
|
+
docId: string,
|
|
266
|
+
branchId: string,
|
|
267
|
+
): Promise<{ branch_id: string; streams: DocumentStreamInfo[] }>;
|
|
268
|
+
createStream(
|
|
269
|
+
docId: string,
|
|
270
|
+
branchId: string,
|
|
271
|
+
name: string,
|
|
272
|
+
): Promise<DocumentStreamInfo>;
|
|
273
|
+
createBranch(
|
|
274
|
+
docId: string,
|
|
275
|
+
input: CreateBranchRequest,
|
|
276
|
+
): Promise<BranchSummary>;
|
|
277
|
+
pushToDrive(
|
|
278
|
+
doc: Document,
|
|
279
|
+
input?: PushToDriveInput,
|
|
280
|
+
): Promise<PushToDriveResult>;
|
|
281
|
+
// Fetch a presigned GET + descriptor for the original file behind an
|
|
282
|
+
// opaque-blob document, so an app that knows the format can download
|
|
283
|
+
// and parse it in-browser. Rejects with a `BrassError` when the doc is
|
|
284
|
+
// not a stored opaque blob or the caller cannot reach it (404). The
|
|
285
|
+
// per-doc instance method `doc.sourceBlob()` routes through this.
|
|
286
|
+
sourceBlob(doc: Document): Promise<DocumentSourceBlob>;
|
|
287
|
+
// The import-claim protocol behind `Brass.registerImporter`. The SDK
|
|
288
|
+
// (never the app) claims the right to run the import of a stored
|
|
289
|
+
// source file, and reports completion so the platform stamps the
|
|
290
|
+
// document with the importing app's schema. Both reject with a
|
|
291
|
+
// `BrassError` on the ineligible / claim-lost outcomes.
|
|
292
|
+
claimImport(docId: string): Promise<ImportClaimResponse>;
|
|
293
|
+
completeImport(
|
|
294
|
+
docId: string,
|
|
295
|
+
): Promise<{ schema_ref: { app_id: string; schema_hash: string } }>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
interface DocumentsFactoryOptions {
|
|
299
|
+
apiBaseUrl: string;
|
|
300
|
+
getAccessToken: () => Promise<string | null>;
|
|
301
|
+
getAppAssertion?: () => Promise<string | null>;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function createDocumentsNamespace(
|
|
305
|
+
opts: DocumentsFactoryOptions,
|
|
306
|
+
): DocumentsNamespace & DocumentsInternalNamespace {
|
|
307
|
+
const apiFetch = createApiFetch(opts);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
get(docId) {
|
|
311
|
+
return apiFetch<DocumentRecord>({
|
|
312
|
+
path: `/documents/${encodeURIComponent(docId)}`,
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
listBranches(docId) {
|
|
316
|
+
return apiFetch<{ default_branch_id: string; branches: BranchSummary[] }>({
|
|
317
|
+
path: `/documents/${encodeURIComponent(docId)}/branches`,
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
listStreams(docId, branchId) {
|
|
321
|
+
return apiFetch<{ branch_id: string; streams: DocumentStreamInfo[] }>({
|
|
322
|
+
path: `/documents/${encodeURIComponent(docId)}/streams?branch=${encodeURIComponent(branchId)}`,
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
createStream(docId, branchId, name) {
|
|
326
|
+
return apiFetch<DocumentStreamInfo>({
|
|
327
|
+
method: 'POST',
|
|
328
|
+
path: `/documents/${encodeURIComponent(docId)}/streams`,
|
|
329
|
+
body: {
|
|
330
|
+
name,
|
|
331
|
+
branch_id: branchId,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
createBranch(docId, input) {
|
|
336
|
+
const body: CreateBranchRequest = {};
|
|
337
|
+
if (input.from_version !== undefined) {
|
|
338
|
+
body.from_version = input.from_version;
|
|
339
|
+
}
|
|
340
|
+
if (input.from_branch_id !== undefined) {
|
|
341
|
+
body.from_branch_id = input.from_branch_id;
|
|
342
|
+
}
|
|
343
|
+
if (input.name !== undefined) body.name = input.name;
|
|
344
|
+
return apiFetch<BranchSummary>({
|
|
345
|
+
method: 'POST',
|
|
346
|
+
path: `/documents/${encodeURIComponent(docId)}/branches`,
|
|
347
|
+
body,
|
|
348
|
+
});
|
|
349
|
+
},
|
|
350
|
+
async setThumbnail(doc, input) {
|
|
351
|
+
const docId = doc.id;
|
|
352
|
+
if (docId === null) {
|
|
353
|
+
throw new BrassError(
|
|
354
|
+
'setThumbnail requires a Document opened from the server',
|
|
355
|
+
{ status: 400 },
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
// Capture the depicted version synchronously, before the async
|
|
359
|
+
// encode, so a concurrent editor's op landing mid-upload cannot
|
|
360
|
+
// shift the anchor away from the state the app rendered.
|
|
361
|
+
const version = doc.currentSeq;
|
|
362
|
+
if (version === null) {
|
|
363
|
+
throw new BrassError(
|
|
364
|
+
'setThumbnail: the document has no resolved version yet',
|
|
365
|
+
{ status: 400 },
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const branchId = doc.branchId;
|
|
369
|
+
const { data, contentType } = await encodeThumbnailImage(input);
|
|
370
|
+
const body: {
|
|
371
|
+
content_type: string;
|
|
372
|
+
data: string;
|
|
373
|
+
version: number;
|
|
374
|
+
branch_id?: string;
|
|
375
|
+
width?: number;
|
|
376
|
+
height?: number;
|
|
377
|
+
} = { content_type: contentType, data, version };
|
|
378
|
+
if (branchId !== null) body.branch_id = branchId;
|
|
379
|
+
if (input.width !== undefined) body.width = input.width;
|
|
380
|
+
if (input.height !== undefined) body.height = input.height;
|
|
381
|
+
const res = await apiFetch<{ thumbnail: DocumentThumbnail }>({
|
|
382
|
+
method: 'POST',
|
|
383
|
+
path: `/documents/${encodeURIComponent(docId)}/thumbnail`,
|
|
384
|
+
body,
|
|
385
|
+
});
|
|
386
|
+
return res.thumbnail;
|
|
387
|
+
},
|
|
388
|
+
async createCheckpoint(doc) {
|
|
389
|
+
const docId = doc.id;
|
|
390
|
+
if (docId === null) {
|
|
391
|
+
throw new BrassError(
|
|
392
|
+
'createCheckpoint requires a Document opened from the server',
|
|
393
|
+
{ status: 400 },
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
// Capture the depicted version synchronously, so a concurrent
|
|
397
|
+
// editor's op landing mid-call cannot shift the anchor away
|
|
398
|
+
// from the state the user is on. Mirrors the setThumbnail
|
|
399
|
+
// version-capture pattern. This is the anchor the server pins
|
|
400
|
+
// the checkpoint to; it is not an app-facing "seal a past
|
|
401
|
+
// version" control.
|
|
402
|
+
const version = doc.currentSeq;
|
|
403
|
+
if (version === null) {
|
|
404
|
+
throw new BrassError(
|
|
405
|
+
'createCheckpoint: the document has no resolved version yet',
|
|
406
|
+
{ status: 400 },
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const branchId = doc.branchId;
|
|
410
|
+
const body: {
|
|
411
|
+
version: number;
|
|
412
|
+
branch_id?: string;
|
|
413
|
+
} = { version };
|
|
414
|
+
if (branchId !== null) body.branch_id = branchId;
|
|
415
|
+
const res = await apiFetch<{ checkpoint: DocumentCheckpoint }>({
|
|
416
|
+
method: 'POST',
|
|
417
|
+
path: `/documents/${encodeURIComponent(docId)}/checkpoints`,
|
|
418
|
+
body,
|
|
419
|
+
});
|
|
420
|
+
return res.checkpoint;
|
|
421
|
+
},
|
|
422
|
+
async pushToDrive(doc, input) {
|
|
423
|
+
const docId = doc.id;
|
|
424
|
+
if (docId === null) {
|
|
425
|
+
throw new BrassError(
|
|
426
|
+
'pushToDrive requires a Document opened from the server',
|
|
427
|
+
{ status: 400 },
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
const body: { message?: string } = {};
|
|
431
|
+
if (input?.message !== undefined) body.message = input.message;
|
|
432
|
+
const res = await apiFetch<PushToDriveResult>({
|
|
433
|
+
method: 'POST',
|
|
434
|
+
path: `/documents/${encodeURIComponent(docId)}/drive-sync/push`,
|
|
435
|
+
body,
|
|
436
|
+
});
|
|
437
|
+
return res;
|
|
438
|
+
},
|
|
439
|
+
async sourceBlob(doc) {
|
|
440
|
+
const docId = doc.id;
|
|
441
|
+
if (docId === null) {
|
|
442
|
+
throw new BrassError(
|
|
443
|
+
'sourceBlob requires a Document opened from the server',
|
|
444
|
+
{ status: 400 },
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
return apiFetch<DocumentSourceBlob>({
|
|
448
|
+
path: `/documents/${encodeURIComponent(docId)}/source-blob`,
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
claimImport(docId) {
|
|
452
|
+
return apiFetch<ImportClaimResponse>({
|
|
453
|
+
method: 'POST',
|
|
454
|
+
path: `/documents/${encodeURIComponent(docId)}/import-claim`,
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
completeImport(docId) {
|
|
458
|
+
return apiFetch<{ schema_ref: { app_id: string; schema_hash: string } }>({
|
|
459
|
+
method: 'POST',
|
|
460
|
+
path: `/documents/${encodeURIComponent(docId)}/import-complete`,
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
async getThumbnail(docId, options) {
|
|
464
|
+
const params = new URLSearchParams();
|
|
465
|
+
if (options?.version !== undefined) {
|
|
466
|
+
params.set('version', String(options.version));
|
|
467
|
+
}
|
|
468
|
+
if (options?.branch !== undefined) params.set('branch', options.branch);
|
|
469
|
+
const qs = params.toString();
|
|
470
|
+
const res = await apiFetch<{ thumbnail: DocumentThumbnail | null }>({
|
|
471
|
+
path: `/documents/${encodeURIComponent(docId)}/thumbnail${
|
|
472
|
+
qs ? `?${qs}` : ''
|
|
473
|
+
}`,
|
|
474
|
+
});
|
|
475
|
+
return res.thumbnail;
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Reads `SetThumbnailInput.image` into base64 plus a resolved MIME
|
|
481
|
+
// type. A `Blob` carries its own `type` (canvas `toBlob` sets it);
|
|
482
|
+
// raw `ArrayBuffer` / `Uint8Array` bytes need an explicit
|
|
483
|
+
// `contentType`.
|
|
484
|
+
async function encodeThumbnailImage(
|
|
485
|
+
input: SetThumbnailInput,
|
|
486
|
+
): Promise<{ data: string; contentType: string }> {
|
|
487
|
+
let bytes: Uint8Array;
|
|
488
|
+
let blobType: string | undefined;
|
|
489
|
+
if (input.image instanceof Uint8Array) {
|
|
490
|
+
bytes = input.image;
|
|
491
|
+
} else if (input.image instanceof ArrayBuffer) {
|
|
492
|
+
bytes = new Uint8Array(input.image);
|
|
493
|
+
} else {
|
|
494
|
+
bytes = new Uint8Array(await input.image.arrayBuffer());
|
|
495
|
+
blobType = input.image.type || undefined;
|
|
496
|
+
}
|
|
497
|
+
const contentType = input.contentType ?? blobType;
|
|
498
|
+
if (!contentType) {
|
|
499
|
+
throw new BrassError(
|
|
500
|
+
'setThumbnail: contentType is required when image is not a typed Blob',
|
|
501
|
+
{ status: 400 },
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return { data: bytesToBase64(bytes), contentType };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Standard-alphabet base64 of a byte array. Chunked so a large image
|
|
508
|
+
// does not blow the argument-count limit on `String.fromCharCode`.
|
|
509
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
510
|
+
let binary = '';
|
|
511
|
+
const chunk = 0x8000;
|
|
512
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
513
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
514
|
+
}
|
|
515
|
+
return btoa(binary);
|
|
516
|
+
}
|