@drawnagency/primitives 0.1.98 → 0.1.100
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/dist/{chunk-EUSWEVOA.js → chunk-3TO4RRHY.js} +12 -1
- package/dist/{chunk-KP4DWTPR.js → chunk-72FMQQIA.js} +246 -3
- package/dist/chunk-BS5AQHZG.js +45 -0
- package/dist/{chunk-NA5EJM6Z.js → chunk-CZLYECHC.js} +19 -8
- package/dist/{chunk-HKHEAH5F.js → chunk-GOCFTXR3.js} +8 -60
- package/dist/{chunk-GYCBWGVW.js → chunk-LBHQWIVL.js} +79 -24
- package/dist/{chunk-HJSE6SK5.js → chunk-REDDTTGJ.js} +3 -3
- package/dist/collab/rebase.d.ts.map +1 -1
- package/dist/collab/rebase.js +3 -1
- package/dist/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/components/docfile/DocFileOverlay.d.ts +12 -0
- package/dist/components/docfile/DocFileOverlay.d.ts.map +1 -0
- package/dist/components/docfile/DocFilePane.d.ts +17 -0
- package/dist/components/docfile/DocFilePane.d.ts.map +1 -0
- package/dist/components/docfile/doc-file-state.d.ts +33 -0
- package/dist/components/docfile/doc-file-state.d.ts.map +1 -0
- package/dist/components/editor/SettingsForm.d.ts.map +1 -1
- package/dist/components/editor/index.js +1 -1
- package/dist/components/sections/Document/index.d.ts +18 -0
- package/dist/components/sections/Document/index.d.ts.map +1 -0
- package/dist/components/sections/all-sections.d.ts +10 -0
- package/dist/components/sections/all-sections.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.d.ts +10 -0
- package/dist/components/sections/brand-guide.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +3 -1
- package/dist/components/sections/register-schemas.js +5 -3
- package/dist/components/sections/register.js +5 -3
- package/dist/components/shared/DocumentSourceField.d.ts +13 -0
- package/dist/components/shared/DocumentSourceField.d.ts.map +1 -0
- package/dist/components/shared/Navigation.d.ts.map +1 -1
- package/dist/components/shell/EditorModal.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -1
- package/dist/components/shell/MediaFolderTile.d.ts +3 -1
- package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
- package/dist/components/shell/MediaFolderTree.d.ts +3 -0
- package/dist/components/shell/MediaFolderTree.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryContext.d.ts +18 -1
- package/dist/components/shell/MediaLibraryContext.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts +21 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/hooks/useDocumentUploads.d.ts +32 -0
- package/dist/hooks/useDocumentUploads.d.ts.map +1 -0
- package/dist/hooks/useEditorPublish.d.ts +23 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts +14 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/hooks/useResolvedDocument.d.ts +12 -0
- package/dist/hooks/useResolvedDocument.d.ts.map +1 -0
- package/dist/index.js +60 -34
- package/dist/lib/block-move.d.ts.map +1 -1
- package/dist/lib/dexie.d.ts +11 -0
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +32 -0
- package/dist/lib/document-refs.d.ts +22 -0
- package/dist/lib/document-refs.d.ts.map +1 -0
- package/dist/lib/document-uploads.d.ts +38 -0
- package/dist/lib/document-uploads.d.ts.map +1 -0
- package/dist/lib/documents.d.ts +65 -0
- package/dist/lib/documents.d.ts.map +1 -0
- package/dist/lib/index-ops.d.ts +1 -0
- package/dist/lib/index-ops.d.ts.map +1 -1
- package/dist/lib/index-ops.js +13 -1
- package/dist/lib/index.js +6 -6
- package/dist/lib/loader.d.ts +6 -0
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/media-folders.d.ts +8 -0
- package/dist/lib/media-folders.d.ts.map +1 -1
- package/dist/lib/media-folders.js +2 -0
- package/dist/lib/nav.d.ts +29 -1
- package/dist/lib/nav.d.ts.map +1 -1
- package/dist/lib/pages.d.ts +18 -0
- package/dist/lib/pages.d.ts.map +1 -1
- package/dist/lib/platform-broker.d.ts +12 -0
- package/dist/lib/platform-broker.d.ts.map +1 -1
- package/dist/lib/platform-broker.js +12 -3
- package/dist/lib/registry.d.ts +10 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/save-chunks.d.ts +15 -0
- package/dist/lib/save-chunks.d.ts.map +1 -1
- package/dist/lib/scroll-lock.d.ts +2 -0
- package/dist/lib/scroll-lock.d.ts.map +1 -0
- package/dist/schemas/documents.d.ts +80 -0
- package/dist/schemas/documents.d.ts.map +1 -0
- package/dist/schemas/documents.js +25 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +24 -2
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +5 -1
- package/dist/storage/types.d.ts +69 -5
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/types/database.d.ts +14 -0
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/collab/rebase.ts +15 -0
- package/src/components/chat/ChatPanel.tsx +5 -4
- package/src/components/docfile/DocFileOverlay.tsx +54 -0
- package/src/components/docfile/DocFilePane.tsx +129 -0
- package/src/components/docfile/doc-file-state.ts +180 -0
- package/src/components/editor/SettingsForm.tsx +10 -0
- package/src/components/sections/Document/index.tsx +163 -0
- package/src/components/sections/brand-guide.ts +2 -1
- package/src/components/shared/DocumentSourceField.tsx +51 -0
- package/src/components/shared/Navigation.tsx +53 -10
- package/src/components/shell/EditorModal.tsx +2 -5
- package/src/components/shell/EditorShell.tsx +154 -10
- package/src/components/shell/MediaBreadcrumb.tsx +14 -1
- package/src/components/shell/MediaFolderTile.tsx +4 -2
- package/src/components/shell/MediaFolderTree.tsx +20 -3
- package/src/components/shell/MediaLibraryContext.tsx +18 -1
- package/src/components/shell/MediaLibraryModal.tsx +223 -45
- package/src/hooks/useDocumentUploads.ts +330 -0
- package/src/hooks/useEditorPublish.ts +197 -11
- package/src/hooks/useMediaPipeline.ts +136 -5
- package/src/hooks/useResolvedDocument.ts +25 -0
- package/src/lib/block-move.ts +15 -4
- package/src/lib/dexie.ts +54 -0
- package/src/lib/document-refs.ts +83 -0
- package/src/lib/document-uploads.ts +66 -0
- package/src/lib/documents.ts +138 -0
- package/src/lib/index-ops.ts +16 -0
- package/src/lib/loader.ts +32 -11
- package/src/lib/media-folders.ts +9 -0
- package/src/lib/nav.ts +46 -2
- package/src/lib/pages.ts +48 -0
- package/src/lib/platform-broker.ts +18 -3
- package/src/lib/registry.ts +9 -0
- package/src/lib/save-chunks.ts +42 -0
- package/src/lib/scroll-lock.ts +27 -0
- package/src/schemas/documents.ts +82 -0
- package/src/schemas/index.ts +1 -0
- package/src/schemas/nav.ts +3 -3
- package/src/storage/index.ts +2 -2
- package/src/storage/types.ts +71 -5
- package/src/types/database.ts +14 -0
- /package/dist/{chunk-FOY5TF7D.js → chunk-ZDQ3TT47.js} +0 -0
|
@@ -7,6 +7,21 @@ function sleep(ms: number): Promise<void> {
|
|
|
7
7
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* A broker call that reached apps/admin and got a non-2xx back. The `status` is
|
|
12
|
+
* carried so callers can distinguish a MISSING endpoint (404/405/501 — the
|
|
13
|
+
* stale-admin window, where the client site has been upgraded ahead of the
|
|
14
|
+
* platform) from a real auth or config failure. Without it every such case
|
|
15
|
+
* surfaces as an opaque 500 the editor cannot act on. `status` is null when the
|
|
16
|
+
* request never got a response at all.
|
|
17
|
+
*/
|
|
18
|
+
export class PlatformBrokerError extends Error {
|
|
19
|
+
constructor(public readonly status: number | null, message: string) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "PlatformBrokerError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
/** True when this runtime should broker privileged ops through the platform. */
|
|
11
26
|
export function isPlatformMode(): boolean {
|
|
12
27
|
return Boolean(env("PLATFORM_API_URL") && env("PLATFORM_API_KEY"));
|
|
@@ -60,15 +75,15 @@ export async function platformBroker<T = unknown>(
|
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
if (res.status >= 500 && !isLast) {
|
|
63
|
-
lastError = new
|
|
78
|
+
lastError = new PlatformBrokerError(res.status, `Platform broker error (${res.status})`);
|
|
64
79
|
await sleep(RETRY_BASE_MS * 2 ** attempt);
|
|
65
80
|
continue;
|
|
66
81
|
}
|
|
67
82
|
|
|
68
|
-
throw new
|
|
83
|
+
throw new PlatformBrokerError(res.status, `Platform broker error (${res.status}): ${await res.text()}`);
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
throw lastError instanceof Error ? lastError : new
|
|
86
|
+
throw lastError instanceof Error ? lastError : new PlatformBrokerError(null, "Platform broker unreachable");
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
/**
|
package/src/lib/registry.ts
CHANGED
|
@@ -62,6 +62,15 @@ export type SettingsFieldDef =
|
|
|
62
62
|
label: string;
|
|
63
63
|
default: VideoSource;
|
|
64
64
|
target?: "content" | "options";
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
/** Opens the media library filtered to documents (PDF/HTML). The renderer
|
|
68
|
+
* lives in SettingsForm; an unrendered kind falls through its `default`
|
|
69
|
+
* arm, so this type can land ahead of the field component. */
|
|
70
|
+
type: "document-source";
|
|
71
|
+
label: string;
|
|
72
|
+
default: { documentId: string };
|
|
73
|
+
target?: "content" | "options";
|
|
65
74
|
};
|
|
66
75
|
|
|
67
76
|
export type SettingsSchema = Record<string, SettingsFieldDef>;
|
package/src/lib/save-chunks.ts
CHANGED
|
@@ -51,3 +51,45 @@ export function packUploads(uploads: MediaUploadPayload[], budget = SAVE_CHUNK_B
|
|
|
51
51
|
if (current.length > 0) chunks.push(current);
|
|
52
52
|
return { chunks, oversized };
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
export interface DocumentUploadPayload {
|
|
56
|
+
id: string;
|
|
57
|
+
path: string;
|
|
58
|
+
base64: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Approximate serialized size of one document upload — base64 dominates; the
|
|
62
|
+
* allowance covers JSON keys, quoting, and the manifest entry riding along. */
|
|
63
|
+
export function documentUploadSizeBytes(upload: DocumentUploadPayload): number {
|
|
64
|
+
return upload.base64.length + upload.path.length + 576;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Greedy in-order packing of document uploads. A 2.5MiB document base64s to
|
|
68
|
+
* ~3.4MB, so two never share a request — hence chunking rather than one blob
|
|
69
|
+
* request. Documents are never packed with sections/index. */
|
|
70
|
+
export function packDocumentUploads(
|
|
71
|
+
uploads: DocumentUploadPayload[],
|
|
72
|
+
budget = SAVE_CHUNK_BUDGET_BYTES,
|
|
73
|
+
): { chunks: DocumentUploadPayload[][]; oversized: DocumentUploadPayload[] } {
|
|
74
|
+
const chunks: DocumentUploadPayload[][] = [];
|
|
75
|
+
const oversized: DocumentUploadPayload[] = [];
|
|
76
|
+
let current: DocumentUploadPayload[] = [];
|
|
77
|
+
let currentBytes = 0;
|
|
78
|
+
|
|
79
|
+
for (const upload of uploads) {
|
|
80
|
+
const size = documentUploadSizeBytes(upload);
|
|
81
|
+
if (size > budget) {
|
|
82
|
+
oversized.push(upload);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (current.length > 0 && currentBytes + size > budget) {
|
|
86
|
+
chunks.push(current);
|
|
87
|
+
current = [];
|
|
88
|
+
currentBytes = 0;
|
|
89
|
+
}
|
|
90
|
+
current.push(upload);
|
|
91
|
+
currentBytes += size;
|
|
92
|
+
}
|
|
93
|
+
if (current.length > 0) chunks.push(current);
|
|
94
|
+
return { chunks, oversized };
|
|
95
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refcounted body-scroll lock.
|
|
3
|
+
*
|
|
4
|
+
* EditorModal and ChatPanel each save/restore `body.style.overflow` themselves,
|
|
5
|
+
* which collides when two lockers overlap — the first to release restores
|
|
6
|
+
* "hidden" as if it were the resting value, or unlocks the page under a still-
|
|
7
|
+
* open surface. The doc overlay and the mobile chat sheet CAN be open at the
|
|
8
|
+
* same time, so this one counts.
|
|
9
|
+
*/
|
|
10
|
+
let depth = 0;
|
|
11
|
+
let previousOverflow = "";
|
|
12
|
+
|
|
13
|
+
export function lockBodyScroll(): () => void {
|
|
14
|
+
if (typeof document === "undefined") return () => {};
|
|
15
|
+
if (depth === 0) {
|
|
16
|
+
previousOverflow = document.body.style.overflow;
|
|
17
|
+
document.body.style.overflow = "hidden";
|
|
18
|
+
}
|
|
19
|
+
depth++;
|
|
20
|
+
let released = false;
|
|
21
|
+
return () => {
|
|
22
|
+
if (released) return;
|
|
23
|
+
released = true;
|
|
24
|
+
depth--;
|
|
25
|
+
if (depth === 0) document.body.style.overflow = previousOverflow;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Document metadata lives in its OWN file, not in image-manifest.json.
|
|
5
|
+
*
|
|
6
|
+
* The image manifest is hard-`.parse`d on every page load across five routes,
|
|
7
|
+
* its remote-merge guard fails open on parse failure, and the authoring CLI
|
|
8
|
+
* parse-catch-WRITES it — so one entry a stale package version cannot
|
|
9
|
+
* represent means whole-site 500s or a wiped manifest. A sidecar is simply
|
|
10
|
+
* absent from an older writer's file list and survives untouched. Same
|
|
11
|
+
* structural answer as nav.json; do not "simplify" this back into either
|
|
12
|
+
* index.json or image-manifest.json.
|
|
13
|
+
*/
|
|
14
|
+
export const DOCUMENT_MANIFEST_PATH = "src/content/document-manifest.json";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Git-tier cap. 2.5 MiB base64-encodes to ~3.4 MB, inside the 4 MB post-base64
|
|
18
|
+
* chunk budget with envelope headroom.
|
|
19
|
+
*/
|
|
20
|
+
export const DOCUMENT_GIT_MAX_BYTES = 2_621_440;
|
|
21
|
+
|
|
22
|
+
/** Bucket-tier cap. Its lower bound is DOCUMENT_GIT_MAX_BYTES — no dead zone. */
|
|
23
|
+
export const DOCUMENT_BUCKET_MAX_BYTES = 209_715_200;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The only two accepted upload types, and the ONLY source of a document's file
|
|
27
|
+
* extension — never the media pipeline's `?? "bin"` fallback.
|
|
28
|
+
*/
|
|
29
|
+
export const DOCUMENT_MIME_TO_EXT = {
|
|
30
|
+
"application/pdf": "pdf",
|
|
31
|
+
"text/html": "html",
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/** Reverse of DOCUMENT_MIME_TO_EXT, for setting Content-Type when serving. */
|
|
35
|
+
export const DOCUMENT_KIND_TO_MIME = {
|
|
36
|
+
pdf: "application/pdf",
|
|
37
|
+
html: "text/html",
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export const DocumentManifestEntrySchema = z.object({
|
|
41
|
+
filename: z.string(),
|
|
42
|
+
size: z.number().int().positive(),
|
|
43
|
+
kind: z.enum(["pdf", "html"]),
|
|
44
|
+
storage: z.enum(["git", "bucket"]),
|
|
45
|
+
/** git: repo path under assets/documents/…; bucket: site-relative object key. */
|
|
46
|
+
path: z.string(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const DocumentManifestSchema = z.record(z.string(), DocumentManifestEntrySchema);
|
|
50
|
+
|
|
51
|
+
export type DocumentManifest = z.infer<typeof DocumentManifestSchema>;
|
|
52
|
+
export type DocumentManifestEntry = z.infer<typeof DocumentManifestEntrySchema>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The gated URL for a document, at BOTH storage tiers. Single-sourced here so
|
|
56
|
+
* the section row, the overlay, the nav group and the tests cannot drift — a
|
|
57
|
+
* bucket-tier document is still fetched through this route, which authorizes
|
|
58
|
+
* the requester and only then 302s to a short-lived presigned GET.
|
|
59
|
+
*/
|
|
60
|
+
export function documentFileUrl(
|
|
61
|
+
documentId: string,
|
|
62
|
+
filename: string,
|
|
63
|
+
opts: { download?: boolean } = {},
|
|
64
|
+
): string {
|
|
65
|
+
const base = `/api/document-file/${documentId}/${encodeURIComponent(filename)}`;
|
|
66
|
+
return opts.download ? `${base}?download=1` : base;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Git-tier repo path. `slugName` comes from sanitizeMediaName(file.name). */
|
|
70
|
+
export function documentGitPath(slugName: string, hash: string, kind: "pdf" | "html"): string {
|
|
71
|
+
return `assets/documents/${slugName}-${hash}/original.${kind}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Bucket-tier object key, SITE-RELATIVE — the store prepends the tenant prefix,
|
|
76
|
+
* so this is also the form stored in the manifest, matched by the delete
|
|
77
|
+
* broker's regex, and compared by the publish-time GC. The id is the 32-hex
|
|
78
|
+
* `documentId` minted by upload-intent, never the client's content hash.
|
|
79
|
+
*/
|
|
80
|
+
export function documentBucketPath(documentId: string, kind: "pdf" | "html"): string {
|
|
81
|
+
return `documents/${documentId}/original.${kind}`;
|
|
82
|
+
}
|
package/src/schemas/index.ts
CHANGED
package/src/schemas/nav.ts
CHANGED
|
@@ -19,9 +19,9 @@ export const NavEntrySchema = z.discriminatedUnion("kind", [
|
|
|
19
19
|
/** May be blank — agent-authored files can omit it; UI falls back to "Untitled group". */
|
|
20
20
|
label: z.string().default(""),
|
|
21
21
|
startCollapsed: z.boolean().default(false),
|
|
22
|
-
// Everything but `id` defaults:
|
|
23
|
-
//
|
|
24
|
-
// missing a key
|
|
22
|
+
// Everything but `id` defaults: the loader parses entries individually and
|
|
23
|
+
// drops the ones that fail — defaulting here keeps an agent-authored group
|
|
24
|
+
// missing a key from being dropped (and its drop marking the nav degraded).
|
|
25
25
|
pageIds: z.array(z.string().min(1)).default([]),
|
|
26
26
|
}),
|
|
27
27
|
]);
|
package/src/storage/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { StorageProvider, FileWrite, AssetStore } from "./types";
|
|
2
|
-
export { StorageConflictError, ASSET_CACHE_CONTROL, IDEMPOTENCY_TRAILER, IDEMPOTENCY_KEY_RE, withIdempotencyTrailer } from "./types";
|
|
1
|
+
export type { StorageProvider, FileWrite, AssetStore, DocumentStore } from "./types";
|
|
2
|
+
export { StorageConflictError, IdempotencyLookupUnavailableError, ASSET_CACHE_CONTROL, DOCUMENT_CACHE_CONTROL, IDEMPOTENCY_TRAILER, IDEMPOTENCY_KEY_RE, withIdempotencyTrailer } from "./types";
|
package/src/storage/types.ts
CHANGED
|
@@ -46,11 +46,18 @@ export interface StorageProvider {
|
|
|
46
46
|
* OPTIONAL: providers that cannot search history omit it, and callers fall
|
|
47
47
|
* back to surfacing the plain conflict.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* `null` means "searched, and this key is not in the window" — which still is
|
|
50
|
+
* NOT proof the write did not land, because the window is BOUNDED and an old
|
|
51
|
+
* key reads as absent. A caller must not tell the user the earlier attempt
|
|
52
|
+
* failed.
|
|
53
|
+
*
|
|
54
|
+
* A failure of the history read itself MUST throw
|
|
55
|
+
* {@link IdempotencyLookupUnavailableError} rather than answer `null`. The two
|
|
56
|
+
* are not interchangeable: a caller deciding whether it is safe to APPLY a
|
|
57
|
+
* write reads `null` as "nothing landed, go ahead", so collapsing a failed
|
|
58
|
+
* read into it turns at-most-once into at-least-once. Callers that are merely
|
|
59
|
+
* REPORTING on a key (or choosing between replay and conflict) can catch it
|
|
60
|
+
* and fall back to the pessimistic answer.
|
|
54
61
|
*/
|
|
55
62
|
findCommitByIdempotencyKey?(
|
|
56
63
|
key: string,
|
|
@@ -58,6 +65,19 @@ export interface StorageProvider {
|
|
|
58
65
|
): Promise<string | null>;
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The idempotency history read failed, so whether the key landed is UNKNOWN.
|
|
70
|
+
*
|
|
71
|
+
* Distinct from a `null` answer, which means the provider successfully searched
|
|
72
|
+
* and did not find it. See {@link StorageProvider.findCommitByIdempotencyKey}.
|
|
73
|
+
*/
|
|
74
|
+
export class IdempotencyLookupUnavailableError extends Error {
|
|
75
|
+
constructor(override readonly cause?: unknown) {
|
|
76
|
+
super("Could not search draft history for the idempotency key");
|
|
77
|
+
this.name = "IdempotencyLookupUnavailableError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
/**
|
|
62
82
|
* Commit-message trailer carrying a write's idempotency key. The commit itself is
|
|
63
83
|
* the store — no table, and no TTL to tune: keys live exactly as long as the
|
|
@@ -131,3 +151,49 @@ export interface AssetStore {
|
|
|
131
151
|
/** Recursive listing under a site-relative prefix. */
|
|
132
152
|
list(prefix: string): Promise<{ path: string; size: number; updatedAt: string }[]>;
|
|
133
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `Cache-Control` every DOCUMENT upload must set. Deliberately NOT
|
|
157
|
+
* ASSET_CACHE_CONTROL: documents live in a PRIVATE bucket reachable only
|
|
158
|
+
* through short-lived presigned GETs, and a shared cache keyed on the full
|
|
159
|
+
* signed URL would otherwise retain the bytes for a year after the signature
|
|
160
|
+
* expired. `private` keeps the copy in the requesting browser only.
|
|
161
|
+
*/
|
|
162
|
+
export const DOCUMENT_CACHE_CONTROL = "private, max-age=31536000, immutable";
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Optional PRIVATE document store — a second bucket with no public domain.
|
|
166
|
+
*
|
|
167
|
+
* Deliberately not an AssetStore: the assets bucket is public by configuration
|
|
168
|
+
* at R2_PUBLIC_BASE (videos depend on it), so a signed GET there would protect
|
|
169
|
+
* nothing and hosted HTML would render unsandboxed on a sibling origin. Hence
|
|
170
|
+
* no `getPublicUrl` here at all — every read is a short-lived signed GET,
|
|
171
|
+
* minted only after the gated route has authorized the requester.
|
|
172
|
+
*
|
|
173
|
+
* Paths are SITE-RELATIVE ("documents/{hash}/original.pdf"); implementations
|
|
174
|
+
* prepend the tenant prefix themselves so a caller can never address another
|
|
175
|
+
* tenant's objects.
|
|
176
|
+
*/
|
|
177
|
+
export interface DocumentStore {
|
|
178
|
+
/** Mint a signed direct-upload URL for the browser (PUT). */
|
|
179
|
+
createUploadUrl(
|
|
180
|
+
path: string,
|
|
181
|
+
opts: { contentType: string },
|
|
182
|
+
): Promise<{ uploadUrl: string; path: string }>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Mint a short-lived signed GET. `downloadFilename`, when given, signs
|
|
186
|
+
* `response-content-disposition: attachment` into the URL — R2 sends no
|
|
187
|
+
* Content-Disposition of its own, so this is the only way ?download=1 can
|
|
188
|
+
* force a download on the bucket tier.
|
|
189
|
+
*/
|
|
190
|
+
createDownloadUrl(
|
|
191
|
+
path: string,
|
|
192
|
+
opts: { expiresInSeconds: number; downloadFilename?: string },
|
|
193
|
+
): Promise<string>;
|
|
194
|
+
|
|
195
|
+
delete(paths: string[]): Promise<void>;
|
|
196
|
+
|
|
197
|
+
/** Recursive listing under a site-relative prefix. */
|
|
198
|
+
list(prefix: string): Promise<Array<{ path: string; size: number; updatedAt: string }>>;
|
|
199
|
+
}
|
package/src/types/database.ts
CHANGED
|
@@ -759,6 +759,20 @@ export type Database = {
|
|
|
759
759
|
retry_after_ms: number
|
|
760
760
|
}[]
|
|
761
761
|
}
|
|
762
|
+
oauth_rotate_refresh_token: {
|
|
763
|
+
Args: {
|
|
764
|
+
p_expected_client_id: string
|
|
765
|
+
p_next_expires_at: string
|
|
766
|
+
p_next_token_hash: string
|
|
767
|
+
p_token_hash: string
|
|
768
|
+
}
|
|
769
|
+
Returns: {
|
|
770
|
+
status: string
|
|
771
|
+
family_id: string | null
|
|
772
|
+
client_id: string | null
|
|
773
|
+
platform_user_id: string | null
|
|
774
|
+
}[]
|
|
775
|
+
}
|
|
762
776
|
provision_claim: { Args: { p_site_id: string }; Returns: boolean }
|
|
763
777
|
rate_limit_cleanup: { Args: { p_max_age_ms: number }; Returns: undefined }
|
|
764
778
|
rate_limit_hit: {
|
|
File without changes
|