target-supabase-sdk 0.2.6 → 0.2.8

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.
@@ -112,13 +112,61 @@ export declare const postTarget: (payload: PostTargetPayload) => Promise<import(
112
112
  export interface PatchTargetPayload extends PostTargetPayload {
113
113
  id: string;
114
114
  }
115
+ export declare const OPTIMISTIC_LOCK_FAILED_MESSAGE = "[updateTargetDetails] Optimistic lock failed: target no longer matches expected state.";
116
+ /** Thrown when UPDATE matched no row and the target id is gone (or no locks were used). */
117
+ export declare const UPDATE_TARGET_NOT_FOUND_MESSAGE = "[updateTarget] Target not found or was deleted.";
118
+ /**
119
+ * @deprecated Use {@link updateTarget} (narrow RMW + optimistic lock) or {@link updateTargetDetails}.
120
+ *
121
+ * Full-row rewrite of create-shaped fields. Forces callers to resupply name/value/category,
122
+ * has no optimistic lock, and can accidentally mutate identity columns. Unused in-tree.
123
+ */
115
124
  export declare const patchTarget: ({ id, ...restPayload }: PatchTargetPayload) => Promise<import("./core.interface").SupabaseResponse<Target>>;
125
+ /** Mutable Target columns allowed by {@link updateTarget} (identity fields excluded). */
126
+ export interface UpdateTargetRowSnapshot<D> {
127
+ tagList: string[];
128
+ details: D;
129
+ extra?: string;
130
+ }
131
+ /**
132
+ * Narrow patch returned by `updateTarget` updateFn.
133
+ * Omitted keys are left unchanged on the row.
134
+ */
135
+ export type UpdateTargetPatch<D> = {
136
+ tagList?: string[];
137
+ details?: D;
138
+ extra?: string;
139
+ };
140
+ /**
141
+ * Read-modify-write mutable Target columns (`tagList` / `details` / `extra`) with optional
142
+ * DB-level optimistic locking. Does not write `name` / `value` / `category`.
143
+ *
144
+ * Prefer this when the update may touch `tagList` (and optionally `details`/`extra`) in one UPDATE.
145
+ * For details-only flows, {@link updateTargetDetails} remains the thinner API.
146
+ */
147
+ export interface UpdateTargetParams<D> {
148
+ /** Target row id. */
149
+ id: string;
150
+ /**
151
+ * Builds a narrow patch from the row fetched immediately before UPDATE.
152
+ * Only returned keys are written; omit a key to leave that column unchanged.
153
+ */
154
+ updateFn: (existing: UpdateTargetRowSnapshot<D>) => UpdateTargetPatch<D>;
155
+ /**
156
+ * Optimistic lock conditions applied on UPDATE (not on the prior SELECT).
157
+ * Same semantics as {@link UpdateTargetDetailsParams.optimisticLockFilterList}.
158
+ */
159
+ optimisticLockFilterList?: QueryFilter[];
160
+ }
161
+ export declare const updateTarget: <T, D>({ id, updateFn, optimisticLockFilterList }: UpdateTargetParams<D>) => Promise<T>;
116
162
  /**
117
163
  * Read-modify-write `target.details` with optional DB-level optimistic locking on UPDATE.
118
164
  *
119
165
  * Do not use application-layer pre-update validators (e.g. checking state after SELECT then
120
166
  * UPDATE with only `id`). That pattern is not atomic and loses under concurrency. Pass expected
121
167
  * row conditions via `optimisticLockFilterList` so they are applied on the UPDATE statement.
168
+ *
169
+ * For updates that also need `tagList`, use {@link updateTarget}.
122
170
  */
123
171
  export interface UpdateTargetDetailsParams<D> {
124
172
  /** Target row id. */
@@ -139,12 +187,14 @@ export interface UpdateTargetDetailsParams<D> {
139
187
  * write and could not prevent concurrent overwrites. These filters become part of the UPDATE
140
188
  * WHERE clause (via `applyQueryFilters`), e.g. `{ field: "details->>status", operator: "eq", value: "TODO" }`.
141
189
  *
142
- * If no row matches after UPDATE, throws "Optimistic lock failed". Empty array = update by id only.
190
+ * If no row matches after UPDATE:
191
+ * - empty locks → not-found (`UPDATE_TARGET_NOT_FOUND_MESSAGE`)
192
+ * - non-empty locks → re-SELECT by id; gone → not-found, still present → `OPTIMISTIC_LOCK_FAILED_MESSAGE`
193
+ * Empty array = update by id only.
143
194
  */
144
195
  optimisticLockFilterList?: QueryFilter[];
145
196
  }
146
197
  export declare const updateTargetDetails: <T, D>({ id, updateFn, updateExtraFn, optimisticLockFilterList, }: UpdateTargetDetailsParams<D>) => Promise<T>;
147
- export declare const OPTIMISTIC_LOCK_FAILED_MESSAGE = "[updateTargetDetails] Optimistic lock failed: target no longer matches expected state.";
148
198
  export declare const OPTIMISTIC_LOCK_ERROR_CODE: "OPTIMISTIC_LOCK";
149
199
  export declare function isOptimisticLockError(error: unknown): boolean;
150
200
  export declare function isOptimisticLockResponse(error: {
@@ -1 +1 @@
1
- {"version":3,"file":"core.api.d.ts","sourceRoot":"","sources":["../src/core.api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.api.d.ts","sourceRoot":"","sources":["../src/core.api.ts"],"names":[],"mappings":"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"}
package/dist/node.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { z } from 'zod';
2
- import { M as MAX_POLL_TARGET_LIST_SIZE, G as validateWithSchema, f as createLogger, w as pollTargetList, i as generateResponse, g as createTarget, D as supabase, u as logManager, I as createScope, c as LogLevel, J as patchScope, A as scanTargetList, K as withModule, j as getPossibleTarget, r as isOptimisticLockResponse, E as updateTargetDetails } from './chunks/list.api-Ab9v8yOg.js';
3
- export { B as BaseValidator, C as CREATE_TARGET_ALREADY_EXISTS_MESSAGE, a as CategoryList, L as LOG_BATCH_LOADER_KEY, b as LOG_PERSIST_LOADER_KEY, d as MAX_TARGET_LIST_PAGE_SIZE, O as OPTIMISTIC_LOCK_ERROR_CODE, e as OPTIMISTIC_LOCK_FAILED_MESSAGE, P as PostListValidator, S as StatusCode, h as deleteTarget, k as getTarget, l as getTargetList, m as getTargetTotalCount, n as handleSupabaseError, o as isCreateTargetAlreadyExistsError, p as isDevEnvironment, q as isOptimisticLockError, s as logDev, t as logDevGroup, v as patchTarget, x as postListCreate, y as postLogListCreate, z as postTarget, F as validateWith } from './chunks/list.api-Ab9v8yOg.js';
4
- import { l as loadEnvFiles, b as readEnv, c as requireEnv, d as createJsonFileStateStore, a as logPersistEnabledFromEnv, e as disableLogPersist, g as getLogPersistStats, s as snapshotLogPersistReady, w as waitForLogPersistReady, f as ensureLogPersistFromEnv } from './chunks/enable-log-persist-BL2FaGQp.js';
5
- export { C as CLI_PROD_FLAG, S as SERVICE_ENV_PROFILE_ENV_KEY, h as SERVICE_ENV_PROFILE_PROD, i as enableLogPersist, j as envBool, k as envInt, m as envMs, n as envNumber, o as envPort, q as envProfileFromProcess, t as parseEnvFile, u as parseEnvLine, p as pinEnvProfileFromArgv, x as pollUntil, y as resolveDefaultEnvFiles, z as resolveLogPersistRegistryPath, r as resolveProjectRootByPackageName, A as resolveProjectRootFromModule, v as validateLogPersistPreloadEnv } from './chunks/enable-log-persist-BL2FaGQp.js';
2
+ import { M as MAX_POLL_TARGET_LIST_SIZE, H as validateWithSchema, f as createLogger, w as pollTargetList, i as generateResponse, g as createTarget, D as supabase, u as logManager, J as createScope, c as LogLevel, K as patchScope, A as scanTargetList, N as withModule, j as getPossibleTarget, r as isOptimisticLockResponse, F as updateTargetDetails } from './chunks/list.api-6Q4_-7oN.js';
3
+ export { B as BaseValidator, C as CREATE_TARGET_ALREADY_EXISTS_MESSAGE, a as CategoryList, L as LOG_BATCH_LOADER_KEY, b as LOG_PERSIST_LOADER_KEY, d as MAX_TARGET_LIST_PAGE_SIZE, O as OPTIMISTIC_LOCK_ERROR_CODE, e as OPTIMISTIC_LOCK_FAILED_MESSAGE, P as PostListValidator, S as StatusCode, U as UPDATE_TARGET_NOT_FOUND_MESSAGE, h as deleteTarget, k as getTarget, l as getTargetList, m as getTargetTotalCount, n as handleSupabaseError, o as isCreateTargetAlreadyExistsError, p as isDevEnvironment, q as isOptimisticLockError, s as logDev, t as logDevGroup, v as patchTarget, x as postListCreate, y as postLogListCreate, z as postTarget, E as updateTarget, G as validateWith } from './chunks/list.api-6Q4_-7oN.js';
4
+ import { l as loadEnvFiles, b as readEnv, c as requireEnv, d as createJsonFileStateStore, a as logPersistEnabledFromEnv, e as disableLogPersist, g as getLogPersistStats, s as snapshotLogPersistReady, w as waitForLogPersistReady, f as ensureLogPersistFromEnv } from './chunks/enable-log-persist-CKxXtE3K.js';
5
+ export { C as CLI_PROD_FLAG, S as SERVICE_ENV_PROFILE_ENV_KEY, h as SERVICE_ENV_PROFILE_PROD, i as enableLogPersist, j as envBool, k as envInt, m as envMs, n as envNumber, o as envPort, q as envProfileFromProcess, t as parseEnvFile, u as parseEnvLine, p as pinEnvProfileFromArgv, x as pollUntil, y as resolveDefaultEnvFiles, z as resolveLogPersistRegistryPath, r as resolveProjectRootByPackageName, A as resolveProjectRootFromModule, v as validateLogPersistPreloadEnv } from './chunks/enable-log-persist-CKxXtE3K.js';
6
6
  import { access, stat, readFile, mkdir, unlink, writeFile, readdir } from 'node:fs/promises';
7
7
  import { resolve, join, dirname, isAbsolute } from 'node:path';
8
- import { bd as sha256Hex, bi as getErrorMessage, aI as postRegisterNode, am as patchChangeNodeStatus, N as NodeStatus, ax as patchStopNode, au as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, b3 as runRegistrySlotGuardCheck, aW as registrySlotRuntimePatchFromGuardResult, d as CategoryNode, L as evaluateBusyNodeLiveness, aw as patchServiceRuntime, k as ServiceRegistryError, v as claimServiceRegistrySlot, f as CategoryRepo, R as ResultCode, bj as LOG_TOPIC_TASK, m as TASK_REPO_USAGE, a4 as getScanRemoteRepoValues, aq as patchClaimTask, bk as CRITICAL_UNKNOWN_TASK_TRACE_ID, ao as patchChangeTaskStatus, o as TaskStatusAction, bl as toError, aQ as postTaskSchema, bm as createTaskRow, n as TaskStatus } from './chunks/task-queue-D_7kYBx3.js';
9
- export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, b as CategoryIdea, c as CategoryLink, e as CategoryParcel, g as CategoryService, h as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, M as Market, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, i as ParcelManager, j as PostLinkValidator, S as ServiceLifecycleStatus, l as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, p as apiDetailsSchema, q as appendSystemRegistrySlots, r as assertRegistrySlotAvailable, s as assertRegistrySlotOwner, t as buildEmptyServiceSlots, u as buildSystemRegistryConfigDetails, w as classifyNetworkError, x as countTasksByType, y as createActiveServiceLifecycle, z as createClaimedRegistrySlotRuntimeState, B as createStorageProviderRegistry, F as createUploadTracker, G as decodeLogBatch, H as decodeLogBatchList, I as defaultL3ServiceDetails, J as deleteParcel, K as deleteParcelSchema, O as fetchBinaryWithRetry, Q as fetchWithRetry, U as fieldDefinitionSchema, V as flattenLogBatch, W as flattenLogBatches, X as formatNetworkError, Y as getApi, Z as getApiSchema, _ as getConfig, $ as getConfigSchema, a0 as getExtraction, a1 as getExtractionSchema, a2 as getParcel, a3 as getParcelSchema, a5 as getScanRemoteRepoValuesSchema, a6 as getService, a7 as getServiceSchema, a8 as getTargetSystemRegistry, a9 as getValueAtPath, aa as installChunkFetchRegistry, ab as isHttpUrl, ac as isLocalFilesystemPath, ad as isLogBatchList, ae as isLogEntry, af as isOpaqueChunkUrl, ag as isRetryableHttpStatus, ah as parcelDetailsSchema, ai as parseLogBatchMeta, aj as parseProviderPrefixedUrl, ak as parseServiceSlot, al as parseServiceSlots, an as patchChangeNodeStatusSchema, ap as patchChangeTaskStatusSchema, ar as patchClaimTaskSchema, as as patchExtractionObjects, at as patchExtractionObjectsSchema, av as patchNodeHeartBeatSchema, ay as patchStopNodeSchema, az as patchTaskProgress, aA as patchTaskProgressSchema, aB as postApi, aC as postApiSchema, aD as postExtraction, aE as postExtractionSchema, aF as postLinkCreate, aG as postParcel, aH as postParcelSchema, aJ as postRegisterNodeSchema, aK as postService, aL as postServiceInstance, aM as postServiceSchema, aN as postSystemRegistryConfig, aO as postSystemRegistryConfigSchema, aP as postTask, aR as postTaskWithRetry, aS as publishParcel, aT as registerProviderChunkResolver, aU as registerService, aV as registerServiceAtStartup, aX as releaseSystemRegistrySlots, aY as requestHasBody, aZ as resetSystemRegistryConfig, a_ as resetSystemRegistryConfigSchema, a$ as resolveActiveRegistryServiceId, b0 as resolveFetchUrl, b1 as restoreParcel, b2 as restoreParcelById, b4 as sanitizeFileName, b5 as sanitizeParcelFileName, b6 as scanExtractionList, b7 as scanExtractionListSchema, b8 as schemaDefinitionSchema, b9 as serviceDetailsSchema, ba as serviceLifecycleSchema, bb as serviceNodeSnapshotSchema, bc as serviceRuntimeSchema, be as summarizeTaskQueue, bf as systemRegistrySeedSlotSchema, bg as unregisterService, bh as unregisterServiceAtShutdown } from './chunks/task-queue-D_7kYBx3.js';
8
+ import { bd as sha256Hex, bi as getErrorMessage, aI as postRegisterNode, am as patchChangeNodeStatus, N as NodeStatus, ax as patchStopNode, au as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, b3 as runRegistrySlotGuardCheck, aW as registrySlotRuntimePatchFromGuardResult, d as CategoryNode, L as evaluateBusyNodeLiveness, aw as patchServiceRuntime, k as ServiceRegistryError, v as claimServiceRegistrySlot, f as CategoryRepo, R as ResultCode, bj as LOG_TOPIC_TASK, m as TASK_REPO_USAGE, a4 as getScanRemoteRepoValues, aq as patchClaimTask, bk as CRITICAL_UNKNOWN_TASK_TRACE_ID, ao as patchChangeTaskStatus, o as TaskStatusAction, bl as toError, aQ as postTaskSchema, bm as createTaskRow, n as TaskStatus } from './chunks/task-queue-D5uWjqVN.js';
9
+ export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, b as CategoryIdea, c as CategoryLink, e as CategoryParcel, g as CategoryService, h as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, M as Market, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, i as ParcelManager, j as PostLinkValidator, S as ServiceLifecycleStatus, l as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, p as apiDetailsSchema, q as appendSystemRegistrySlots, r as assertRegistrySlotAvailable, s as assertRegistrySlotOwner, t as buildEmptyServiceSlots, u as buildSystemRegistryConfigDetails, w as classifyNetworkError, x as countTasksByType, y as createActiveServiceLifecycle, z as createClaimedRegistrySlotRuntimeState, B as createStorageProviderRegistry, F as createUploadTracker, G as decodeLogBatch, H as decodeLogBatchList, I as defaultL3ServiceDetails, J as deleteParcel, K as deleteParcelSchema, O as fetchBinaryWithRetry, Q as fetchWithRetry, U as fieldDefinitionSchema, V as flattenLogBatch, W as flattenLogBatches, X as formatNetworkError, Y as getApi, Z as getApiSchema, _ as getConfig, $ as getConfigSchema, a0 as getExtraction, a1 as getExtractionSchema, a2 as getParcel, a3 as getParcelSchema, a5 as getScanRemoteRepoValuesSchema, a6 as getService, a7 as getServiceSchema, a8 as getTargetSystemRegistry, a9 as getValueAtPath, aa as installChunkFetchRegistry, ab as isHttpUrl, ac as isLocalFilesystemPath, ad as isLogBatchList, ae as isLogEntry, af as isOpaqueChunkUrl, ag as isRetryableHttpStatus, ah as parcelDetailsSchema, ai as parseLogBatchMeta, aj as parseProviderPrefixedUrl, ak as parseServiceSlot, al as parseServiceSlots, an as patchChangeNodeStatusSchema, ap as patchChangeTaskStatusSchema, ar as patchClaimTaskSchema, as as patchExtractionObjects, at as patchExtractionObjectsSchema, av as patchNodeHeartBeatSchema, ay as patchStopNodeSchema, az as patchTaskProgress, aA as patchTaskProgressSchema, aB as postApi, aC as postApiSchema, aD as postExtraction, aE as postExtractionSchema, aF as postLinkCreate, aG as postParcel, aH as postParcelSchema, aJ as postRegisterNodeSchema, aK as postService, aL as postServiceInstance, aM as postServiceSchema, aN as postSystemRegistryConfig, aO as postSystemRegistryConfigSchema, aP as postTask, aR as postTaskWithRetry, aS as publishParcel, aT as registerProviderChunkResolver, aU as registerService, aV as registerServiceAtStartup, aX as releaseSystemRegistrySlots, aY as requestHasBody, aZ as resetSystemRegistryConfig, a_ as resetSystemRegistryConfigSchema, a$ as resolveActiveRegistryServiceId, b0 as resolveFetchUrl, b1 as restoreParcel, b2 as restoreParcelById, b4 as sanitizeFileName, b5 as sanitizeParcelFileName, b6 as scanExtractionList, b7 as scanExtractionListSchema, b8 as schemaDefinitionSchema, b9 as serviceDetailsSchema, ba as serviceLifecycleSchema, bb as serviceNodeSnapshotSchema, bc as serviceRuntimeSchema, be as summarizeTaskQueue, bf as systemRegistrySeedSlotSchema, bg as unregisterService, bh as unregisterServiceAtShutdown } from './chunks/task-queue-D5uWjqVN.js';
10
10
  import { spawn } from 'node:child_process';
11
11
  import { createHash } from 'node:crypto';
12
12
  import { tmpdir } from 'node:os';
package/dist/preload.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { resolve } from 'node:path';
2
- import { r as resolveProjectRootByPackageName, p as pinEnvProfileFromArgv, l as loadEnvFiles, v as validateLogPersistPreloadEnv, a as logPersistEnabledFromEnv } from './chunks/enable-log-persist-BL2FaGQp.js';
2
+ import { r as resolveProjectRootByPackageName, p as pinEnvProfileFromArgv, l as loadEnvFiles, v as validateLogPersistPreloadEnv, a as logPersistEnabledFromEnv } from './chunks/enable-log-persist-CKxXtE3K.js';
3
3
  import 'node:fs';
4
4
  import 'node:url';
5
5
  import 'node:fs/promises';
6
- import './chunks/list.api-Ab9v8yOg.js';
6
+ import './chunks/list.api-6Q4_-7oN.js';
7
7
  import 'lodash-es';
8
8
  import '@supabase/supabase-js';
9
9
  import 'node:crypto';
@@ -1 +1 @@
1
- {"version":3,"file":"config.api.d.ts","sourceRoot":"","sources":["../../src/service/config.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,EAAkB,KAAK,MAAM,EAAE,KAAK,aAAa,EAA8B,MAAM,oBAAoB,CAAC;AACjH,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,qBAAqB,CAAC;AAI1E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;EAYvB,CAAC;AAEN,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,wEAAwE;AACxE,eAAO,MAAM,4BAA4B;;;;;;EAEvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,8BAA8B;IACvC,wEAAwE;;;;;;;;;;;;;;;;;;;EAG1E,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAE7F,qGAAqG;AACrG,eAAO,MAAM,+BAA+B;IARxC,wEAAwE;;;;;;;;;;;;;;;;;;;EAQC,CAAC;AAE9E,MAAM,MAAM,gCAAgC,GAAG,+BAA+B,CAAC;AAK/E,uFAAuF;AACvF,eAAO,MAAM,kCAAkC,EAAE,SAAS,sBAAsB,EAMtE,CAAC;AAkBX,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,GAAG,WAAW,EAAE,CAMlG;AAgBD,yEAAyE;AACzE,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,aAAa,CAO7F;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS;;;;UAaW,MAAM;;;;;;YA3GnC,CAAC;;;;;;;;;YAAD,CAAC;;;UA2G4B,MAAM;;;;;;YA3GnC,CAAC;;;;;;;;;YAAD,CAAC;;EA8HH,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;;;;4FAsBnC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;;;;;4FA2CpC,CAAC"}
1
+ {"version":3,"file":"config.api.d.ts","sourceRoot":"","sources":["../../src/service/config.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,EAAkB,KAAK,MAAM,EAAE,KAAK,aAAa,EAA8B,MAAM,oBAAoB,CAAC;AACjH,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,qBAAqB,CAAC;AAI1E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;EAYvB,CAAC;AAEN,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,wEAAwE;AACxE,eAAO,MAAM,4BAA4B;;;;;;EAEvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,8BAA8B;IACvC,wEAAwE;;;;;;;;;;;;;;;;;;;EAG1E,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAE7F,qGAAqG;AACrG,eAAO,MAAM,+BAA+B;IARxC,wEAAwE;;;;;;;;;;;;;;;;;;;EAQC,CAAC;AAE9E,MAAM,MAAM,gCAAgC,GAAG,+BAA+B,CAAC;AAK/E,uFAAuF;AACvF,eAAO,MAAM,kCAAkC,EAAE,SAAS,sBAAsB,EAOtE,CAAC;AAkBX,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,GAAG,WAAW,EAAE,CAMlG;AAgBD,yEAAyE;AACzE,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,aAAa,CAO7F;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS;;;;UAaW,MAAM;;;;;;YA5GnC,CAAC;;;;;;;;;YAAD,CAAC;;;UA4G4B,MAAM;;;;;;YA5GnC,CAAC;;;;;;;;;YAAD,CAAC;;EA+HH,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;;;;4FAsBnC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;;;;;4FA2CpC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "target-supabase-sdk",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Supabase client initializer and data access APIs",
5
5
  "license": "MIT",
6
6
  "author": "KyLoc20",
@@ -53,6 +53,8 @@
53
53
  "format": "biome check --write .",
54
54
  "format:check": "biome ci",
55
55
  "lint": "biome lint .",
56
+ "verify": "pnpm typecheck && pnpm typecheck:scripts && pnpm format:check",
57
+ "prepare": "git config core.hooksPath .githooks",
56
58
  "worker:trigger": "tsx scripts/run-trigger-node.ts",
57
59
  "post-command": "tsx scripts/post-command.ts",
58
60
  "post-trigger": "tsx scripts/post-trigger.ts",
package/src/core.api.ts CHANGED
@@ -420,6 +420,18 @@ export interface PatchTargetPayload extends PostTargetPayload {
420
420
  id: string;
421
421
  }
422
422
 
423
+ export const OPTIMISTIC_LOCK_FAILED_MESSAGE =
424
+ "[updateTargetDetails] Optimistic lock failed: target no longer matches expected state.";
425
+
426
+ /** Thrown when UPDATE matched no row and the target id is gone (or no locks were used). */
427
+ export const UPDATE_TARGET_NOT_FOUND_MESSAGE = "[updateTarget] Target not found or was deleted.";
428
+
429
+ /**
430
+ * @deprecated Use {@link updateTarget} (narrow RMW + optimistic lock) or {@link updateTargetDetails}.
431
+ *
432
+ * Full-row rewrite of create-shaped fields. Forces callers to resupply name/value/category,
433
+ * has no optimistic lock, and can accidentally mutate identity columns. Unused in-tree.
434
+ */
423
435
  export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) => {
424
436
  const { data: currentData, error: fetchError } = await supabase.client
425
437
  .from("target")
@@ -448,69 +460,87 @@ export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) =>
448
460
  return generateResponse.success<Target>(data as Target);
449
461
  };
450
462
 
463
+ /** Mutable Target columns allowed by {@link updateTarget} (identity fields excluded). */
464
+ export interface UpdateTargetRowSnapshot<D> {
465
+ tagList: string[];
466
+ details: D;
467
+ extra?: string;
468
+ }
469
+
451
470
  /**
452
- * Read-modify-write `target.details` with optional DB-level optimistic locking on UPDATE.
471
+ * Narrow patch returned by `updateTarget` updateFn.
472
+ * Omitted keys are left unchanged on the row.
473
+ */
474
+ export type UpdateTargetPatch<D> = {
475
+ tagList?: string[];
476
+ details?: D;
477
+ extra?: string;
478
+ };
479
+
480
+ /**
481
+ * Read-modify-write mutable Target columns (`tagList` / `details` / `extra`) with optional
482
+ * DB-level optimistic locking. Does not write `name` / `value` / `category`.
453
483
  *
454
- * Do not use application-layer pre-update validators (e.g. checking state after SELECT then
455
- * UPDATE with only `id`). That pattern is not atomic and loses under concurrency. Pass expected
456
- * row conditions via `optimisticLockFilterList` so they are applied on the UPDATE statement.
484
+ * Prefer this when the update may touch `tagList` (and optionally `details`/`extra`) in one UPDATE.
485
+ * For details-only flows, {@link updateTargetDetails} remains the thinner API.
457
486
  */
458
- export interface UpdateTargetDetailsParams<D> {
487
+ export interface UpdateTargetParams<D> {
459
488
  /** Target row id. */
460
489
  id: string;
461
490
  /**
462
- * Computes new details from the row fetched immediately before UPDATE.
463
- * Runs after SELECT; the write still relies on `optimisticLockFilterList` for atomic guards.
464
- */
465
- updateFn: (existing: D) => D;
466
- /**
467
- * Optional `target.extra` derived from existing details (written together with details).
491
+ * Builds a narrow patch from the row fetched immediately before UPDATE.
492
+ * Only returned keys are written; omit a key to leave that column unchanged.
468
493
  */
469
- updateExtraFn?: (existing: D) => string;
494
+ updateFn: (existing: UpdateTargetRowSnapshot<D>) => UpdateTargetPatch<D>;
470
495
  /**
471
496
  * Optimistic lock conditions applied on UPDATE (not on the prior SELECT).
472
- *
473
- * Replaces the removed `beforeUpdateValidator`: validators ran in app code between read and
474
- * write and could not prevent concurrent overwrites. These filters become part of the UPDATE
475
- * WHERE clause (via `applyQueryFilters`), e.g. `{ field: "details->>status", operator: "eq", value: "TODO" }`.
476
- *
477
- * If no row matches after UPDATE, throws "Optimistic lock failed". Empty array = update by id only.
497
+ * Same semantics as {@link UpdateTargetDetailsParams.optimisticLockFilterList}.
478
498
  */
479
499
  optimisticLockFilterList?: QueryFilter[];
480
500
  }
481
501
 
482
- export const updateTargetDetails = async <T, D>({
483
- id,
484
- updateFn,
485
- updateExtraFn,
486
- optimisticLockFilterList = [],
487
- }: UpdateTargetDetailsParams<D>) => {
502
+ export const updateTarget = async <T, D>({ id, updateFn, optimisticLockFilterList = [] }: UpdateTargetParams<D>) => {
488
503
  const { data: currentData, error: fetchError } = await supabase.client
489
504
  .from("target")
490
- .select("details")
505
+ .select("tagList, details, extra")
491
506
  .eq("id", id)
492
507
  .single();
493
508
 
494
509
  if (fetchError) {
495
- handleSupabaseError("updateTargetDetails", fetchError, "Failed to fetch target.");
510
+ handleSupabaseError("updateTarget", fetchError, "Failed to fetch target.");
496
511
  }
497
512
  if (!currentData) {
498
- const msg = `[updateTargetDetails] Target NOT exists: ${id}`;
513
+ const msg = `[updateTarget] Target NOT exists: ${id}`;
499
514
  console.error(msg);
500
515
  throw new Error(msg);
501
516
  }
502
517
 
503
- const currentDetails = currentData.details as D;
504
- const updatedDetails: D = updateFn(currentDetails);
505
- const updated =
506
- updateExtraFn == null
507
- ? {
508
- details: updatedDetails,
509
- }
510
- : {
511
- details: updatedDetails,
512
- extra: updateExtraFn(currentDetails),
513
- };
518
+ const existing: UpdateTargetRowSnapshot<D> = {
519
+ tagList: Array.isArray(currentData.tagList) ? (currentData.tagList as string[]) : [],
520
+ details: currentData.details as D,
521
+ extra: typeof currentData.extra === "string" ? currentData.extra : undefined,
522
+ };
523
+
524
+ const patch = updateFn(existing);
525
+ const updated: {
526
+ tagList?: string[];
527
+ details?: D;
528
+ extra?: string;
529
+ } = {};
530
+
531
+ if (patch.tagList !== undefined) {
532
+ updated.tagList = patch.tagList;
533
+ }
534
+ if (patch.details !== undefined) {
535
+ updated.details = patch.details;
536
+ }
537
+ if (patch.extra !== undefined) {
538
+ updated.extra = patch.extra;
539
+ }
540
+
541
+ if (Object.keys(updated).length === 0) {
542
+ throw new Error("[updateTarget] updateFn returned an empty patch");
543
+ }
514
544
 
515
545
  const updateQuery = applyQueryFilters(
516
546
  supabase.client.from("target").update(updated).eq("id", id),
@@ -520,21 +550,88 @@ export const updateTargetDetails = async <T, D>({
520
550
  const { data, error } = await updateQuery.select().maybeSingle();
521
551
 
522
552
  if (error) {
523
- handleSupabaseError("updateTargetDetails", error, "Failed to update target details.");
553
+ handleSupabaseError("updateTarget", error, "Failed to update target.");
524
554
  }
525
555
  if (!data) {
526
- const msg =
527
- optimisticLockFilterList.length > 0
528
- ? OPTIMISTIC_LOCK_FAILED_MESSAGE
529
- : "[updateTargetDetails] Target not found or was deleted.";
530
- throw new Error(msg);
556
+ if (optimisticLockFilterList.length === 0) {
557
+ throw new Error(UPDATE_TARGET_NOT_FOUND_MESSAGE);
558
+ }
559
+ // Empty UPDATE with locks is ambiguous: concurrent delete vs lock predicate miss.
560
+ // Re-SELECT by id only to disambiguate (same id, no lock filters).
561
+ const { data: stillThere, error: recheckError } = await supabase.client
562
+ .from("target")
563
+ .select("id")
564
+ .eq("id", id)
565
+ .maybeSingle();
566
+ if (recheckError) {
567
+ handleSupabaseError("updateTarget", recheckError, "Failed to recheck target after lock miss.");
568
+ }
569
+ if (!stillThere) {
570
+ throw new Error(UPDATE_TARGET_NOT_FOUND_MESSAGE);
571
+ }
572
+ throw new Error(OPTIMISTIC_LOCK_FAILED_MESSAGE);
531
573
  }
532
574
 
533
575
  return data as T;
534
576
  };
535
577
 
536
- export const OPTIMISTIC_LOCK_FAILED_MESSAGE =
537
- "[updateTargetDetails] Optimistic lock failed: target no longer matches expected state.";
578
+ /**
579
+ * Read-modify-write `target.details` with optional DB-level optimistic locking on UPDATE.
580
+ *
581
+ * Do not use application-layer pre-update validators (e.g. checking state after SELECT then
582
+ * UPDATE with only `id`). That pattern is not atomic and loses under concurrency. Pass expected
583
+ * row conditions via `optimisticLockFilterList` so they are applied on the UPDATE statement.
584
+ *
585
+ * For updates that also need `tagList`, use {@link updateTarget}.
586
+ */
587
+ export interface UpdateTargetDetailsParams<D> {
588
+ /** Target row id. */
589
+ id: string;
590
+ /**
591
+ * Computes new details from the row fetched immediately before UPDATE.
592
+ * Runs after SELECT; the write still relies on `optimisticLockFilterList` for atomic guards.
593
+ */
594
+ updateFn: (existing: D) => D;
595
+ /**
596
+ * Optional `target.extra` derived from existing details (written together with details).
597
+ */
598
+ updateExtraFn?: (existing: D) => string;
599
+ /**
600
+ * Optimistic lock conditions applied on UPDATE (not on the prior SELECT).
601
+ *
602
+ * Replaces the removed `beforeUpdateValidator`: validators ran in app code between read and
603
+ * write and could not prevent concurrent overwrites. These filters become part of the UPDATE
604
+ * WHERE clause (via `applyQueryFilters`), e.g. `{ field: "details->>status", operator: "eq", value: "TODO" }`.
605
+ *
606
+ * If no row matches after UPDATE:
607
+ * - empty locks → not-found (`UPDATE_TARGET_NOT_FOUND_MESSAGE`)
608
+ * - non-empty locks → re-SELECT by id; gone → not-found, still present → `OPTIMISTIC_LOCK_FAILED_MESSAGE`
609
+ * Empty array = update by id only.
610
+ */
611
+ optimisticLockFilterList?: QueryFilter[];
612
+ }
613
+
614
+ export const updateTargetDetails = async <T, D>({
615
+ id,
616
+ updateFn,
617
+ updateExtraFn,
618
+ optimisticLockFilterList = [],
619
+ }: UpdateTargetDetailsParams<D>) => {
620
+ return updateTarget<T, D>({
621
+ id,
622
+ optimisticLockFilterList,
623
+ updateFn: (existing) => {
624
+ const nextDetails = updateFn(existing.details);
625
+ if (updateExtraFn == null) {
626
+ return { details: nextDetails };
627
+ }
628
+ return {
629
+ details: nextDetails,
630
+ extra: updateExtraFn(existing.details),
631
+ };
632
+ },
633
+ });
634
+ };
538
635
 
539
636
  export const OPTIMISTIC_LOCK_ERROR_CODE = "OPTIMISTIC_LOCK" as const;
540
637
 
@@ -60,6 +60,7 @@ export const DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS: readonly SystemRegistrySeedSlot
60
60
  { serviceValue: "download-service" },
61
61
  { serviceValue: "storage-service" },
62
62
  { serviceValue: "gc-service" },
63
+ { serviceValue: "upload-service" },
63
64
  ] as const;
64
65
 
65
66
  const configCategoryFilter: QueryFilter = {