target-supabase-sdk 0.2.19 → 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.
package/dist/node.js CHANGED
@@ -6,8 +6,8 @@ export { C as CLI_PROD_FLAG, S as SERVICE_ENV_PROFILE_ENV_KEY, w as SERVICE_ENV_
6
6
  import { randomBytes, createHash } from 'node:crypto';
7
7
  import { readFile, unlink, mkdir, writeFile, rename, access, stat, readdir, constants } from 'node:fs/promises';
8
8
  import { dirname, resolve, join, isAbsolute } from 'node:path';
9
- import { bv as sha256Hex, bB as getErrorMessage, bC as LOG_TOPIC_NODE, aZ as postRegisterNode, aD as patchChangeNodeStatus, N as NodeStatus, aO as patchStopNode, aL as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, bl as runRegistrySlotGuardCheck, bb as registrySlotRuntimePatchFromGuardResult, c as CategoryNode, a0 as evaluateBusyNodeLiveness, aN as patchServiceRuntime, as as isLogEntry, I as claimServiceRegistrySlot, m as ServiceRegistryError, a3 as fetchWithRetry, av as isRetryableHttpStatus, a2 as fetchBinaryWithRetry, au as isOpaqueChunkUrl, e as CategoryRepo, ap as isHttpUrl, bD as isFeedIngestSource, bE as resolveFeedOriginalStorageProvider, b as CategoryLink, bF as buildFeedTagList, ao as isFeedValue, am as guessMediaMime, M as MEDIA_CONTENT_HASH_ALGORITHM, at as isMediaValue, ax as mediaLinkDedupFilters, B as buildMediaLinkDraft, R as ResultCode, bG as LOG_TOPIC_TASK, o as TASK_REPO_USAGE, ag as getScanRemoteRepoValues, aH as patchClaimTask, bH as CRITICAL_UNKNOWN_TASK_TRACE_ID, aF as patchChangeTaskStatus, q as TaskStatusAction, bI as toError, b5 as postTaskSchema, bJ as createTaskRow, p as TaskStatus } from './chunks/task-queue-CUa40Vb5.js';
10
- export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, d as CategoryParcel, f as CategoryService, g as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, bK as FEED_INGEST_SOURCES, F as FEED_VALUES, L as LINK_MANIFEST_VERSION, h as LIST_MANIFEST_VERSION, i as MEDIA_CONTENT_HASH_PREFIX_LENGTH, j as MEDIA_VALUES, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, k as ParcelManager, l as PostLinkValidator, S as ServiceLifecycleStatus, n as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, r as apiDetailsSchema, s as appendSystemRegistryEmptySlots, t as assertRegistrySlotAvailable, u as assertRegistrySlotOwner, v as buildEmptyServiceSlots, w as buildFeedLinkDraft, x as buildFeedLinkName, y as buildLinkTargetDraft, z as buildListTargetDraft, G as buildMediaNameFromLocator, H as buildSystemRegistryConfigDetails, J as classifyNetworkError, K as countTasksByType, O as createActiveServiceLifecycle, Q as createClaimedRegistrySlotRuntimeState, U as createStorageProviderRegistry, V as createUploadTracker, W as decodeLogBatch, X as decodeLogBatchList, Y as defaultL3ServiceDetails, Z as deleteParcel, _ as deleteParcelSchema, $ as escapeIlikePattern, a1 as feedLinkDedupFilters, a4 as fieldDefinitionSchema, a5 as flattenLogBatch, a6 as flattenLogBatches, a7 as formatNetworkError, a8 as getApi, a9 as getApiSchema, aa as getConfig, ab as getConfigSchema, ac as getExtraction, ad as getExtractionSchema, ae as getParcel, af as getParcelSchema, ah as getScanRemoteRepoValuesSchema, ai as getService, aj as getServiceSchema, ak as getTargetSystemRegistry, al as getValueAtPath, an as installChunkFetchRegistry, aq as isLocalFilesystemPath, ar as isLogBatchList, aw as linkTargetDraftBuildInputSchema, ay as parcelDetailsSchema, az as parseLogBatchMeta, aA as parseProviderPrefixedUrl, aB as parseServiceSlot, aC as parseServiceSlots, aE as patchChangeNodeStatusSchema, aG as patchChangeTaskStatusSchema, aI as patchClaimTaskSchema, aJ as patchExtractionObjects, aK as patchExtractionObjectsSchema, aM as patchNodeHeartBeatSchema, aP as patchStopNodeSchema, aQ as patchTaskProgress, aR as patchTaskProgressSchema, aS as postApi, aT as postApiSchema, aU as postExtraction, aV as postExtractionSchema, aW as postLinkCreate, aX as postParcel, aY as postParcelSchema, a_ as postRegisterNodeSchema, a$ as postService, b0 as postServiceInstance, b1 as postServiceSchema, b2 as postSystemRegistryConfig, b3 as postSystemRegistryConfigSchema, b4 as postTask, b6 as postTaskWithRetry, b7 as publishParcel, b8 as registerProviderChunkResolver, b9 as registerService, ba as registerServiceAtStartup, bc as releaseSystemRegistrySlots, bd as releaseSystemRegistrySlotsByServiceId, be as requestHasBody, bf as resetSystemRegistryConfig, bg as resetSystemRegistryConfigSchema, bh as resolveActiveRegistryServiceId, bi as resolveFetchUrl, bj as restoreParcel, bk as restoreParcelById, bm as sanitizeFileName, bn as sanitizeParcelFileName, bo as scanExtractionList, bp as scanExtractionListSchema, bq as schemaDefinitionSchema, br as serviceDetailsSchema, bs as serviceLifecycleSchema, bt as serviceNodeSnapshotSchema, bu as serviceRuntimeSchema, bw as summarizeTaskQueue, bx as systemRegistrySeedSlotSchema, by as toPostgrestTextArrayLiteral, bz as unregisterService, bA as unregisterServiceAtShutdown } from './chunks/task-queue-CUa40Vb5.js';
9
+ import { bx as sha256Hex, bD as getErrorMessage, bE as LOG_TOPIC_NODE, a_ as postRegisterNode, aE as patchChangeNodeStatus, N as NodeStatus, aP as patchStopNode, aM as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, bn as runRegistrySlotGuardCheck, bc as registrySlotRuntimePatchFromGuardResult, c as CategoryNode, a1 as evaluateBusyNodeLiveness, aO as patchServiceRuntime, at as isLogEntry, J as claimServiceRegistrySlot, m as ServiceRegistryError, a4 as fetchWithRetry, aw as isRetryableHttpStatus, a3 as fetchBinaryWithRetry, av as isOpaqueChunkUrl, e as CategoryRepo, aq as isHttpUrl, bF as isFeedIngestSource, bG as resolveFeedOriginalStorageProvider, b as CategoryLink, bH as buildFeedTagList, ap as isFeedValue, an as guessMediaMime, M as MEDIA_CONTENT_HASH_ALGORITHM, au as isMediaValue, ay as mediaLinkDedupFilters, B as buildMediaLinkDraft, R as ResultCode, bI as LOG_TOPIC_TASK, o as TASK_REPO_USAGE, ah as getScanRemoteRepoValues, aI as patchClaimTask, bJ as CRITICAL_UNKNOWN_TASK_TRACE_ID, aG as patchChangeTaskStatus, q as TaskStatusAction, bK as toError, b6 as postTaskSchema, bL as createTaskRow, p as TaskStatus } from './chunks/task-queue-CqmIVCBi.js';
10
+ export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, d as CategoryParcel, f as CategoryService, g as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, bM as FEED_INGEST_SOURCES, F as FEED_VALUES, L as LINK_MANIFEST_VERSION, h as LIST_MANIFEST_VERSION, i as MEDIA_CONTENT_HASH_PREFIX_LENGTH, j as MEDIA_VALUES, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, k as ParcelManager, l as PostLinkValidator, S as ServiceLifecycleStatus, n as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, r as apiDetailsSchema, s as appendSystemRegistryEmptySlots, t as assertRegistrySlotAvailable, u as assertRegistrySlotOwner, v as buildEmptyServiceSlots, w as buildFeedLinkDraft, x as buildFeedLinkName, y as buildLinkTargetDraft, z as buildListTargetDraft, G as buildMediaNameFromLocator, H as buildReviewLinkDraft, I as buildSystemRegistryConfigDetails, K as classifyNetworkError, O as countTasksByType, Q as createActiveServiceLifecycle, U as createClaimedRegistrySlotRuntimeState, V as createStorageProviderRegistry, W as createUploadTracker, X as decodeLogBatch, Y as decodeLogBatchList, Z as defaultL3ServiceDetails, _ as deleteParcel, $ as deleteParcelSchema, a0 as escapeIlikePattern, a2 as feedLinkDedupFilters, a5 as fieldDefinitionSchema, a6 as flattenLogBatch, a7 as flattenLogBatches, a8 as formatNetworkError, a9 as getApi, aa as getApiSchema, ab as getConfig, ac as getConfigSchema, ad as getExtraction, ae as getExtractionSchema, af as getParcel, ag as getParcelSchema, ai as getScanRemoteRepoValuesSchema, aj as getService, ak as getServiceSchema, al as getTargetSystemRegistry, am as getValueAtPath, ao as installChunkFetchRegistry, ar as isLocalFilesystemPath, as as isLogBatchList, ax as linkTargetDraftBuildInputSchema, az as parcelDetailsSchema, aA as parseLogBatchMeta, aB as parseProviderPrefixedUrl, aC as parseServiceSlot, aD as parseServiceSlots, aF as patchChangeNodeStatusSchema, aH as patchChangeTaskStatusSchema, aJ as patchClaimTaskSchema, aK as patchExtractionObjects, aL as patchExtractionObjectsSchema, aN as patchNodeHeartBeatSchema, aQ as patchStopNodeSchema, aR as patchTaskProgress, aS as patchTaskProgressSchema, aT as postApi, aU as postApiSchema, aV as postExtraction, aW as postExtractionSchema, aX as postLinkCreate, aY as postParcel, aZ as postParcelSchema, a$ as postRegisterNodeSchema, b0 as postService, b1 as postServiceInstance, b2 as postServiceSchema, b3 as postSystemRegistryConfig, b4 as postSystemRegistryConfigSchema, b5 as postTask, b7 as postTaskWithRetry, b8 as publishParcel, b9 as registerProviderChunkResolver, ba as registerService, bb as registerServiceAtStartup, bd as releaseSystemRegistrySlots, be as releaseSystemRegistrySlotsByServiceId, bf as requestHasBody, bg as resetSystemRegistryConfig, bh as resetSystemRegistryConfigSchema, bi as resolveActiveRegistryServiceId, bj as resolveFetchUrl, bk as restoreParcel, bl as restoreParcelById, bm as reviewLinksForTargetFilters, bo as sanitizeFileName, bp as sanitizeParcelFileName, bq as scanExtractionList, br as scanExtractionListSchema, bs as schemaDefinitionSchema, bt as serviceDetailsSchema, bu as serviceLifecycleSchema, bv as serviceNodeSnapshotSchema, bw as serviceRuntimeSchema, by as summarizeTaskQueue, bz as systemRegistrySeedSlotSchema, bA as toPostgrestTextArrayLiteral, bB as unregisterService, bC as unregisterServiceAtShutdown } from './chunks/task-queue-CqmIVCBi.js';
11
11
  import { spawn } from 'node:child_process';
12
12
  import { ProxyAgent } from 'undici';
13
13
  import { tmpdir } from 'node:os';
@@ -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.19",
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
@@ -241,6 +241,8 @@ export {
241
241
  MEDIA_VALUES,
242
242
  mediaLinkDedupFilters,
243
243
  } from "./shared/media";
244
+ export type { BuildReviewLinkDraftInput } from "./shared/review";
245
+ export { buildReviewLinkDraft, reviewLinksForTargetFilters } from "./shared/review";
244
246
  export { isHttpUrl, resolveFetchUrl } from "./shared/utils/fetch-url";
245
247
  export { getValueAtPath } from "./shared/utils/get-value-at-path";
246
248
  export type {
@@ -0,0 +1,128 @@
1
+ # Review Links (`shared/review`)
2
+
3
+ Review is a **convention on `Link`**, not a new Target category (`category` stays `link`).
4
+
5
+ A **Review** records **one user interaction** with a **subject Target** (for example: open, play, complete, rate, dismiss). **Each interaction creates a new Review row.** The subject is linked by storing the Target id on `Link.name` (`refId` at create time). **Many review Links may share the same `refId`** — the SDK does not define dedup or idempotent create for reviews.
6
+
7
+ All review code lives under **`src/shared/review/`**. There is no Node-only ingest layer (unlike feed/media register flows) — create drafts on any host and persist with `postTarget` / `createTarget`.
8
+
9
+ ```text
10
+ src/shared/review/
11
+ index.ts
12
+ review.interface.ts
13
+ review.build.ts
14
+ review.query.ts
15
+ README.md
16
+ ```
17
+
18
+ ## Package entry
19
+
20
+ | Consumer import | What you get |
21
+ |-----------------|--------------|
22
+ | `from "target-supabase-sdk"` | Draft builder, constants, list filters |
23
+
24
+ ```typescript
25
+ import {
26
+ buildReviewLinkDraft,
27
+ reviewLinksForTargetFilters,
28
+ } from "target-supabase-sdk";
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Meaning
34
+
35
+ | Concept | Where it lives |
36
+ |---------|----------------|
37
+ | Subject Target | `Link.name` — the associated Target id (`refId` when building the draft) |
38
+ | Interaction body | `Link.details.original` — caller-defined JSON-serializable payload |
39
+ | Review kind | Fixed `Link.value` / `details.loaderKey` = `"review"` |
40
+ | Labels | Optional `tagList` (e.g. interaction type, client, experiment) |
41
+
42
+ Each persisted Review is its **own** Target row (its own `id`). Repeating the same `refId` on `name` is expected when listing interaction history for one subject.
43
+
44
+ `details.original` is intentionally **untyped** (`unknown`) in the SDK so products can store scores, dwell time, UI state, model output, etc., without a shared schema in this package.
45
+
46
+ ---
47
+
48
+ ## Create input → Link mapping
49
+
50
+ Built via `buildReviewLinkDraft` → `buildLinkTargetDraft`.
51
+
52
+ | Create field | Required | Link field |
53
+ |--------------|----------|------------|
54
+ | `value` | yes | `value` — must be `"review"` |
55
+ | `refId` | yes | `name` — subject Target id |
56
+ | `original` | yes | `details.original` |
57
+ | `tagList` | no | `tagList` (default `[]`) |
58
+ | `description` | no | `details.description` (default `""`) |
59
+
60
+ Fixed at build time (not on create input):
61
+
62
+ | Link field | Value |
63
+ |------------|--------|
64
+ | `category` | `link` |
65
+ | `details.loaderKey` | `"review"` (same as `value`) |
66
+ | `details.manifestVersion` | `0` |
67
+ | `details.preview` | `""` |
68
+
69
+ ---
70
+
71
+ ## Example
72
+
73
+ ```typescript
74
+ import { buildReviewLinkDraft } from "target-supabase-sdk";
75
+ import { postTarget } from "target-supabase-sdk"; // or your create API
76
+
77
+ const draft = buildReviewLinkDraft({
78
+ value: "review",
79
+ refId: subjectTargetId,
80
+ original: {
81
+ action: "complete",
82
+ progress: 1,
83
+ at: new Date().toISOString(),
84
+ },
85
+ tagList: ["client:web"],
86
+ });
87
+
88
+ await postTarget(draft);
89
+ ```
90
+
91
+ Every interaction: build a new draft (same `refId` is fine) and insert again.
92
+
93
+ ---
94
+
95
+ ## Query
96
+
97
+ List review Links for one subject Target:
98
+
99
+ ```typescript
100
+ import { reviewLinksForTargetFilters, scanTargetList } from "target-supabase-sdk";
101
+
102
+ const reviews = await scanTargetList({
103
+ filters: reviewLinksForTargetFilters(subjectTargetId),
104
+ });
105
+ ```
106
+
107
+ Filters: `category=link`, `value=review`, `name=refId`. Use pagination / `scanTargetList` when history is long.
108
+
109
+ There is **no** SDK helper for dedup or “find existing review” — callers always create new rows unless they add their own policy.
110
+
111
+ ---
112
+
113
+ ## Recognizing review Links
114
+
115
+ ```typescript
116
+ if (link.category === "link" && link.value === "review") {
117
+ const subjectTargetId = link.name;
118
+ const payload = link.details.original;
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Related
125
+
126
+ - `src/link/link.build.ts` — `buildLinkTargetDraft`
127
+ - `src/shared/media/README.md` — same Link-convention pattern for media
128
+ - `src/shared/feed/README.md` — feed Links with richer lifecycle
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Review Link — browser-safe barrel.
3
+ */
4
+
5
+ export type { BuildReviewLinkDraftInput } from "./review.build";
6
+ export { buildReviewLinkDraft } from "./review.build";
7
+ export { reviewLinksForTargetFilters } from "./review.query";
@@ -0,0 +1,39 @@
1
+ import type { TargetDraft } from "../../core.interface";
2
+ import { buildLinkTargetDraft } from "../../link/link.build";
3
+ import type { Link } from "../../link/link.interface";
4
+ import { isReviewValue, REVIEW_VALUE, type ReviewValue } from "./review.interface";
5
+
6
+ export interface BuildReviewLinkDraftInput {
7
+ /** Must be {@link REVIEW_VALUE}. */
8
+ value: ReviewValue;
9
+ /** Associated Target id — written to `Link.name`. */
10
+ refId: string;
11
+ /** Interaction payload — written to `Link.details.original`. */
12
+ original: unknown;
13
+ tagList?: string[];
14
+ description?: string;
15
+ }
16
+
17
+ /**
18
+ * Assemble a review {@link Link} draft (`category=link`, `loaderKey === value === "review"`).
19
+ * Does not validate `refId` existence or write to Supabase.
20
+ */
21
+ export function buildReviewLinkDraft(input: BuildReviewLinkDraftInput): TargetDraft<Link> {
22
+ if (!isReviewValue(input.value)) {
23
+ throw new Error(`buildReviewLinkDraft: unsupported review value: ${String(input.value)}`);
24
+ }
25
+ const refId = input.refId.trim();
26
+ if (refId === "") {
27
+ throw new Error("buildReviewLinkDraft: refId is empty");
28
+ }
29
+
30
+ return buildLinkTargetDraft({
31
+ name: refId,
32
+ value: REVIEW_VALUE,
33
+ description: input.description ?? "",
34
+ preview: "",
35
+ loaderKey: REVIEW_VALUE,
36
+ tagList: input.tagList ?? [],
37
+ original: input.original,
38
+ });
39
+ }
@@ -0,0 +1,19 @@
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
+
9
+ export const REVIEW_VALUE = "review" as const;
10
+
11
+ /** Discriminator stored in both `Link.value` and `details.loaderKey`. */
12
+ export type ReviewValue = typeof REVIEW_VALUE;
13
+
14
+ /** Caller-defined payload stored in `Link.details.original`. */
15
+ export type ReviewOriginal = unknown;
16
+
17
+ export function isReviewValue(value: string): value is ReviewValue {
18
+ return value === REVIEW_VALUE;
19
+ }
@@ -0,0 +1,16 @@
1
+ import type { QueryFilter } from "../../core.interface";
2
+ import { CategoryLink } from "../../link/link.interface";
3
+ import { REVIEW_VALUE } from "./review.interface";
4
+
5
+ /**
6
+ * Filters for all review Links whose subject Target is `refId` (`Link.name === refId`).
7
+ * Many rows may match — the SDK does not define dedup for reviews.
8
+ */
9
+ export function reviewLinksForTargetFilters(refId: string): QueryFilter[] {
10
+ const name = refId.trim();
11
+ return [
12
+ { field: "category", operator: "eq", value: CategoryLink.LINK },
13
+ { field: "value", operator: "eq", value: REVIEW_VALUE },
14
+ { field: "name", operator: "eq", value: name },
15
+ ];
16
+ }