@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.
Files changed (57) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +59 -0
  3. package/dist/image-variant.d.ts +90 -0
  4. package/dist/image-variant.js +115 -0
  5. package/dist/image-variant.js.map +1 -0
  6. package/dist/index.d.ts +772 -0
  7. package/dist/index.js +353 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/manifest-Dk6_xyNy.d.ts +204 -0
  10. package/dist/media-exts.d.ts +92 -0
  11. package/dist/media-exts.js +160 -0
  12. package/dist/media-exts.js.map +1 -0
  13. package/dist/plugin-asset-limits.d.ts +16 -0
  14. package/dist/plugin-asset-limits.js +13 -0
  15. package/dist/plugin-asset-limits.js.map +1 -0
  16. package/dist/plugin-capabilities.d.ts +64 -0
  17. package/dist/plugin-capabilities.js +37 -0
  18. package/dist/plugin-capabilities.js.map +1 -0
  19. package/dist/plugin.d.ts +49 -0
  20. package/dist/plugin.js +12 -0
  21. package/dist/plugin.js.map +1 -0
  22. package/dist/resource.d.ts +26 -0
  23. package/dist/resource.js +9 -0
  24. package/dist/resource.js.map +1 -0
  25. package/dist/result.d.ts +47 -0
  26. package/dist/result.js +20 -0
  27. package/dist/result.js.map +1 -0
  28. package/dist/schema.d.ts +29 -0
  29. package/dist/schema.js +124 -0
  30. package/dist/schema.js.map +1 -0
  31. package/dist/template.d.ts +67 -0
  32. package/dist/template.js +137 -0
  33. package/dist/template.js.map +1 -0
  34. package/dist/text-limits.d.ts +11 -0
  35. package/dist/text-limits.js +7 -0
  36. package/dist/text-limits.js.map +1 -0
  37. package/package.json +102 -0
  38. package/src/file-list.ts +14 -0
  39. package/src/image-variant.test.ts +140 -0
  40. package/src/image-variant.ts +234 -0
  41. package/src/index.ts +115 -0
  42. package/src/manifest.ts +186 -0
  43. package/src/media-exts.ts +245 -0
  44. package/src/plugin-asset-limits.ts +23 -0
  45. package/src/plugin-asset.ts +127 -0
  46. package/src/plugin-capabilities.ts +91 -0
  47. package/src/plugin-definition.test.ts +117 -0
  48. package/src/plugin-definition.ts +902 -0
  49. package/src/plugin.ts +54 -0
  50. package/src/read-range.ts +12 -0
  51. package/src/resource.ts +28 -0
  52. package/src/result.test.ts +64 -0
  53. package/src/result.ts +73 -0
  54. package/src/schema.ts +29 -0
  55. package/src/template.test.ts +116 -0
  56. package/src/template.ts +199 -0
  57. package/src/text-limits.ts +11 -0
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@hoardodile/sdk-types",
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "description": "Plugin manifest contract, shared types and constants for hoardodile content plugins.",
6
+ "keywords": [
7
+ "hoardodile",
8
+ "plugin",
9
+ "sdk",
10
+ "types",
11
+ "manifest",
12
+ "constants"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/hoardodile/hoardodile.git",
17
+ "directory": "plugins/sdk-types"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "development": "./src/index.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./image-variant": {
27
+ "types": "./dist/image-variant.d.ts",
28
+ "development": "./src/image-variant.ts",
29
+ "default": "./dist/image-variant.js"
30
+ },
31
+ "./media-exts": {
32
+ "types": "./dist/media-exts.d.ts",
33
+ "development": "./src/media-exts.ts",
34
+ "default": "./dist/media-exts.js"
35
+ },
36
+ "./plugin": {
37
+ "types": "./dist/plugin.d.ts",
38
+ "development": "./src/plugin.ts",
39
+ "default": "./dist/plugin.js"
40
+ },
41
+ "./plugin-asset-limits": {
42
+ "types": "./dist/plugin-asset-limits.d.ts",
43
+ "development": "./src/plugin-asset-limits.ts",
44
+ "default": "./dist/plugin-asset-limits.js"
45
+ },
46
+ "./plugin-capabilities": {
47
+ "types": "./dist/plugin-capabilities.d.ts",
48
+ "development": "./src/plugin-capabilities.ts",
49
+ "default": "./dist/plugin-capabilities.js"
50
+ },
51
+ "./resource": {
52
+ "types": "./dist/resource.d.ts",
53
+ "development": "./src/resource.ts",
54
+ "default": "./dist/resource.js"
55
+ },
56
+ "./result": {
57
+ "types": "./dist/result.d.ts",
58
+ "development": "./src/result.ts",
59
+ "default": "./dist/result.js"
60
+ },
61
+ "./schema": {
62
+ "types": "./dist/schema.d.ts",
63
+ "development": "./src/schema.ts",
64
+ "default": "./dist/schema.js"
65
+ },
66
+ "./template": {
67
+ "types": "./dist/template.d.ts",
68
+ "development": "./src/template.ts",
69
+ "default": "./dist/template.js"
70
+ },
71
+ "./text-limits": {
72
+ "types": "./dist/text-limits.d.ts",
73
+ "development": "./src/text-limits.ts",
74
+ "default": "./dist/text-limits.js"
75
+ }
76
+ },
77
+ "files": [
78
+ "dist",
79
+ "src"
80
+ ],
81
+ "sideEffects": false,
82
+ "engines": {
83
+ "node": ">=24"
84
+ },
85
+ "publishConfig": {
86
+ "access": "public"
87
+ },
88
+ "peerDependencies": {
89
+ "zod": "^4.4.3"
90
+ },
91
+ "devDependencies": {
92
+ "tsup": "^8.5.1",
93
+ "typescript": "5.9.3",
94
+ "vitest": "^4.1.11",
95
+ "zod": "^4.4.3"
96
+ },
97
+ "scripts": {
98
+ "build": "tsup",
99
+ "lint": "tsc --noEmit",
100
+ "test": "vitest run --maxWorkers=2"
101
+ }
102
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One item in a serialized file list: a bare filename string or a
3
+ * metadata object. Object entries carry `filename` plus any extra
4
+ * fields; the host renders covers and chips from this shape.
5
+ */
6
+ export type SerializedFileEntry =
7
+ | string
8
+ | Record<string, string | number | boolean>
9
+
10
+ /**
11
+ * Serialized file list as stored in the sidecar cache and sent over the
12
+ * wire. Order is the display order; the host preserves it verbatim.
13
+ */
14
+ export type SerializedFileList = readonly SerializedFileEntry[]
@@ -0,0 +1,140 @@
1
+ import { describe, expect, test } from "vitest"
2
+ import {
3
+ imageVariantCanonical,
4
+ imageVariantQuery,
5
+ normalizeImageVariantSpec,
6
+ parseImageVariantQuery,
7
+ } from "./image-variant.ts"
8
+ import { RESOURCE_PREVIEW_MAX_AREA } from "./resource.ts"
9
+
10
+ const QUALITY_DEFAULTS = { avifQuality: 65, webpQuality: 90 }
11
+
12
+ describe("parseImageVariantQuery", () => {
13
+ test("no variant parameters means the original", () => {
14
+ expect(parseImageVariantQuery({})).toEqual({ kind: "none" })
15
+ expect(parseImageVariantQuery({ size: "original" })).toEqual({
16
+ kind: "none",
17
+ })
18
+ })
19
+
20
+ test("size=preview alone requests the default variant", () => {
21
+ expect(parseImageVariantQuery({ size: "preview" })).toEqual({
22
+ kind: "variant",
23
+ spec: {},
24
+ })
25
+ })
26
+
27
+ test("any variant parameter requests a variant", () => {
28
+ const result = parseImageVariantQuery({
29
+ size: "preview",
30
+ fmt: "webp",
31
+ fit: "exact",
32
+ area: "2000000",
33
+ q: "80",
34
+ })
35
+ expect(result).toEqual({
36
+ kind: "variant",
37
+ spec: { format: "webp", fit: "exact", maxArea: 2000000, quality: 80 },
38
+ })
39
+ })
40
+
41
+ test("numeric strings are coerced", () => {
42
+ expect(parseImageVariantQuery({ area: "4000000", q: 50 })).toEqual({
43
+ kind: "variant",
44
+ spec: { maxArea: 4000000, quality: 50 },
45
+ })
46
+ })
47
+
48
+ test("invalid format, fit, area and quality are rejected", () => {
49
+ expect(parseImageVariantQuery({ fmt: "png" })).toMatchObject({
50
+ kind: "invalid",
51
+ })
52
+ expect(parseImageVariantQuery({ fit: "cover" })).toMatchObject({
53
+ kind: "invalid",
54
+ })
55
+ expect(parseImageVariantQuery({ area: "0" })).toMatchObject({
56
+ kind: "invalid",
57
+ })
58
+ expect(parseImageVariantQuery({ area: "1.5" })).toMatchObject({
59
+ kind: "invalid",
60
+ })
61
+ expect(parseImageVariantQuery({ q: "101" })).toMatchObject({
62
+ kind: "invalid",
63
+ })
64
+ expect(parseImageVariantQuery({ q: "abc" })).toMatchObject({
65
+ kind: "invalid",
66
+ })
67
+ })
68
+ })
69
+
70
+ describe("normalizeImageVariantSpec", () => {
71
+ test("empty spec fills every default", () => {
72
+ expect(normalizeImageVariantSpec({}, QUALITY_DEFAULTS)).toEqual({
73
+ format: "avif",
74
+ fit: "inside",
75
+ maxArea: RESOURCE_PREVIEW_MAX_AREA,
76
+ avifQuality: 65,
77
+ webpQuality: 90,
78
+ })
79
+ })
80
+
81
+ test("quality maps onto both per-format qualities", () => {
82
+ const resolved = normalizeImageVariantSpec(
83
+ { format: "webp", fit: "exact", quality: 80 },
84
+ QUALITY_DEFAULTS,
85
+ )
86
+ expect(resolved).toEqual({
87
+ format: "webp",
88
+ fit: "exact",
89
+ maxArea: RESOURCE_PREVIEW_MAX_AREA,
90
+ avifQuality: 80,
91
+ webpQuality: 80,
92
+ })
93
+ })
94
+
95
+ test("out-of-range values are clamped, not rejected", () => {
96
+ const resolved = normalizeImageVariantSpec(
97
+ { maxArea: 0, quality: 500 },
98
+ QUALITY_DEFAULTS,
99
+ )
100
+ expect(resolved.maxArea).toBe(1)
101
+ expect(resolved.avifQuality).toBe(100)
102
+ expect(resolved.webpQuality).toBe(100)
103
+ })
104
+ })
105
+
106
+ describe("imageVariantCanonical", () => {
107
+ test("identical variants share one canonical string", () => {
108
+ const a = normalizeImageVariantSpec(
109
+ { format: "webp", fit: "exact", quality: 80 },
110
+ QUALITY_DEFAULTS,
111
+ )
112
+ // The explicit area equals the default, so both requests render
113
+ // identically and must share a cache identity.
114
+ const b = normalizeImageVariantSpec(
115
+ {
116
+ format: "webp",
117
+ fit: "exact",
118
+ maxArea: RESOURCE_PREVIEW_MAX_AREA,
119
+ quality: 80,
120
+ },
121
+ QUALITY_DEFAULTS,
122
+ )
123
+ expect(imageVariantCanonical(a)).toBe(imageVariantCanonical(b))
124
+ })
125
+
126
+ test("different qualities diverge", () => {
127
+ const a = normalizeImageVariantSpec({ quality: 80 }, QUALITY_DEFAULTS)
128
+ const b = normalizeImageVariantSpec({ quality: 81 }, QUALITY_DEFAULTS)
129
+ expect(imageVariantCanonical(a)).not.toBe(imageVariantCanonical(b))
130
+ })
131
+ })
132
+
133
+ describe("imageVariantQuery", () => {
134
+ test("always carries size=preview so old servers degrade safely", () => {
135
+ expect(imageVariantQuery({})).toBe("size=preview")
136
+ expect(
137
+ imageVariantQuery({ format: "webp", fit: "exact", quality: 80 }),
138
+ ).toBe("size=preview&fmt=webp&fit=exact&q=80")
139
+ })
140
+ })
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Image variant contract: how a plugin asks the host to derive an image
3
+ * from a resource file, and how that request travels on the wire.
4
+ *
5
+ * One {@link ImageVariantSpec} has three projections, all derived from
6
+ * this module:
7
+ *
8
+ * - the URL query (`imageVariantQuery`) the iframe client emits,
9
+ * - the resolved render spec (`normalizeImageVariantSpec`) the
10
+ * pipelines execute,
11
+ * - the canonical cache identity (`imageVariantCanonical`) that keys
12
+ * the on-disk caches.
13
+ *
14
+ * The per-format encode qualities are pipeline constants, not part of
15
+ * the wire contract — callers fill them in at the render boundary, so
16
+ * this module stays dependency-free and browser-safe.
17
+ */
18
+ import { RESOURCE_PREVIEW_MAX_AREA } from "./resource.ts"
19
+
20
+ export const IMAGE_VARIANT_FORMATS = ["avif", "webp"] as const
21
+ export type ImageVariantFormat = (typeof IMAGE_VARIANT_FORMATS)[number]
22
+
23
+ /**
24
+ * How the source image is fitted into the variant:
25
+ * - `inside` — downscaled (never upscaled) to fit within `maxArea`;
26
+ * - `exact` — transcode only: output pixels are exactly the source
27
+ * dimensions (needed when downstream code maps coordinates onto the
28
+ * texture, e.g. Live2D models).
29
+ */
30
+ export const IMAGE_VARIANT_FITS = ["inside", "exact"] as const
31
+ export type ImageVariantFit = (typeof IMAGE_VARIANT_FITS)[number]
32
+
33
+ /** Upper bound for the `area` query param (bounded cache-key space). */
34
+ export const IMAGE_VARIANT_MAX_AREA = 1_000_000_000
35
+
36
+ /** Encode-quality range for the `q` query param. */
37
+ export const IMAGE_VARIANT_MIN_QUALITY = 1
38
+ export const IMAGE_VARIANT_MAX_QUALITY = 100
39
+
40
+ /**
41
+ * Client-declared variant request. Every field is optional; omitted
42
+ * fields fall back to the defaults (`format` avif, `fit` inside,
43
+ * `maxArea` {@link RESOURCE_PREVIEW_MAX_AREA}, per-format quality).
44
+ * `maxArea` only has an effect when `fit` is `inside`; it is carried
45
+ * through (and cached under) regardless so URL identity stays stable.
46
+ */
47
+ export type ImageVariantSpec = {
48
+ readonly format?: ImageVariantFormat
49
+ readonly fit?: ImageVariantFit
50
+ readonly maxArea?: number
51
+ readonly quality?: number
52
+ }
53
+
54
+ /**
55
+ * A fully resolved variant: every field concrete, including the
56
+ * per-format encode qualities the pipeline consumes. This is the render
57
+ * boundary shape and the input to {@link imageVariantCanonical}.
58
+ */
59
+ export type ResolvedImageVariant = {
60
+ readonly format: ImageVariantFormat
61
+ readonly fit: ImageVariantFit
62
+ readonly maxArea: number
63
+ readonly webpQuality: number
64
+ readonly avifQuality: number
65
+ }
66
+
67
+ /** Query parameters accepted by the resource file route. */
68
+ export type ImageVariantQuery = {
69
+ readonly size?: string
70
+ readonly fmt?: string
71
+ readonly fit?: string
72
+ readonly area?: string | number
73
+ readonly q?: string | number
74
+ }
75
+
76
+ export type ImageVariantParseResult =
77
+ | { readonly kind: "none" }
78
+ | { readonly kind: "variant"; readonly spec: ImageVariantSpec }
79
+ | { readonly kind: "invalid"; readonly reason: string }
80
+
81
+ export function isImageVariantFormat(
82
+ value: string,
83
+ ): value is ImageVariantFormat {
84
+ return IMAGE_VARIANT_FORMATS.some((format) => format === value)
85
+ }
86
+
87
+ export function isImageVariantFit(value: string): value is ImageVariantFit {
88
+ return IMAGE_VARIANT_FITS.some((fit) => fit === value)
89
+ }
90
+
91
+ /**
92
+ * Interpret the file route's query. A variant is requested when
93
+ * `size=preview` (the compatibility alias) or any variant parameter is
94
+ * present; otherwise the route serves the original bytes.
95
+ * Returns `invalid` for out-of-range or malformed values.
96
+ */
97
+ export function parseImageVariantQuery(
98
+ query: ImageVariantQuery,
99
+ ): ImageVariantParseResult {
100
+ const requested =
101
+ query.size === "preview" ||
102
+ query.fmt !== undefined ||
103
+ query.fit !== undefined ||
104
+ query.area !== undefined ||
105
+ query.q !== undefined
106
+ if (!requested) return { kind: "none" }
107
+
108
+ let format: ImageVariantFormat | undefined
109
+ let fit: ImageVariantFit | undefined
110
+ let maxArea: number | undefined
111
+ let quality: number | undefined
112
+ if (query.fmt !== undefined) {
113
+ if (!isImageVariantFormat(query.fmt)) {
114
+ return invalidFormat(query.fmt)
115
+ }
116
+ format = query.fmt
117
+ }
118
+ if (query.fit !== undefined) {
119
+ if (!isImageVariantFit(query.fit)) {
120
+ return invalidFit(query.fit)
121
+ }
122
+ fit = query.fit
123
+ }
124
+ if (query.area !== undefined) {
125
+ const area = parseVariantInteger(query.area, IMAGE_VARIANT_MAX_AREA)
126
+ if (area === undefined) return invalidArea(query.area)
127
+ maxArea = area
128
+ }
129
+ if (query.q !== undefined) {
130
+ const q = parseVariantInteger(query.q, IMAGE_VARIANT_MAX_QUALITY)
131
+ if (q === undefined) return invalidQuality(query.q)
132
+ quality = q
133
+ }
134
+ return { kind: "variant", spec: { format, fit, maxArea, quality } }
135
+ }
136
+
137
+ function invalidFormat(value: string): ImageVariantParseResult {
138
+ return {
139
+ kind: "invalid",
140
+ reason: `invalid format "${value}" (expected ${IMAGE_VARIANT_FORMATS.join(", ")})`,
141
+ }
142
+ }
143
+
144
+ function invalidFit(value: string): ImageVariantParseResult {
145
+ return {
146
+ kind: "invalid",
147
+ reason: `invalid fit "${value}" (expected ${IMAGE_VARIANT_FITS.join(", ")})`,
148
+ }
149
+ }
150
+
151
+ function invalidArea(value: unknown): ImageVariantParseResult {
152
+ return {
153
+ kind: "invalid",
154
+ reason: `area must be an integer between 1 and ${IMAGE_VARIANT_MAX_AREA} (got "${String(value)}")`,
155
+ }
156
+ }
157
+
158
+ function invalidQuality(value: unknown): ImageVariantParseResult {
159
+ return {
160
+ kind: "invalid",
161
+ reason: `quality must be an integer between ${IMAGE_VARIANT_MIN_QUALITY} and ${IMAGE_VARIANT_MAX_QUALITY} (got "${String(value)}")`,
162
+ }
163
+ }
164
+
165
+ function parseVariantInteger(
166
+ value: string | number,
167
+ max: number,
168
+ ): number | undefined {
169
+ const num = typeof value === "number" ? value : Number(value)
170
+ if (!Number.isInteger(num)) return undefined
171
+ if (num < 1 || num > max) return undefined
172
+ return num
173
+ }
174
+
175
+ /**
176
+ * Fill the request defaults and clamp out-of-range values. `quality` is
177
+ * the single client knob; it maps onto both per-format pipeline
178
+ * qualities, which default to the caller's constants when omitted.
179
+ */
180
+ export function normalizeImageVariantSpec(
181
+ spec: ImageVariantSpec,
182
+ qualityDefaults: {
183
+ readonly avifQuality: number
184
+ readonly webpQuality: number
185
+ },
186
+ ): ResolvedImageVariant {
187
+ return {
188
+ format: spec.format ?? "avif",
189
+ fit: spec.fit ?? "inside",
190
+ maxArea: clampArea(spec.maxArea ?? RESOURCE_PREVIEW_MAX_AREA),
191
+ avifQuality: clampQuality(spec.quality ?? qualityDefaults.avifQuality),
192
+ webpQuality: clampQuality(spec.quality ?? qualityDefaults.webpQuality),
193
+ }
194
+ }
195
+
196
+ function clampArea(value: number): number {
197
+ return Math.min(Math.max(1, Math.round(value)), IMAGE_VARIANT_MAX_AREA)
198
+ }
199
+
200
+ function clampQuality(value: number): number {
201
+ return Math.min(
202
+ Math.max(IMAGE_VARIANT_MIN_QUALITY, Math.round(value)),
203
+ IMAGE_VARIANT_MAX_QUALITY,
204
+ )
205
+ }
206
+
207
+ /**
208
+ * Stable string identity of a resolved variant — the cache key input.
209
+ * Two requests produce the same identity iff they render identically.
210
+ */
211
+ export function imageVariantCanonical(variant: ResolvedImageVariant): string {
212
+ return [
213
+ variant.format,
214
+ variant.fit,
215
+ variant.maxArea,
216
+ variant.webpQuality,
217
+ variant.avifQuality,
218
+ ].join(":")
219
+ }
220
+
221
+ /**
222
+ * Encode a variant request as the file route's query string. Always
223
+ * carries `size=preview` so an older server that does not know the
224
+ * variant parameters still degrades to its default preview instead of
225
+ * serving the original bytes.
226
+ */
227
+ export function imageVariantQuery(spec: ImageVariantSpec): string {
228
+ const params: string[] = ["size=preview"]
229
+ if (spec.format !== undefined) params.push(`fmt=${spec.format}`)
230
+ if (spec.fit !== undefined) params.push(`fit=${spec.fit}`)
231
+ if (spec.maxArea !== undefined) params.push(`area=${spec.maxArea}`)
232
+ if (spec.quality !== undefined) params.push(`q=${spec.quality}`)
233
+ return params.join("&")
234
+ }
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @hoardodile/sdk-types — the plugin contract: manifest schema, the
3
+ * plugin definition contract (`PluginDefinition`/`ResourceAPI`/
4
+ * `definePlugin`/fixtures), and the shared message/danmaku/anchor wire
5
+ * shapes. Single source of truth consumed by every SDK package, the
6
+ * host, and the app; nothing here touches the DOM or node.
7
+ *
8
+ * This package is the contract — plugins normally import it for types
9
+ * (`PluginSchema`, `PluginManifest`, ...) and (via
10
+ * `@hoardodile/sdk-server`) the `definePlugin` factory. The zod
11
+ * runtime validators (`pluginManifest`, `anchorData`) live behind the
12
+ * `@hoardodile/sdk-types/schema` subpath so plugin bundles never pull
13
+ * zod.
14
+ *
15
+ * Plugin-facing constants (data, plus the pure lookups that read it)
16
+ * live in subpaths mirroring their source files — there is no root
17
+ * export for them:
18
+ *
19
+ * - `@hoardodile/sdk-types/image-variant` — derived-image variant
20
+ * contract: spec types, query parsing/encoding, canonical cache
21
+ * identity
22
+ * - `@hoardodile/sdk-types/media-exts` — media-type tables: extension
23
+ * sets, extension ↔ MIME, MIME ↔ media kind
24
+ * - `@hoardodile/sdk-types/plugin` — plugin runtime limits (read cap,
25
+ * probe/stat fan-out bounds)
26
+ * - `@hoardodile/sdk-types/resource` — resource caps (search-meta version,
27
+ * preview eligibility)
28
+ * - `@hoardodile/sdk-types/template` — the host cover/message template
29
+ * grammar (fragment splitter, tokeniser, parser — shared with the
30
+ * web renderer and the CLI's build-time lint)
31
+ * - `@hoardodile/sdk-types/text-limits` — plugin input limits (danmaku
32
+ * body, comment body)
33
+ *
34
+ * App-only constants live in `@hoardodile/shared` (infra limits) and
35
+ * `@hoardodile/schemas` (field lengths) instead.
36
+ */
37
+ export * from "./file-list.ts"
38
+ export type {
39
+ CoverKindUi,
40
+ CoverKindUiMap,
41
+ PluginManifest,
42
+ PluginManifestId,
43
+ PluginManifestUi,
44
+ PluginPermissions,
45
+ SearchKind,
46
+ } from "./manifest.ts"
47
+ export * from "./plugin-asset.ts"
48
+ export * from "./plugin-definition.ts"
49
+ export * from "./read-range.ts"
50
+ export * from "./result.ts"
51
+ export type { AnchorData } from "./schema.ts"
52
+
53
+ /** Web plugin danmaku mode. */
54
+ export type DanmakuMode = "scroll" | "top" | "bottom"
55
+
56
+ /**
57
+ * Client-side danmaku list filter: every entry is matched by strict
58
+ * equality against the same key in the danmaku's anchor `data`. Keys are
59
+ * plugin-defined vocabulary (e.g. `{ kind: "videoTime", filename }` in
60
+ * an official content plugin) — the SDK only defines the matching semantics,
61
+ * not which keys exist.
62
+ */
63
+ export type DanmakuListFilter = Readonly<
64
+ Record<string, string | number | boolean>
65
+ >
66
+
67
+ /**
68
+ * Anchor as returned in `Message` / `Danmaku` API shapes: the resource
69
+ * the anchor points into plus the plugin location payload. The server
70
+ * derives `resId` from the row's own `anchor_resource_id` column —
71
+ * plugin code never supplies it (see `anchorData` in
72
+ * `@hoardodile/sdk-types/schema`).
73
+ */
74
+ export type ResAnchor = {
75
+ readonly resId: string
76
+ readonly data?: unknown
77
+ }
78
+
79
+ /** Web plugin message shape. */
80
+ export type Message = {
81
+ readonly id: string
82
+ readonly parentId?: string
83
+ readonly body: string
84
+ readonly createdAt: number
85
+ readonly deletedAt?: number
86
+ readonly charIds: readonly string[]
87
+ readonly resIds: readonly string[]
88
+ readonly likeCount: number
89
+ readonly dislikeCount: number
90
+ readonly replyCount: number
91
+ readonly floor?: number
92
+ readonly anchor?: ResAnchor
93
+ }
94
+
95
+ /** Web plugin danmaku shape. */
96
+ export type Danmaku = {
97
+ readonly id: string
98
+ readonly anchor: ResAnchor
99
+ readonly text: string
100
+ readonly color: string
101
+ readonly mode: DanmakuMode
102
+ readonly createdAt: number
103
+ }
104
+
105
+ /** Plugin-facing file stats slice of a resource. */
106
+ export type FileStats = {
107
+ readonly sizeBytes?: number
108
+ readonly count?: number
109
+ }
110
+
111
+ /** Plugin-produced search metadata. The host enforces its own schema at ingestion time. */
112
+ export type SearchMeta = {
113
+ readonly v: number
114
+ readonly facets?: Readonly<Record<string, boolean>>
115
+ }