@rotorsoft/act-sqlite 1.20.2 → 1.20.4

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/index.js CHANGED
@@ -654,30 +654,45 @@ var SqliteStore = class {
654
654
  // Cleared by ack/block/reset/unblock. See {@link Store.defer}.
655
655
  async defer(input, deferred_at) {
656
656
  const at_iso = new Date(deferred_at).toISOString();
657
+ try {
658
+ return await this._update(input, "SET deferred_at = ?, retry = -1", [
659
+ at_iso
660
+ ]);
661
+ } catch (e) {
662
+ throw new StoreError("defer", { cause: e });
663
+ }
664
+ }
665
+ /**
666
+ * Runs `UPDATE streams <set>` on the streams named by `input`, in one
667
+ * write transaction that rolls back on failure. An explicit list is
668
+ * de-duplicated, so a repeated name counts once (matching PG's set-based
669
+ * `stream = ANY(...)`), and updated one name per statement, so a long list
670
+ * never meets SQLite's bound-parameter limit. `set` and `where` bind
671
+ * `params` in order; libSQL `?` placeholders are positional and can't be
672
+ * reused, so a value used twice is passed twice.
673
+ *
674
+ * @returns Count of rows updated.
675
+ */
676
+ async _update(input, set, params = [], where) {
677
+ const selections = Array.isArray(input) ? [...new Set(input)].map((stream) => ({
678
+ clause: "stream = ?",
679
+ args: [stream]
680
+ })) : [this._filter_clause(input)];
657
681
  const tx = await this.client.transaction("write");
658
682
  try {
659
683
  let count = 0;
660
- if (Array.isArray(input)) {
661
- for (const stream of new Set(input)) {
662
- const r = await tx.execute({
663
- sql: `UPDATE streams SET deferred_at = ?, retry = -1 WHERE stream = ?`,
664
- args: [at_iso, stream]
665
- });
666
- count += r.rowsAffected;
667
- }
668
- } else {
669
- const { clause, args } = this._filter_clause(input);
684
+ for (const { clause, args } of selections) {
670
685
  const r = await tx.execute({
671
- sql: `UPDATE streams SET deferred_at = ?, retry = -1 WHERE ${clause}`,
672
- args: [at_iso, ...args]
686
+ sql: `UPDATE streams ${set} WHERE ${where ? `${where} AND ` : ""}${clause}`,
687
+ args: [...params, ...args]
673
688
  });
674
- count = r.rowsAffected;
689
+ count += r.rowsAffected;
675
690
  }
676
691
  await tx.commit();
677
692
  return count;
678
693
  } catch (e) {
679
694
  await tx.rollback();
680
- throw new StoreError("defer", { cause: e });
695
+ throw e;
681
696
  }
682
697
  }
683
698
  /**
@@ -718,103 +733,32 @@ var SqliteStore = class {
718
733
  return { clause: conditions.length ? conditions.join(" AND ") : "1", args };
719
734
  }
720
735
  // --- reset: transactional, accepts names or filter ---
721
- async reset(input) {
722
- const set_clause = `SET at = -1, retry = -1, blocked = 0, error = '',
723
- leased_by = NULL, leased_until = NULL, deferred_at = NULL`;
724
- const tx = await this.client.transaction("write");
725
- try {
726
- let count = 0;
727
- if (Array.isArray(input)) {
728
- for (const stream of new Set(input)) {
729
- const r = await tx.execute({
730
- sql: `UPDATE streams ${set_clause} WHERE stream = ?`,
731
- args: [stream]
732
- });
733
- count += r.rowsAffected;
734
- }
735
- } else {
736
- const { clause, args } = this._filter_clause(input);
737
- const r = await tx.execute({
738
- sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
739
- args
740
- });
741
- count = r.rowsAffected;
742
- }
743
- await tx.commit();
744
- return count;
745
- } catch (e) {
746
- await tx.rollback();
747
- throw e;
748
- }
736
+ reset(input) {
737
+ return this._update(
738
+ input,
739
+ `SET at = -1, retry = -1, blocked = 0, error = '',
740
+ leased_by = NULL, leased_until = NULL, deferred_at = NULL`
741
+ );
749
742
  }
750
743
  // --- unblock: clear blocked + retry + lease without touching watermark ---
751
744
  // `retry = -1` so claim's post-bump returns retry=0 (first attempt),
752
- // matching the InMemoryStore convention.
753
- async unblock(input) {
754
- const set_clause = `SET retry = -1, blocked = 0, error = '',
755
- leased_by = NULL, leased_until = NULL, deferred_at = NULL`;
756
- const tx = await this.client.transaction("write");
757
- try {
758
- let count = 0;
759
- if (Array.isArray(input)) {
760
- for (const stream of input) {
761
- const r = await tx.execute({
762
- sql: `UPDATE streams ${set_clause}
763
- WHERE stream = ? AND blocked = 1`,
764
- args: [stream]
765
- });
766
- count += r.rowsAffected;
767
- }
768
- } else {
769
- const { clause, args } = this._filter_clause({
770
- ...input,
771
- blocked: true
772
- });
773
- const r = await tx.execute({
774
- sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
775
- args
776
- });
777
- count = r.rowsAffected;
778
- }
779
- await tx.commit();
780
- return count;
781
- } catch (e) {
782
- await tx.rollback();
783
- throw e;
784
- }
745
+ // matching the InMemoryStore convention. Always restricted to blocked
746
+ // rows, whatever the filter says: there is no use case for "unblock
747
+ // unblocked streams."
748
+ unblock(input) {
749
+ return this._update(
750
+ Array.isArray(input) ? input : { ...input, blocked: void 0 },
751
+ `SET retry = -1, blocked = 0, error = '',
752
+ leased_by = NULL, leased_until = NULL, deferred_at = NULL`,
753
+ [],
754
+ "blocked = 1"
755
+ );
785
756
  }
786
757
  // --- query_streams: read-only introspection with filters ---
787
758
  async query_streams(callback, query) {
788
759
  const limit = query?.limit ?? 100;
789
- let sql = "SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane, deferred_at, correlated_at FROM streams WHERE 1=1";
790
- const args = [];
791
- if (query?.stream !== void 0) {
792
- if (query.stream_exact) {
793
- sql += " AND stream = ?";
794
- args.push(query.stream);
795
- } else {
796
- sql += " AND stream GLOB ?";
797
- args.push(streamPatternToGlob(query.stream));
798
- }
799
- }
800
- if (query?.source !== void 0) {
801
- sql += " AND source IS NOT NULL";
802
- if (query.source_exact) {
803
- sql += " AND source = ?";
804
- args.push(query.source);
805
- } else {
806
- sql += " AND source GLOB ?";
807
- args.push(streamPatternToGlob(query.source));
808
- }
809
- }
810
- if (query?.blocked !== void 0) {
811
- sql += " AND blocked = ?";
812
- args.push(query.blocked ? 1 : 0);
813
- }
814
- if (query?.lane !== void 0) {
815
- sql += " AND lane = ?";
816
- args.push(query.lane);
817
- }
760
+ const { clause, args } = this._filter_clause(query ?? {});
761
+ let sql = `SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane, deferred_at, correlated_at FROM streams WHERE ${clause}`;
818
762
  if (query?.after !== void 0) {
819
763
  sql += " AND stream > ?";
820
764
  args.push(query.after);
@@ -1073,15 +1017,13 @@ var SqliteStore = class {
1073
1017
  return out;
1074
1018
  }
1075
1019
  // --- prioritize: bulk priority update with filter ---
1076
- async prioritize(filter, priority) {
1077
- const { clause, args: filterArgs } = this._filter_clause(filter);
1078
- const sql = `UPDATE streams SET priority = ?
1079
- WHERE priority <> ? AND ${clause}`;
1080
- const result = await this.client.execute({
1081
- sql,
1082
- args: [priority, priority, ...filterArgs]
1083
- });
1084
- return result.rowsAffected;
1020
+ prioritize(filter, priority) {
1021
+ return this._update(
1022
+ filter,
1023
+ "SET priority = ?",
1024
+ [priority, priority],
1025
+ "priority <> ?"
1026
+ );
1085
1027
  }
1086
1028
  // --- truncate: transactional delete + seed ---
1087
1029
  // Windowed targets (`before` set) prune the prefix below the closest safe