@kubun/plugin-workflow 0.16.0 → 0.17.1

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.
@@ -44,8 +44,17 @@ export function createWorkflowControlPlaneHelpers(getP2P) {
44
44
  discoverWorkflows: async (groupID, options)=>(await getP2P()).discoverWorkflows(groupID, options),
45
45
  listWorkflows: async (groupID, targetDID)=>(await getP2P()).listWorkflows(groupID, targetDID),
46
46
  workflowStatus: async (groupID, targetDID, instanceID)=>(await getP2P()).workflowStatus(groupID, targetDID, instanceID),
47
- enqueueWorkflow: async (groupID, targetDID, name, params, opts)=>unwrap(await (await getP2P()).enqueueWorkflow(groupID, targetDID, name, params, opts)),
48
- cancelWorkflow: async (groupID, targetDID, instanceID)=>unwrap(await (await getP2P()).cancelWorkflow(groupID, targetDID, instanceID)),
49
- retryWorkflow: async (groupID, targetDID, instanceID)=>unwrap(await (await getP2P()).retryWorkflow(groupID, targetDID, instanceID))
47
+ enqueueWorkflow: async (groupID, targetDID, name, params, opts)=>{
48
+ const p2p = await getP2P();
49
+ return unwrap(await p2p.enqueueWorkflow(groupID, targetDID, name, params, opts));
50
+ },
51
+ cancelWorkflow: async (groupID, targetDID, instanceID)=>{
52
+ const p2p = await getP2P();
53
+ return unwrap(await p2p.cancelWorkflow(groupID, targetDID, instanceID));
54
+ },
55
+ retryWorkflow: async (groupID, targetDID, instanceID)=>{
56
+ const p2p = await getP2P();
57
+ return unwrap(await p2p.retryWorkflow(groupID, targetDID, instanceID));
58
+ }
50
59
  };
51
60
  }
package/lib/engine.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Logger } from '@kubun/logger';
2
2
  import type { RetryPolicy, RetryRefusalReason, WorkflowAPI } from '@kubun/plugin-workflow-api';
3
3
  import type { WorkflowStoreAPI } from '@kubun/store-workflow';
4
4
  import type { GetRandomID } from '@sozai/runtime';
5
+ import { type ResolvedFinishedInstanceCleanup } from './instance-cleanup.js';
5
6
  export declare function backoff(attempts: number, policy?: RetryPolicy): number;
6
7
  export declare function parseEvery(spec: string): number;
7
8
  export declare class WorkflowRetryError extends Error {
@@ -15,15 +16,18 @@ export type WorkflowEngineDeps = {
15
16
  getStore: () => Promise<WorkflowStoreAPI>;
16
17
  logger: Logger;
17
18
  now?: () => number;
18
- getRandomID?: GetRandomID;
19
+ getRandomID: GetRandomID;
19
20
  tickMs?: number;
20
21
  disposeTimeoutMs?: number;
21
22
  ready?: () => Promise<void>;
23
+ finishedInstanceCleanup?: ResolvedFinishedInstanceCleanup | null;
24
+ cleanupBatchSize?: number;
22
25
  };
23
26
  export type WorkflowEngine = {
24
27
  api: WorkflowAPI;
25
28
  recover(): Promise<Array<string>>;
26
29
  tick(): Promise<void>;
30
+ cleanupFinished(): Promise<number>;
27
31
  dispose(): Promise<void>;
28
32
  };
29
33
  export declare function createWorkflowEngine(deps: WorkflowEngineDeps): WorkflowEngine;
package/lib/engine.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { getWorkflowStore } from '@kubun/store-workflow';
1
2
  import { EventEmitter } from '@sozai/event';
2
3
  import { createFlow } from '@sozai/flow';
3
4
  import { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
5
+ import { FINISHED_INSTANCE_CLEANUP_BATCH_SIZE } from './instance-cleanup.js';
4
6
  // One runner may hold an instance's lease for this long before another boot may
5
7
  // reclaim it as stranded.
6
8
  const LEASE_MS = 30_000;
@@ -27,7 +29,9 @@ const INTERVAL_UNITS = {
27
29
  export function parseEvery(spec) {
28
30
  const match = /^(\d+)(ms|s|m|h)?$/.exec(spec.trim());
29
31
  if (match == null) throw new Error(`Invalid interval "${spec}"`);
30
- return Number(match[1]) * (match[2] != null ? INTERVAL_UNITS[match[2]] : 1);
32
+ const unit = match[2];
33
+ const multiplier = unit != null ? INTERVAL_UNITS[unit] ?? 1 : 1;
34
+ return Number(match[1]) * multiplier;
31
35
  }
32
36
  const TERMINAL = new Set([
33
37
  'completed',
@@ -60,7 +64,8 @@ function toStatus(row) {
60
64
  };
61
65
  }
62
66
  // The active (pending/running) instance for (name, singletonKey) if one exists,
63
- // else the most recent terminal by updated_at, else null. Instances are promoted
67
+ // else the most recent terminal by updated_at (a tie goes to the highest
68
+ // instance_id, as the finished-instance cleanup keeps), else null. Instances are promoted
64
69
  // with singleton_key = subject, so the subject keys the lookup.
65
70
  async function currentInstanceRow(s, name, singletonKey) {
66
71
  const rows = await s.list({
@@ -68,7 +73,7 @@ async function currentInstanceRow(s, name, singletonKey) {
68
73
  });
69
74
  const forSubject = rows.filter((r)=>r.singleton_key === singletonKey);
70
75
  const active = forSubject.find((r)=>r.status === 'pending' || r.status === 'running');
71
- return active ?? forSubject.reduce((best, r)=>best == null || r.updated_at > best.updated_at ? r : best, null);
76
+ return active ?? forSubject.reduce((best, r)=>best == null || r.updated_at > best.updated_at || r.updated_at === best.updated_at && r.instance_id > best.instance_id ? r : best, null);
72
77
  }
73
78
  // Project an adaptive schedule row as a PeriodicSync. `currentStatus` is the
74
79
  // schedule subject's current instance (see `currentInstanceRow`), null if never
@@ -95,7 +100,7 @@ export function createWorkflowEngine(deps) {
95
100
  const registry = new Map();
96
101
  // A per-process identity for lease ownership. A fresh engine over the same DB
97
102
  // (a "reboot") gets a new one, which is what makes crash-reclaim observable.
98
- const bootID = (deps.getRandomID ?? (()=>crypto.randomUUID()))();
103
+ const bootID = deps.getRandomID();
99
104
  // Instances currently being driven in THIS process, so `cancel` can signal the
100
105
  // running flow via its AbortController.
101
106
  const running = new Map();
@@ -278,6 +283,58 @@ export function createWorkflowEngine(deps) {
278
283
  running.delete(instanceID);
279
284
  }
280
285
  }
286
+ // --- Finished-instance retention cleanup ---
287
+ const cleanup = deps.finishedInstanceCleanup ?? null;
288
+ const cleanupBatchSize = deps.cleanupBatchSize ?? FINISHED_INSTANCE_CLEANUP_BATCH_SIZE;
289
+ // Due immediately: the first scheduler pass after `ready` runs it.
290
+ let nextCleanupAt = Number.NEGATIVE_INFINITY;
291
+ let cleanupRun = null;
292
+ // One batch per statement until a short batch, or until dispose(). Errors are
293
+ // logged, never thrown: the next interval retries.
294
+ async function collectFinished(retentionMs) {
295
+ let total = 0;
296
+ try {
297
+ const s = await store();
298
+ const before = now() - retentionMs;
299
+ while(!disposed){
300
+ const removed = await s.purgeFinished({
301
+ before,
302
+ limit: cleanupBatchSize
303
+ });
304
+ total += removed;
305
+ if (removed < cleanupBatchSize) break;
306
+ }
307
+ if (total > 0) {
308
+ logger.debug('workflow cleanup removed {count} finished instances', {
309
+ count: total
310
+ });
311
+ }
312
+ } catch (cause) {
313
+ logger.warn('workflow finished-instance cleanup failed', {
314
+ error: cause
315
+ });
316
+ }
317
+ return total;
318
+ }
319
+ function cleanupFinished() {
320
+ if (cleanup == null || disposed) return Promise.resolve(0);
321
+ if (cleanupRun == null) {
322
+ const run = collectFinished(cleanup.retentionMs).finally(()=>{
323
+ cleanupRun = null;
324
+ });
325
+ cleanupRun = run;
326
+ // Tracked so dispose() drains a run in progress before the store closes.
327
+ track(run.then(()=>{}));
328
+ }
329
+ return cleanupRun;
330
+ }
331
+ // Called from each scheduler pass; starts a run once the interval is due. The
332
+ // pass does not wait for it, so a long cleanup never delays claims.
333
+ function maybeCleanup(at) {
334
+ if (cleanup == null || cleanupRun != null || at < nextCleanupAt) return;
335
+ nextCleanupAt = at + cleanup.intervalMs;
336
+ void cleanupFinished();
337
+ }
281
338
  // Per-lane concurrency. Defaults to 1; `defineQueue` raises a lane's cap.
282
339
  const concurrency = new Map();
283
340
  function laneConcurrency(lane) {
@@ -377,6 +434,7 @@ export function createWorkflowEngine(deps) {
377
434
  // in-flight when dispose() fired from claiming fresh work after shutdown began.
378
435
  if (disposed) return;
379
436
  const at = now();
437
+ maybeCleanup(at);
380
438
  // Promote due schedules first so their instances are claimable this pass.
381
439
  await promoteSchedules(s, at);
382
440
  if (disposed) return;
@@ -468,7 +526,7 @@ export function createWorkflowEngine(deps) {
468
526
  async schedule (name, params, when) {
469
527
  const s = await store();
470
528
  const everyMs = parseEvery(when.every);
471
- const id = crypto.randomUUID();
529
+ const id = deps.getRandomID();
472
530
  const at = now();
473
531
  // First fire one interval out: a restart storm must not refire everything
474
532
  // that was ever scheduled.
@@ -484,7 +542,8 @@ export function createWorkflowEngine(deps) {
484
542
  const handle = {
485
543
  id,
486
544
  async disable () {
487
- await (await store()).setScheduleEnabled(id, false, now());
545
+ const s2 = await store();
546
+ await s2.setScheduleEnabled(id, false, now());
488
547
  },
489
548
  async enable () {
490
549
  // Re-arm one interval out from re-enable so a long-disabled schedule
@@ -496,7 +555,8 @@ export function createWorkflowEngine(deps) {
496
555
  },
497
556
  async cancel () {
498
557
  // No store delete today: permanent disable is the cancel semantics.
499
- await (await store()).setScheduleEnabled(id, false, now());
558
+ const s2 = await store();
559
+ await s2.setScheduleEnabled(id, false, now());
500
560
  }
501
561
  };
502
562
  return handle;
@@ -523,7 +583,8 @@ export function createWorkflowEngine(deps) {
523
583
  const handle = {
524
584
  id,
525
585
  async disable () {
526
- await (await store()).setScheduleEnabled(id, false, now());
586
+ const s2 = await store();
587
+ await s2.setScheduleEnabled(id, false, now());
527
588
  },
528
589
  async enable () {
529
590
  // Re-arm eagerly (now + changed), mirroring reactivation above.
@@ -538,7 +599,8 @@ export function createWorkflowEngine(deps) {
538
599
  await s2.setScheduleEnabled(id, true, reAt);
539
600
  },
540
601
  async cancel () {
541
- await (await store()).setScheduleEnabled(id, false, now());
602
+ const s2 = await store();
603
+ await s2.setScheduleEnabled(id, false, now());
542
604
  }
543
605
  };
544
606
  return handle;
@@ -549,7 +611,7 @@ export function createWorkflowEngine(deps) {
549
611
  throw new Error(`Workflow "${name}" is not registered`);
550
612
  }
551
613
  const s = await store();
552
- const instanceID = crypto.randomUUID();
614
+ const instanceID = deps.getRandomID();
553
615
  const createdAt = now();
554
616
  const initialAction = params !== undefined ? {
555
617
  name: def.initialAction.name,
@@ -583,6 +645,53 @@ export function createWorkflowEngine(deps) {
583
645
  instanceID
584
646
  };
585
647
  },
648
+ async enqueueTx (stores, name, params, opts) {
649
+ const def = registry.get(name);
650
+ if (def == null) {
651
+ throw new Error(`Workflow "${name}" is not registered`);
652
+ }
653
+ // Resolve the store from the CALLER's request-scoped provider, not the
654
+ // top-level `store()` — when `stores` is a tx-scoped StoreProvider
655
+ // (e.g. from `db.withTransaction`), this binds the returned store to
656
+ // that already-open transaction.
657
+ const s = await getWorkflowStore(stores);
658
+ const instanceID = deps.getRandomID();
659
+ const createdAt = now();
660
+ const initialAction = params !== undefined ? {
661
+ name: def.initialAction.name,
662
+ params
663
+ } : def.initialAction;
664
+ const input = {
665
+ instanceID,
666
+ name,
667
+ queue: opts?.queue ?? def.queue ?? name,
668
+ idempotencyKey: opts?.idempotencyKey ?? null,
669
+ singletonKey: opts?.singletonKey ?? null,
670
+ priority: opts?.priority ?? 0,
671
+ state: {},
672
+ nextAction: initialAction,
673
+ runAfter: createdAt,
674
+ createdAt
675
+ };
676
+ // insertInstanceTx runs directly on the tx-bound store — no nested
677
+ // transaction — inheriting atomicity from the caller's own transaction.
678
+ const result = await s.insertInstanceTx(input);
679
+ // Idempotent or singleton hit: return the existing instance, drive nothing.
680
+ if (!result.created) {
681
+ return {
682
+ instanceID: result.row.instance_id
683
+ };
684
+ }
685
+ // Defer the wake until AFTER the caller's transaction commits: `stores`
686
+ // IS that transaction here, so an inline scheduleTick() (as `enqueue`
687
+ // does above) could wake the scheduler to claim a row whose insert has
688
+ // not committed yet. onCommit fires post-commit, or never if the
689
+ // transaction rolls back.
690
+ stores.onCommit(()=>scheduleTick());
691
+ return {
692
+ instanceID
693
+ };
694
+ },
586
695
  async cancel (instanceID) {
587
696
  const controller = running.get(instanceID);
588
697
  if (controller != null) {
@@ -718,6 +827,7 @@ export function createWorkflowEngine(deps) {
718
827
  track(p);
719
828
  return p;
720
829
  },
830
+ cleanupFinished,
721
831
  async dispose () {
722
832
  disposed = true;
723
833
  if (interval != null) {
package/lib/index.d.ts CHANGED
@@ -1,10 +1,30 @@
1
1
  import type { KubunPlugin, PluginFactoryParams } from '@kubun/engine';
2
- export type WorkflowPluginOptions = Record<string, never>;
3
- export declare function createWorkflowPlugin(_options?: WorkflowPluginOptions): (params: PluginFactoryParams) => KubunPlugin;
2
+ import { type FinishedInstanceCleanupOptions } from './instance-cleanup.js';
3
+ export type WorkflowPluginOptions = {
4
+ /**
5
+ * Retention cleanup of finished workflow instances. Every run of a workflow,
6
+ * scheduled ones included, leaves an instance row; once it has been finished
7
+ * (completed, failed or cancelled) for `retentionMs`, the cleanup deletes it.
8
+ * Defaults: runs every 6 hours, keeps 30 days. Omit it for the defaults,
9
+ * pass an object to change either value, or `false` to keep every instance.
10
+ *
11
+ * It never deletes a pending or running instance, nor the newest instance of
12
+ * a periodic sync's subject, which is what its status and
13
+ * `getCurrentInstance` report. A deleted instance is unknown to `get`, cannot be retried,
14
+ * and no longer holds its idempotency key: an enqueue reusing that key
15
+ * creates a new instance.
16
+ *
17
+ * Each value must be a positive finite number, or the plugin throws when it
18
+ * is created.
19
+ */
20
+ finishedInstanceCleanup?: FinishedInstanceCleanupOptions;
21
+ };
22
+ export declare function createWorkflowPlugin(options?: WorkflowPluginOptions): (params: PluginFactoryParams) => KubunPlugin;
4
23
  export { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
5
24
  export { createWorkflowControlPlaneHelpers, WorkflowCommandRefusedError, type WorkflowControlPlaneHelpers, } from './control-plane.js';
6
25
  export { defineWorkflow, isRemotelyCommandable, isRemotelyObservable } from './definition.js';
7
26
  export type { WorkflowEngine, WorkflowEngineDeps } from './engine.js';
8
27
  export { createWorkflowEngine, parseEvery, WorkflowRetryError } from './engine.js';
28
+ export { DEFAULT_FINISHED_INSTANCE_CLEANUP_INTERVAL_MS, DEFAULT_FINISHED_INSTANCE_RETENTION_MS, FINISHED_INSTANCE_CLEANUP_BATCH_SIZE, type FinishedInstanceCleanupOptions, type ResolvedFinishedInstanceCleanup, resolveFinishedInstanceCleanup, } from './instance-cleanup.js';
9
29
  export type { WorkflowContext, WorkflowEventResult } from './schema.js';
10
30
  export { createWorkflowContextFactory, createWorkflowSchemaExtension, subscribeToWorkflowEvents, } from './schema.js';
package/lib/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { getWorkflowStore, workflowStoreDefinition } from '@kubun/store-workflow';
2
2
  import { createWorkflowControlPlaneHelpers } from './control-plane.js';
3
3
  import { createWorkflowEngine } from './engine.js';
4
+ import { resolveFinishedInstanceCleanup } from './instance-cleanup.js';
4
5
  import { createWorkflowContextFactory, createWorkflowSchemaExtension } from './schema.js';
5
- export function createWorkflowPlugin(_options = {}) {
6
+ export function createWorkflowPlugin(options = {}) {
7
+ // Validated here, so a bad value fails when the plugin is created.
8
+ const cleanupSettings = resolveFinishedInstanceCleanup(options.finishedInstanceCleanup);
6
9
  return (params)=>{
7
10
  params.db.register(workflowStoreDefinition);
8
11
  const logger = params.getLogger('workflow');
@@ -14,7 +17,8 @@ export function createWorkflowPlugin(_options = {}) {
14
17
  // the interval claim no work until `ready()` resolves, and an identity
15
18
  // mismatch leaves the engine inert rather than driving jobs on a database it
16
19
  // was rejected from.
17
- ready: ()=>params.engine.ready()
20
+ ready: ()=>params.engine.ready(),
21
+ finishedInstanceCleanup: cleanupSettings
18
22
  });
19
23
  // Re-drive stranded instances once on boot, but only after the host engine
20
24
  // verifies its database identity — a re-driven instance mints under the engine's
@@ -44,4 +48,5 @@ export { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
44
48
  export { createWorkflowControlPlaneHelpers, WorkflowCommandRefusedError } from './control-plane.js';
45
49
  export { defineWorkflow, isRemotelyCommandable, isRemotelyObservable } from './definition.js';
46
50
  export { createWorkflowEngine, parseEvery, WorkflowRetryError } from './engine.js';
51
+ export { DEFAULT_FINISHED_INSTANCE_CLEANUP_INTERVAL_MS, DEFAULT_FINISHED_INSTANCE_RETENTION_MS, FINISHED_INSTANCE_CLEANUP_BATCH_SIZE, resolveFinishedInstanceCleanup } from './instance-cleanup.js';
47
52
  export { createWorkflowContextFactory, createWorkflowSchemaExtension, subscribeToWorkflowEvents } from './schema.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Retention cleanup of finished workflow instances (completed, failed or
3
+ * cancelled). `false` turns it off; an object (or the option left out) turns
4
+ * it on, each value defaulting when absent.
5
+ */
6
+ export type FinishedInstanceCleanupOptions = false | {
7
+ /** How often the cleanup runs, in milliseconds. Default 6 hours. */
8
+ intervalMs?: number;
9
+ /** How long a finished instance is kept, in milliseconds. Default 30 days. */
10
+ retentionMs?: number;
11
+ };
12
+ export declare const DEFAULT_FINISHED_INSTANCE_CLEANUP_INTERVAL_MS: number;
13
+ export declare const DEFAULT_FINISHED_INSTANCE_RETENTION_MS: number;
14
+ /** The most rows one cleanup statement deletes. */
15
+ export declare const FINISHED_INSTANCE_CLEANUP_BATCH_SIZE = 500;
16
+ export type ResolvedFinishedInstanceCleanup = {
17
+ intervalMs: number;
18
+ retentionMs: number;
19
+ };
20
+ /**
21
+ * The cleanup settings `options` describe, or null when cleanup is off.
22
+ * Throws on a value that is not a positive whole number of milliseconds.
23
+ */
24
+ export declare function resolveFinishedInstanceCleanup(options: FinishedInstanceCleanupOptions | undefined): ResolvedFinishedInstanceCleanup | null;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Retention cleanup of finished workflow instances (completed, failed or
3
+ * cancelled). `false` turns it off; an object (or the option left out) turns
4
+ * it on, each value defaulting when absent.
5
+ */ export const DEFAULT_FINISHED_INSTANCE_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1000;
6
+ export const DEFAULT_FINISHED_INSTANCE_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
7
+ /** The most rows one cleanup statement deletes. */ export const FINISHED_INSTANCE_CLEANUP_BATCH_SIZE = 500;
8
+ function assertDuration(value, label) {
9
+ // Whole milliseconds only: the cutoff is compared with integer timestamp
10
+ // columns, which Postgres will not compare with a fractional parameter.
11
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
12
+ throw new Error(`finishedInstanceCleanup.${label} must be a positive whole number of milliseconds`);
13
+ }
14
+ return value;
15
+ }
16
+ /**
17
+ * The cleanup settings `options` describe, or null when cleanup is off.
18
+ * Throws on a value that is not a positive whole number of milliseconds.
19
+ */ export function resolveFinishedInstanceCleanup(options) {
20
+ if (options === false) return null;
21
+ if (options != null && (typeof options !== 'object' || Array.isArray(options))) {
22
+ throw new Error('finishedInstanceCleanup must be false or an object');
23
+ }
24
+ return {
25
+ intervalMs: assertDuration(options?.intervalMs ?? DEFAULT_FINISHED_INSTANCE_CLEANUP_INTERVAL_MS, 'intervalMs'),
26
+ retentionMs: assertDuration(options?.retentionMs ?? DEFAULT_FINISHED_INSTANCE_RETENTION_MS, 'retentionMs')
27
+ };
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-workflow",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,22 +15,22 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@kubun/db": "^0.16.0",
19
- "@kubun/engine": "^0.16.0",
20
- "@kubun/graphql": "^0.16.0",
21
- "@kubun/id": "^0.16.0",
22
- "@kubun/logger": "^0.16.0",
23
- "@kubun/plugin-p2p-api": "^0.16.0",
24
- "@kubun/plugin-workflow-api": "^0.16.0",
25
- "@kubun/store-workflow": "^0.16.0",
18
+ "@kubun/db": "^0.17.0",
19
+ "@kubun/engine": "^0.17.2",
20
+ "@kubun/graphql": "^0.17.2",
21
+ "@kubun/id": "^0.17.0",
22
+ "@kubun/logger": "^0.17.0",
23
+ "@kubun/plugin-p2p-api": "^0.17.0",
24
+ "@kubun/plugin-workflow-api": "^0.17.1",
25
+ "@kubun/store-workflow": "^0.17.1",
26
26
  "@sozai/event": "^0.1.3",
27
27
  "@sozai/flow": "^0.3.1",
28
28
  "@sozai/runtime": "^0.1.0",
29
29
  "@sozai/schema": "^0.1.2"
30
30
  },
31
31
  "devDependencies": {
32
- "@kubun/db-better-sqlite": "^0.16.0",
33
- "@kubun/db-postgres": "^0.16.0",
32
+ "@kubun/db-better-sqlite": "^0.17.0",
33
+ "@kubun/db-postgres": "^0.17.1",
34
34
  "@testcontainers/postgresql": "^12.1.0"
35
35
  },
36
36
  "publishConfig": {
@@ -39,8 +39,8 @@
39
39
  "scripts": {
40
40
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
41
41
  "build:clean": "del lib",
42
- "build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
43
- "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
42
+ "build:js": "del 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
43
+ "build:types": "del 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
44
44
  "build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
45
45
  "test": "pnpm run test:types && pnpm run test:unit",
46
46
  "test:types": "tsc --noEmit -p tsconfig.test.json",