target-supabase-sdk 0.2.18 → 0.2.20

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 (52) hide show
  1. package/README.md +28 -0
  2. package/dist/browser.d.ts +4 -0
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/browser.js +1 -1
  5. package/dist/chunks/{task-queue-DufG7PlX.js → task-queue-CqmIVCBi.js} +165 -2
  6. package/dist/chunks/task-queue-CqmIVCBi.js.map +1 -0
  7. package/dist/node.d.ts +2 -0
  8. package/dist/node.d.ts.map +1 -1
  9. package/dist/node.js +251 -10
  10. package/dist/node.js.map +1 -1
  11. package/dist/shared/feed/feed.build.d.ts +33 -0
  12. package/dist/shared/feed/feed.build.d.ts.map +1 -0
  13. package/dist/shared/feed/feed.interface.d.ts +35 -0
  14. package/dist/shared/feed/feed.interface.d.ts.map +1 -0
  15. package/dist/shared/feed/feed.name.d.ts +8 -0
  16. package/dist/shared/feed/feed.name.d.ts.map +1 -0
  17. package/dist/shared/feed/feed.query.d.ts +8 -0
  18. package/dist/shared/feed/feed.query.d.ts.map +1 -0
  19. package/dist/shared/feed/index.d.ts +13 -0
  20. package/dist/shared/feed/index.d.ts.map +1 -0
  21. package/dist/shared/feed/node/feed.access.d.ts +34 -0
  22. package/dist/shared/feed/node/feed.access.d.ts.map +1 -0
  23. package/dist/shared/feed/node/feed.service.d.ts +23 -0
  24. package/dist/shared/feed/node/feed.service.d.ts.map +1 -0
  25. package/dist/shared/feed/node/index.d.ts +11 -0
  26. package/dist/shared/feed/node/index.d.ts.map +1 -0
  27. package/dist/shared/review/index.d.ts +7 -0
  28. package/dist/shared/review/index.d.ts.map +1 -0
  29. package/dist/shared/review/review.build.d.ts +19 -0
  30. package/dist/shared/review/review.build.d.ts.map +1 -0
  31. package/dist/shared/review/review.interface.d.ts +14 -0
  32. package/dist/shared/review/review.interface.d.ts.map +1 -0
  33. package/dist/shared/review/review.query.d.ts +7 -0
  34. package/dist/shared/review/review.query.d.ts.map +1 -0
  35. package/package.json +1 -1
  36. package/src/browser.ts +10 -1
  37. package/src/node.ts +14 -0
  38. package/src/shared/feed/README.md +163 -0
  39. package/src/shared/feed/feed.build.ts +101 -0
  40. package/src/shared/feed/feed.interface.ts +50 -0
  41. package/src/shared/feed/feed.name.ts +20 -0
  42. package/src/shared/feed/feed.query.ts +13 -0
  43. package/src/shared/feed/index.ts +13 -0
  44. package/src/shared/feed/node/feed.access.ts +134 -0
  45. package/src/shared/feed/node/feed.service.ts +183 -0
  46. package/src/shared/feed/node/index.ts +11 -0
  47. package/src/shared/review/README.md +128 -0
  48. package/src/shared/review/index.ts +7 -0
  49. package/src/shared/review/review.build.ts +39 -0
  50. package/src/shared/review/review.interface.ts +19 -0
  51. package/src/shared/review/review.query.ts +16 -0
  52. 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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Review Link — browser-safe barrel.
3
+ */
4
+ export type { BuildReviewLinkDraftInput } from "./review.build";
5
+ export { buildReviewLinkDraft } from "./review.build";
6
+ export { reviewLinksForTargetFilters } from "./review.query";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/review/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { TargetDraft } from "../../core.interface";
2
+ import type { Link } from "../../link/link.interface";
3
+ import { type ReviewValue } from "./review.interface";
4
+ export interface BuildReviewLinkDraftInput {
5
+ /** Must be {@link REVIEW_VALUE}. */
6
+ value: ReviewValue;
7
+ /** Associated Target id — written to `Link.name`. */
8
+ refId: string;
9
+ /** Interaction payload — written to `Link.details.original`. */
10
+ original: unknown;
11
+ tagList?: string[];
12
+ description?: string;
13
+ }
14
+ /**
15
+ * Assemble a review {@link Link} draft (`category=link`, `loaderKey === value === "review"`).
16
+ * Does not validate `refId` existence or write to Supabase.
17
+ */
18
+ export declare function buildReviewLinkDraft(input: BuildReviewLinkDraftInput): TargetDraft<Link>;
19
+ //# sourceMappingURL=review.build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.build.d.ts","sourceRoot":"","sources":["../../../src/shared/review/review.build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnF,MAAM,WAAW,yBAAyB;IACtC,oCAAoC;IACpC,KAAK,EAAE,WAAW,CAAC;IACnB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,WAAW,CAAC,IAAI,CAAC,CAkBxF"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Review Link protocol — Link rows with `value` / `loaderKey` === {@link REVIEW_VALUE}.
3
+ * No separate Target category; category remains `link`.
4
+ *
5
+ * A Review records one user interaction with a subject Target (`Link.name` = that Target's id).
6
+ * Many review Links may share the same subject id.
7
+ */
8
+ export declare const REVIEW_VALUE: "review";
9
+ /** Discriminator stored in both `Link.value` and `details.loaderKey`. */
10
+ export type ReviewValue = typeof REVIEW_VALUE;
11
+ /** Caller-defined payload stored in `Link.details.original`. */
12
+ export type ReviewOriginal = unknown;
13
+ export declare function isReviewValue(value: string): value is ReviewValue;
14
+ //# sourceMappingURL=review.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.interface.d.ts","sourceRoot":"","sources":["../../../src/shared/review/review.interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY,EAAG,QAAiB,CAAC;AAE9C,yEAAyE;AACzE,MAAM,MAAM,WAAW,GAAG,OAAO,YAAY,CAAC;AAE9C,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC;AAErC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAEjE"}
@@ -0,0 +1,7 @@
1
+ import type { QueryFilter } from "../../core.interface";
2
+ /**
3
+ * Filters for all review Links whose subject Target is `refId` (`Link.name === refId`).
4
+ * Many rows may match — the SDK does not define dedup for reviews.
5
+ */
6
+ export declare function reviewLinksForTargetFilters(refId: string): QueryFilter[];
7
+ //# sourceMappingURL=review.query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.query.d.ts","sourceRoot":"","sources":["../../../src/shared/review/review.query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAOxE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "target-supabase-sdk",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Supabase client initializer and data access APIs",
5
5
  "license": "MIT",
6
6
  "author": "KyLoc20",
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,
@@ -234,6 +241,8 @@ export {
234
241
  MEDIA_VALUES,
235
242
  mediaLinkDedupFilters,
236
243
  } from "./shared/media";
244
+ export type { BuildReviewLinkDraftInput } from "./shared/review";
245
+ export { buildReviewLinkDraft, reviewLinksForTargetFilters } from "./shared/review";
237
246
  export { isHttpUrl, resolveFetchUrl } from "./shared/utils/fetch-url";
238
247
  export { getValueAtPath } from "./shared/utils/get-value-at-path";
239
248
  export type {
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
+ }