target-supabase-sdk 0.2.18 → 0.2.19
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 +28 -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-DufG7PlX.js → task-queue-CUa40Vb5.js} +117 -2
- package/dist/chunks/task-queue-CUa40Vb5.js.map +1 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +251 -10
- package/dist/node.js.map +1 -1
- package/dist/shared/feed/feed.build.d.ts +33 -0
- package/dist/shared/feed/feed.build.d.ts.map +1 -0
- package/dist/shared/feed/feed.interface.d.ts +35 -0
- package/dist/shared/feed/feed.interface.d.ts.map +1 -0
- package/dist/shared/feed/feed.name.d.ts +8 -0
- package/dist/shared/feed/feed.name.d.ts.map +1 -0
- package/dist/shared/feed/feed.query.d.ts +8 -0
- package/dist/shared/feed/feed.query.d.ts.map +1 -0
- package/dist/shared/feed/index.d.ts +13 -0
- package/dist/shared/feed/index.d.ts.map +1 -0
- package/dist/shared/feed/node/feed.access.d.ts +34 -0
- package/dist/shared/feed/node/feed.access.d.ts.map +1 -0
- package/dist/shared/feed/node/feed.service.d.ts +23 -0
- package/dist/shared/feed/node/feed.service.d.ts.map +1 -0
- package/dist/shared/feed/node/index.d.ts +11 -0
- package/dist/shared/feed/node/index.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/browser.ts +8 -1
- package/src/node.ts +14 -0
- package/src/shared/feed/README.md +163 -0
- package/src/shared/feed/feed.build.ts +101 -0
- package/src/shared/feed/feed.interface.ts +50 -0
- package/src/shared/feed/feed.name.ts +20 -0
- package/src/shared/feed/feed.query.ts +13 -0
- package/src/shared/feed/index.ts +13 -0
- package/src/shared/feed/node/feed.access.ts +134 -0
- package/src/shared/feed/node/feed.service.ts +183 -0
- package/src/shared/feed/node/index.ts +11 -0
- package/dist/chunks/task-queue-DufG7PlX.js.map +0 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TargetDraft } from "../../core.interface";
|
|
2
|
+
import type { Link } from "../../link/link.interface";
|
|
3
|
+
import { type FeedSource, type FeedValue } from "./feed.interface";
|
|
4
|
+
export interface BuildFeedLinkDraftInput {
|
|
5
|
+
value: FeedValue;
|
|
6
|
+
/** Dots are normalized to spaces in {@link buildFeedLinkName} before joining with `feedKey`. */
|
|
7
|
+
platformName: string;
|
|
8
|
+
feedKey: string;
|
|
9
|
+
source: FeedSource;
|
|
10
|
+
locator: string;
|
|
11
|
+
/**
|
|
12
|
+
* Written to `original.storageProvider`. Must be empty when `source` is `.public`;
|
|
13
|
+
* required when `source` is `.local` or `.oss`.
|
|
14
|
+
*/
|
|
15
|
+
storageProvider?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
preview?: string;
|
|
18
|
+
/** Locators for related resources of the same feed; stored on `original.twinList`. */
|
|
19
|
+
twinList?: string[];
|
|
20
|
+
tagList?: string[];
|
|
21
|
+
}
|
|
22
|
+
/** Trim entries and drop empties; `undefined` when input omitted. */
|
|
23
|
+
export declare function normalizeFeedTwinList(twinList?: string[]): string[] | undefined;
|
|
24
|
+
/** `original.storageProvider` from optional input and required `source` tier. */
|
|
25
|
+
export declare function resolveFeedOriginalStorageProvider(source: FeedSource, storageProvider?: string): string;
|
|
26
|
+
/** Merge user tags with required `source`; strip any other feed source tags from user input. */
|
|
27
|
+
export declare function buildFeedTagList(source: FeedSource, tagList?: string[]): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Assemble a feed {@link Link} draft (`category=link`, `loaderKey === value`).
|
|
30
|
+
* Does not validate locator reachability or write to Supabase.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildFeedLinkDraft(input: BuildFeedLinkDraftInput): TargetDraft<Link>;
|
|
33
|
+
//# sourceMappingURL=feed.build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.build.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAGH,KAAK,UAAU,EACf,KAAK,SAAS,EAGjB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,gGAAgG;IAChG,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,CAK/E;AAED,iFAAiF;AACjF,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAcvG;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKjF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,IAAI,CAAC,CA+BpF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed Link protocol — Link rows with value/loaderKey in {@link FEED_VALUES}.
|
|
3
|
+
* No separate Target category; category remains `link`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FEED_VALUES: readonly ["feed.image", "feed.audio", "feed.video", "feed.image-list"];
|
|
6
|
+
/** Discriminator stored in both `Link.value` and `details.loaderKey`. */
|
|
7
|
+
export type FeedValue = (typeof FEED_VALUES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Feed lifecycle tier — exactly one must appear in `tagList` (via `buildFeedTagList`).
|
|
10
|
+
*
|
|
11
|
+
* Leading `.` marks **system** tags (distinct from user-defined `tagList` entries).
|
|
12
|
+
*/
|
|
13
|
+
export declare const FEED_SOURCE_TAGS: readonly [".public", ".local", ".oss"];
|
|
14
|
+
export type FeedSource = (typeof FEED_SOURCE_TAGS)[number];
|
|
15
|
+
/** `registerFeed` only transitions `.public` → `.local` or `.oss`. */
|
|
16
|
+
export declare const FEED_INGEST_SOURCES: readonly [".local", ".oss"];
|
|
17
|
+
export type FeedIngestSource = (typeof FEED_INGEST_SOURCES)[number];
|
|
18
|
+
export declare function isFeedIngestSource(value: string): value is FeedIngestSource;
|
|
19
|
+
/** `Link.details.original` for feed Links. */
|
|
20
|
+
export interface FeedOriginal {
|
|
21
|
+
/**
|
|
22
|
+
* Backend / host id when {@link FeedSource} is `.local` or `.oss` (e.g. `LOCAL_STORAGE_PROVIDER`).
|
|
23
|
+
* Empty string when source is `.public`.
|
|
24
|
+
*/
|
|
25
|
+
storageProvider: string;
|
|
26
|
+
locator: string;
|
|
27
|
+
/**
|
|
28
|
+
* Locators for sibling resources of the same feed (e.g. other media types).
|
|
29
|
+
* Omitted when not supplied on create.
|
|
30
|
+
*/
|
|
31
|
+
twinList?: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function isFeedValue(value: string): value is FeedValue;
|
|
34
|
+
export declare function isFeedSource(value: string): value is FeedSource;
|
|
35
|
+
//# sourceMappingURL=feed.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.interface.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,WAAW,wEAAyE,CAAC;AAElG,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,wCAAyC,CAAC;AAEvE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,6BAA8B,CAAC;AAE/D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,SAAS,CAE7D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Normalize `platformName` so `Link.name` stays `{platform}.{feedKey}` with a single separator dot. */
|
|
2
|
+
export declare function sanitizeFeedPlatformName(platformName: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Default dedup `Link.name` for feed Links: `{platformName}.{feedKey}`.
|
|
5
|
+
* Dots in `platformName` are replaced with spaces before joining (see {@link sanitizeFeedPlatformName}).
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildFeedLinkName(platformName: string, feedKey: string): string;
|
|
8
|
+
//# sourceMappingURL=feed.name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.name.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.name.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAU/E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { QueryFilter } from "../../core.interface";
|
|
2
|
+
import type { FeedValue } from "./feed.interface";
|
|
3
|
+
/** Dedup key: `category=link` + `value` + `name` (matches register / find). */
|
|
4
|
+
export declare function feedLinkDedupFilters(input: {
|
|
5
|
+
value: FeedValue;
|
|
6
|
+
name: string;
|
|
7
|
+
}): QueryFilter[];
|
|
8
|
+
//# sourceMappingURL=feed.query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.query.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,EAAE,CAO7F"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed Link — browser-safe public barrel only.
|
|
3
|
+
*
|
|
4
|
+
* Node-only code lives in `./node/` and is exported from `src/node.ts`,
|
|
5
|
+
* never from this file.
|
|
6
|
+
*/
|
|
7
|
+
export type { BuildFeedLinkDraftInput } from "./feed.build";
|
|
8
|
+
export { buildFeedLinkDraft } from "./feed.build";
|
|
9
|
+
export type { FeedOriginal, FeedSource, FeedValue } from "./feed.interface";
|
|
10
|
+
export { FEED_VALUES, isFeedValue } from "./feed.interface";
|
|
11
|
+
export { buildFeedLinkName } from "./feed.name";
|
|
12
|
+
export { feedLinkDedupFilters } from "./feed.query";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FeedValue } from "../feed.interface";
|
|
2
|
+
export interface CheckFeedLocalAvailabilityInput {
|
|
3
|
+
value: FeedValue;
|
|
4
|
+
storageProvider: string;
|
|
5
|
+
locator: string;
|
|
6
|
+
/** This host id (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). */
|
|
7
|
+
localStorageProvider: string;
|
|
8
|
+
}
|
|
9
|
+
export interface FeedAvailabilityResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/** Absolute path when the locator was resolved on this host. */
|
|
12
|
+
absolutePath?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Local reachability for feed ingest (`.local` source).
|
|
17
|
+
*
|
|
18
|
+
* - Requires `storageProvider === localStorageProvider`.
|
|
19
|
+
* - `locator` must be an absolute path.
|
|
20
|
+
* - `feed.image-list`: directory exists.
|
|
21
|
+
* - Other feed values: non-empty file.
|
|
22
|
+
*
|
|
23
|
+
* **Node-only**
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkFeedLocalAvailability(input: CheckFeedLocalAvailabilityInput): Promise<FeedAvailabilityResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Remote reachability for feed ingest (`.oss` source) via HTTP(S).
|
|
28
|
+
*
|
|
29
|
+
* Tries `HEAD`, then `GET` with `Range: bytes=0-0` when `HEAD` is not allowed.
|
|
30
|
+
*
|
|
31
|
+
* **Node-only**
|
|
32
|
+
*/
|
|
33
|
+
export declare function checkFeedOssAvailability(locator: string): Promise<FeedAvailabilityResult>;
|
|
34
|
+
//# sourceMappingURL=feed.access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.access.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/feed.access.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,+BAA+B;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,oBAAoB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,+BAA+B,GACvC,OAAO,CAAC,sBAAsB,CAAC,CA0CjC;AAoBD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAsB/F"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FeedIngestSource } from "../feed.interface";
|
|
2
|
+
export interface RegisterFeedInput {
|
|
3
|
+
/** Existing feed Link row id (must still be `.public` unless already ingested with same payload). */
|
|
4
|
+
id: string;
|
|
5
|
+
source: FeedIngestSource;
|
|
6
|
+
storageProvider: string;
|
|
7
|
+
locator: string;
|
|
8
|
+
/** Required when `source` is `.local` (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). Ignored for `.oss`. */
|
|
9
|
+
localStorageProvider?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RegisterFeedResult {
|
|
12
|
+
id: string;
|
|
13
|
+
/** `false` when row was already ingested with the same source, storageProvider, and locator. */
|
|
14
|
+
updated: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ingest a `.public` feed Link: validate locator, then promote `tagList` source to `.local` or `.oss`
|
|
18
|
+
* and update `details.original.storageProvider` + `locator`.
|
|
19
|
+
*
|
|
20
|
+
* **Node-only** — under `shared/feed/node/`; do not import from the browser entry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerFeed(input: RegisterFeedInput): Promise<RegisterFeedResult>;
|
|
23
|
+
//# sourceMappingURL=feed.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.service.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/feed.service.ts"],"names":[],"mappings":"AAKA,OAAO,EACH,KAAK,gBAAgB,EAKxB,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,iBAAiB;IAC9B,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,gGAAgG;IAChG,OAAO,EAAE,OAAO,CAAC;CACpB;AAmDD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+FxF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only feed surface — for `src/node.ts` / `target-supabase-sdk/node` only.
|
|
3
|
+
* Never re-export from `../index.ts` or `browser.ts`.
|
|
4
|
+
*/
|
|
5
|
+
export type { FeedIngestSource } from "../feed.interface";
|
|
6
|
+
export { FEED_INGEST_SOURCES, isFeedIngestSource } from "../feed.interface";
|
|
7
|
+
export type { CheckFeedLocalAvailabilityInput, FeedAvailabilityResult } from "./feed.access";
|
|
8
|
+
export { checkFeedLocalAvailability, checkFeedOssAvailability } from "./feed.access";
|
|
9
|
+
export type { RegisterFeedInput, RegisterFeedResult } from "./feed.service";
|
|
10
|
+
export { registerFeed } from "./feed.service";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,+BAA+B,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACrF,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -189,7 +189,14 @@ export {
|
|
|
189
189
|
unregisterService,
|
|
190
190
|
unregisterServiceAtShutdown,
|
|
191
191
|
} from "./service/index";
|
|
192
|
-
|
|
192
|
+
export type { BuildFeedLinkDraftInput, FeedOriginal, FeedSource, FeedValue } from "./shared/feed";
|
|
193
|
+
export {
|
|
194
|
+
buildFeedLinkDraft,
|
|
195
|
+
buildFeedLinkName,
|
|
196
|
+
FEED_VALUES,
|
|
197
|
+
feedLinkDedupFilters,
|
|
198
|
+
isFeedValue,
|
|
199
|
+
} from "./shared/feed";
|
|
193
200
|
export type { FetchInitFactory, FetchRetryOptions } from "./shared/http/fetch-retry";
|
|
194
201
|
export {
|
|
195
202
|
fetchBinaryWithRetry,
|
package/src/node.ts
CHANGED
|
@@ -158,6 +158,20 @@ export {
|
|
|
158
158
|
telegramFileDownloadUrl,
|
|
159
159
|
} from "./node/telegram";
|
|
160
160
|
export { RepoManager } from "./repo/repo-manager";
|
|
161
|
+
export type {
|
|
162
|
+
CheckFeedLocalAvailabilityInput,
|
|
163
|
+
FeedAvailabilityResult,
|
|
164
|
+
FeedIngestSource,
|
|
165
|
+
RegisterFeedInput,
|
|
166
|
+
RegisterFeedResult,
|
|
167
|
+
} from "./shared/feed/node";
|
|
168
|
+
export {
|
|
169
|
+
checkFeedLocalAvailability,
|
|
170
|
+
checkFeedOssAvailability,
|
|
171
|
+
FEED_INGEST_SOURCES,
|
|
172
|
+
isFeedIngestSource,
|
|
173
|
+
registerFeed,
|
|
174
|
+
} from "./shared/feed/node";
|
|
161
175
|
export { runCollectLogTick } from "./shared/log/spool/collector";
|
|
162
176
|
export { LOG_SPOOL_SERVICE_ID_ENV } from "./shared/log/spool/config";
|
|
163
177
|
export type { LogSpoolCoordinator, LogSpoolCoordinatorOptions } from "./shared/log/spool/coordinator";
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Feed Links (`shared/feed`)
|
|
2
|
+
|
|
3
|
+
Feed is a **convention on `Link`**, not a new Target category (`category` stays `link`).
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
src/shared/feed/
|
|
7
|
+
index.ts ← browser-safe barrel ONLY (never export ./node)
|
|
8
|
+
feed.interface.ts
|
|
9
|
+
feed.build.ts
|
|
10
|
+
feed.name.ts
|
|
11
|
+
feed.query.ts
|
|
12
|
+
README.md
|
|
13
|
+
node/ ← Node-only (ingest / availability)
|
|
14
|
+
index.ts ← barrel for src/node.ts only
|
|
15
|
+
feed.access.ts
|
|
16
|
+
feed.service.ts
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Package entry split
|
|
20
|
+
|
|
21
|
+
| Consumer import | What you get | Source |
|
|
22
|
+
|-----------------|--------------|--------|
|
|
23
|
+
| `from "target-supabase-sdk"` | Draft, name, tag merge, dedup filters | `shared/feed/index.ts` → `browser.ts` |
|
|
24
|
+
| `from "target-supabase-sdk/node"` | + `registerFeed`, availability checks | `shared/feed/node/` → `node.ts` |
|
|
25
|
+
|
|
26
|
+
**Do not** re-export `./node` from `index.ts` / `browser.ts`.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Default / browser — create draft, dedup, recognize feed Links
|
|
30
|
+
import {
|
|
31
|
+
buildFeedLinkDraft,
|
|
32
|
+
buildFeedLinkName,
|
|
33
|
+
feedLinkDedupFilters,
|
|
34
|
+
FEED_VALUES,
|
|
35
|
+
isFeedValue,
|
|
36
|
+
} from "target-supabase-sdk";
|
|
37
|
+
|
|
38
|
+
// Node — ingest after .public draft exists
|
|
39
|
+
import { registerFeed, FEED_INGEST_SOURCES } from "target-supabase-sdk/node";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Lifecycle
|
|
45
|
+
|
|
46
|
+
1. **Create (browser or any host)** — `buildFeedLinkDraft` with `source: ".public"`, public `locator` (often a platform URL), `original.storageProvider: ""`. Persist via `postTarget` / `createTarget`.
|
|
47
|
+
2. **Ingest (Node)** — `registerFeed` on an existing row `id`: validate the new locator, then promote `tagList` from `.public` to `.local` or `.oss` and update `original.storageProvider` + `locator`. Preserves `twinList` and other non-source tags.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Link field rules
|
|
52
|
+
|
|
53
|
+
| Field | Rule |
|
|
54
|
+
|-------|------|
|
|
55
|
+
| `value` | `feed.image` \| `feed.audio` \| `feed.video` \| `feed.image-list` |
|
|
56
|
+
| `details.loaderKey` | Always equals `value` |
|
|
57
|
+
| `name` | `{platformName}.{feedKey}` via `buildFeedLinkName` |
|
|
58
|
+
| Dedup | `category=link` + `value` + `name` (`feedLinkDedupFilters`) |
|
|
59
|
+
| `tagList` | Exactly one feed **system** source tag (`.public` \| `.local` \| `.oss`) |
|
|
60
|
+
| `description` / `preview` | Optional; default `""` |
|
|
61
|
+
|
|
62
|
+
### Naming
|
|
63
|
+
|
|
64
|
+
- `sanitizeFeedPlatformName` replaces `.` in `platformName` with spaces before ``${platform}.${feedKey}``.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## `details.original`
|
|
69
|
+
|
|
70
|
+
| Key | `.public` draft | After ingest |
|
|
71
|
+
|-----|-----------------|--------------|
|
|
72
|
+
| `storageProvider` | `""` | Backend id (required for `.local` / `.oss`) |
|
|
73
|
+
| `locator` | Public / platform locator | Validated path (`.local`) or URL (`.oss`) |
|
|
74
|
+
| `twinList` | Optional sibling locators | Unchanged by `registerFeed` |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## System vs user tags
|
|
79
|
+
|
|
80
|
+
Leading `.` marks **system** tags (not user tags).
|
|
81
|
+
|
|
82
|
+
| Tag | Meaning |
|
|
83
|
+
|-----|---------|
|
|
84
|
+
| `.public` | Public-network link; ingest when stable copy exists |
|
|
85
|
+
| `.local` | In Target on this device (`LOCAL_STORAGE_PROVIDER` + absolute file/dir path) |
|
|
86
|
+
| `.oss` | In Target on shared storage (HTTP(S) locator must respond) |
|
|
87
|
+
|
|
88
|
+
`buildFeedTagList` strips `.public` / `.local` / `.oss` from user input and appends the chosen `source` once.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Register flow (`registerFeed`)
|
|
93
|
+
|
|
94
|
+
**Input**
|
|
95
|
+
|
|
96
|
+
| Field | Rule |
|
|
97
|
+
|-------|------|
|
|
98
|
+
| `id` | Existing feed Link row |
|
|
99
|
+
| `source` | `.local` or `.oss` only |
|
|
100
|
+
| `storageProvider` | Non-empty (validated by `resolveFeedOriginalStorageProvider`) |
|
|
101
|
+
| `locator` | New canonical locator after ingest |
|
|
102
|
+
| `localStorageProvider` | **Required** when `source` is `.local` (typically `process.env.LOCAL_STORAGE_PROVIDER`). Ignored for `.oss`. |
|
|
103
|
+
|
|
104
|
+
**Steps**
|
|
105
|
+
|
|
106
|
+
1. Load target by `id`; must be `category=link` with `isFeedValue(value)` and `loaderKey === value`.
|
|
107
|
+
2. **Idempotent** — if `tagList` already has the target `source` and `original` matches `storageProvider` + `locator` → `{ id, updated: false }`.
|
|
108
|
+
3. Require `tagList` to include `.public` (not already ingested with different state).
|
|
109
|
+
4. **Availability**
|
|
110
|
+
- `.local` — `checkFeedLocalAvailability`: `storageProvider === localStorageProvider`, absolute path, file (or directory for `feed.image-list`) exists and is non-empty where applicable.
|
|
111
|
+
- `.oss` — `checkFeedOssAvailability`: `http(s)` URL; `HEAD`, then `GET` with `Range: bytes=0-0` if `HEAD` is not supported.
|
|
112
|
+
5. **Update** — `updateTarget` with optimistic lock `details->original->>storageProvider` eq `""` (still `.public` tier). Sets `tagList` via `buildFeedTagList`, updates `original.storageProvider` and `locator`, keeps `twinList`.
|
|
113
|
+
|
|
114
|
+
**Errors (representative)**
|
|
115
|
+
|
|
116
|
+
| Condition | Result |
|
|
117
|
+
|-----------|--------|
|
|
118
|
+
| Not a feed Link | throw |
|
|
119
|
+
| Already `.local`/`.oss` with different locator | throw |
|
|
120
|
+
| Missing `.public` tag | throw |
|
|
121
|
+
| Locator check fails | throw |
|
|
122
|
+
| Concurrent ingest (storageProvider already set) | optimistic lock failure |
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { registerFeed } from "target-supabase-sdk/node";
|
|
126
|
+
|
|
127
|
+
const { id, updated } = await registerFeed({
|
|
128
|
+
id: feedLinkId,
|
|
129
|
+
source: ".local",
|
|
130
|
+
storageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
|
|
131
|
+
locator: "D:\\\\data\\\\feeds\\\\bilibili.12345.mp4",
|
|
132
|
+
localStorageProvider: process.env.LOCAL_STORAGE_PROVIDER!,
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Env (Node hosts)
|
|
139
|
+
|
|
140
|
+
| Variable | Role |
|
|
141
|
+
|----------|------|
|
|
142
|
+
| `LOCAL_STORAGE_PROVIDER` | This machine’s storage id; pass as both `storageProvider` and `localStorageProvider` for `.local` ingest on this host |
|
|
143
|
+
|
|
144
|
+
Same variable as media register — see `src/shared/media/README.md`.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Design notes / limitations
|
|
149
|
+
|
|
150
|
+
| Topic | Current behavior | Possible follow-up |
|
|
151
|
+
|-------|------------------|-------------------|
|
|
152
|
+
| OSS auth | Only anonymous `HEAD`/`GET` | Signed URLs or provider-specific probes |
|
|
153
|
+
| `.local` on another host | Fails availability (by design, like media) | Remote agent ingest |
|
|
154
|
+
| `twinList` on ingest | Not re-validated | Optional probe per twin |
|
|
155
|
+
| Re-ingest / migrate `.local` → `.oss` | Not supported | New API with lock on prior source tag |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Related
|
|
160
|
+
|
|
161
|
+
- `.cursor/skills/feed-link/SKILL.md`
|
|
162
|
+
- Media register pattern: `src/shared/media/README.md`
|
|
163
|
+
- Optimistic lock: `.cursor/skills/optimistic-lock-update/SKILL.md`
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { TargetDraft } from "../../core.interface";
|
|
2
|
+
import { buildLinkTargetDraft } from "../../link/link.build";
|
|
3
|
+
import type { Link } from "../../link/link.interface";
|
|
4
|
+
import {
|
|
5
|
+
FEED_SOURCE_TAGS,
|
|
6
|
+
type FeedOriginal,
|
|
7
|
+
type FeedSource,
|
|
8
|
+
type FeedValue,
|
|
9
|
+
isFeedSource,
|
|
10
|
+
isFeedValue,
|
|
11
|
+
} from "./feed.interface";
|
|
12
|
+
import { buildFeedLinkName } from "./feed.name";
|
|
13
|
+
|
|
14
|
+
export interface BuildFeedLinkDraftInput {
|
|
15
|
+
value: FeedValue;
|
|
16
|
+
/** Dots are normalized to spaces in {@link buildFeedLinkName} before joining with `feedKey`. */
|
|
17
|
+
platformName: string;
|
|
18
|
+
feedKey: string;
|
|
19
|
+
source: FeedSource;
|
|
20
|
+
locator: string;
|
|
21
|
+
/**
|
|
22
|
+
* Written to `original.storageProvider`. Must be empty when `source` is `.public`;
|
|
23
|
+
* required when `source` is `.local` or `.oss`.
|
|
24
|
+
*/
|
|
25
|
+
storageProvider?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
preview?: string;
|
|
28
|
+
/** Locators for related resources of the same feed; stored on `original.twinList`. */
|
|
29
|
+
twinList?: string[];
|
|
30
|
+
tagList?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Trim entries and drop empties; `undefined` when input omitted. */
|
|
34
|
+
export function normalizeFeedTwinList(twinList?: string[]): string[] | undefined {
|
|
35
|
+
if (twinList === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return twinList.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `original.storageProvider` from optional input and required `source` tier. */
|
|
42
|
+
export function resolveFeedOriginalStorageProvider(source: FeedSource, storageProvider?: string): string {
|
|
43
|
+
const trimmed = storageProvider?.trim() ?? "";
|
|
44
|
+
if (source === ".public") {
|
|
45
|
+
if (trimmed !== "") {
|
|
46
|
+
throw new Error("resolveFeedOriginalStorageProvider: storageProvider must be empty when source is .public");
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
if (trimmed === "") {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"resolveFeedOriginalStorageProvider: storageProvider is required when source is .local or .oss",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Merge user tags with required `source`; strip any other feed source tags from user input. */
|
|
59
|
+
export function buildFeedTagList(source: FeedSource, tagList?: string[]): string[] {
|
|
60
|
+
const sourceSet = new Set<string>(FEED_SOURCE_TAGS);
|
|
61
|
+
const user = tagList ?? [];
|
|
62
|
+
const filtered = user.filter((tag) => !sourceSet.has(tag));
|
|
63
|
+
return [...filtered, source];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Assemble a feed {@link Link} draft (`category=link`, `loaderKey === value`).
|
|
68
|
+
* Does not validate locator reachability or write to Supabase.
|
|
69
|
+
*/
|
|
70
|
+
export function buildFeedLinkDraft(input: BuildFeedLinkDraftInput): TargetDraft<Link> {
|
|
71
|
+
if (!isFeedValue(input.value)) {
|
|
72
|
+
throw new Error(`buildFeedLinkDraft: unsupported feed value: ${String(input.value)}`);
|
|
73
|
+
}
|
|
74
|
+
if (!isFeedSource(input.source)) {
|
|
75
|
+
throw new Error(`buildFeedLinkDraft: unsupported feed source: ${String(input.source)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const name = buildFeedLinkName(input.platformName, input.feedKey);
|
|
79
|
+
const locator = input.locator.trim();
|
|
80
|
+
if (locator === "") {
|
|
81
|
+
throw new Error("buildFeedLinkDraft: locator is empty");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const twinList = normalizeFeedTwinList(input.twinList);
|
|
85
|
+
const storageProvider = resolveFeedOriginalStorageProvider(input.source, input.storageProvider);
|
|
86
|
+
const original: FeedOriginal = {
|
|
87
|
+
storageProvider,
|
|
88
|
+
locator,
|
|
89
|
+
...(twinList !== undefined ? { twinList } : {}),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return buildLinkTargetDraft({
|
|
93
|
+
name,
|
|
94
|
+
value: input.value,
|
|
95
|
+
description: input.description ?? "",
|
|
96
|
+
preview: input.preview ?? "",
|
|
97
|
+
loaderKey: input.value,
|
|
98
|
+
tagList: buildFeedTagList(input.source, input.tagList),
|
|
99
|
+
original,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed Link protocol — Link rows with value/loaderKey in {@link FEED_VALUES}.
|
|
3
|
+
* No separate Target category; category remains `link`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const FEED_VALUES = ["feed.image", "feed.audio", "feed.video", "feed.image-list"] as const;
|
|
7
|
+
|
|
8
|
+
/** Discriminator stored in both `Link.value` and `details.loaderKey`. */
|
|
9
|
+
export type FeedValue = (typeof FEED_VALUES)[number];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Feed lifecycle tier — exactly one must appear in `tagList` (via `buildFeedTagList`).
|
|
13
|
+
*
|
|
14
|
+
* Leading `.` marks **system** tags (distinct from user-defined `tagList` entries).
|
|
15
|
+
*/
|
|
16
|
+
export const FEED_SOURCE_TAGS = [".public", ".local", ".oss"] as const;
|
|
17
|
+
|
|
18
|
+
export type FeedSource = (typeof FEED_SOURCE_TAGS)[number];
|
|
19
|
+
|
|
20
|
+
/** `registerFeed` only transitions `.public` → `.local` or `.oss`. */
|
|
21
|
+
export const FEED_INGEST_SOURCES = [".local", ".oss"] as const;
|
|
22
|
+
|
|
23
|
+
export type FeedIngestSource = (typeof FEED_INGEST_SOURCES)[number];
|
|
24
|
+
|
|
25
|
+
export function isFeedIngestSource(value: string): value is FeedIngestSource {
|
|
26
|
+
return (FEED_INGEST_SOURCES as readonly string[]).includes(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `Link.details.original` for feed Links. */
|
|
30
|
+
export interface FeedOriginal {
|
|
31
|
+
/**
|
|
32
|
+
* Backend / host id when {@link FeedSource} is `.local` or `.oss` (e.g. `LOCAL_STORAGE_PROVIDER`).
|
|
33
|
+
* Empty string when source is `.public`.
|
|
34
|
+
*/
|
|
35
|
+
storageProvider: string;
|
|
36
|
+
locator: string;
|
|
37
|
+
/**
|
|
38
|
+
* Locators for sibling resources of the same feed (e.g. other media types).
|
|
39
|
+
* Omitted when not supplied on create.
|
|
40
|
+
*/
|
|
41
|
+
twinList?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isFeedValue(value: string): value is FeedValue {
|
|
45
|
+
return (FEED_VALUES as readonly string[]).includes(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isFeedSource(value: string): value is FeedSource {
|
|
49
|
+
return (FEED_SOURCE_TAGS as readonly string[]).includes(value);
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Normalize `platformName` so `Link.name` stays `{platform}.{feedKey}` with a single separator dot. */
|
|
2
|
+
export function sanitizeFeedPlatformName(platformName: string): string {
|
|
3
|
+
return platformName.trim().replaceAll(".", " ");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default dedup `Link.name` for feed Links: `{platformName}.{feedKey}`.
|
|
8
|
+
* Dots in `platformName` are replaced with spaces before joining (see {@link sanitizeFeedPlatformName}).
|
|
9
|
+
*/
|
|
10
|
+
export function buildFeedLinkName(platformName: string, feedKey: string): string {
|
|
11
|
+
const platform = sanitizeFeedPlatformName(platformName);
|
|
12
|
+
const key = feedKey.trim();
|
|
13
|
+
if (platform === "") {
|
|
14
|
+
throw new Error("buildFeedLinkName: platformName is empty");
|
|
15
|
+
}
|
|
16
|
+
if (key === "") {
|
|
17
|
+
throw new Error("buildFeedLinkName: feedKey is empty");
|
|
18
|
+
}
|
|
19
|
+
return `${platform}.${key}`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { QueryFilter } from "../../core.interface";
|
|
2
|
+
import { CategoryLink } from "../../link/link.interface";
|
|
3
|
+
import type { FeedValue } from "./feed.interface";
|
|
4
|
+
|
|
5
|
+
/** Dedup key: `category=link` + `value` + `name` (matches register / find). */
|
|
6
|
+
export function feedLinkDedupFilters(input: { value: FeedValue; 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
|
+
* Feed Link — browser-safe public barrel only.
|
|
3
|
+
*
|
|
4
|
+
* Node-only code lives in `./node/` and is exported from `src/node.ts`,
|
|
5
|
+
* never from this file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { BuildFeedLinkDraftInput } from "./feed.build";
|
|
9
|
+
export { buildFeedLinkDraft } from "./feed.build";
|
|
10
|
+
export type { FeedOriginal, FeedSource, FeedValue } from "./feed.interface";
|
|
11
|
+
export { FEED_VALUES, isFeedValue } from "./feed.interface";
|
|
12
|
+
export { buildFeedLinkName } from "./feed.name";
|
|
13
|
+
export { feedLinkDedupFilters } from "./feed.query";
|