@ai-matrx/media 0.1.0 → 0.1.1
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/CHANGELOG.md +18 -0
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/react.d.cts +2 -1
- package/dist/react.d.ts +2 -1
- package/dist/use-media-upload-Dg2HSvTq.d.ts +53 -0
- package/dist/use-media-upload-v5Uq9Ruq.d.cts +53 -0
- package/package.json +1 -1
- package/dist/use-media-upload-VjUhqbyY.d.cts +0 -270
- package/dist/use-media-upload-VjUhqbyY.d.ts +0 -270
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @ai-matrx/media
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-08-29
|
|
4
|
+
|
|
5
|
+
Types-only fix; runtime behavior unchanged.
|
|
6
|
+
|
|
7
|
+
- **Fixed: the `DurableSrc` unique-symbol brand was duplicated in the built
|
|
8
|
+
declarations.** The dts was built in two passes (mirroring the two-config
|
|
9
|
+
"use client" banner split), so the root `index.d.ts` declared its own copy
|
|
10
|
+
of the brand while `/core` and `/react` used a shared-chunk copy — two
|
|
11
|
+
distinct `unique symbol`s. Result: a `MediaClient` typed via the root entry
|
|
12
|
+
was nominally incompatible with `/core`'s `MediaProvider`, forcing a
|
|
13
|
+
documented cast in the host. The dts now builds in ONE pass over all three
|
|
14
|
+
entries (the JS banner split is unchanged), so the brand is declared once
|
|
15
|
+
in `index.d.ts` and the other entries' declarations import it.
|
|
16
|
+
- **Canary added:** `verify:tarball` now compiles a consumer against the
|
|
17
|
+
packed tarball that assigns a root-typed `MediaClient` into
|
|
18
|
+
`/core`'s `MediaProvider` props with **no cast** — the brand-duplication
|
|
19
|
+
regression class fails `check:package` from now on.
|
|
20
|
+
|
|
3
21
|
## 0.1.0 — 2026-08-29
|
|
4
22
|
|
|
5
23
|
Initial release (campaign ruling C19). Root types + `/core` headless hooks +
|
package/dist/core.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { MediaClient, MediaHostPorts, MediaRefLike, MediaResolution, MediaUnavailableReason, DurableSrc, MediaActionContext, MediaActionKind, MediaSlotComponent, MediaSharePopoverProps } from './index.cjs';
|
|
4
|
+
export { M as MediaUploadEntry, a as MediaUploadEntryStatus, b as MediaUploadProgress, U as UseMediaUploadResult, u as useMediaUpload } from './use-media-upload-v5Uq9Ruq.cjs';
|
|
5
5
|
|
|
6
6
|
interface MediaProviderProps {
|
|
7
7
|
client: MediaClient;
|
package/dist/core.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { MediaClient, MediaHostPorts, MediaRefLike, MediaResolution, MediaUnavailableReason, DurableSrc, MediaActionContext, MediaActionKind, MediaSlotComponent, MediaSharePopoverProps } from './index.js';
|
|
4
|
+
export { M as MediaUploadEntry, a as MediaUploadEntryStatus, b as MediaUploadProgress, U as UseMediaUploadResult, u as useMediaUpload } from './use-media-upload-Dg2HSvTq.js';
|
|
5
5
|
|
|
6
6
|
interface MediaProviderProps {
|
|
7
7
|
client: MediaClient;
|
package/dist/react.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import react__default, { SVGProps, HTMLAttributes, InputHTMLAttributes, ReactNode } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { MediaRefLike, MediaUploadOptions, MediaActionKind } from './index.cjs';
|
|
4
|
+
import { M as MediaUploadEntry } from './use-media-upload-v5Uq9Ruq.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* src/react/cn.ts — conditional class join with last-wins Tailwind conflict
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import react__default, { SVGProps, HTMLAttributes, InputHTMLAttributes, ReactNode } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { MediaRefLike, MediaUploadOptions, MediaActionKind } from './index.js';
|
|
4
|
+
import { M as MediaUploadEntry } from './use-media-upload-Dg2HSvTq.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* src/react/cn.ts — conditional class join with last-wins Tailwind conflict
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MediaUploadOptions, MediaUploadResult, MediaMultiUploadResult } from './index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/use-media-upload.ts — the React-side upload primitive.
|
|
5
|
+
*
|
|
6
|
+
* Ported from matrx-frontend `handler/hooks/useFileUpload.ts` with two seam
|
|
7
|
+
* inversions: the transport is `MediaClient.upload` (never touched directly),
|
|
8
|
+
* and the multi-file pre-flight (SHA-256 dedup dialog) is the client's
|
|
9
|
+
* optional `uploadMany` — when absent, the batch runs sequentially through
|
|
10
|
+
* `upload` with per-file results.
|
|
11
|
+
*
|
|
12
|
+
* Additionally exposes a per-file `entries` tray state (name, size, bytes,
|
|
13
|
+
* status) so the dropzone's UploadProgressList works without any app store —
|
|
14
|
+
* the Redux `selectVisibleUploads` coupling of the original, inverted.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MediaUploadProgress {
|
|
18
|
+
loaded: number;
|
|
19
|
+
total: number;
|
|
20
|
+
ratio: number;
|
|
21
|
+
}
|
|
22
|
+
type MediaUploadEntryStatus = "uploading" | "success" | "error";
|
|
23
|
+
interface MediaUploadEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
fileName: string;
|
|
26
|
+
fileSize: number;
|
|
27
|
+
bytesUploaded: number;
|
|
28
|
+
status: MediaUploadEntryStatus;
|
|
29
|
+
error: string | null;
|
|
30
|
+
fileId: string | null;
|
|
31
|
+
completedAt: number | null;
|
|
32
|
+
}
|
|
33
|
+
interface UseMediaUploadResult {
|
|
34
|
+
/** Upload one file/blob. Returns the durable identity. */
|
|
35
|
+
upload: (file: File | Blob, opts?: MediaUploadOptions) => Promise<MediaUploadResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Upload many files. Uses the client's `uploadMany` (host pre-flight /
|
|
38
|
+
* dedup dialog) when implemented; otherwise a sequential per-file loop with
|
|
39
|
+
* real per-file error messages.
|
|
40
|
+
*/
|
|
41
|
+
uploadMany: (files: File[], opts?: MediaUploadOptions) => Promise<MediaMultiUploadResult>;
|
|
42
|
+
uploading: boolean;
|
|
43
|
+
progress: MediaUploadProgress | null;
|
|
44
|
+
result: MediaUploadResult | null;
|
|
45
|
+
error: Error | null;
|
|
46
|
+
/** Live per-file tray entries for progress UI. */
|
|
47
|
+
entries: MediaUploadEntry[];
|
|
48
|
+
clearEntry: (id: string) => void;
|
|
49
|
+
reset: () => void;
|
|
50
|
+
}
|
|
51
|
+
declare function useMediaUpload(): UseMediaUploadResult;
|
|
52
|
+
|
|
53
|
+
export { type MediaUploadEntry as M, type UseMediaUploadResult as U, type MediaUploadEntryStatus as a, type MediaUploadProgress as b, useMediaUpload as u };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MediaUploadOptions, MediaUploadResult, MediaMultiUploadResult } from './index.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/use-media-upload.ts — the React-side upload primitive.
|
|
5
|
+
*
|
|
6
|
+
* Ported from matrx-frontend `handler/hooks/useFileUpload.ts` with two seam
|
|
7
|
+
* inversions: the transport is `MediaClient.upload` (never touched directly),
|
|
8
|
+
* and the multi-file pre-flight (SHA-256 dedup dialog) is the client's
|
|
9
|
+
* optional `uploadMany` — when absent, the batch runs sequentially through
|
|
10
|
+
* `upload` with per-file results.
|
|
11
|
+
*
|
|
12
|
+
* Additionally exposes a per-file `entries` tray state (name, size, bytes,
|
|
13
|
+
* status) so the dropzone's UploadProgressList works without any app store —
|
|
14
|
+
* the Redux `selectVisibleUploads` coupling of the original, inverted.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MediaUploadProgress {
|
|
18
|
+
loaded: number;
|
|
19
|
+
total: number;
|
|
20
|
+
ratio: number;
|
|
21
|
+
}
|
|
22
|
+
type MediaUploadEntryStatus = "uploading" | "success" | "error";
|
|
23
|
+
interface MediaUploadEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
fileName: string;
|
|
26
|
+
fileSize: number;
|
|
27
|
+
bytesUploaded: number;
|
|
28
|
+
status: MediaUploadEntryStatus;
|
|
29
|
+
error: string | null;
|
|
30
|
+
fileId: string | null;
|
|
31
|
+
completedAt: number | null;
|
|
32
|
+
}
|
|
33
|
+
interface UseMediaUploadResult {
|
|
34
|
+
/** Upload one file/blob. Returns the durable identity. */
|
|
35
|
+
upload: (file: File | Blob, opts?: MediaUploadOptions) => Promise<MediaUploadResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Upload many files. Uses the client's `uploadMany` (host pre-flight /
|
|
38
|
+
* dedup dialog) when implemented; otherwise a sequential per-file loop with
|
|
39
|
+
* real per-file error messages.
|
|
40
|
+
*/
|
|
41
|
+
uploadMany: (files: File[], opts?: MediaUploadOptions) => Promise<MediaMultiUploadResult>;
|
|
42
|
+
uploading: boolean;
|
|
43
|
+
progress: MediaUploadProgress | null;
|
|
44
|
+
result: MediaUploadResult | null;
|
|
45
|
+
error: Error | null;
|
|
46
|
+
/** Live per-file tray entries for progress UI. */
|
|
47
|
+
entries: MediaUploadEntry[];
|
|
48
|
+
clearEntry: (id: string) => void;
|
|
49
|
+
reset: () => void;
|
|
50
|
+
}
|
|
51
|
+
declare function useMediaUpload(): UseMediaUploadResult;
|
|
52
|
+
|
|
53
|
+
export { type MediaUploadEntry as M, type UseMediaUploadResult as U, type MediaUploadEntryStatus as a, type MediaUploadProgress as b, useMediaUpload as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/media",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The AI Matrx media components kit: durable-ref-only renderers for images, video, audio and file thumbnails that just work with the Matrx file system — headless core hooks plus DOM bindings, wired to an injected MediaClient. A signed URL is a handoff, never an identity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @ai-matrx/media — the root types: the MediaClient port and the host ports.
|
|
3
|
-
*
|
|
4
|
-
* The package renders Matrx durable media; it NEVER talks to a network.
|
|
5
|
-
* Everything data-shaped arrives through the injected {@link MediaClient}
|
|
6
|
-
* (canonically implemented by `@ai-matrx/data/files`; any structural
|
|
7
|
-
* implementation works — the strangler step wires it over an app's existing
|
|
8
|
-
* file handler). The five file-handling laws are enforced here at the type
|
|
9
|
-
* level:
|
|
10
|
-
*
|
|
11
|
-
* 1. Never fetch a media URL yourself — the package contains zero network
|
|
12
|
-
* primitives; bytes exist only via {@link MediaClient.getBlob}.
|
|
13
|
-
* 2. Off-boundary code resolves media itself — every component takes a
|
|
14
|
-
* {@link MediaRefLike} and calls `resolve()` unconditionally. There is
|
|
15
|
-
* NO `src` prop on any component in this package.
|
|
16
|
-
* 3. A signed URL is a handoff, never an identity — the only string that
|
|
17
|
-
* reaches a media element is the branded {@link DurableSrc}, which only
|
|
18
|
-
* a MediaClient implementation can mint (via {@link mintDurableSrc}).
|
|
19
|
-
* 4. Public-facing media is persisted public, never signed —
|
|
20
|
-
* {@link MediaClient.shareableUrl} fails closed (`null` → the action
|
|
21
|
-
* renders disabled).
|
|
22
|
-
* 5. One URL contract; the engine builds URLs — zero URL construction in
|
|
23
|
-
* this package; `resolve()` is the only door.
|
|
24
|
-
*/
|
|
25
|
-
/** The canonical durable media reference. `file_id` is the identity. */
|
|
26
|
-
interface MediaRef {
|
|
27
|
-
file_id?: string | null;
|
|
28
|
-
url?: string | null;
|
|
29
|
-
mime_type?: string | null;
|
|
30
|
-
metadata?: Record<string, unknown> | null;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Identity in. A raw string is a fileId or an EXTERNAL public URL — never a
|
|
34
|
-
* signed URL (the client's `resolve()` classifies and screams on one).
|
|
35
|
-
*/
|
|
36
|
-
type MediaRefLike = MediaRef | string;
|
|
37
|
-
declare const durable: unique symbol;
|
|
38
|
-
/**
|
|
39
|
-
* A URL that has passed through `MediaClient.resolve()` — the only string
|
|
40
|
-
* type any media element in this package will bind. A caller cannot pass an
|
|
41
|
-
* expiring URL through the package's types without an explicit unsafe cast.
|
|
42
|
-
*/
|
|
43
|
-
type DurableSrc = string & {
|
|
44
|
-
readonly [durable]: true;
|
|
45
|
-
};
|
|
46
|
-
type MediaKind = "image" | "video" | "audio" | "file";
|
|
47
|
-
interface MediaResolution {
|
|
48
|
-
src: DurableSrc;
|
|
49
|
-
/** RN auth lane (`<Image source={{uri, headers}}>`); absent on web (cookie session). */
|
|
50
|
-
headers?: Record<string, string> | undefined;
|
|
51
|
-
mimeType?: string | undefined;
|
|
52
|
-
kind: MediaKind;
|
|
53
|
-
/**
|
|
54
|
-
* How pixels must travel. `"element"` (default): bind `src` directly to the
|
|
55
|
-
* media element. `"blob"`: the bytes require bearer auth the element cannot
|
|
56
|
-
* attach — consumers go through {@link MediaClient.getBlob} and render the
|
|
57
|
-
* returned object URL (the private-image lane).
|
|
58
|
-
*/
|
|
59
|
-
transport?: "element" | "blob" | undefined;
|
|
60
|
-
/**
|
|
61
|
-
* A server-rendered thumbnail for this file, when the client knows one
|
|
62
|
-
* (Matrx: `FileRecord.thumbnail_url` / asset variants). Consumed by the
|
|
63
|
-
* thumbnail tier machine.
|
|
64
|
-
*/
|
|
65
|
-
thumbnailSrc?: DurableSrc | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* True when this is a permanent public CDN URL — lets a host-supplied
|
|
68
|
-
* `ImageComponent` (e.g. a next/image adapter) take the render, and marks
|
|
69
|
-
* the URL CORS-safe for element-level `crossOrigin`.
|
|
70
|
-
*/
|
|
71
|
-
isCdn?: boolean | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* Whether a load error can be recovered by the client's ONE retry policy
|
|
74
|
-
* (session-refresh → same-URL retry). False for foreign/external URLs —
|
|
75
|
-
* a session refresh can't resurrect someone else's link rot. Default true.
|
|
76
|
-
*/
|
|
77
|
-
recoverable?: boolean | undefined;
|
|
78
|
-
}
|
|
79
|
-
/** Typed unavailable states — render-as-state, never thrown as incidents. */
|
|
80
|
-
type MediaUnavailableReason = "access_denied" | "not_found" | "deleted" | "unknown";
|
|
81
|
-
interface MediaBlobHandle {
|
|
82
|
-
/** `blob:` object URL safe for any browser API. */
|
|
83
|
-
url: string;
|
|
84
|
-
blob: Blob;
|
|
85
|
-
/** Release the handle. Implementations backed by a shared cache may no-op. */
|
|
86
|
-
release(): void;
|
|
87
|
-
}
|
|
88
|
-
interface MediaUploadOptions {
|
|
89
|
-
onProgress?: ((loaded: number, total: number) => void) | undefined;
|
|
90
|
-
fileName?: string | undefined;
|
|
91
|
-
mimeType?: string | undefined;
|
|
92
|
-
/** Host visibility vocabulary passes through structurally. */
|
|
93
|
-
visibility?: string | undefined;
|
|
94
|
-
parentFolderId?: string | null | undefined;
|
|
95
|
-
metadata?: Record<string, unknown> | undefined;
|
|
96
|
-
}
|
|
97
|
-
/** Upload → durable identity. `fileId` is required at compile time and runtime. */
|
|
98
|
-
interface MediaUploadResult {
|
|
99
|
-
fileId: string;
|
|
100
|
-
ref: MediaRef;
|
|
101
|
-
}
|
|
102
|
-
interface MediaMultiUploadResult {
|
|
103
|
-
/** File ids for every successful upload. */
|
|
104
|
-
uploaded: string[];
|
|
105
|
-
/** Per-file failure with the real backend error message. */
|
|
106
|
-
failed: Array<{
|
|
107
|
-
name: string;
|
|
108
|
-
error: string;
|
|
109
|
-
}>;
|
|
110
|
-
/** True when the host's pre-flight (e.g. a duplicate dialog) cancelled the batch. */
|
|
111
|
-
cancelled: boolean;
|
|
112
|
-
}
|
|
113
|
-
interface MediaClient {
|
|
114
|
-
/**
|
|
115
|
-
* Synchronous durable-URL build + classification. Returns `null` when the
|
|
116
|
-
* ref carries no usable identity (components render their fallback).
|
|
117
|
-
* THROWS (and screams via its own diagnostics sink) on a signed/expiring
|
|
118
|
-
* URL — the D108 guard lives behind this door.
|
|
119
|
-
*/
|
|
120
|
-
resolve(ref: MediaRefLike): MediaResolution | null;
|
|
121
|
-
/** Bytes for canvas/crossOrigin/private-pixel consumers. */
|
|
122
|
-
getBlob(ref: MediaRefLike): Promise<MediaBlobHandle>;
|
|
123
|
-
/**
|
|
124
|
-
* Session bootstrap + the ONE retry policy: refresh-then-same-URL-retry,
|
|
125
|
-
* second failure terminal. Called by the package's onError path, never
|
|
126
|
-
* re-implemented.
|
|
127
|
-
*/
|
|
128
|
-
recoverLoadError(src: DurableSrc, attempt: number): Promise<"retry" | "terminal">;
|
|
129
|
-
/** Upload → durable identity. */
|
|
130
|
-
upload(file: File | Blob, opts?: MediaUploadOptions): Promise<MediaUploadResult>;
|
|
131
|
-
/**
|
|
132
|
-
* Optional batch upload with host pre-flight (dedup dialogs etc.). When
|
|
133
|
-
* absent, `useMediaUpload().uploadMany` falls back to sequential `upload`.
|
|
134
|
-
*/
|
|
135
|
-
uploadMany?(files: File[], opts?: MediaUploadOptions): Promise<MediaMultiUploadResult>;
|
|
136
|
-
/** Share affordances — fails closed (null), never emits a signed URL. */
|
|
137
|
-
shareableUrl(ref: MediaRefLike): Promise<string | null>;
|
|
138
|
-
/** Typed unavailable states — render-as-state, never thrown as incidents. */
|
|
139
|
-
classifyError(err: unknown): MediaUnavailableReason;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Structural component slot. `(props) => unknown` keeps the root entry free
|
|
143
|
-
* of React type imports; any React function component satisfies it.
|
|
144
|
-
*/
|
|
145
|
-
type MediaSlotComponent<P> = (props: P) => unknown;
|
|
146
|
-
interface MediaImageComponentProps {
|
|
147
|
-
src: string;
|
|
148
|
-
alt: string;
|
|
149
|
-
width: number;
|
|
150
|
-
height: number;
|
|
151
|
-
className?: string | undefined;
|
|
152
|
-
onError?: ((event: unknown) => void) | undefined;
|
|
153
|
-
onClick?: ((event: unknown) => void) | undefined;
|
|
154
|
-
onKeyDown?: ((event: unknown) => void) | undefined;
|
|
155
|
-
role?: string | undefined;
|
|
156
|
-
tabIndex?: number | undefined;
|
|
157
|
-
}
|
|
158
|
-
interface PlaybackSessionRegistration {
|
|
159
|
-
isPlaying: boolean;
|
|
160
|
-
source: string;
|
|
161
|
-
label: string;
|
|
162
|
-
trackKey?: string | undefined;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* The host's audio system (exclusive-playback lock, output-sink routing).
|
|
166
|
-
* Both members are HOOKS — the package calls them unconditionally on every
|
|
167
|
-
* render of a media element, so implementations must obey the Rules of Hooks
|
|
168
|
-
* and the port object must be referentially stable.
|
|
169
|
-
*/
|
|
170
|
-
interface PlaybackSessionPort {
|
|
171
|
-
/**
|
|
172
|
-
* Returns a ref callback for the underlying media element (routes audio
|
|
173
|
-
* output / setSinkId on hosts that support it) and forwards to the caller's
|
|
174
|
-
* own element ref.
|
|
175
|
-
*/
|
|
176
|
-
useMediaElementSink(forward: ((node: unknown) => void) | {
|
|
177
|
-
current: unknown;
|
|
178
|
-
} | null | undefined): (node: unknown) => void;
|
|
179
|
-
/** Joins the host's unified playback session for the element. */
|
|
180
|
-
usePlaybackSession(registration: PlaybackSessionRegistration, elementRef: {
|
|
181
|
-
current: unknown;
|
|
182
|
-
}): void;
|
|
183
|
-
}
|
|
184
|
-
type MediaActionKind = "share" | "download" | "copy" | "open";
|
|
185
|
-
interface MediaActionContext {
|
|
186
|
-
ref: MediaRefLike;
|
|
187
|
-
resolution: MediaResolution | null;
|
|
188
|
-
fileName?: string | undefined;
|
|
189
|
-
alt?: string | undefined;
|
|
190
|
-
}
|
|
191
|
-
interface MediaSharePopoverProps {
|
|
192
|
-
context: MediaActionContext;
|
|
193
|
-
onClose: () => void;
|
|
194
|
-
}
|
|
195
|
-
type MediaActionHandler = (ctx: MediaActionContext) => Promise<void>;
|
|
196
|
-
interface MediaActionsPort {
|
|
197
|
-
/**
|
|
198
|
-
* Quick-share handler (copy a share link, open the native share sheet…).
|
|
199
|
-
* Implementations obtain URLs ONLY via `MediaClient.shareableUrl` /
|
|
200
|
-
* durable identities — never a signed URL (law 4).
|
|
201
|
-
*/
|
|
202
|
-
share?: MediaActionHandler | undefined;
|
|
203
|
-
download?: MediaActionHandler | undefined;
|
|
204
|
-
copy?: MediaActionHandler | undefined;
|
|
205
|
-
open?: MediaActionHandler | undefined;
|
|
206
|
-
/**
|
|
207
|
-
* Rich share UI body — the slot the associations package fills. When
|
|
208
|
-
* present, the shells open it instead of (or on top of) the `share`
|
|
209
|
-
* handler. Receives {@link MediaSharePopoverProps}.
|
|
210
|
-
*/
|
|
211
|
-
SharePopover?: MediaSlotComponent<MediaSharePopoverProps> | undefined;
|
|
212
|
-
}
|
|
213
|
-
interface MediaHostPorts {
|
|
214
|
-
playbackSession?: PlaybackSessionPort | undefined;
|
|
215
|
-
actions?: MediaActionsPort | undefined;
|
|
216
|
-
/** Web host may pass a next/image adapter; default is a plain `<img>`. */
|
|
217
|
-
ImageComponent?: MediaSlotComponent<MediaImageComponentProps> | undefined;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* src/core/use-media-upload.ts — the React-side upload primitive.
|
|
222
|
-
*
|
|
223
|
-
* Ported from matrx-frontend `handler/hooks/useFileUpload.ts` with two seam
|
|
224
|
-
* inversions: the transport is `MediaClient.upload` (never touched directly),
|
|
225
|
-
* and the multi-file pre-flight (SHA-256 dedup dialog) is the client's
|
|
226
|
-
* optional `uploadMany` — when absent, the batch runs sequentially through
|
|
227
|
-
* `upload` with per-file results.
|
|
228
|
-
*
|
|
229
|
-
* Additionally exposes a per-file `entries` tray state (name, size, bytes,
|
|
230
|
-
* status) so the dropzone's UploadProgressList works without any app store —
|
|
231
|
-
* the Redux `selectVisibleUploads` coupling of the original, inverted.
|
|
232
|
-
*/
|
|
233
|
-
|
|
234
|
-
interface MediaUploadProgress {
|
|
235
|
-
loaded: number;
|
|
236
|
-
total: number;
|
|
237
|
-
ratio: number;
|
|
238
|
-
}
|
|
239
|
-
type MediaUploadEntryStatus = "uploading" | "success" | "error";
|
|
240
|
-
interface MediaUploadEntry {
|
|
241
|
-
id: string;
|
|
242
|
-
fileName: string;
|
|
243
|
-
fileSize: number;
|
|
244
|
-
bytesUploaded: number;
|
|
245
|
-
status: MediaUploadEntryStatus;
|
|
246
|
-
error: string | null;
|
|
247
|
-
fileId: string | null;
|
|
248
|
-
completedAt: number | null;
|
|
249
|
-
}
|
|
250
|
-
interface UseMediaUploadResult {
|
|
251
|
-
/** Upload one file/blob. Returns the durable identity. */
|
|
252
|
-
upload: (file: File | Blob, opts?: MediaUploadOptions) => Promise<MediaUploadResult>;
|
|
253
|
-
/**
|
|
254
|
-
* Upload many files. Uses the client's `uploadMany` (host pre-flight /
|
|
255
|
-
* dedup dialog) when implemented; otherwise a sequential per-file loop with
|
|
256
|
-
* real per-file error messages.
|
|
257
|
-
*/
|
|
258
|
-
uploadMany: (files: File[], opts?: MediaUploadOptions) => Promise<MediaMultiUploadResult>;
|
|
259
|
-
uploading: boolean;
|
|
260
|
-
progress: MediaUploadProgress | null;
|
|
261
|
-
result: MediaUploadResult | null;
|
|
262
|
-
error: Error | null;
|
|
263
|
-
/** Live per-file tray entries for progress UI. */
|
|
264
|
-
entries: MediaUploadEntry[];
|
|
265
|
-
clearEntry: (id: string) => void;
|
|
266
|
-
reset: () => void;
|
|
267
|
-
}
|
|
268
|
-
declare function useMediaUpload(): UseMediaUploadResult;
|
|
269
|
-
|
|
270
|
-
export { type DurableSrc as D, type MediaClient as M, type UseMediaUploadResult as U, type MediaHostPorts as a, type MediaRefLike as b, type MediaResolution as c, type MediaUnavailableReason as d, type MediaActionContext as e, type MediaActionKind as f, type MediaSlotComponent as g, type MediaSharePopoverProps as h, type MediaUploadEntry as i, type MediaUploadEntryStatus as j, type MediaUploadProgress as k, type MediaUploadOptions as l, useMediaUpload as u };
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @ai-matrx/media — the root types: the MediaClient port and the host ports.
|
|
3
|
-
*
|
|
4
|
-
* The package renders Matrx durable media; it NEVER talks to a network.
|
|
5
|
-
* Everything data-shaped arrives through the injected {@link MediaClient}
|
|
6
|
-
* (canonically implemented by `@ai-matrx/data/files`; any structural
|
|
7
|
-
* implementation works — the strangler step wires it over an app's existing
|
|
8
|
-
* file handler). The five file-handling laws are enforced here at the type
|
|
9
|
-
* level:
|
|
10
|
-
*
|
|
11
|
-
* 1. Never fetch a media URL yourself — the package contains zero network
|
|
12
|
-
* primitives; bytes exist only via {@link MediaClient.getBlob}.
|
|
13
|
-
* 2. Off-boundary code resolves media itself — every component takes a
|
|
14
|
-
* {@link MediaRefLike} and calls `resolve()` unconditionally. There is
|
|
15
|
-
* NO `src` prop on any component in this package.
|
|
16
|
-
* 3. A signed URL is a handoff, never an identity — the only string that
|
|
17
|
-
* reaches a media element is the branded {@link DurableSrc}, which only
|
|
18
|
-
* a MediaClient implementation can mint (via {@link mintDurableSrc}).
|
|
19
|
-
* 4. Public-facing media is persisted public, never signed —
|
|
20
|
-
* {@link MediaClient.shareableUrl} fails closed (`null` → the action
|
|
21
|
-
* renders disabled).
|
|
22
|
-
* 5. One URL contract; the engine builds URLs — zero URL construction in
|
|
23
|
-
* this package; `resolve()` is the only door.
|
|
24
|
-
*/
|
|
25
|
-
/** The canonical durable media reference. `file_id` is the identity. */
|
|
26
|
-
interface MediaRef {
|
|
27
|
-
file_id?: string | null;
|
|
28
|
-
url?: string | null;
|
|
29
|
-
mime_type?: string | null;
|
|
30
|
-
metadata?: Record<string, unknown> | null;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Identity in. A raw string is a fileId or an EXTERNAL public URL — never a
|
|
34
|
-
* signed URL (the client's `resolve()` classifies and screams on one).
|
|
35
|
-
*/
|
|
36
|
-
type MediaRefLike = MediaRef | string;
|
|
37
|
-
declare const durable: unique symbol;
|
|
38
|
-
/**
|
|
39
|
-
* A URL that has passed through `MediaClient.resolve()` — the only string
|
|
40
|
-
* type any media element in this package will bind. A caller cannot pass an
|
|
41
|
-
* expiring URL through the package's types without an explicit unsafe cast.
|
|
42
|
-
*/
|
|
43
|
-
type DurableSrc = string & {
|
|
44
|
-
readonly [durable]: true;
|
|
45
|
-
};
|
|
46
|
-
type MediaKind = "image" | "video" | "audio" | "file";
|
|
47
|
-
interface MediaResolution {
|
|
48
|
-
src: DurableSrc;
|
|
49
|
-
/** RN auth lane (`<Image source={{uri, headers}}>`); absent on web (cookie session). */
|
|
50
|
-
headers?: Record<string, string> | undefined;
|
|
51
|
-
mimeType?: string | undefined;
|
|
52
|
-
kind: MediaKind;
|
|
53
|
-
/**
|
|
54
|
-
* How pixels must travel. `"element"` (default): bind `src` directly to the
|
|
55
|
-
* media element. `"blob"`: the bytes require bearer auth the element cannot
|
|
56
|
-
* attach — consumers go through {@link MediaClient.getBlob} and render the
|
|
57
|
-
* returned object URL (the private-image lane).
|
|
58
|
-
*/
|
|
59
|
-
transport?: "element" | "blob" | undefined;
|
|
60
|
-
/**
|
|
61
|
-
* A server-rendered thumbnail for this file, when the client knows one
|
|
62
|
-
* (Matrx: `FileRecord.thumbnail_url` / asset variants). Consumed by the
|
|
63
|
-
* thumbnail tier machine.
|
|
64
|
-
*/
|
|
65
|
-
thumbnailSrc?: DurableSrc | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* True when this is a permanent public CDN URL — lets a host-supplied
|
|
68
|
-
* `ImageComponent` (e.g. a next/image adapter) take the render, and marks
|
|
69
|
-
* the URL CORS-safe for element-level `crossOrigin`.
|
|
70
|
-
*/
|
|
71
|
-
isCdn?: boolean | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* Whether a load error can be recovered by the client's ONE retry policy
|
|
74
|
-
* (session-refresh → same-URL retry). False for foreign/external URLs —
|
|
75
|
-
* a session refresh can't resurrect someone else's link rot. Default true.
|
|
76
|
-
*/
|
|
77
|
-
recoverable?: boolean | undefined;
|
|
78
|
-
}
|
|
79
|
-
/** Typed unavailable states — render-as-state, never thrown as incidents. */
|
|
80
|
-
type MediaUnavailableReason = "access_denied" | "not_found" | "deleted" | "unknown";
|
|
81
|
-
interface MediaBlobHandle {
|
|
82
|
-
/** `blob:` object URL safe for any browser API. */
|
|
83
|
-
url: string;
|
|
84
|
-
blob: Blob;
|
|
85
|
-
/** Release the handle. Implementations backed by a shared cache may no-op. */
|
|
86
|
-
release(): void;
|
|
87
|
-
}
|
|
88
|
-
interface MediaUploadOptions {
|
|
89
|
-
onProgress?: ((loaded: number, total: number) => void) | undefined;
|
|
90
|
-
fileName?: string | undefined;
|
|
91
|
-
mimeType?: string | undefined;
|
|
92
|
-
/** Host visibility vocabulary passes through structurally. */
|
|
93
|
-
visibility?: string | undefined;
|
|
94
|
-
parentFolderId?: string | null | undefined;
|
|
95
|
-
metadata?: Record<string, unknown> | undefined;
|
|
96
|
-
}
|
|
97
|
-
/** Upload → durable identity. `fileId` is required at compile time and runtime. */
|
|
98
|
-
interface MediaUploadResult {
|
|
99
|
-
fileId: string;
|
|
100
|
-
ref: MediaRef;
|
|
101
|
-
}
|
|
102
|
-
interface MediaMultiUploadResult {
|
|
103
|
-
/** File ids for every successful upload. */
|
|
104
|
-
uploaded: string[];
|
|
105
|
-
/** Per-file failure with the real backend error message. */
|
|
106
|
-
failed: Array<{
|
|
107
|
-
name: string;
|
|
108
|
-
error: string;
|
|
109
|
-
}>;
|
|
110
|
-
/** True when the host's pre-flight (e.g. a duplicate dialog) cancelled the batch. */
|
|
111
|
-
cancelled: boolean;
|
|
112
|
-
}
|
|
113
|
-
interface MediaClient {
|
|
114
|
-
/**
|
|
115
|
-
* Synchronous durable-URL build + classification. Returns `null` when the
|
|
116
|
-
* ref carries no usable identity (components render their fallback).
|
|
117
|
-
* THROWS (and screams via its own diagnostics sink) on a signed/expiring
|
|
118
|
-
* URL — the D108 guard lives behind this door.
|
|
119
|
-
*/
|
|
120
|
-
resolve(ref: MediaRefLike): MediaResolution | null;
|
|
121
|
-
/** Bytes for canvas/crossOrigin/private-pixel consumers. */
|
|
122
|
-
getBlob(ref: MediaRefLike): Promise<MediaBlobHandle>;
|
|
123
|
-
/**
|
|
124
|
-
* Session bootstrap + the ONE retry policy: refresh-then-same-URL-retry,
|
|
125
|
-
* second failure terminal. Called by the package's onError path, never
|
|
126
|
-
* re-implemented.
|
|
127
|
-
*/
|
|
128
|
-
recoverLoadError(src: DurableSrc, attempt: number): Promise<"retry" | "terminal">;
|
|
129
|
-
/** Upload → durable identity. */
|
|
130
|
-
upload(file: File | Blob, opts?: MediaUploadOptions): Promise<MediaUploadResult>;
|
|
131
|
-
/**
|
|
132
|
-
* Optional batch upload with host pre-flight (dedup dialogs etc.). When
|
|
133
|
-
* absent, `useMediaUpload().uploadMany` falls back to sequential `upload`.
|
|
134
|
-
*/
|
|
135
|
-
uploadMany?(files: File[], opts?: MediaUploadOptions): Promise<MediaMultiUploadResult>;
|
|
136
|
-
/** Share affordances — fails closed (null), never emits a signed URL. */
|
|
137
|
-
shareableUrl(ref: MediaRefLike): Promise<string | null>;
|
|
138
|
-
/** Typed unavailable states — render-as-state, never thrown as incidents. */
|
|
139
|
-
classifyError(err: unknown): MediaUnavailableReason;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Structural component slot. `(props) => unknown` keeps the root entry free
|
|
143
|
-
* of React type imports; any React function component satisfies it.
|
|
144
|
-
*/
|
|
145
|
-
type MediaSlotComponent<P> = (props: P) => unknown;
|
|
146
|
-
interface MediaImageComponentProps {
|
|
147
|
-
src: string;
|
|
148
|
-
alt: string;
|
|
149
|
-
width: number;
|
|
150
|
-
height: number;
|
|
151
|
-
className?: string | undefined;
|
|
152
|
-
onError?: ((event: unknown) => void) | undefined;
|
|
153
|
-
onClick?: ((event: unknown) => void) | undefined;
|
|
154
|
-
onKeyDown?: ((event: unknown) => void) | undefined;
|
|
155
|
-
role?: string | undefined;
|
|
156
|
-
tabIndex?: number | undefined;
|
|
157
|
-
}
|
|
158
|
-
interface PlaybackSessionRegistration {
|
|
159
|
-
isPlaying: boolean;
|
|
160
|
-
source: string;
|
|
161
|
-
label: string;
|
|
162
|
-
trackKey?: string | undefined;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* The host's audio system (exclusive-playback lock, output-sink routing).
|
|
166
|
-
* Both members are HOOKS — the package calls them unconditionally on every
|
|
167
|
-
* render of a media element, so implementations must obey the Rules of Hooks
|
|
168
|
-
* and the port object must be referentially stable.
|
|
169
|
-
*/
|
|
170
|
-
interface PlaybackSessionPort {
|
|
171
|
-
/**
|
|
172
|
-
* Returns a ref callback for the underlying media element (routes audio
|
|
173
|
-
* output / setSinkId on hosts that support it) and forwards to the caller's
|
|
174
|
-
* own element ref.
|
|
175
|
-
*/
|
|
176
|
-
useMediaElementSink(forward: ((node: unknown) => void) | {
|
|
177
|
-
current: unknown;
|
|
178
|
-
} | null | undefined): (node: unknown) => void;
|
|
179
|
-
/** Joins the host's unified playback session for the element. */
|
|
180
|
-
usePlaybackSession(registration: PlaybackSessionRegistration, elementRef: {
|
|
181
|
-
current: unknown;
|
|
182
|
-
}): void;
|
|
183
|
-
}
|
|
184
|
-
type MediaActionKind = "share" | "download" | "copy" | "open";
|
|
185
|
-
interface MediaActionContext {
|
|
186
|
-
ref: MediaRefLike;
|
|
187
|
-
resolution: MediaResolution | null;
|
|
188
|
-
fileName?: string | undefined;
|
|
189
|
-
alt?: string | undefined;
|
|
190
|
-
}
|
|
191
|
-
interface MediaSharePopoverProps {
|
|
192
|
-
context: MediaActionContext;
|
|
193
|
-
onClose: () => void;
|
|
194
|
-
}
|
|
195
|
-
type MediaActionHandler = (ctx: MediaActionContext) => Promise<void>;
|
|
196
|
-
interface MediaActionsPort {
|
|
197
|
-
/**
|
|
198
|
-
* Quick-share handler (copy a share link, open the native share sheet…).
|
|
199
|
-
* Implementations obtain URLs ONLY via `MediaClient.shareableUrl` /
|
|
200
|
-
* durable identities — never a signed URL (law 4).
|
|
201
|
-
*/
|
|
202
|
-
share?: MediaActionHandler | undefined;
|
|
203
|
-
download?: MediaActionHandler | undefined;
|
|
204
|
-
copy?: MediaActionHandler | undefined;
|
|
205
|
-
open?: MediaActionHandler | undefined;
|
|
206
|
-
/**
|
|
207
|
-
* Rich share UI body — the slot the associations package fills. When
|
|
208
|
-
* present, the shells open it instead of (or on top of) the `share`
|
|
209
|
-
* handler. Receives {@link MediaSharePopoverProps}.
|
|
210
|
-
*/
|
|
211
|
-
SharePopover?: MediaSlotComponent<MediaSharePopoverProps> | undefined;
|
|
212
|
-
}
|
|
213
|
-
interface MediaHostPorts {
|
|
214
|
-
playbackSession?: PlaybackSessionPort | undefined;
|
|
215
|
-
actions?: MediaActionsPort | undefined;
|
|
216
|
-
/** Web host may pass a next/image adapter; default is a plain `<img>`. */
|
|
217
|
-
ImageComponent?: MediaSlotComponent<MediaImageComponentProps> | undefined;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* src/core/use-media-upload.ts — the React-side upload primitive.
|
|
222
|
-
*
|
|
223
|
-
* Ported from matrx-frontend `handler/hooks/useFileUpload.ts` with two seam
|
|
224
|
-
* inversions: the transport is `MediaClient.upload` (never touched directly),
|
|
225
|
-
* and the multi-file pre-flight (SHA-256 dedup dialog) is the client's
|
|
226
|
-
* optional `uploadMany` — when absent, the batch runs sequentially through
|
|
227
|
-
* `upload` with per-file results.
|
|
228
|
-
*
|
|
229
|
-
* Additionally exposes a per-file `entries` tray state (name, size, bytes,
|
|
230
|
-
* status) so the dropzone's UploadProgressList works without any app store —
|
|
231
|
-
* the Redux `selectVisibleUploads` coupling of the original, inverted.
|
|
232
|
-
*/
|
|
233
|
-
|
|
234
|
-
interface MediaUploadProgress {
|
|
235
|
-
loaded: number;
|
|
236
|
-
total: number;
|
|
237
|
-
ratio: number;
|
|
238
|
-
}
|
|
239
|
-
type MediaUploadEntryStatus = "uploading" | "success" | "error";
|
|
240
|
-
interface MediaUploadEntry {
|
|
241
|
-
id: string;
|
|
242
|
-
fileName: string;
|
|
243
|
-
fileSize: number;
|
|
244
|
-
bytesUploaded: number;
|
|
245
|
-
status: MediaUploadEntryStatus;
|
|
246
|
-
error: string | null;
|
|
247
|
-
fileId: string | null;
|
|
248
|
-
completedAt: number | null;
|
|
249
|
-
}
|
|
250
|
-
interface UseMediaUploadResult {
|
|
251
|
-
/** Upload one file/blob. Returns the durable identity. */
|
|
252
|
-
upload: (file: File | Blob, opts?: MediaUploadOptions) => Promise<MediaUploadResult>;
|
|
253
|
-
/**
|
|
254
|
-
* Upload many files. Uses the client's `uploadMany` (host pre-flight /
|
|
255
|
-
* dedup dialog) when implemented; otherwise a sequential per-file loop with
|
|
256
|
-
* real per-file error messages.
|
|
257
|
-
*/
|
|
258
|
-
uploadMany: (files: File[], opts?: MediaUploadOptions) => Promise<MediaMultiUploadResult>;
|
|
259
|
-
uploading: boolean;
|
|
260
|
-
progress: MediaUploadProgress | null;
|
|
261
|
-
result: MediaUploadResult | null;
|
|
262
|
-
error: Error | null;
|
|
263
|
-
/** Live per-file tray entries for progress UI. */
|
|
264
|
-
entries: MediaUploadEntry[];
|
|
265
|
-
clearEntry: (id: string) => void;
|
|
266
|
-
reset: () => void;
|
|
267
|
-
}
|
|
268
|
-
declare function useMediaUpload(): UseMediaUploadResult;
|
|
269
|
-
|
|
270
|
-
export { type DurableSrc as D, type MediaClient as M, type UseMediaUploadResult as U, type MediaHostPorts as a, type MediaRefLike as b, type MediaResolution as c, type MediaUnavailableReason as d, type MediaActionContext as e, type MediaActionKind as f, type MediaSlotComponent as g, type MediaSharePopoverProps as h, type MediaUploadEntry as i, type MediaUploadEntryStatus as j, type MediaUploadProgress as k, type MediaUploadOptions as l, useMediaUpload as u };
|