target-supabase-sdk 0.1.8 → 0.1.9

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 (144) hide show
  1. package/README.md +8 -6
  2. package/dist/auth/auth.api.d.ts.map +1 -1
  3. package/dist/auth/auth.api.js +2 -1
  4. package/dist/auth/auth.api.js.map +1 -1
  5. package/dist/command/command.api.d.ts +43 -0
  6. package/dist/command/command.api.d.ts.map +1 -0
  7. package/dist/command/command.api.js +58 -0
  8. package/dist/command/command.api.js.map +1 -0
  9. package/dist/command/command.interface.d.ts +14 -0
  10. package/dist/command/command.interface.d.ts.map +1 -0
  11. package/dist/command/command.interface.js +9 -0
  12. package/dist/command/command.interface.js.map +1 -0
  13. package/dist/core.api.d.ts +70 -6
  14. package/dist/core.api.d.ts.map +1 -1
  15. package/dist/core.api.js +191 -13
  16. package/dist/core.api.js.map +1 -1
  17. package/dist/file/file-manager.d.ts +1 -1
  18. package/dist/file/file-manager.d.ts.map +1 -1
  19. package/dist/file/file-manager.js +1 -1
  20. package/dist/file/file-manager.js.map +1 -1
  21. package/dist/index.d.ts +9 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/node/node-manager.d.ts +48 -0
  26. package/dist/node/node-manager.d.ts.map +1 -0
  27. package/dist/node/node-manager.js +619 -0
  28. package/dist/node/node-manager.js.map +1 -0
  29. package/dist/node/node-task.preset.d.ts +1 -0
  30. package/dist/node/node-task.preset.d.ts.map +1 -0
  31. package/dist/node/node-task.preset.js +14 -0
  32. package/dist/node/node-task.preset.js.map +1 -0
  33. package/dist/node/node.api.d.ts +67 -31
  34. package/dist/node/node.api.d.ts.map +1 -1
  35. package/dist/node/node.api.js +65 -102
  36. package/dist/node/node.api.js.map +1 -1
  37. package/dist/node/node.interface.d.ts +7 -0
  38. package/dist/node/node.interface.d.ts.map +1 -1
  39. package/dist/node/node.interface.js +2 -0
  40. package/dist/node/node.interface.js.map +1 -1
  41. package/dist/node/node.utils.d.ts +5 -0
  42. package/dist/node/node.utils.d.ts.map +1 -0
  43. package/dist/node/node.utils.js +36 -0
  44. package/dist/node/node.utils.js.map +1 -0
  45. package/dist/parcel/parcel-manager.d.ts +1 -1
  46. package/dist/parcel/parcel-manager.d.ts.map +1 -1
  47. package/dist/parcel/parcel-manager.js +1 -1
  48. package/dist/parcel/parcel-manager.js.map +1 -1
  49. package/dist/repo/repo-context.utils.d.ts +24 -0
  50. package/dist/repo/repo-context.utils.d.ts.map +1 -0
  51. package/dist/repo/repo-context.utils.js +81 -0
  52. package/dist/repo/repo-context.utils.js.map +1 -0
  53. package/dist/repo/repo-manager.d.ts +31 -0
  54. package/dist/repo/repo-manager.d.ts.map +1 -0
  55. package/dist/repo/repo-manager.js +156 -0
  56. package/dist/repo/repo-manager.js.map +1 -0
  57. package/dist/repo/repo.api.d.ts +16 -0
  58. package/dist/repo/repo.api.d.ts.map +1 -0
  59. package/dist/repo/repo.api.js +40 -0
  60. package/dist/repo/repo.api.js.map +1 -0
  61. package/dist/repo/repo.interface.d.ts +12 -5
  62. package/dist/repo/repo.interface.d.ts.map +1 -1
  63. package/dist/repo/repo.interface.js +2 -2
  64. package/dist/repo/repo.interface.js.map +1 -1
  65. package/dist/repo/repo.script-loader.d.ts +19 -0
  66. package/dist/repo/repo.script-loader.d.ts.map +1 -0
  67. package/dist/repo/repo.script-loader.js +108 -0
  68. package/dist/repo/repo.script-loader.js.map +1 -0
  69. package/dist/shared/log/create-api-logger.d.ts +17 -0
  70. package/dist/shared/log/create-api-logger.d.ts.map +1 -0
  71. package/dist/shared/log/create-api-logger.js +15 -0
  72. package/dist/shared/log/create-api-logger.js.map +1 -0
  73. package/dist/shared/log/log-manager.d.ts +114 -0
  74. package/dist/shared/log/log-manager.d.ts.map +1 -0
  75. package/dist/shared/log/log-manager.js +218 -0
  76. package/dist/shared/log/log-manager.js.map +1 -0
  77. package/dist/shared/utils/config-path.utils.d.ts +38 -0
  78. package/dist/shared/utils/config-path.utils.d.ts.map +1 -0
  79. package/dist/shared/utils/config-path.utils.js +85 -0
  80. package/dist/shared/utils/config-path.utils.js.map +1 -0
  81. package/dist/shared/utils/error.utils.d.ts +11 -0
  82. package/dist/shared/utils/error.utils.d.ts.map +1 -0
  83. package/dist/shared/utils/error.utils.js +15 -0
  84. package/dist/shared/utils/error.utils.js.map +1 -0
  85. package/dist/shared/utils/id.utils.d.ts +3 -0
  86. package/dist/shared/utils/id.utils.d.ts.map +1 -0
  87. package/dist/shared/utils/id.utils.js +5 -0
  88. package/dist/shared/utils/id.utils.js.map +1 -0
  89. package/dist/supabase.js +1 -1
  90. package/dist/supabase.js.map +1 -1
  91. package/dist/task/local-task-registry.d.ts +27 -0
  92. package/dist/task/local-task-registry.d.ts.map +1 -0
  93. package/dist/task/local-task-registry.js +153 -0
  94. package/dist/task/local-task-registry.js.map +1 -0
  95. package/dist/task/task-manager.d.ts +52 -0
  96. package/dist/task/task-manager.d.ts.map +1 -0
  97. package/dist/task/task-manager.js +217 -0
  98. package/dist/task/task-manager.js.map +1 -0
  99. package/dist/task/task-repo-context.d.ts +95 -0
  100. package/dist/task/task-repo-context.d.ts.map +1 -0
  101. package/dist/task/task-repo-context.js +12 -0
  102. package/dist/task/task-repo-context.js.map +1 -0
  103. package/dist/task/task.api.d.ts +240 -0
  104. package/dist/task/task.api.d.ts.map +1 -1
  105. package/dist/task/task.api.js +350 -1
  106. package/dist/task/task.api.js.map +1 -1
  107. package/dist/task/task.interface.d.ts +9 -2
  108. package/dist/task/task.interface.d.ts.map +1 -1
  109. package/dist/task/task.interface.js +10 -0
  110. package/dist/task/task.interface.js.map +1 -1
  111. package/package.json +10 -3
  112. package/src/auth/auth.api.ts +3 -1
  113. package/src/command/command.api.ts +88 -0
  114. package/src/command/command.interface.ts +17 -0
  115. package/src/core.api.ts +305 -26
  116. package/src/file/file-manager.ts +1 -1
  117. package/src/index.ts +18 -3
  118. package/src/node/node-manager.ts +673 -0
  119. package/src/node/node-task.preset.ts +13 -0
  120. package/src/node/node.api.ts +95 -117
  121. package/src/node/node.interface.ts +8 -0
  122. package/src/node/node.utils.ts +36 -0
  123. package/src/parcel/parcel-manager.ts +1 -1
  124. package/src/repo/repo-context.utils.ts +111 -0
  125. package/src/repo/repo-manager.ts +217 -0
  126. package/src/repo/repo.api.ts +50 -0
  127. package/src/repo/repo.interface.ts +13 -5
  128. package/src/repo/repo.script-loader.ts +140 -0
  129. package/src/shared/log/create-api-logger.ts +25 -0
  130. package/src/shared/log/log-manager.ts +340 -0
  131. package/src/shared/utils/config-path.utils.ts +104 -0
  132. package/src/shared/utils/error.utils.ts +15 -0
  133. package/src/shared/utils/id.utils.ts +4 -0
  134. package/src/supabase.ts +1 -1
  135. package/src/task/local-task-registry.ts +195 -0
  136. package/src/task/task-manager.ts +312 -0
  137. package/src/task/task-repo-context.ts +103 -0
  138. package/src/task/task.api.ts +429 -0
  139. package/src/task/task.interface.ts +10 -2
  140. package/dist/task/task-node-manager.d.ts +0 -1
  141. package/dist/task/task-node-manager.d.ts.map +0 -1
  142. package/dist/task/task-node-manager.js +0 -2
  143. package/dist/task/task-node-manager.js.map +0 -1
  144. package/src/task/task-node-manager.ts +0 -0
package/src/core.api.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { generateResponse, Target, TargetPayload } from "./core.interface";
2
- import { supabase } from ".";
2
+ import { SupabaseInitializer } from "./supabase";
3
+
4
+ const supabase = SupabaseInitializer.getInstance();
3
5
  import { BaseValidator, handleSupabaseError } from "./core.utils";
4
6
  import { PostgrestFilterBuilder } from "@supabase/postgrest-js";
7
+ import type { ZodType } from "zod";
8
+ import { ZodError } from "zod";
5
9
 
6
10
  export interface QueryFilter {
7
11
  field: string;
@@ -9,6 +13,85 @@ export interface QueryFilter {
9
13
  value: unknown;
10
14
  }
11
15
 
16
+ export const MAX_TARGET_LIST_PAGE_SIZE = 100;
17
+
18
+ /** Alias for {@link pollTargetList} batch size cap. */
19
+ export const MAX_POLL_TARGET_LIST_SIZE = MAX_TARGET_LIST_PAGE_SIZE;
20
+
21
+ const TARGET_LIST_ORDER_FIELDS = new Set<string>(["created_at", "name", "value", "category"]);
22
+
23
+ // biome-ignore lint/suspicious/noExplicitAny: PostgrestFilterBuilder is generic over schema
24
+ export type TargetFilterBuilder = PostgrestFilterBuilder<any, any, any[], "target", unknown>;
25
+
26
+ function validateTargetListPagination(pageNum: number, pageSize: number): void {
27
+ if (!Number.isInteger(pageNum) || pageNum < 0) {
28
+ throw new Error(`[getTargetList] pageNum must be a non-negative integer, got ${pageNum}`);
29
+ }
30
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > MAX_TARGET_LIST_PAGE_SIZE) {
31
+ throw new Error(
32
+ `[getTargetList] pageSize must be an integer between 1 and ${MAX_TARGET_LIST_PAGE_SIZE}, got ${pageSize}`
33
+ );
34
+ }
35
+ }
36
+
37
+ function resolveTargetListPagination(params: {
38
+ pageNum?: number;
39
+ pageSize?: number;
40
+ limit?: number;
41
+ }): {
42
+ pageNum: number;
43
+ pageSize: number;
44
+ } {
45
+ if (params.limit != null) {
46
+ validateTargetListPagination(0, params.limit);
47
+ return { pageNum: 0, pageSize: params.limit };
48
+ }
49
+ if (params.pageNum === undefined || params.pageSize === undefined) {
50
+ throw new Error("[getTargetList] Provide pageNum and pageSize, or limit.");
51
+ }
52
+ validateTargetListPagination(params.pageNum, params.pageSize);
53
+ return { pageNum: params.pageNum, pageSize: params.pageSize };
54
+ }
55
+
56
+ function validateTargetListOrderBy(orderBy: QueryOrderBy): void {
57
+ if (!TARGET_LIST_ORDER_FIELDS.has(orderBy.field)) {
58
+ throw new Error(
59
+ `[getTargetList] Unsupported orderBy.field "${orderBy.field}"; allowed: ${[...TARGET_LIST_ORDER_FIELDS].join(", ")}`
60
+ );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * 构建带 category 作用域的 target 查询(供 getTargetList / getTargetTotalCount / pollTargetList 共用)。
66
+ *
67
+ * 必要性:
68
+ * 1. category 是 target 表的分区维度,所有列表/计数/出队都必须限定 category,避免跨类型扫表。
69
+ * 2. 传入 filterBuilder 时仍强制 `.eq("category", category)`,防止自定义 builder 漏加 category 导致误查或安全问题。
70
+ * 3. 统一 select 普通列表与 count head 两种查询形态,再叠加 filterList,避免三处复制分支逻辑。
71
+ */
72
+ function buildCategoryScopedQuery({
73
+ category,
74
+ filterList,
75
+ filterBuilder,
76
+ select,
77
+ selectOptions,
78
+ }: {
79
+ category: Target["category"];
80
+ filterList: QueryFilter[];
81
+ filterBuilder?: TargetFilterBuilder;
82
+ select: string;
83
+ selectOptions?: { count: "exact"; head: true };
84
+ }) {
85
+ const base =
86
+ filterBuilder != null
87
+ ? filterBuilder.eq("category", category)
88
+ : selectOptions != null
89
+ ? supabase.client.from("target").select(select, selectOptions).eq("category", category)
90
+ : supabase.client.from("target").select(select).eq("category", category);
91
+
92
+ return applyQueryFilters(base, filterList);
93
+ }
94
+
12
95
  // biome-ignore lint/suspicious/noExplicitAny: PostgrestFilterBuilder is generic over schema
13
96
  function applyQueryFilter<Q extends PostgrestFilterBuilder<any, any, any, any, any>>(
14
97
  query: Q,
@@ -21,8 +104,10 @@ function applyQueryFilter<Q extends PostgrestFilterBuilder<any, any, any, any, a
21
104
  return query.neq(filter.field, filter.value) as Q;
22
105
  case "in":
23
106
  return query.in(filter.field, filter.value as unknown[]) as Q;
24
- default:
25
- return query;
107
+ default: {
108
+ const unsupportedOperator: never = filter.operator;
109
+ throw new Error(`[applyQueryFilter] Unsupported operator: ${unsupportedOperator}`);
110
+ }
26
111
  }
27
112
  }
28
113
 
@@ -68,55 +153,149 @@ export const getPossibleTarget = async ({ filterList }: { filterList: QueryFilte
68
153
  };
69
154
 
70
155
  export interface GetTargetListParams extends BaseQueryParams {
71
- pageNum: number;
72
- pageSize: number;
73
156
  category: Target["category"];
157
+ /** 0-based page index. Required with pageSize unless `limit` is set. */
158
+ pageNum?: number;
159
+ pageSize?: number;
160
+ /** Shorthand for `{ pageNum: 0, pageSize: limit }`. Max {@link MAX_TARGET_LIST_PAGE_SIZE}. */
161
+ limit?: number;
162
+ /** PostgREST select clause; defaults to `"*"`. */
163
+ selectFields?: string;
164
+ /** Custom query builder; `category` and `filterList` are still applied. */
165
+ filterBuilder?: TargetFilterBuilder;
74
166
  }
75
167
 
76
- export const getTargetList = async ({
168
+ /**
169
+ * Paginated target list by category. Failures throw via `handleSupabaseError` (no `{ error }` envelope).
170
+ */
171
+ export const getTargetList = async <T extends Target = Target>({
77
172
  pageNum,
78
173
  pageSize,
174
+ limit,
79
175
  category,
80
176
  filterList = [],
81
177
  orderBy = { field: "created_at", ascending: false },
178
+ selectFields = "*",
82
179
  filterBuilder,
83
- }: GetTargetListParams & {
84
- // biome-ignore lint/suspicious/noExplicitAny: PostgrestFilterBuilder is generic over schema
85
- filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
86
- }) => {
87
- const query =
88
- filterBuilder ??
89
- applyQueryFilters(supabase.client.from("target").select("*").eq("category", category), filterList);
180
+ }: GetTargetListParams) => {
181
+ const { pageNum: resolvedPageNum, pageSize: resolvedPageSize } = resolveTargetListPagination({
182
+ pageNum,
183
+ pageSize,
184
+ limit,
185
+ });
186
+ validateTargetListOrderBy(orderBy);
187
+
188
+ const query = buildCategoryScopedQuery({
189
+ category,
190
+ filterList,
191
+ filterBuilder,
192
+ select: selectFields,
193
+ });
90
194
 
91
195
  const { data, error } = await query
92
196
  .order(orderBy.field, { ascending: orderBy.ascending })
93
- .range(pageNum * pageSize, (pageNum + 1) * pageSize - 1);
197
+ .range(resolvedPageNum * resolvedPageSize, (resolvedPageNum + 1) * resolvedPageSize - 1);
94
198
 
95
199
  if (error) {
96
200
  handleSupabaseError("getTargetList", error, "Failed to fetch target list.");
97
201
  }
98
- return generateResponse.success<Target[]>(data);
202
+ return generateResponse.success<T[]>((data ?? []) as T[]);
203
+ };
204
+
205
+ export interface ScanTargetListParams {
206
+ category: Target["category"];
207
+ filterList?: QueryFilter[];
208
+ orderBy?: QueryOrderBy;
209
+ selectFields?: string;
210
+ filterBuilder?: TargetFilterBuilder;
211
+ /** Stop after this many rows (optional guard against runaway scans). */
212
+ maxRows?: number;
213
+ }
214
+
215
+ /**
216
+ * Scan all rows matching `category` + `filterList` by paging until exhausted.
217
+ *
218
+ * Use for worker bootstrap / sync jobs that need the full matching set.
219
+ * For UI pagination, use {@link getTargetList} (single page, explicit pageNum).
220
+ *
221
+ * Internal batch size is fixed at {@link MAX_TARGET_LIST_PAGE_SIZE} (PostgREST max-rows).
222
+ * Callers receive the full merged result; they do not pass a per-request page size.
223
+ *
224
+ * Uses offset pagination (`.range`). Stable enough for small catalogs (e.g. Repo registry);
225
+ * for large tables under concurrent writes, consider a DB RPC or keyset pagination later.
226
+ */
227
+ export const scanTargetList = async <T extends Target = Target>({
228
+ category,
229
+ filterList = [],
230
+ orderBy = { field: "created_at", ascending: false },
231
+ selectFields = "*",
232
+ filterBuilder,
233
+ maxRows,
234
+ }: ScanTargetListParams) => {
235
+ const batchSize = MAX_TARGET_LIST_PAGE_SIZE;
236
+ validateTargetListOrderBy(orderBy);
237
+ if (maxRows != null && (!Number.isInteger(maxRows) || maxRows < 1)) {
238
+ throw new Error(`[scanTargetList] maxRows must be a positive integer, got ${maxRows}`);
239
+ }
240
+
241
+ const rows: T[] = [];
242
+ let pageNum = 0;
243
+
244
+ while (true) {
245
+ const query = buildCategoryScopedQuery({
246
+ category,
247
+ filterList,
248
+ filterBuilder,
249
+ select: selectFields,
250
+ });
251
+
252
+ const { data, error } = await query
253
+ .order(orderBy.field, { ascending: orderBy.ascending })
254
+ .range(pageNum * batchSize, (pageNum + 1) * batchSize - 1);
255
+
256
+ if (error) {
257
+ handleSupabaseError("scanTargetList", error, "Failed to scan target list.");
258
+ }
259
+
260
+ const page = (data ?? []) as T[];
261
+ if (page.length === 0) {
262
+ break;
263
+ }
264
+
265
+ rows.push(...page);
266
+
267
+ if (maxRows != null && rows.length >= maxRows) {
268
+ rows.length = maxRows;
269
+ break;
270
+ }
271
+
272
+ if (page.length < batchSize) {
273
+ break;
274
+ }
275
+
276
+ pageNum += 1;
277
+ }
278
+
279
+ return generateResponse.success(rows);
99
280
  };
100
281
 
101
282
  export interface GetTargetTotalCountParams extends BaseQueryParams {
102
283
  category: Target["category"];
103
- // biome-ignore lint/suspicious/noExplicitAny: PostgrestFilterBuilder is generic over schema
104
- filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
284
+ /** Custom query builder; `category` and `filterList` are still applied. */
285
+ filterBuilder?: TargetFilterBuilder;
105
286
  }
106
287
  export const getTargetTotalCount = async ({
107
288
  category,
108
289
  filterList = [],
109
290
  filterBuilder,
110
291
  }: GetTargetTotalCountParams) => {
111
- const query =
112
- filterBuilder ??
113
- applyQueryFilters(
114
- supabase.client
115
- .from("target")
116
- .select("id", { count: "exact", head: true })
117
- .eq("category", category),
118
- filterList
119
- );
292
+ const query = buildCategoryScopedQuery({
293
+ category,
294
+ filterList,
295
+ filterBuilder,
296
+ select: "id",
297
+ selectOptions: { count: "exact", head: true },
298
+ });
120
299
 
121
300
  const { count, error } = await query;
122
301
 
@@ -126,6 +305,76 @@ export const getTargetTotalCount = async ({
126
305
  return generateResponse.success<number>(count ?? 0);
127
306
  };
128
307
 
308
+ function validatePollTargetListSize(size: number): void {
309
+ if (!Number.isInteger(size) || size < 1 || size > MAX_POLL_TARGET_LIST_SIZE) {
310
+ throw new Error(
311
+ `[pollTargetList] size must be an integer between 1 and ${MAX_POLL_TARGET_LIST_SIZE}, got ${size}`
312
+ );
313
+ }
314
+ }
315
+
316
+ export interface PollTargetListParams {
317
+ category: Target["category"];
318
+ /** Max rows to read and attempt dequeue (not offset pagination). */
319
+ size: number;
320
+ filterList?: QueryFilter[];
321
+ /** Order by `created_at`. Default `true` (oldest first). */
322
+ ascending?: boolean;
323
+ selectFields?: string;
324
+ }
325
+
326
+ /**
327
+ * Queue poll: SELECT up to `size` rows matching `category` + `filterList`, ordered by
328
+ * `created_at`, then DELETE each by `id`. Returns only dequeued rows (at-most-once).
329
+ *
330
+ * Delete failures (concurrent consumer) are skipped silently.
331
+ * Failures on the initial SELECT throw via `handleSupabaseError`.
332
+ *
333
+ * TODO(concurrency): 当前为 SELECT → 逐条 DELETE,非 DB 原子出队,多 consumer 并发时可能:
334
+ * - 重叠读取:两个 worker 同一批 SELECT 命中相同行,靠 DELETE 竞速,后者 skip(多数 at-most-once)
335
+ * - TOCTOU:SELECT 与 DELETE 之间无锁,极端情况下 DELETE 0 行仍不报错 → 可能重复计入 polled
336
+ * - 非 exactly-once:行已删但 worker 崩溃未处理 → 消息丢失
337
+ * 若同一 filter 多实例 poll(或多 worker 同 nodeId),需改为 DB 侧原子方案,例如:
338
+ * - RPC:`DELETE … WHERE id IN (SELECT … FOR UPDATE SKIP LOCKED) RETURNING *`
339
+ * - 或 claim 状态机:UPDATE … WHERE pending → DELETE/标记已消费(类似 patchClaimTask 乐观锁)
340
+ * - 短期加固:DELETE 后校验影响行数,0 行则不 push 到 polled
341
+ */
342
+ export const pollTargetList = async <T extends Target = Target>({
343
+ category,
344
+ size,
345
+ filterList = [],
346
+ ascending = true,
347
+ selectFields = "*",
348
+ }: PollTargetListParams) => {
349
+ validatePollTargetListSize(size);
350
+
351
+ const query = buildCategoryScopedQuery({
352
+ category,
353
+ filterList,
354
+ select: selectFields,
355
+ });
356
+
357
+ const { data, error } = await query.order("created_at", { ascending }).limit(size);
358
+
359
+ if (error) {
360
+ handleSupabaseError("pollTargetList", error, "Failed to fetch targets for poll.");
361
+ }
362
+
363
+ const candidates = (data ?? []) as T[];
364
+ const polled: T[] = [];
365
+
366
+ for (const row of candidates) {
367
+ try {
368
+ await deleteTarget({ id: row.id });
369
+ polled.push(row);
370
+ } catch {
371
+ // Concurrent dequeue — row already removed
372
+ }
373
+ }
374
+
375
+ return generateResponse.success<T[]>(polled);
376
+ };
377
+
129
378
  export const deleteTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
130
379
  // Check whether the target exists given filterList
131
380
  if (filterList != null && filterList.length > 0) {
@@ -413,3 +662,33 @@ export function validateWith<P extends object, V extends BaseValidator<P>>(Valid
413
662
  };
414
663
  };
415
664
  }
665
+
666
+ function formatZodValidationError(schemaName: string, error: ZodError): Error {
667
+ const errorList = error.issues
668
+ .map((issue, index) => {
669
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
670
+ return ` ${index + 1}. ${path}: ${issue.message}`;
671
+ })
672
+ .join("\n");
673
+ const errorCount = error.issues.length;
674
+ return new Error(
675
+ `[${schemaName}] Validation failed (${errorCount} error${errorCount > 1 ? "s" : ""}):\n${errorList}`
676
+ );
677
+ }
678
+
679
+ /** Zod-based counterpart to {@link validateWith} — requires `zod` as a peer dependency. */
680
+ export function validateWithSchema<T extends ZodType>(
681
+ schema: T,
682
+ schemaName = "Schema"
683
+ ) {
684
+ type Parsed = T["_output"];
685
+ return <R>(fn: (validPayload: Parsed) => R | Promise<R>) => {
686
+ return (payload: Parsed): R | Promise<R> => {
687
+ const result = schema.safeParse(payload);
688
+ if (!result.success) {
689
+ throw formatZodValidationError(schemaName, result.error);
690
+ }
691
+ return fn(result.data);
692
+ };
693
+ };
694
+ }
@@ -142,4 +142,4 @@ const FileManager = {
142
142
  verifyFileHash,
143
143
  };
144
144
 
145
- export default FileManager;
145
+ export { FileManager };
package/src/index.ts CHANGED
@@ -24,7 +24,7 @@ export type {
24
24
  LifecycleStatus as ParcelLifecycleStatus,
25
25
  Lifecycle as ParcelLifecycle,
26
26
  } from "./parcel/parcel.interface";
27
- export { default as ParcelManager } from "./parcel/parcel-manager";
27
+ export { ParcelManager } from "./parcel/parcel-manager";
28
28
  export type { StorageAdapter, ParcelTargetBase } from "./parcel/parcel-manager";
29
29
 
30
30
  export * from "./file/file.interface";
@@ -60,5 +60,20 @@ export * from "./list/list.api";
60
60
  export * from "./word/word.interface";
61
61
 
62
62
  export * from "./task/task.interface";
63
-
64
- export * from "./repo/repo.interface";
63
+ export * from "./task/task.api";
64
+ export { TaskManager } from "./task/task-manager";
65
+ export type {
66
+ RegisterTasksOptions,
67
+ RegisterTasksResult,
68
+ PrepareTaskResponse,
69
+ PrepareTaskParams,
70
+ PrepareTaskFailureReason,
71
+ } from "./task/task-manager";
72
+
73
+ export * from "./node/node.interface";
74
+ export * from "./node/node.api";
75
+ export { NodeManager } from "./node/node-manager";
76
+
77
+ export * from "./repo/repo.interface";
78
+ export * from "./repo/repo.api";
79
+ export { RepoManager } from "./repo/repo-manager";