target-supabase-sdk 0.2.16 → 0.2.17
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/README.md +20 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/chunks/{task-queue-Gi4DSq74.js → task-queue-DufG7PlX.js} +162 -2
- package/dist/chunks/task-queue-DufG7PlX.js.map +1 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +211 -6
- package/dist/node.js.map +1 -1
- package/dist/shared/media/index.d.ts +14 -0
- package/dist/shared/media/index.d.ts.map +1 -0
- package/dist/shared/media/media.build.d.ts +20 -0
- package/dist/shared/media/media.build.d.ts.map +1 -0
- package/dist/shared/media/media.interface.d.ts +38 -0
- package/dist/shared/media/media.interface.d.ts.map +1 -0
- package/dist/shared/media/media.mime.d.ts +4 -0
- package/dist/shared/media/media.mime.d.ts.map +1 -0
- package/dist/shared/media/media.name.d.ts +6 -0
- package/dist/shared/media/media.name.d.ts.map +1 -0
- package/dist/shared/media/media.query.d.ts +8 -0
- package/dist/shared/media/media.query.d.ts.map +1 -0
- package/dist/shared/media/node/index.d.ts +9 -0
- package/dist/shared/media/node/index.d.ts.map +1 -0
- package/dist/shared/media/node/media.access.d.ts +40 -0
- package/dist/shared/media/node/media.access.d.ts.map +1 -0
- package/dist/shared/media/node/media.service.d.ts +30 -0
- package/dist/shared/media/node/media.service.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/browser.ts +11 -0
- package/src/node.ts +13 -0
- package/src/shared/media/README.md +109 -0
- package/src/shared/media/index.ts +19 -0
- package/src/shared/media/media.build.ts +69 -0
- package/src/shared/media/media.interface.ts +45 -0
- package/src/shared/media/media.mime.ts +64 -0
- package/src/shared/media/media.name.ts +33 -0
- package/src/shared/media/media.query.ts +13 -0
- package/src/shared/media/node/index.ts +13 -0
- package/src/shared/media/node/media.access.ts +148 -0
- package/src/shared/media/node/media.service.ts +129 -0
- package/dist/chunks/task-queue-Gi4DSq74.js.map +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Link — browser-safe barrel only.
|
|
3
|
+
*
|
|
4
|
+
* Node-only code lives in `./node/` and is exported from `src/node.ts`,
|
|
5
|
+
* never from this file (see README.md).
|
|
6
|
+
*/
|
|
7
|
+
export type { BuildMediaLinkDraftInput } from "./media.build";
|
|
8
|
+
export { buildMediaLinkDraft } from "./media.build";
|
|
9
|
+
export type { MediaOriginal, MediaValue } from "./media.interface";
|
|
10
|
+
export { isMediaValue, MEDIA_CONTENT_HASH_ALGORITHM, MEDIA_CONTENT_HASH_PREFIX_LENGTH, MEDIA_VALUES, } from "./media.interface";
|
|
11
|
+
export { guessMediaMime } from "./media.mime";
|
|
12
|
+
export { buildMediaNameFromLocator } from "./media.name";
|
|
13
|
+
export { mediaLinkDedupFilters } from "./media.query";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/media/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EACH,YAAY,EACZ,4BAA4B,EAC5B,gCAAgC,EAChC,YAAY,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TargetDraft } from "../../core.interface";
|
|
2
|
+
import type { Link } from "../../link/link.interface";
|
|
3
|
+
import { type MediaValue } from "./media.interface";
|
|
4
|
+
export interface BuildMediaLinkDraftInput {
|
|
5
|
+
value: MediaValue;
|
|
6
|
+
name: string;
|
|
7
|
+
storageProvider: string;
|
|
8
|
+
locator: string;
|
|
9
|
+
contentHash: string;
|
|
10
|
+
size: number;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
tagList?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Assemble a media {@link Link} draft (`category=link`, `loaderKey === value`).
|
|
17
|
+
* Does not validate filesystem or write to Supabase.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildMediaLinkDraft(input: BuildMediaLinkDraftInput): TargetDraft<Link>;
|
|
20
|
+
//# sourceMappingURL=media.build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.build.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEtF,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,WAAW,CAAC,IAAI,CAAC,CA+CtF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Link protocol — Link rows with value/loaderKey in {@link MEDIA_VALUES}.
|
|
3
|
+
* No separate Target category; category remains `link`.
|
|
4
|
+
*/
|
|
5
|
+
/** Fixed digest algorithm for single-file media `contentHash`. */
|
|
6
|
+
export declare const MEDIA_CONTENT_HASH_ALGORITHM: "sha256";
|
|
7
|
+
/** Hex prefix length for {@link buildMediaNameFromLocator}. */
|
|
8
|
+
export declare const MEDIA_CONTENT_HASH_PREFIX_LENGTH = 6;
|
|
9
|
+
export declare const MEDIA_VALUES: readonly ["video", "audio", "image", "image-list"];
|
|
10
|
+
/** Discriminator stored in both `Link.value` and `details.loaderKey`. */
|
|
11
|
+
export type MediaValue = (typeof MEDIA_VALUES)[number];
|
|
12
|
+
/**
|
|
13
|
+
* `Link.details.original` for media Links.
|
|
14
|
+
*
|
|
15
|
+
* - `locator` — absolute filesystem path, or (future) network URL keyed by `storageProvider`.
|
|
16
|
+
* - `image-list`: `locator` is an absolute directory path; only the **immediate** children
|
|
17
|
+
* (one level, non-recursive) are counted. `contentHash` and `mimeType` are `""`;
|
|
18
|
+
* `size` is the number of files in that directory (not bytes).
|
|
19
|
+
* - other values: `size` is byte length; `contentHash` is SHA-256 hex; `mimeType` from the file.
|
|
20
|
+
*/
|
|
21
|
+
export interface MediaOriginal {
|
|
22
|
+
storageProvider: string;
|
|
23
|
+
locator: string;
|
|
24
|
+
/**
|
|
25
|
+
* SHA-256 hex of file bytes, or `""` when `value === "image-list"`.
|
|
26
|
+
* Algorithm is always {@link MEDIA_CONTENT_HASH_ALGORITHM}.
|
|
27
|
+
*/
|
|
28
|
+
contentHash: string;
|
|
29
|
+
/**
|
|
30
|
+
* Single-file media: byte length.
|
|
31
|
+
* `image-list`: number of files in the directory (one level).
|
|
32
|
+
*/
|
|
33
|
+
size: number;
|
|
34
|
+
/** e.g. `video/mp4`, or `""` when `value === "image-list"`. */
|
|
35
|
+
mimeType: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function isMediaValue(value: string): value is MediaValue;
|
|
38
|
+
//# sourceMappingURL=media.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.interface.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,kEAAkE;AAClE,eAAO,MAAM,4BAA4B,EAAG,QAAiB,CAAC;AAE9D,+DAA+D;AAC/D,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,eAAO,MAAM,YAAY,oDAAqD,CAAC;AAE/E,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.mime.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.mime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAkDpD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,CAYvG"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `{basename}.{hash6}.{ext}` from an absolute (or path-like) locator and full content hash.
|
|
3
|
+
* Does not touch the filesystem.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildMediaNameFromLocator(locator: string, contentHash: string): string;
|
|
6
|
+
//# sourceMappingURL=media.name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.name.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.name.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CA0BtF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { QueryFilter } from "../../core.interface";
|
|
2
|
+
import type { MediaValue } from "./media.interface";
|
|
3
|
+
/** Dedup key: `category=link` + `value` + `name` (matches register / find). */
|
|
4
|
+
export declare function mediaLinkDedupFilters(input: {
|
|
5
|
+
value: MediaValue;
|
|
6
|
+
name: string;
|
|
7
|
+
}): QueryFilter[];
|
|
8
|
+
//# sourceMappingURL=media.query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.query.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,EAAE,CAO/F"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only media surface — for `src/node.ts` / `target-supabase-sdk/node` only.
|
|
3
|
+
* Never re-export from `../index.ts` or `browser.ts`.
|
|
4
|
+
*/
|
|
5
|
+
export type { CheckMediaAvailabilityInput, MediaAvailabilityResult } from "./media.access";
|
|
6
|
+
export { checkMediaAvailability, probeMediaOriginal } from "./media.access";
|
|
7
|
+
export type { FindMediaLinkInput, RegisterMediaLinkInput, RegisterMediaLinkResult, } from "./media.service";
|
|
8
|
+
export { findMediaLink, registerMediaLink } from "./media.service";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EACR,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type MediaOriginal, type MediaValue } from "../media.interface";
|
|
2
|
+
export interface CheckMediaAvailabilityInput {
|
|
3
|
+
value: MediaValue;
|
|
4
|
+
storageProvider: string;
|
|
5
|
+
locator: string;
|
|
6
|
+
/**
|
|
7
|
+
* This host's storage id (e.g. from `LOCAL_STORAGE_PROVIDER`).
|
|
8
|
+
* Local path checks only run when it equals `storageProvider`.
|
|
9
|
+
*/
|
|
10
|
+
localStorageProvider: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MediaAvailabilityResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
/** Absolute path when the locator was resolved on this host. */
|
|
15
|
+
absolutePath?: string;
|
|
16
|
+
reason?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fast local reachability check for a media locator.
|
|
20
|
+
*
|
|
21
|
+
* - Requires `storageProvider === localStorageProvider` (cross-host → not ok).
|
|
22
|
+
* - `locator` must be an absolute path.
|
|
23
|
+
* - `image-list`: directory exists; only immediate children are considered.
|
|
24
|
+
* - other values: path exists and is a non-empty file.
|
|
25
|
+
*
|
|
26
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkMediaAvailability(input: CheckMediaAvailabilityInput): Promise<MediaAvailabilityResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Derive `contentHash` / `size` / `mimeType` for {@link MediaOriginal} after availability succeeds.
|
|
31
|
+
*
|
|
32
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
33
|
+
*/
|
|
34
|
+
export declare function probeMediaOriginal(input: {
|
|
35
|
+
value: MediaValue;
|
|
36
|
+
storageProvider: string;
|
|
37
|
+
locator: string;
|
|
38
|
+
absolutePath: string;
|
|
39
|
+
}): Promise<MediaOriginal>;
|
|
40
|
+
//# sourceMappingURL=media.access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.access.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/media.access.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgC,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGvG,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AA+BD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA0CjH;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC5C,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4BzB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Link } from "../../../link/link.interface";
|
|
2
|
+
import { type MediaValue } from "../media.interface";
|
|
3
|
+
export interface FindMediaLinkInput {
|
|
4
|
+
value: MediaValue;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/** Dedup lookup: same `category` + `value` + `name`. */
|
|
8
|
+
export declare function findMediaLink(input: FindMediaLinkInput): Promise<Link | null>;
|
|
9
|
+
export interface RegisterMediaLinkInput {
|
|
10
|
+
value: MediaValue;
|
|
11
|
+
name: string;
|
|
12
|
+
storageProvider: string;
|
|
13
|
+
locator: string;
|
|
14
|
+
/** This host id for availability (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). */
|
|
15
|
+
localStorageProvider: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
tagList?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface RegisterMediaLinkResult {
|
|
20
|
+
id: string;
|
|
21
|
+
created: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Register a media Link: dedup → availability → probe → `postTarget`.
|
|
25
|
+
* Idempotent when `category`+`value`+`name` already exist.
|
|
26
|
+
*
|
|
27
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerMediaLink(input: RegisterMediaLinkInput): Promise<RegisterMediaLinkResult>;
|
|
30
|
+
//# sourceMappingURL=media.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.service.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/media.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMnE,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,wDAAwD;AACxD,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAYnF;AAED,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA4EvG"}
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -223,6 +223,17 @@ export {
|
|
|
223
223
|
parseLogBatchMeta,
|
|
224
224
|
resolveLogMinLevel,
|
|
225
225
|
} from "./shared/log";
|
|
226
|
+
export type { BuildMediaLinkDraftInput, MediaOriginal, MediaValue } from "./shared/media";
|
|
227
|
+
export {
|
|
228
|
+
buildMediaLinkDraft,
|
|
229
|
+
buildMediaNameFromLocator,
|
|
230
|
+
guessMediaMime,
|
|
231
|
+
isMediaValue,
|
|
232
|
+
MEDIA_CONTENT_HASH_ALGORITHM,
|
|
233
|
+
MEDIA_CONTENT_HASH_PREFIX_LENGTH,
|
|
234
|
+
MEDIA_VALUES,
|
|
235
|
+
mediaLinkDedupFilters,
|
|
236
|
+
} from "./shared/media";
|
|
226
237
|
export { isHttpUrl, resolveFetchUrl } from "./shared/utils/fetch-url";
|
|
227
238
|
export { getValueAtPath } from "./shared/utils/get-value-at-path";
|
|
228
239
|
export type {
|
package/src/node.ts
CHANGED
|
@@ -165,6 +165,19 @@ export {
|
|
|
165
165
|
shutdownLogSpool,
|
|
166
166
|
} from "./shared/log/spool/service-lifecycle";
|
|
167
167
|
export type { LogBatchMeta, LogPersistLane } from "./shared/log/upload/interface";
|
|
168
|
+
export type {
|
|
169
|
+
CheckMediaAvailabilityInput,
|
|
170
|
+
FindMediaLinkInput,
|
|
171
|
+
MediaAvailabilityResult,
|
|
172
|
+
RegisterMediaLinkInput,
|
|
173
|
+
RegisterMediaLinkResult,
|
|
174
|
+
} from "./shared/media/node";
|
|
175
|
+
export {
|
|
176
|
+
checkMediaAvailability,
|
|
177
|
+
findMediaLink,
|
|
178
|
+
probeMediaOriginal,
|
|
179
|
+
registerMediaLink,
|
|
180
|
+
} from "./shared/media/node";
|
|
168
181
|
export type {
|
|
169
182
|
ConfigSchema,
|
|
170
183
|
LoadCachedJsConfigOptions,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Media Links (`shared/media`)
|
|
2
|
+
|
|
3
|
+
Media is a **convention on `Link`**, not a new Target category (`category` stays `link`).
|
|
4
|
+
|
|
5
|
+
All media code lives under **`src/shared/media/`**. Node-only modules are kept in the **`node/`** subdirectory so the filesystem path matches the package entry split.
|
|
6
|
+
|
|
7
|
+
## Package entry split (important)
|
|
8
|
+
|
|
9
|
+
| Consumer import | What you get | Source |
|
|
10
|
+
|-----------------|--------------|--------|
|
|
11
|
+
| `from "target-supabase-sdk"` | Browser-safe helpers only | `shared/media/index.ts` → `browser.ts` |
|
|
12
|
+
| `from "target-supabase-sdk/node"` | + register / availability / probe | `shared/media/node/` → `node.ts` |
|
|
13
|
+
|
|
14
|
+
There is **no** public subpath like `target-supabase-sdk/shared/media/node` — consumers always use the package `/node` entry.
|
|
15
|
+
|
|
16
|
+
**Do not** re-export anything from `node/` in `index.ts` / `browser.ts`. Those files import `node:fs` / `node:crypto` / `node:path`. Pulling them into the default entry breaks Chrome/Webpack bundles (`verify-browser-entry` will fail).
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
src/shared/media/
|
|
20
|
+
index.ts ← browser-safe barrel ONLY (never export ./node)
|
|
21
|
+
media.interface.ts
|
|
22
|
+
media.build.ts
|
|
23
|
+
media.mime.ts
|
|
24
|
+
media.name.ts
|
|
25
|
+
media.query.ts
|
|
26
|
+
README.md
|
|
27
|
+
node/ ← Node-only (fs / register)
|
|
28
|
+
index.ts ← barrel for src/node.ts only
|
|
29
|
+
media.access.ts
|
|
30
|
+
media.service.ts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Correct consumer imports
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Default / browser — draft, mime, name, filters
|
|
37
|
+
import {
|
|
38
|
+
buildMediaLinkDraft,
|
|
39
|
+
buildMediaNameFromLocator,
|
|
40
|
+
MEDIA_VALUES,
|
|
41
|
+
mediaLinkDedupFilters,
|
|
42
|
+
} from "target-supabase-sdk";
|
|
43
|
+
|
|
44
|
+
// Node workers / CLI — dedup + availability + register
|
|
45
|
+
import {
|
|
46
|
+
findMediaLink,
|
|
47
|
+
checkMediaAvailability,
|
|
48
|
+
registerMediaLink,
|
|
49
|
+
} from "target-supabase-sdk/node";
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Incorrect
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// BAD: Node-only APIs are not on the default entry
|
|
56
|
+
import { registerMediaLink } from "target-supabase-sdk";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Internal SDK code:
|
|
60
|
+
|
|
61
|
+
- Browser-safe leaves: `from "../media.build"` (etc.)
|
|
62
|
+
- Node leaves: `from "./media.access"` inside `node/`, or `from "../shared/media/node"` from `src/node.ts`
|
|
63
|
+
- Never pull `./node` through the parent `index.ts`
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Link field rules
|
|
68
|
+
|
|
69
|
+
| Field | Rule |
|
|
70
|
+
|-------|------|
|
|
71
|
+
| `value` | `video` \| `audio` \| `image` \| `image-list` |
|
|
72
|
+
| `details.loaderKey` | Always equals `value` |
|
|
73
|
+
| `name` | Caller-provided unique key (or `buildMediaNameFromLocator`) |
|
|
74
|
+
| `tagList` / `description` | Caller-defined (e.g. producer / scene tags) |
|
|
75
|
+
| `details.manifestVersion` | `0` (via `buildLinkTargetDraft`) |
|
|
76
|
+
| `details.preview` | `""` |
|
|
77
|
+
|
|
78
|
+
## `details.original`
|
|
79
|
+
|
|
80
|
+
| Key | Single file (`video`/`audio`/`image`) | `image-list` |
|
|
81
|
+
|-----|----------------------------------------|--------------|
|
|
82
|
+
| `storageProvider` | Host / backend id (e.g. `windows-storage:HOSTNAME`) | same |
|
|
83
|
+
| `locator` | Absolute file path | Absolute **directory** path |
|
|
84
|
+
| `contentHash` | SHA-256 hex (`MEDIA_CONTENT_HASH_ALGORITHM`) | `""` |
|
|
85
|
+
| `size` | Byte length | File count (immediate children only, non-recursive) |
|
|
86
|
+
| `mimeType` | e.g. `video/mp4` | `""` |
|
|
87
|
+
|
|
88
|
+
`image-list` rules:
|
|
89
|
+
|
|
90
|
+
1. `locator` must be an **absolute** directory path.
|
|
91
|
+
2. Only **one level** under that directory is considered (no recursion).
|
|
92
|
+
3. `size` counts **files** in that level (directories among children are ignored).
|
|
93
|
+
|
|
94
|
+
## Register flow (`registerMediaLink`)
|
|
95
|
+
|
|
96
|
+
1. **Dedup** — `category` + `value` + `name` via `findMediaLink`. If present → `{ id, created: false }`.
|
|
97
|
+
2. **Availability** — `storageProvider` must equal `localStorageProvider` (typically `LOCAL_STORAGE_PROVIDER` from the service `.env`); `locator` absolute and reachable.
|
|
98
|
+
3. **Probe** — stream SHA-256 / `stat` / MIME (or directory file count for `image-list`).
|
|
99
|
+
4. **Insert** — `buildMediaLinkDraft` → `postTarget` (race → re-find).
|
|
100
|
+
|
|
101
|
+
## Env (Node hosts)
|
|
102
|
+
|
|
103
|
+
| Variable | Role |
|
|
104
|
+
|----------|------|
|
|
105
|
+
| `LOCAL_STORAGE_PROVIDER` | This machine’s storage id; pass as `localStorageProvider` into availability / register |
|
|
106
|
+
|
|
107
|
+
Cross-host `storageProvider` cannot be verified on the current process — availability returns `ok: false`.
|
|
108
|
+
|
|
109
|
+
See `.cursor/skills/media-link/SKILL.md`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Link — browser-safe barrel only.
|
|
3
|
+
*
|
|
4
|
+
* Node-only code lives in `./node/` and is exported from `src/node.ts`,
|
|
5
|
+
* never from this file (see README.md).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { BuildMediaLinkDraftInput } from "./media.build";
|
|
9
|
+
export { buildMediaLinkDraft } from "./media.build";
|
|
10
|
+
export type { MediaOriginal, MediaValue } from "./media.interface";
|
|
11
|
+
export {
|
|
12
|
+
isMediaValue,
|
|
13
|
+
MEDIA_CONTENT_HASH_ALGORITHM,
|
|
14
|
+
MEDIA_CONTENT_HASH_PREFIX_LENGTH,
|
|
15
|
+
MEDIA_VALUES,
|
|
16
|
+
} from "./media.interface";
|
|
17
|
+
export { guessMediaMime } from "./media.mime";
|
|
18
|
+
export { buildMediaNameFromLocator } from "./media.name";
|
|
19
|
+
export { mediaLinkDedupFilters } from "./media.query";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { TargetDraft } from "../../core.interface";
|
|
2
|
+
import { buildLinkTargetDraft } from "../../link/link.build";
|
|
3
|
+
import type { Link } from "../../link/link.interface";
|
|
4
|
+
import { isMediaValue, type MediaOriginal, type MediaValue } from "./media.interface";
|
|
5
|
+
|
|
6
|
+
export interface BuildMediaLinkDraftInput {
|
|
7
|
+
value: MediaValue;
|
|
8
|
+
name: string;
|
|
9
|
+
storageProvider: string;
|
|
10
|
+
locator: string;
|
|
11
|
+
contentHash: string;
|
|
12
|
+
size: number;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
tagList?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Assemble a media {@link Link} draft (`category=link`, `loaderKey === value`).
|
|
20
|
+
* Does not validate filesystem or write to Supabase.
|
|
21
|
+
*/
|
|
22
|
+
export function buildMediaLinkDraft(input: BuildMediaLinkDraftInput): TargetDraft<Link> {
|
|
23
|
+
if (!isMediaValue(input.value)) {
|
|
24
|
+
throw new Error(`buildMediaLinkDraft: unsupported media value: ${String(input.value)}`);
|
|
25
|
+
}
|
|
26
|
+
const name = input.name.trim();
|
|
27
|
+
if (name === "") {
|
|
28
|
+
throw new Error("buildMediaLinkDraft: name is empty");
|
|
29
|
+
}
|
|
30
|
+
const storageProvider = input.storageProvider.trim();
|
|
31
|
+
if (storageProvider === "") {
|
|
32
|
+
throw new Error("buildMediaLinkDraft: storageProvider is empty");
|
|
33
|
+
}
|
|
34
|
+
const locator = input.locator.trim();
|
|
35
|
+
if (locator === "") {
|
|
36
|
+
throw new Error("buildMediaLinkDraft: locator is empty");
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isFinite(input.size) || input.size < 0) {
|
|
39
|
+
throw new Error("buildMediaLinkDraft: size must be a non-negative number");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isList = input.value === "image-list";
|
|
43
|
+
const contentHash = isList ? "" : input.contentHash.trim().toLowerCase();
|
|
44
|
+
const mimeType = isList ? "" : input.mimeType.trim();
|
|
45
|
+
if (!isList && contentHash === "") {
|
|
46
|
+
throw new Error("buildMediaLinkDraft: contentHash is required for single-file media");
|
|
47
|
+
}
|
|
48
|
+
if (!isList && mimeType === "") {
|
|
49
|
+
throw new Error("buildMediaLinkDraft: mimeType is required for single-file media");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const original: MediaOriginal = {
|
|
53
|
+
storageProvider,
|
|
54
|
+
locator,
|
|
55
|
+
contentHash,
|
|
56
|
+
size: Math.floor(input.size),
|
|
57
|
+
mimeType,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return buildLinkTargetDraft({
|
|
61
|
+
name,
|
|
62
|
+
value: input.value,
|
|
63
|
+
description: input.description ?? "",
|
|
64
|
+
preview: "",
|
|
65
|
+
loaderKey: input.value,
|
|
66
|
+
tagList: input.tagList ?? [],
|
|
67
|
+
original,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Link protocol — Link rows with value/loaderKey in {@link MEDIA_VALUES}.
|
|
3
|
+
* No separate Target category; category remains `link`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Fixed digest algorithm for single-file media `contentHash`. */
|
|
7
|
+
export const MEDIA_CONTENT_HASH_ALGORITHM = "sha256" as const;
|
|
8
|
+
|
|
9
|
+
/** Hex prefix length for {@link buildMediaNameFromLocator}. */
|
|
10
|
+
export const MEDIA_CONTENT_HASH_PREFIX_LENGTH = 6;
|
|
11
|
+
|
|
12
|
+
export const MEDIA_VALUES = ["video", "audio", "image", "image-list"] as const;
|
|
13
|
+
|
|
14
|
+
/** Discriminator stored in both `Link.value` and `details.loaderKey`. */
|
|
15
|
+
export type MediaValue = (typeof MEDIA_VALUES)[number];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `Link.details.original` for media Links.
|
|
19
|
+
*
|
|
20
|
+
* - `locator` — absolute filesystem path, or (future) network URL keyed by `storageProvider`.
|
|
21
|
+
* - `image-list`: `locator` is an absolute directory path; only the **immediate** children
|
|
22
|
+
* (one level, non-recursive) are counted. `contentHash` and `mimeType` are `""`;
|
|
23
|
+
* `size` is the number of files in that directory (not bytes).
|
|
24
|
+
* - other values: `size` is byte length; `contentHash` is SHA-256 hex; `mimeType` from the file.
|
|
25
|
+
*/
|
|
26
|
+
export interface MediaOriginal {
|
|
27
|
+
storageProvider: string;
|
|
28
|
+
locator: string;
|
|
29
|
+
/**
|
|
30
|
+
* SHA-256 hex of file bytes, or `""` when `value === "image-list"`.
|
|
31
|
+
* Algorithm is always {@link MEDIA_CONTENT_HASH_ALGORITHM}.
|
|
32
|
+
*/
|
|
33
|
+
contentHash: string;
|
|
34
|
+
/**
|
|
35
|
+
* Single-file media: byte length.
|
|
36
|
+
* `image-list`: number of files in the directory (one level).
|
|
37
|
+
*/
|
|
38
|
+
size: number;
|
|
39
|
+
/** e.g. `video/mp4`, or `""` when `value === "image-list"`. */
|
|
40
|
+
mimeType: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isMediaValue(value: string): value is MediaValue {
|
|
44
|
+
return (MEDIA_VALUES as readonly string[]).includes(value);
|
|
45
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { MediaValue } from "./media.interface";
|
|
2
|
+
|
|
3
|
+
const VIDEO_EXT_MIME: Record<string, string> = {
|
|
4
|
+
mp4: "video/mp4",
|
|
5
|
+
m4v: "video/mp4",
|
|
6
|
+
mov: "video/quicktime",
|
|
7
|
+
mkv: "video/x-matroska",
|
|
8
|
+
webm: "video/webm",
|
|
9
|
+
avi: "video/x-msvideo",
|
|
10
|
+
mpeg: "video/mpeg",
|
|
11
|
+
mpg: "video/mpeg",
|
|
12
|
+
wmv: "video/x-ms-wmv",
|
|
13
|
+
flv: "video/x-flv",
|
|
14
|
+
ts: "video/mp2t",
|
|
15
|
+
m2ts: "video/mp2t",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const AUDIO_EXT_MIME: Record<string, string> = {
|
|
19
|
+
wav: "audio/wav",
|
|
20
|
+
mp3: "audio/mpeg",
|
|
21
|
+
m4a: "audio/mp4",
|
|
22
|
+
aac: "audio/aac",
|
|
23
|
+
flac: "audio/flac",
|
|
24
|
+
ogg: "audio/ogg",
|
|
25
|
+
oga: "audio/ogg",
|
|
26
|
+
opus: "audio/opus",
|
|
27
|
+
wma: "audio/x-ms-wma",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const IMAGE_EXT_MIME: Record<string, string> = {
|
|
31
|
+
jpg: "image/jpeg",
|
|
32
|
+
jpeg: "image/jpeg",
|
|
33
|
+
png: "image/png",
|
|
34
|
+
webp: "image/webp",
|
|
35
|
+
gif: "image/gif",
|
|
36
|
+
bmp: "image/bmp",
|
|
37
|
+
tif: "image/tiff",
|
|
38
|
+
tiff: "image/tiff",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function extensionOf(pathOrUrl: string): string {
|
|
42
|
+
const cleaned = pathOrUrl.split(/[?#]/)[0] ?? pathOrUrl;
|
|
43
|
+
const base = cleaned.replace(/\\/g, "/").split("/").pop() ?? cleaned;
|
|
44
|
+
const dot = base.lastIndexOf(".");
|
|
45
|
+
if (dot <= 0 || dot === base.length - 1) {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
return base.slice(dot + 1).toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Guess MIME from path extension; `null` when unknown. */
|
|
52
|
+
export function guessMediaMime(locator: string, value: Exclude<MediaValue, "image-list">): string | null {
|
|
53
|
+
const ext = extensionOf(locator);
|
|
54
|
+
if (ext === "") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (value === "video") {
|
|
58
|
+
return VIDEO_EXT_MIME[ext] ?? null;
|
|
59
|
+
}
|
|
60
|
+
if (value === "audio") {
|
|
61
|
+
return AUDIO_EXT_MIME[ext] ?? null;
|
|
62
|
+
}
|
|
63
|
+
return IMAGE_EXT_MIME[ext] ?? null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MEDIA_CONTENT_HASH_PREFIX_LENGTH } from "./media.interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `{basename}.{hash6}.{ext}` from an absolute (or path-like) locator and full content hash.
|
|
5
|
+
* Does not touch the filesystem.
|
|
6
|
+
*/
|
|
7
|
+
export function buildMediaNameFromLocator(locator: string, contentHash: string): string {
|
|
8
|
+
const trimmedLocator = locator.trim();
|
|
9
|
+
if (trimmedLocator === "") {
|
|
10
|
+
throw new Error("buildMediaNameFromLocator: locator is empty");
|
|
11
|
+
}
|
|
12
|
+
const hash = contentHash.trim().toLowerCase();
|
|
13
|
+
if (hash.length < MEDIA_CONTENT_HASH_PREFIX_LENGTH) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`buildMediaNameFromLocator: contentHash must be at least ${MEDIA_CONTENT_HASH_PREFIX_LENGTH} hex chars`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const hash6 = hash.slice(0, MEDIA_CONTENT_HASH_PREFIX_LENGTH);
|
|
19
|
+
|
|
20
|
+
const normalized = trimmedLocator.replace(/\\/g, "/");
|
|
21
|
+
const base = normalized.split("/").pop() ?? normalized;
|
|
22
|
+
if (base === "" || base === "." || base === "..") {
|
|
23
|
+
throw new Error(`buildMediaNameFromLocator: invalid basename from locator: ${locator}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dot = base.lastIndexOf(".");
|
|
27
|
+
if (dot <= 0 || dot === base.length - 1) {
|
|
28
|
+
return `${base}.${hash6}`;
|
|
29
|
+
}
|
|
30
|
+
const stem = base.slice(0, dot);
|
|
31
|
+
const ext = base.slice(dot + 1);
|
|
32
|
+
return `${stem}.${hash6}.${ext}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { QueryFilter } from "../../core.interface";
|
|
2
|
+
import { CategoryLink } from "../../link/link.interface";
|
|
3
|
+
import type { MediaValue } from "./media.interface";
|
|
4
|
+
|
|
5
|
+
/** Dedup key: `category=link` + `value` + `name` (matches register / find). */
|
|
6
|
+
export function mediaLinkDedupFilters(input: { value: MediaValue; name: string }): QueryFilter[] {
|
|
7
|
+
const name = input.name.trim();
|
|
8
|
+
return [
|
|
9
|
+
{ field: "category", operator: "eq", value: CategoryLink.LINK },
|
|
10
|
+
{ field: "value", operator: "eq", value: input.value },
|
|
11
|
+
{ field: "name", operator: "eq", value: name },
|
|
12
|
+
];
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only media surface — for `src/node.ts` / `target-supabase-sdk/node` only.
|
|
3
|
+
* Never re-export from `../index.ts` or `browser.ts`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type { CheckMediaAvailabilityInput, MediaAvailabilityResult } from "./media.access";
|
|
7
|
+
export { checkMediaAvailability, probeMediaOriginal } from "./media.access";
|
|
8
|
+
export type {
|
|
9
|
+
FindMediaLinkInput,
|
|
10
|
+
RegisterMediaLinkInput,
|
|
11
|
+
RegisterMediaLinkResult,
|
|
12
|
+
} from "./media.service";
|
|
13
|
+
export { findMediaLink, registerMediaLink } from "./media.service";
|