@hoardodile/sdk-types 0.0.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/LICENSE +18 -0
- package/README.md +59 -0
- package/dist/image-variant.d.ts +90 -0
- package/dist/image-variant.js +115 -0
- package/dist/image-variant.js.map +1 -0
- package/dist/index.d.ts +772 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest-Dk6_xyNy.d.ts +204 -0
- package/dist/media-exts.d.ts +92 -0
- package/dist/media-exts.js +160 -0
- package/dist/media-exts.js.map +1 -0
- package/dist/plugin-asset-limits.d.ts +16 -0
- package/dist/plugin-asset-limits.js +13 -0
- package/dist/plugin-asset-limits.js.map +1 -0
- package/dist/plugin-capabilities.d.ts +64 -0
- package/dist/plugin-capabilities.js +37 -0
- package/dist/plugin-capabilities.js.map +1 -0
- package/dist/plugin.d.ts +49 -0
- package/dist/plugin.js +12 -0
- package/dist/plugin.js.map +1 -0
- package/dist/resource.d.ts +26 -0
- package/dist/resource.js +9 -0
- package/dist/resource.js.map +1 -0
- package/dist/result.d.ts +47 -0
- package/dist/result.js +20 -0
- package/dist/result.js.map +1 -0
- package/dist/schema.d.ts +29 -0
- package/dist/schema.js +124 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +67 -0
- package/dist/template.js +137 -0
- package/dist/template.js.map +1 -0
- package/dist/text-limits.d.ts +11 -0
- package/dist/text-limits.js +7 -0
- package/dist/text-limits.js.map +1 -0
- package/package.json +102 -0
- package/src/file-list.ts +14 -0
- package/src/image-variant.test.ts +140 -0
- package/src/image-variant.ts +234 -0
- package/src/index.ts +115 -0
- package/src/manifest.ts +186 -0
- package/src/media-exts.ts +245 -0
- package/src/plugin-asset-limits.ts +23 -0
- package/src/plugin-asset.ts +127 -0
- package/src/plugin-capabilities.ts +91 -0
- package/src/plugin-definition.test.ts +117 -0
- package/src/plugin-definition.ts +902 -0
- package/src/plugin.ts +54 -0
- package/src/read-range.ts +12 -0
- package/src/resource.ts +28 -0
- package/src/result.test.ts +64 -0
- package/src/result.ts +73 -0
- package/src/schema.ts +29 -0
- package/src/template.test.ts +116 -0
- package/src/template.ts +199 -0
- package/src/text-limits.ts +11 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin manifest UUID (v4). Generated once when scaffolding a plugin
|
|
5
|
+
* (e.g. `crypto.randomUUID()`) and never reused across plugins — the
|
|
6
|
+
* server keys installed plugins by this id.
|
|
7
|
+
*/
|
|
8
|
+
declare const pluginManifestId: z.ZodString;
|
|
9
|
+
type PluginManifestId = z.infer<typeof pluginManifestId>;
|
|
10
|
+
/**
|
|
11
|
+
* Declared plugin capabilities. Each flag gates the corresponding API
|
|
12
|
+
* surface: a plugin without `danmaku` gets no danmaku methods and the
|
|
13
|
+
* host enforces the permission at the capability guard, so a manifest
|
|
14
|
+
* that does not declare a capability cannot call it.
|
|
15
|
+
*/
|
|
16
|
+
declare const pluginPermissions: z.ZodObject<{
|
|
17
|
+
sourceMeta: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
searchMeta: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
danmaku: z.ZodDefault<z.ZodBoolean>;
|
|
20
|
+
message: z.ZodDefault<z.ZodBoolean>;
|
|
21
|
+
imageHashes: z.ZodDefault<z.ZodBoolean>;
|
|
22
|
+
container: z.ZodDefault<z.ZodBoolean>;
|
|
23
|
+
download: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
type PluginPermissions = z.infer<typeof pluginPermissions>;
|
|
26
|
+
/**
|
|
27
|
+
* Label key → locale table: `{ "cover.open": { "en": "Open", "zh-CN": "打开" } }`.
|
|
28
|
+
* The host's template engine resolves `t('cover.open')` against the
|
|
29
|
+
* resource's locale from this map.
|
|
30
|
+
*/
|
|
31
|
+
declare const localeString: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
32
|
+
/** Icon reference: an asset path inside the plugin zip (`assets/icon.svg`). */
|
|
33
|
+
declare const iconRef: z.ZodString;
|
|
34
|
+
/**
|
|
35
|
+
* Corner template slots for one content kind. Templates are rendered by
|
|
36
|
+
* the host's template engine over the resource's file list; supported
|
|
37
|
+
* directives include `{{data.field}}`, `{{duration(ms)}}`, `{{inc(n)}}`
|
|
38
|
+
* and `{{t('key')}}`.
|
|
39
|
+
*/
|
|
40
|
+
declare const coverKindUi: z.ZodObject<{
|
|
41
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
43
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
44
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
/**
|
|
47
|
+
* Cover templates per content kind. A plugin declares the kinds it can
|
|
48
|
+
* produce; the host picks the block matching the resource's cover type
|
|
49
|
+
* (`image`/`video`/`audio`/`default`) and renders each corner as
|
|
50
|
+
* specified, or falls back to the default cover when no block matches.
|
|
51
|
+
*/
|
|
52
|
+
declare const coverKindUiMap: z.ZodObject<{
|
|
53
|
+
image: z.ZodOptional<z.ZodObject<{
|
|
54
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
55
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
56
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
57
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
58
|
+
}, z.core.$strip>>;
|
|
59
|
+
video: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
62
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
63
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
64
|
+
}, z.core.$strip>>;
|
|
65
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
66
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
67
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
68
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
69
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
70
|
+
}, z.core.$strip>>;
|
|
71
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
72
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
73
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
74
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
75
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
|
+
}, z.core.$strip>>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
/**
|
|
79
|
+
* A search facet kind: a named dimension with an icon, rendered as a
|
|
80
|
+
* facet group in the host's search UI. `key` becomes the facet key in
|
|
81
|
+
* the search metadata the plugin produces.
|
|
82
|
+
*/
|
|
83
|
+
declare const searchKind: z.ZodObject<{
|
|
84
|
+
key: z.ZodString;
|
|
85
|
+
label: z.ZodString;
|
|
86
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
type SearchKind = z.infer<typeof searchKind>;
|
|
89
|
+
/**
|
|
90
|
+
* Manifest-declared UI preferences. These shape how the host app
|
|
91
|
+
* presents the plugin's iframe without the plugin shipping any host
|
|
92
|
+
* integration code.
|
|
93
|
+
*/
|
|
94
|
+
declare const pluginManifestUi: z.ZodObject<{
|
|
95
|
+
height: z.ZodOptional<z.ZodString>;
|
|
96
|
+
aspect: z.ZodOptional<z.ZodString>;
|
|
97
|
+
card: z.ZodOptional<z.ZodObject<{
|
|
98
|
+
image: z.ZodOptional<z.ZodObject<{
|
|
99
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
100
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
101
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
102
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
103
|
+
}, z.core.$strip>>;
|
|
104
|
+
video: z.ZodOptional<z.ZodObject<{
|
|
105
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
106
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
107
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
108
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
111
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
112
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
113
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
114
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
117
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
118
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
119
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
120
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
121
|
+
}, z.core.$strip>>;
|
|
122
|
+
}, z.core.$strip>>;
|
|
123
|
+
search: z.ZodOptional<z.ZodObject<{
|
|
124
|
+
kinds: z.ZodArray<z.ZodObject<{
|
|
125
|
+
key: z.ZodString;
|
|
126
|
+
label: z.ZodString;
|
|
127
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
message: z.ZodOptional<z.ZodObject<{
|
|
131
|
+
anchor: z.ZodOptional<z.ZodString>;
|
|
132
|
+
}, z.core.$strip>>;
|
|
133
|
+
inheritFont: z.ZodOptional<z.ZodBoolean>;
|
|
134
|
+
}, z.core.$strip>;
|
|
135
|
+
type PluginManifestUi = z.infer<typeof pluginManifestUi>;
|
|
136
|
+
type CoverKindUi = z.infer<typeof coverKindUi>;
|
|
137
|
+
type CoverKindUiMap = z.infer<typeof coverKindUiMap>;
|
|
138
|
+
/**
|
|
139
|
+
* The plugin manifest contract — the single schema validated everywhere
|
|
140
|
+
* via its parse: the server at install time, the build CLI, and the
|
|
141
|
+
* workbench. A manifest lives at the zip root of a built plugin next to
|
|
142
|
+
* `main.js` and `index.html`.
|
|
143
|
+
*/
|
|
144
|
+
declare const pluginManifest: z.ZodObject<{
|
|
145
|
+
id: z.ZodString;
|
|
146
|
+
name: z.ZodString;
|
|
147
|
+
description: z.ZodString;
|
|
148
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
149
|
+
version: z.ZodString;
|
|
150
|
+
permissions: z.ZodObject<{
|
|
151
|
+
sourceMeta: z.ZodDefault<z.ZodBoolean>;
|
|
152
|
+
searchMeta: z.ZodDefault<z.ZodBoolean>;
|
|
153
|
+
danmaku: z.ZodDefault<z.ZodBoolean>;
|
|
154
|
+
message: z.ZodDefault<z.ZodBoolean>;
|
|
155
|
+
imageHashes: z.ZodDefault<z.ZodBoolean>;
|
|
156
|
+
container: z.ZodDefault<z.ZodBoolean>;
|
|
157
|
+
download: z.ZodDefault<z.ZodBoolean>;
|
|
158
|
+
}, z.core.$strip>;
|
|
159
|
+
i18n: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
160
|
+
ui: z.ZodOptional<z.ZodObject<{
|
|
161
|
+
height: z.ZodOptional<z.ZodString>;
|
|
162
|
+
aspect: z.ZodOptional<z.ZodString>;
|
|
163
|
+
card: z.ZodOptional<z.ZodObject<{
|
|
164
|
+
image: z.ZodOptional<z.ZodObject<{
|
|
165
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
166
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
167
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
168
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
169
|
+
}, z.core.$strip>>;
|
|
170
|
+
video: z.ZodOptional<z.ZodObject<{
|
|
171
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
172
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
173
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
174
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
175
|
+
}, z.core.$strip>>;
|
|
176
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
177
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
178
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
179
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
180
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
181
|
+
}, z.core.$strip>>;
|
|
182
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
183
|
+
tl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
184
|
+
tr: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
185
|
+
bl: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
186
|
+
br: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
187
|
+
}, z.core.$strip>>;
|
|
188
|
+
}, z.core.$strip>>;
|
|
189
|
+
search: z.ZodOptional<z.ZodObject<{
|
|
190
|
+
kinds: z.ZodArray<z.ZodObject<{
|
|
191
|
+
key: z.ZodString;
|
|
192
|
+
label: z.ZodString;
|
|
193
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
194
|
+
}, z.core.$strip>>;
|
|
195
|
+
}, z.core.$strip>>;
|
|
196
|
+
message: z.ZodOptional<z.ZodObject<{
|
|
197
|
+
anchor: z.ZodOptional<z.ZodString>;
|
|
198
|
+
}, z.core.$strip>>;
|
|
199
|
+
inheritFont: z.ZodOptional<z.ZodBoolean>;
|
|
200
|
+
}, z.core.$strip>>;
|
|
201
|
+
}, z.core.$strip>;
|
|
202
|
+
type PluginManifest = z.infer<typeof pluginManifest>;
|
|
203
|
+
|
|
204
|
+
export { type CoverKindUi as C, type PluginManifest as P, type SearchKind as S, type CoverKindUiMap as a, type PluginManifestId as b, type PluginManifestUi as c, type PluginPermissions as d, pluginManifestId as e, pluginManifestUi as f, pluginPermissions as g, iconRef as i, localeString as l, pluginManifest as p, searchKind as s };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical media-type knowledge: the extension sets, the extension →
|
|
3
|
+
* MIME table and the MIME → media-kind mapping shared by content
|
|
4
|
+
* plugins, the runtime host's sniffer and the server's classification
|
|
5
|
+
* pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Extensions are a **hint**, never the verdict: `ResourceAPI.sniff`
|
|
8
|
+
* reads the file's magic bytes and only falls back to the tables here
|
|
9
|
+
* when the content carries no recognizable signature (text formats).
|
|
10
|
+
* The sets below therefore answer "which extensions do we expect to
|
|
11
|
+
* decode", not "what is this file".
|
|
12
|
+
*
|
|
13
|
+
* Lower-case, with leading dot — match the output of
|
|
14
|
+
* `path.extname(name).toLowerCase()`.
|
|
15
|
+
*
|
|
16
|
+
* Adding a new extension here widens classification everywhere at
|
|
17
|
+
* once. Before adding, verify:
|
|
18
|
+
* - sharp can extract width/height (image)
|
|
19
|
+
* - ffprobe can read width/height/duration (video)
|
|
20
|
+
* - ffprobe can read the stream/format metadata (audio), and the
|
|
21
|
+
* extension has an entry in `AUDIO_FFMPEG_INPUT_FORMAT`
|
|
22
|
+
* - the extension has an entry in {@link EXT_MIME}
|
|
23
|
+
* - the gallery plugin's transcode-required set reflects the format
|
|
24
|
+
* (browser-renderable originals stay native; HEIC/TIFF need the
|
|
25
|
+
* sharp preview pipeline)
|
|
26
|
+
*/
|
|
27
|
+
declare const IMAGE_EXTS: ReadonlySet<string>;
|
|
28
|
+
declare const VIDEO_EXTS: ReadonlySet<string>;
|
|
29
|
+
declare const AUDIO_EXTS: ReadonlySet<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Video containers ffmpeg can demux from a forward-only pipe (matroska,
|
|
32
|
+
* avi). ISO-BMFF files (.mp4/.mov/.m4v) keep their moov index at the end
|
|
33
|
+
* of the file, so a stream attempt on a zip-entry source is guaranteed to
|
|
34
|
+
* fail after burning a full probesize read — consumers (thumb pipeline,
|
|
35
|
+
* cover probing) send those straight to the materialized entry instead.
|
|
36
|
+
*/
|
|
37
|
+
declare const STREAMABLE_VIDEO_EXTS: ReadonlySet<string>;
|
|
38
|
+
/**
|
|
39
|
+
* ffmpeg `-f` container name for piped audio bytes (no filename hint).
|
|
40
|
+
* `.opus` files are Ogg containers; `.m4a` is ISO-BMFF, demuxed by the
|
|
41
|
+
* mp4 demuxer.
|
|
42
|
+
*/
|
|
43
|
+
declare const AUDIO_FFMPEG_INPUT_FORMAT: Readonly<Record<string, string>>;
|
|
44
|
+
/**
|
|
45
|
+
* ffmpeg `-f` container name keyed by **sniffed MIME type**, for piped
|
|
46
|
+
* sources that have no filename ffprobe could key off. Content-derived
|
|
47
|
+
* routing is what lets a mislabelled file still demux correctly, so
|
|
48
|
+
* this table — not the extension one — drives `ResourceAPI.probe`.
|
|
49
|
+
*
|
|
50
|
+
* Several spellings map to the same container because magic-byte
|
|
51
|
+
* matchers and IANA disagree on the canonical name (`audio/wav` vs
|
|
52
|
+
* `audio/x-wav`, `video/vnd.avi` vs `video/x-msvideo`).
|
|
53
|
+
*/
|
|
54
|
+
declare const MIME_FFMPEG_INPUT_FORMAT: Readonly<Record<string, string>>;
|
|
55
|
+
/**
|
|
56
|
+
* The audio mirror of {@link STREAMABLE_VIDEO_EXTS}: containers whose
|
|
57
|
+
* headers lead the file, so ffmpeg/ffprobe can demux them from a
|
|
58
|
+
* forward-only pipe. `.m4a` is ISO-BMFF with a trailing moov index, so
|
|
59
|
+
* it must be probed from a materialized (seekable) entry.
|
|
60
|
+
*/
|
|
61
|
+
declare const STREAMABLE_AUDIO_EXTS: ReadonlySet<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Media families a file can belong to. `other` covers everything the
|
|
64
|
+
* media pipeline does not decode (text, documents, archives, ...) — it
|
|
65
|
+
* is a real answer, not a failure.
|
|
66
|
+
*/
|
|
67
|
+
declare const MEDIA_KINDS: readonly ["image", "video", "audio", "other"];
|
|
68
|
+
type MediaKind = (typeof MEDIA_KINDS)[number];
|
|
69
|
+
/**
|
|
70
|
+
* Extension → canonical MIME type. Used as the *fallback* branch of
|
|
71
|
+
* content sniffing: magic-byte detection covers binary media, while
|
|
72
|
+
* text-based formats (`.txt`, `.md`, `.csv`, subtitles, ...) carry no
|
|
73
|
+
* signature and can only be named by their extension.
|
|
74
|
+
*/
|
|
75
|
+
declare const EXT_MIME: Readonly<Record<string, string>>;
|
|
76
|
+
/**
|
|
77
|
+
* Container MIME types whose top-level type does not describe the
|
|
78
|
+
* payload. Ogg and Matroska carry audio *or* video, and `application/*`
|
|
79
|
+
* says nothing either way — the values here are the common case, and
|
|
80
|
+
* `ResourceAPI.probe` overrides them with the stream layout ffprobe
|
|
81
|
+
* actually reports.
|
|
82
|
+
*/
|
|
83
|
+
declare const MIME_KIND_OVERRIDES: Readonly<Record<string, MediaKind>>;
|
|
84
|
+
/**
|
|
85
|
+
* Media family of a MIME type: the override table first, then the
|
|
86
|
+
* top-level type. Never throws — unknown types are `other`.
|
|
87
|
+
*/
|
|
88
|
+
declare function mimeToKind(mime: string): MediaKind;
|
|
89
|
+
/** Canonical MIME type for an extension (leading dot), or `undefined`. */
|
|
90
|
+
declare function extToMime(ext: string): string | undefined;
|
|
91
|
+
|
|
92
|
+
export { AUDIO_EXTS, AUDIO_FFMPEG_INPUT_FORMAT, EXT_MIME, IMAGE_EXTS, MEDIA_KINDS, MIME_FFMPEG_INPUT_FORMAT, MIME_KIND_OVERRIDES, type MediaKind, STREAMABLE_AUDIO_EXTS, STREAMABLE_VIDEO_EXTS, VIDEO_EXTS, extToMime, mimeToKind };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/media-exts.ts
|
|
2
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([
|
|
3
|
+
".jpg",
|
|
4
|
+
".jpeg",
|
|
5
|
+
".png",
|
|
6
|
+
".webp",
|
|
7
|
+
".gif",
|
|
8
|
+
".bmp",
|
|
9
|
+
".avif",
|
|
10
|
+
".heic",
|
|
11
|
+
".heif",
|
|
12
|
+
".tif",
|
|
13
|
+
".tiff",
|
|
14
|
+
".svg",
|
|
15
|
+
".jp2",
|
|
16
|
+
".j2k",
|
|
17
|
+
".jpx"
|
|
18
|
+
]);
|
|
19
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set([
|
|
20
|
+
".mp4",
|
|
21
|
+
".webm",
|
|
22
|
+
".mov",
|
|
23
|
+
".mkv",
|
|
24
|
+
".m4v",
|
|
25
|
+
".avi",
|
|
26
|
+
".3gp"
|
|
27
|
+
]);
|
|
28
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set([
|
|
29
|
+
".mp3",
|
|
30
|
+
".flac",
|
|
31
|
+
".ogg",
|
|
32
|
+
".m4a",
|
|
33
|
+
".wav",
|
|
34
|
+
".opus",
|
|
35
|
+
".aac"
|
|
36
|
+
]);
|
|
37
|
+
var STREAMABLE_VIDEO_EXTS = /* @__PURE__ */ new Set([
|
|
38
|
+
".webm",
|
|
39
|
+
".mkv",
|
|
40
|
+
".avi"
|
|
41
|
+
]);
|
|
42
|
+
var AUDIO_FFMPEG_INPUT_FORMAT = {
|
|
43
|
+
".mp3": "mp3",
|
|
44
|
+
".flac": "flac",
|
|
45
|
+
".ogg": "ogg",
|
|
46
|
+
".opus": "ogg",
|
|
47
|
+
".wav": "wav",
|
|
48
|
+
".m4a": "mp4",
|
|
49
|
+
".aac": "aac"
|
|
50
|
+
};
|
|
51
|
+
var MIME_FFMPEG_INPUT_FORMAT = {
|
|
52
|
+
"video/mp4": "mp4",
|
|
53
|
+
"video/quicktime": "mov",
|
|
54
|
+
"video/webm": "webm",
|
|
55
|
+
"video/x-matroska": "matroska",
|
|
56
|
+
"video/matroska": "matroska",
|
|
57
|
+
"video/vnd.avi": "avi",
|
|
58
|
+
"video/x-msvideo": "avi",
|
|
59
|
+
"video/avi": "avi",
|
|
60
|
+
"video/ogg": "ogg",
|
|
61
|
+
"audio/mpeg": "mp3",
|
|
62
|
+
"audio/mp3": "mp3",
|
|
63
|
+
"audio/flac": "flac",
|
|
64
|
+
"audio/x-flac": "flac",
|
|
65
|
+
"audio/ogg": "ogg",
|
|
66
|
+
"audio/opus": "ogg",
|
|
67
|
+
"audio/vorbis": "ogg",
|
|
68
|
+
"audio/wav": "wav",
|
|
69
|
+
"audio/x-wav": "wav",
|
|
70
|
+
"audio/vnd.wave": "wav",
|
|
71
|
+
"audio/wave": "wav",
|
|
72
|
+
"audio/mp4": "mp4",
|
|
73
|
+
"audio/x-m4a": "mp4",
|
|
74
|
+
"audio/aac": "aac",
|
|
75
|
+
"video/3gpp": "mp4",
|
|
76
|
+
"application/ogg": "ogg",
|
|
77
|
+
"application/x-matroska": "matroska",
|
|
78
|
+
"application/mp4": "mp4"
|
|
79
|
+
};
|
|
80
|
+
var STREAMABLE_AUDIO_EXTS = /* @__PURE__ */ new Set([
|
|
81
|
+
".mp3",
|
|
82
|
+
".flac",
|
|
83
|
+
".ogg",
|
|
84
|
+
".opus",
|
|
85
|
+
".wav",
|
|
86
|
+
".aac"
|
|
87
|
+
]);
|
|
88
|
+
var MEDIA_KINDS = ["image", "video", "audio", "other"];
|
|
89
|
+
var EXT_MIME = {
|
|
90
|
+
".jpg": "image/jpeg",
|
|
91
|
+
".jpeg": "image/jpeg",
|
|
92
|
+
".png": "image/png",
|
|
93
|
+
".webp": "image/webp",
|
|
94
|
+
".gif": "image/gif",
|
|
95
|
+
".bmp": "image/bmp",
|
|
96
|
+
".avif": "image/avif",
|
|
97
|
+
".heic": "image/heic",
|
|
98
|
+
".heif": "image/heif",
|
|
99
|
+
".tif": "image/tiff",
|
|
100
|
+
".tiff": "image/tiff",
|
|
101
|
+
".jp2": "image/jp2",
|
|
102
|
+
".j2k": "image/jp2",
|
|
103
|
+
".jpx": "image/jp2",
|
|
104
|
+
".mp4": "video/mp4",
|
|
105
|
+
".m4v": "video/mp4",
|
|
106
|
+
".webm": "video/webm",
|
|
107
|
+
".mov": "video/quicktime",
|
|
108
|
+
".mkv": "video/x-matroska",
|
|
109
|
+
".avi": "video/vnd.avi",
|
|
110
|
+
".3gp": "video/3gpp",
|
|
111
|
+
".mp3": "audio/mpeg",
|
|
112
|
+
".flac": "audio/flac",
|
|
113
|
+
".ogg": "audio/ogg",
|
|
114
|
+
".opus": "audio/opus",
|
|
115
|
+
".m4a": "audio/mp4",
|
|
116
|
+
".wav": "audio/wav",
|
|
117
|
+
".aac": "audio/aac",
|
|
118
|
+
".txt": "text/plain",
|
|
119
|
+
".md": "text/markdown",
|
|
120
|
+
".csv": "text/csv",
|
|
121
|
+
".json": "application/json",
|
|
122
|
+
".xml": "text/xml",
|
|
123
|
+
".html": "text/html",
|
|
124
|
+
".htm": "text/html",
|
|
125
|
+
".svg": "image/svg+xml",
|
|
126
|
+
".srt": "application/x-subrip",
|
|
127
|
+
".vtt": "text/vtt",
|
|
128
|
+
".ass": "text/x-ssa",
|
|
129
|
+
".epub": "application/epub+zip",
|
|
130
|
+
".pdf": "application/pdf",
|
|
131
|
+
".zip": "application/zip",
|
|
132
|
+
".cbz": "application/vnd.comicbook+zip",
|
|
133
|
+
".cbr": "application/vnd.comicbook-rar",
|
|
134
|
+
".rar": "application/vnd.rar",
|
|
135
|
+
".7z": "application/x-7z-compressed",
|
|
136
|
+
".cb7": "application/x-7z-compressed",
|
|
137
|
+
".tar": "application/x-tar",
|
|
138
|
+
".cbt": "application/x-tar"
|
|
139
|
+
};
|
|
140
|
+
var MIME_KIND_OVERRIDES = {
|
|
141
|
+
"application/ogg": "audio",
|
|
142
|
+
"application/x-matroska": "video",
|
|
143
|
+
"application/mp4": "video"
|
|
144
|
+
};
|
|
145
|
+
function mimeToKind(mime) {
|
|
146
|
+
const normalized = mime.toLowerCase();
|
|
147
|
+
const override = MIME_KIND_OVERRIDES[normalized];
|
|
148
|
+
if (override !== void 0) return override;
|
|
149
|
+
if (normalized.startsWith("image/")) return "image";
|
|
150
|
+
if (normalized.startsWith("video/")) return "video";
|
|
151
|
+
if (normalized.startsWith("audio/")) return "audio";
|
|
152
|
+
return "other";
|
|
153
|
+
}
|
|
154
|
+
function extToMime(ext) {
|
|
155
|
+
return EXT_MIME[ext.toLowerCase()];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { AUDIO_EXTS, AUDIO_FFMPEG_INPUT_FORMAT, EXT_MIME, IMAGE_EXTS, MEDIA_KINDS, MIME_FFMPEG_INPUT_FORMAT, MIME_KIND_OVERRIDES, STREAMABLE_AUDIO_EXTS, STREAMABLE_VIDEO_EXTS, VIDEO_EXTS, extToMime, mimeToKind };
|
|
159
|
+
//# sourceMappingURL=media-exts.js.map
|
|
160
|
+
//# sourceMappingURL=media-exts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/media-exts.ts"],"names":[],"mappings":"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","file":"media-exts.js","sourcesContent":["/**\n * Canonical media-type knowledge: the extension sets, the extension →\n * MIME table and the MIME → media-kind mapping shared by content\n * plugins, the runtime host's sniffer and the server's classification\n * pipeline.\n *\n * Extensions are a **hint**, never the verdict: `ResourceAPI.sniff`\n * reads the file's magic bytes and only falls back to the tables here\n * when the content carries no recognizable signature (text formats).\n * The sets below therefore answer \"which extensions do we expect to\n * decode\", not \"what is this file\".\n *\n * Lower-case, with leading dot — match the output of\n * `path.extname(name).toLowerCase()`.\n *\n * Adding a new extension here widens classification everywhere at\n * once. Before adding, verify:\n * - sharp can extract width/height (image)\n * - ffprobe can read width/height/duration (video)\n * - ffprobe can read the stream/format metadata (audio), and the\n * extension has an entry in `AUDIO_FFMPEG_INPUT_FORMAT`\n * - the extension has an entry in {@link EXT_MIME}\n * - the gallery plugin's transcode-required set reflects the format\n * (browser-renderable originals stay native; HEIC/TIFF need the\n * sharp preview pipeline)\n */\nexport const IMAGE_EXTS: ReadonlySet<string> = new Set([\n\t\".jpg\",\n\t\".jpeg\",\n\t\".png\",\n\t\".webp\",\n\t\".gif\",\n\t\".bmp\",\n\t\".avif\",\n\t\".heic\",\n\t\".heif\",\n\t\".tif\",\n\t\".tiff\",\n\t\".svg\",\n\t\".jp2\",\n\t\".j2k\",\n\t\".jpx\",\n])\n\nexport const VIDEO_EXTS: ReadonlySet<string> = new Set([\n\t\".mp4\",\n\t\".webm\",\n\t\".mov\",\n\t\".mkv\",\n\t\".m4v\",\n\t\".avi\",\n\t\".3gp\",\n])\n\nexport const AUDIO_EXTS: ReadonlySet<string> = new Set([\n\t\".mp3\",\n\t\".flac\",\n\t\".ogg\",\n\t\".m4a\",\n\t\".wav\",\n\t\".opus\",\n\t\".aac\",\n])\n\n/**\n * Video containers ffmpeg can demux from a forward-only pipe (matroska,\n * avi). ISO-BMFF files (.mp4/.mov/.m4v) keep their moov index at the end\n * of the file, so a stream attempt on a zip-entry source is guaranteed to\n * fail after burning a full probesize read — consumers (thumb pipeline,\n * cover probing) send those straight to the materialized entry instead.\n */\nexport const STREAMABLE_VIDEO_EXTS: ReadonlySet<string> = new Set([\n\t\".webm\",\n\t\".mkv\",\n\t\".avi\",\n])\n\n/**\n * ffmpeg `-f` container name for piped audio bytes (no filename hint).\n * `.opus` files are Ogg containers; `.m4a` is ISO-BMFF, demuxed by the\n * mp4 demuxer.\n */\nexport const AUDIO_FFMPEG_INPUT_FORMAT: Readonly<Record<string, string>> = {\n\t\".mp3\": \"mp3\",\n\t\".flac\": \"flac\",\n\t\".ogg\": \"ogg\",\n\t\".opus\": \"ogg\",\n\t\".wav\": \"wav\",\n\t\".m4a\": \"mp4\",\n\t\".aac\": \"aac\",\n}\n\n/**\n * ffmpeg `-f` container name keyed by **sniffed MIME type**, for piped\n * sources that have no filename ffprobe could key off. Content-derived\n * routing is what lets a mislabelled file still demux correctly, so\n * this table — not the extension one — drives `ResourceAPI.probe`.\n *\n * Several spellings map to the same container because magic-byte\n * matchers and IANA disagree on the canonical name (`audio/wav` vs\n * `audio/x-wav`, `video/vnd.avi` vs `video/x-msvideo`).\n */\nexport const MIME_FFMPEG_INPUT_FORMAT: Readonly<Record<string, string>> = {\n\t\"video/mp4\": \"mp4\",\n\t\"video/quicktime\": \"mov\",\n\t\"video/webm\": \"webm\",\n\t\"video/x-matroska\": \"matroska\",\n\t\"video/matroska\": \"matroska\",\n\t\"video/vnd.avi\": \"avi\",\n\t\"video/x-msvideo\": \"avi\",\n\t\"video/avi\": \"avi\",\n\t\"video/ogg\": \"ogg\",\n\t\"audio/mpeg\": \"mp3\",\n\t\"audio/mp3\": \"mp3\",\n\t\"audio/flac\": \"flac\",\n\t\"audio/x-flac\": \"flac\",\n\t\"audio/ogg\": \"ogg\",\n\t\"audio/opus\": \"ogg\",\n\t\"audio/vorbis\": \"ogg\",\n\t\"audio/wav\": \"wav\",\n\t\"audio/x-wav\": \"wav\",\n\t\"audio/vnd.wave\": \"wav\",\n\t\"audio/wave\": \"wav\",\n\t\"audio/mp4\": \"mp4\",\n\t\"audio/x-m4a\": \"mp4\",\n\t\"audio/aac\": \"aac\",\n\t\"video/3gpp\": \"mp4\",\n\t\"application/ogg\": \"ogg\",\n\t\"application/x-matroska\": \"matroska\",\n\t\"application/mp4\": \"mp4\",\n}\n\n/**\n * The audio mirror of {@link STREAMABLE_VIDEO_EXTS}: containers whose\n * headers lead the file, so ffmpeg/ffprobe can demux them from a\n * forward-only pipe. `.m4a` is ISO-BMFF with a trailing moov index, so\n * it must be probed from a materialized (seekable) entry.\n */\nexport const STREAMABLE_AUDIO_EXTS: ReadonlySet<string> = new Set([\n\t\".mp3\",\n\t\".flac\",\n\t\".ogg\",\n\t\".opus\",\n\t\".wav\",\n\t\".aac\",\n])\n\n/**\n * Media families a file can belong to. `other` covers everything the\n * media pipeline does not decode (text, documents, archives, ...) — it\n * is a real answer, not a failure.\n */\nexport const MEDIA_KINDS = [\"image\", \"video\", \"audio\", \"other\"] as const\n\nexport type MediaKind = (typeof MEDIA_KINDS)[number]\n\n/**\n * Extension → canonical MIME type. Used as the *fallback* branch of\n * content sniffing: magic-byte detection covers binary media, while\n * text-based formats (`.txt`, `.md`, `.csv`, subtitles, ...) carry no\n * signature and can only be named by their extension.\n */\nexport const EXT_MIME: Readonly<Record<string, string>> = {\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".png\": \"image/png\",\n\t\".webp\": \"image/webp\",\n\t\".gif\": \"image/gif\",\n\t\".bmp\": \"image/bmp\",\n\t\".avif\": \"image/avif\",\n\t\".heic\": \"image/heic\",\n\t\".heif\": \"image/heif\",\n\t\".tif\": \"image/tiff\",\n\t\".tiff\": \"image/tiff\",\n\t\".jp2\": \"image/jp2\",\n\t\".j2k\": \"image/jp2\",\n\t\".jpx\": \"image/jp2\",\n\t\".mp4\": \"video/mp4\",\n\t\".m4v\": \"video/mp4\",\n\t\".webm\": \"video/webm\",\n\t\".mov\": \"video/quicktime\",\n\t\".mkv\": \"video/x-matroska\",\n\t\".avi\": \"video/vnd.avi\",\n\t\".3gp\": \"video/3gpp\",\n\t\".mp3\": \"audio/mpeg\",\n\t\".flac\": \"audio/flac\",\n\t\".ogg\": \"audio/ogg\",\n\t\".opus\": \"audio/opus\",\n\t\".m4a\": \"audio/mp4\",\n\t\".wav\": \"audio/wav\",\n\t\".aac\": \"audio/aac\",\n\t\".txt\": \"text/plain\",\n\t\".md\": \"text/markdown\",\n\t\".csv\": \"text/csv\",\n\t\".json\": \"application/json\",\n\t\".xml\": \"text/xml\",\n\t\".html\": \"text/html\",\n\t\".htm\": \"text/html\",\n\t\".svg\": \"image/svg+xml\",\n\t\".srt\": \"application/x-subrip\",\n\t\".vtt\": \"text/vtt\",\n\t\".ass\": \"text/x-ssa\",\n\t\".epub\": \"application/epub+zip\",\n\t\".pdf\": \"application/pdf\",\n\t\".zip\": \"application/zip\",\n\t\".cbz\": \"application/vnd.comicbook+zip\",\n\t\".cbr\": \"application/vnd.comicbook-rar\",\n\t\".rar\": \"application/vnd.rar\",\n\t\".7z\": \"application/x-7z-compressed\",\n\t\".cb7\": \"application/x-7z-compressed\",\n\t\".tar\": \"application/x-tar\",\n\t\".cbt\": \"application/x-tar\",\n}\n\n/**\n * Container MIME types whose top-level type does not describe the\n * payload. Ogg and Matroska carry audio *or* video, and `application/*`\n * says nothing either way — the values here are the common case, and\n * `ResourceAPI.probe` overrides them with the stream layout ffprobe\n * actually reports.\n */\nexport const MIME_KIND_OVERRIDES: Readonly<Record<string, MediaKind>> = {\n\t\"application/ogg\": \"audio\",\n\t\"application/x-matroska\": \"video\",\n\t\"application/mp4\": \"video\",\n}\n\n/**\n * Media family of a MIME type: the override table first, then the\n * top-level type. Never throws — unknown types are `other`.\n */\nexport function mimeToKind(mime: string): MediaKind {\n\tconst normalized = mime.toLowerCase()\n\tconst override = MIME_KIND_OVERRIDES[normalized]\n\tif (override !== undefined) return override\n\tif (normalized.startsWith(\"image/\")) return \"image\"\n\tif (normalized.startsWith(\"video/\")) return \"video\"\n\tif (normalized.startsWith(\"audio/\")) return \"audio\"\n\treturn \"other\"\n}\n\n/** Canonical MIME type for an extension (leading dot), or `undefined`. */\nexport function extToMime(ext: string): string | undefined {\n\treturn EXT_MIME[ext.toLowerCase()]\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin asset runtime limits — the constants shared by the host, the
|
|
3
|
+
* SDK validators and the tooling. Backed by the `./plugin-asset-limits`
|
|
4
|
+
* subpath (plugin-facing constants never export from the root entry).
|
|
5
|
+
*/
|
|
6
|
+
/** Max length of a vault-relative `dest` (a plugin path is bounded, not arbitrary). */
|
|
7
|
+
declare const PLUGIN_ASSET_DEST_MAX_LENGTH = 256;
|
|
8
|
+
/** Max length of the optional human `reason` shown in the consent dialog. */
|
|
9
|
+
declare const PLUGIN_ASSET_REASON_MAX_LENGTH = 200;
|
|
10
|
+
/** Expected shape of an SRI-style sha256 pin: 64 lowercase hex characters. */
|
|
11
|
+
declare const PLUGIN_ASSET_SHA256_PATTERN: RegExp;
|
|
12
|
+
/** The machine-readable asset error names, in contract order. */
|
|
13
|
+
declare const PLUGIN_ASSET_ERROR_NAMES: readonly ["DENIED", "UNAVAILABLE", "POLICY"];
|
|
14
|
+
type PluginAssetErrorName = (typeof PLUGIN_ASSET_ERROR_NAMES)[number];
|
|
15
|
+
|
|
16
|
+
export { PLUGIN_ASSET_DEST_MAX_LENGTH, PLUGIN_ASSET_ERROR_NAMES, PLUGIN_ASSET_REASON_MAX_LENGTH, PLUGIN_ASSET_SHA256_PATTERN, type PluginAssetErrorName };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/plugin-asset-limits.ts
|
|
2
|
+
var PLUGIN_ASSET_DEST_MAX_LENGTH = 256;
|
|
3
|
+
var PLUGIN_ASSET_REASON_MAX_LENGTH = 200;
|
|
4
|
+
var PLUGIN_ASSET_SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
5
|
+
var PLUGIN_ASSET_ERROR_NAMES = [
|
|
6
|
+
"DENIED",
|
|
7
|
+
"UNAVAILABLE",
|
|
8
|
+
"POLICY"
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export { PLUGIN_ASSET_DEST_MAX_LENGTH, PLUGIN_ASSET_ERROR_NAMES, PLUGIN_ASSET_REASON_MAX_LENGTH, PLUGIN_ASSET_SHA256_PATTERN };
|
|
12
|
+
//# sourceMappingURL=plugin-asset-limits.js.map
|
|
13
|
+
//# sourceMappingURL=plugin-asset-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin-asset-limits.ts"],"names":[],"mappings":";AAOO,IAAM,4BAAA,GAA+B;AAGrC,IAAM,8BAAA,GAAiC;AAGvC,IAAM,2BAAA,GAA8B;AAGpC,IAAM,wBAAA,GAA2B;AAAA,EACvC,QAAA;AAAA,EACA,aAAA;AAAA,EACA;AACD","file":"plugin-asset-limits.js","sourcesContent":["/**\n * Plugin asset runtime limits — the constants shared by the host, the\n * SDK validators and the tooling. Backed by the `./plugin-asset-limits`\n * subpath (plugin-facing constants never export from the root entry).\n */\n\n/** Max length of a vault-relative `dest` (a plugin path is bounded, not arbitrary). */\nexport const PLUGIN_ASSET_DEST_MAX_LENGTH = 256\n\n/** Max length of the optional human `reason` shown in the consent dialog. */\nexport const PLUGIN_ASSET_REASON_MAX_LENGTH = 200\n\n/** Expected shape of an SRI-style sha256 pin: 64 lowercase hex characters. */\nexport const PLUGIN_ASSET_SHA256_PATTERN = /^[0-9a-f]{64}$/\n\n/** The machine-readable asset error names, in contract order. */\nexport const PLUGIN_ASSET_ERROR_NAMES = [\n\t\"DENIED\",\n\t\"UNAVAILABLE\",\n\t\"POLICY\",\n] as const\n\nexport type PluginAssetErrorName = (typeof PLUGIN_ASSET_ERROR_NAMES)[number]\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { d as PluginPermissions } from './manifest-Dk6_xyNy.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The single permission→capability declaration: every manifest
|
|
6
|
+
* permission key, what it gates, and which runtime layers enforce it.
|
|
7
|
+
* Consumers read this table instead of hand-mirrored sets — a new
|
|
8
|
+
* permission is declared once here, and the compile-time coverage
|
|
9
|
+
* checks below make a missing declaration impossible.
|
|
10
|
+
*
|
|
11
|
+
* This module is pure TypeScript (no zod): it is type-driven off
|
|
12
|
+
* {@link PluginPermissions} and consumed by the host sandbox, the
|
|
13
|
+
* server domain and the tooling. Plugin bundles never import it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type PluginCapabilityGate = {
|
|
17
|
+
/** One-line contract description (mirrors the manifest schema docs). */
|
|
18
|
+
readonly description: string;
|
|
19
|
+
/**
|
|
20
|
+
* ResourceAPI method names gated at the sandbox RPC boundary. Absent
|
|
21
|
+
* means the permission is enforced at the host/service layer only
|
|
22
|
+
* (meta hooks, web routes).
|
|
23
|
+
*/
|
|
24
|
+
readonly sandboxMethods?: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The capability gates, keyed by the manifest permission key. The
|
|
28
|
+
* `satisfies` below fails to compile when a permission is declared on
|
|
29
|
+
* the manifest but missing here; the AssertTrue checks at the bottom
|
|
30
|
+
* cover the reverse direction.
|
|
31
|
+
*/
|
|
32
|
+
declare const PLUGIN_CAPABILITY_GATES: {
|
|
33
|
+
readonly sourceMeta: {
|
|
34
|
+
readonly description: "Read/write the resource's source metadata (sourceMeta hook).";
|
|
35
|
+
};
|
|
36
|
+
readonly searchMeta: {
|
|
37
|
+
readonly description: "Produce and store search metadata facets (searchMeta hook).";
|
|
38
|
+
};
|
|
39
|
+
readonly danmaku: {
|
|
40
|
+
readonly description: "Create/list danmaku for resources this plugin renders.";
|
|
41
|
+
};
|
|
42
|
+
readonly message: {
|
|
43
|
+
readonly description: "Create/list messages for resources this plugin renders.";
|
|
44
|
+
};
|
|
45
|
+
readonly imageHashes: {
|
|
46
|
+
readonly description: "Produce content hashes for duplicate detection / image similarity.";
|
|
47
|
+
};
|
|
48
|
+
readonly container: {
|
|
49
|
+
readonly description: "List and extract archive (zip/tar/7z/…) entries; the only API surface with a write side effect.";
|
|
50
|
+
readonly sandboxMethods: readonly ["listContainer", "extractArchive"];
|
|
51
|
+
};
|
|
52
|
+
readonly download: {
|
|
53
|
+
readonly description: "The plugin asset vault: user-consented downloads into the plugin's own vault/ plus the vault read/delete methods; denied by default and per-download by the user.";
|
|
54
|
+
readonly sandboxMethods: readonly ["download", "statAsset", "readAsset", "deleteAsset"];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
type PluginCapabilityKey = keyof typeof PLUGIN_CAPABILITY_GATES;
|
|
58
|
+
type AssertTrue<T extends true> = T;
|
|
59
|
+
type _ManifestKeysCovered = AssertTrue<keyof PluginPermissions extends keyof typeof PLUGIN_CAPABILITY_GATES ? true : false>;
|
|
60
|
+
type _TableKeysCovered = AssertTrue<keyof typeof PLUGIN_CAPABILITY_GATES extends keyof PluginPermissions ? true : false>;
|
|
61
|
+
/** Sandbox API method → capability key, derived once from the table. */
|
|
62
|
+
declare const CAPABILITY_BY_METHOD: ReadonlyMap<string, PluginCapabilityKey>;
|
|
63
|
+
|
|
64
|
+
export { CAPABILITY_BY_METHOD, PLUGIN_CAPABILITY_GATES, type PluginCapabilityGate, type PluginCapabilityKey, type _ManifestKeysCovered, type _TableKeysCovered };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/plugin-capabilities.ts
|
|
2
|
+
var PLUGIN_CAPABILITY_GATES = {
|
|
3
|
+
sourceMeta: {
|
|
4
|
+
description: "Read/write the resource's source metadata (sourceMeta hook)."
|
|
5
|
+
},
|
|
6
|
+
searchMeta: {
|
|
7
|
+
description: "Produce and store search metadata facets (searchMeta hook)."
|
|
8
|
+
},
|
|
9
|
+
danmaku: {
|
|
10
|
+
description: "Create/list danmaku for resources this plugin renders."
|
|
11
|
+
},
|
|
12
|
+
message: {
|
|
13
|
+
description: "Create/list messages for resources this plugin renders."
|
|
14
|
+
},
|
|
15
|
+
imageHashes: {
|
|
16
|
+
description: "Produce content hashes for duplicate detection / image similarity."
|
|
17
|
+
},
|
|
18
|
+
container: {
|
|
19
|
+
description: "List and extract archive (zip/tar/7z/\u2026) entries; the only API surface with a write side effect.",
|
|
20
|
+
sandboxMethods: ["listContainer", "extractArchive"]
|
|
21
|
+
},
|
|
22
|
+
download: {
|
|
23
|
+
description: "The plugin asset vault: user-consented downloads into the plugin's own vault/ plus the vault read/delete methods; denied by default and per-download by the user.",
|
|
24
|
+
sandboxMethods: ["download", "statAsset", "readAsset", "deleteAsset"]
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var CAPABILITY_BY_METHOD = new Map(
|
|
28
|
+
Object.entries(PLUGIN_CAPABILITY_GATES).flatMap(
|
|
29
|
+
([capability, gate]) => (gate.sandboxMethods ?? []).map(
|
|
30
|
+
(method) => [method, capability]
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export { CAPABILITY_BY_METHOD, PLUGIN_CAPABILITY_GATES };
|
|
36
|
+
//# sourceMappingURL=plugin-capabilities.js.map
|
|
37
|
+
//# sourceMappingURL=plugin-capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin-capabilities.ts"],"names":[],"mappings":";AA8BO,IAAM,uBAAA,GAA0B;AAAA,EACtC,UAAA,EAAY;AAAA,IACX,WAAA,EAAa;AAAA,GACd;AAAA,EACA,UAAA,EAAY;AAAA,IACX,WAAA,EAAa;AAAA,GACd;AAAA,EACA,OAAA,EAAS;AAAA,IACR,WAAA,EAAa;AAAA,GACd;AAAA,EACA,OAAA,EAAS;AAAA,IACR,WAAA,EAAa;AAAA,GACd;AAAA,EACA,WAAA,EAAa;AAAA,IACZ,WAAA,EACC;AAAA,GACF;AAAA,EACA,SAAA,EAAW;AAAA,IACV,WAAA,EACC,sGAAA;AAAA,IACD,cAAA,EAAgB,CAAC,eAAA,EAAiB,gBAAgB;AAAA,GACnD;AAAA,EACA,QAAA,EAAU;AAAA,IACT,WAAA,EACC,mKAAA;AAAA,IACD,cAAA,EAAgB,CAAC,UAAA,EAAY,WAAA,EAAa,aAAa,aAAa;AAAA;AAEtE;AAqBO,IAAM,uBACZ,IAAI,GAAA;AAAA,EAEF,MAAA,CAAO,OAAA,CAAQ,uBAAuB,CAAA,CAIrC,OAAA;AAAA,IAAQ,CAAC,CAAC,UAAA,EAAY,IAAI,OAC1B,IAAA,CAAK,cAAA,IAAkB,EAAC,EAAG,GAAA;AAAA,MAC3B,CAAC,MAAA,KAAW,CAAC,MAAA,EAAQ,UAAU;AAAA;AAChC;AAEF","file":"plugin-capabilities.js","sourcesContent":["/**\n * The single permission→capability declaration: every manifest\n * permission key, what it gates, and which runtime layers enforce it.\n * Consumers read this table instead of hand-mirrored sets — a new\n * permission is declared once here, and the compile-time coverage\n * checks below make a missing declaration impossible.\n *\n * This module is pure TypeScript (no zod): it is type-driven off\n * {@link PluginPermissions} and consumed by the host sandbox, the\n * server domain and the tooling. Plugin bundles never import it.\n */\nimport type { PluginPermissions } from \"./manifest.ts\"\n\nexport type PluginCapabilityGate = {\n\t/** One-line contract description (mirrors the manifest schema docs). */\n\treadonly description: string\n\t/**\n\t * ResourceAPI method names gated at the sandbox RPC boundary. Absent\n\t * means the permission is enforced at the host/service layer only\n\t * (meta hooks, web routes).\n\t */\n\treadonly sandboxMethods?: readonly string[]\n}\n\n/**\n * The capability gates, keyed by the manifest permission key. The\n * `satisfies` below fails to compile when a permission is declared on\n * the manifest but missing here; the AssertTrue checks at the bottom\n * cover the reverse direction.\n */\nexport const PLUGIN_CAPABILITY_GATES = {\n\tsourceMeta: {\n\t\tdescription: \"Read/write the resource's source metadata (sourceMeta hook).\",\n\t},\n\tsearchMeta: {\n\t\tdescription: \"Produce and store search metadata facets (searchMeta hook).\",\n\t},\n\tdanmaku: {\n\t\tdescription: \"Create/list danmaku for resources this plugin renders.\",\n\t},\n\tmessage: {\n\t\tdescription: \"Create/list messages for resources this plugin renders.\",\n\t},\n\timageHashes: {\n\t\tdescription:\n\t\t\t\"Produce content hashes for duplicate detection / image similarity.\",\n\t},\n\tcontainer: {\n\t\tdescription:\n\t\t\t\"List and extract archive (zip/tar/7z/…) entries; the only API surface with a write side effect.\",\n\t\tsandboxMethods: [\"listContainer\", \"extractArchive\"],\n\t},\n\tdownload: {\n\t\tdescription:\n\t\t\t\"The plugin asset vault: user-consented downloads into the plugin's own vault/ plus the vault read/delete methods; denied by default and per-download by the user.\",\n\t\tsandboxMethods: [\"download\", \"statAsset\", \"readAsset\", \"deleteAsset\"],\n\t},\n} as const satisfies Record<keyof PluginPermissions, PluginCapabilityGate>\n\nexport type PluginCapabilityKey = keyof typeof PLUGIN_CAPABILITY_GATES\n\n// -- compile-time coverage --------------------------------------------------\n// Every manifest permission key is declared here and nothing more — the\n// two assertions fail the build on either drift. Exported only so\n// noUnusedLocals keeps them alive — never import.\ntype AssertTrue<T extends true> = T\nexport type _ManifestKeysCovered = AssertTrue<\n\tkeyof PluginPermissions extends keyof typeof PLUGIN_CAPABILITY_GATES\n\t\t? true\n\t\t: false\n>\nexport type _TableKeysCovered = AssertTrue<\n\tkeyof typeof PLUGIN_CAPABILITY_GATES extends keyof PluginPermissions\n\t\t? true\n\t\t: false\n>\n\n/** Sandbox API method → capability key, derived once from the table. */\nexport const CAPABILITY_BY_METHOD: ReadonlyMap<string, PluginCapabilityKey> =\n\tnew Map(\n\t\t(\n\t\t\tObject.entries(PLUGIN_CAPABILITY_GATES) as [\n\t\t\t\tPluginCapabilityKey,\n\t\t\t\tPluginCapabilityGate,\n\t\t\t][]\n\t\t).flatMap(([capability, gate]) =>\n\t\t\t(gate.sandboxMethods ?? []).map(\n\t\t\t\t(method) => [method, capability] as [string, PluginCapabilityKey],\n\t\t\t),\n\t\t),\n\t)\n"]}
|