@syncular/client 0.15.26 → 0.15.28

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/README.md CHANGED
@@ -293,6 +293,35 @@ Only bounded, non-empty, code-like values on plaintext string schema columns
293
293
  are accepted. There is intentionally no full-table mode. The result exposes
294
294
  counts only—never row ids or selector values.
295
295
 
296
+ ## Application-authorized projection rebootstrap
297
+
298
+ `rebootstrapLocalData({ rebootstrapId })` is the recovery primitive for a
299
+ locally damaged or persistently inconsistent replicated projection. It drops
300
+ and recreates only Syncular's server-derived tables, rewinds the existing
301
+ subscription registrations, and requests a fresh bootstrap. It preserves the
302
+ client id, lease state, pending outbox commits, commit outcomes, subscription
303
+ intent, and protected bookkeeping.
304
+
305
+ ```ts
306
+ const result = await client.rebootstrapLocalData({
307
+ rebootstrapId: crypto.randomUUID(),
308
+ });
309
+ ```
310
+
311
+ The reset, durable idempotency marker, and optimistic outbox replay are one
312
+ SQLite transaction. An interruption therefore leaves either the old
313
+ projection or the fully reset projection with pending offline work still
314
+ visible. Reusing the same id returns `alreadyApplied: true`. The counts-only
315
+ result reports retained commits and reset subscriptions without exposing ids,
316
+ rows, scopes, or clinical values.
317
+
318
+ This API is not a security erase, sign-out, membership revocation, schema
319
+ upgrade, or draft deletion mechanism. It fails closed during security
320
+ preflight and while a schema-floor stop is active. The application must show a
321
+ preview and explicit confirmation, preserve app-owned drafts/files separately,
322
+ and reserve the operation for diagnostics/support recovery rather than normal
323
+ startup.
324
+
296
325
  Validator rejections may include bounded `details` (`fieldPaths`, `reason`,
297
326
  `requiredAction`, and explicitly safe `references`). The details persist with
298
327
  the rejection. Treat every value as a machine hint: map known values to
package/dist/client.d.ts CHANGED
@@ -15,6 +15,7 @@ import type { EncryptionConfig } from './encryption.js';
15
15
  import { type ClientChangeListener, type CommandResult, type InvalidationListener, type LocalRevision, type SyncIntent, type SyncStatusSnapshot } from './invalidation.js';
16
16
  import { type LeaderLock } from './leader-lock.js';
17
17
  import { type LocalDataPurgeInput, type LocalDataPurgeResult } from './local-purge.js';
18
+ import { type LocalDataRebootstrapInput, type LocalDataRebootstrapResult } from './local-rebootstrap.js';
18
19
  import { type OutboxCommit } from './outbox.js';
19
20
  import { type CommitOutcome, type CommitOutcomeQuery, type ConflictRecord, type RejectionRecord, type ResolveCommitOutcomeInput } from './outcomes.js';
20
21
  import { type ClientSchema } from './schema.js';
@@ -435,6 +436,17 @@ export declare class SyncClient {
435
436
  * reused id with different selectors fails closed.
436
437
  */
437
438
  purgeLocalData(input: LocalDataPurgeInput): LocalDataPurgeResult;
439
+ /**
440
+ * Rebuild the server-derived local projection without sacrificing offline
441
+ * work or device identity. The reset, subscription rewind, durable
442
+ * idempotency marker, and optimistic outbox replay are one SQLite
443
+ * transaction, so an interruption cannot expose a half-repaired replica.
444
+ *
445
+ * This is an application-authorized repair primitive, not a security purge.
446
+ * It is unavailable during security preflight and while a schema-floor stop
447
+ * is active: neither condition can be repaired by redownloading data.
448
+ */
449
+ rebootstrapLocalData(input: LocalDataRebootstrapInput): LocalDataRebootstrapResult;
438
450
  /** Host-facing patch result with explicit network work intent (§7.5). */
439
451
  patchCommand(table: string, rowId: string, partial: Readonly<Record<string, unknown>>, options?: {
440
452
  readonly baseVersion?: number;
package/dist/client.js CHANGED
@@ -16,6 +16,7 @@ import { ClientSyncError } from './errors.js';
16
16
  import { ChangeAccumulator, ChangeEmitter, InvalidationEmitter, invalidationFromChange, } from './invalidation.js';
17
17
  import { singleOwnerLock, } from './leader-lock.js';
18
18
  import { compileLocalDataPurge, localDataPurgeMetaKey, localDataPurgeTargetMatches, } from './local-purge.js';
19
+ import { compileLocalDataRebootstrap, localDataRebootstrapMetaKey, } from './local-rebootstrap.js';
19
20
  import { appendOutboxCommit, deleteOutboxCommit, dropOutboxCommitsInScope, encodeOutboxCommit, listOutbox, listOutboxBeforeImages, OutboxEncodeError, replaceOutboxBeforeImages, } from './outbox.js';
20
21
  import { activeFailureRecords, listCommitOutcomes, persistCommitOutcomeResolution, pruneCommitOutcomes, commitOutcome as readCommitOutcome, recordCommitOutcome, } from './outcomes.js';
21
22
  import { assertReadOnlyQuery } from './query-guard.js';
@@ -1494,6 +1495,65 @@ export class SyncClient {
1494
1495
  throw error;
1495
1496
  }
1496
1497
  }
1498
+ /**
1499
+ * Rebuild the server-derived local projection without sacrificing offline
1500
+ * work or device identity. The reset, subscription rewind, durable
1501
+ * idempotency marker, and optimistic outbox replay are one SQLite
1502
+ * transaction, so an interruption cannot expose a half-repaired replica.
1503
+ *
1504
+ * This is an application-authorized repair primitive, not a security purge.
1505
+ * It is unavailable during security preflight and while a schema-floor stop
1506
+ * is active: neither condition can be repaired by redownloading data.
1507
+ */
1508
+ rebootstrapLocalData(input) {
1509
+ this.#requireActive();
1510
+ const rebootstrapId = compileLocalDataRebootstrap(input);
1511
+ const metaKey = localDataRebootstrapMetaKey(rebootstrapId);
1512
+ if (getMeta(this.#db, metaKey) !== undefined) {
1513
+ return {
1514
+ alreadyApplied: true,
1515
+ retainedCommits: 0,
1516
+ resetSubscriptions: 0,
1517
+ };
1518
+ }
1519
+ if (this.#schemaFloor !== undefined) {
1520
+ throw new ClientSyncError('sync.invalid_request', 'local rebootstrap cannot bypass an active schema-floor stop; update the application first');
1521
+ }
1522
+ const pending = listOutbox(this.#db);
1523
+ const resetSubscriptions = loadSubscriptions(this.#db).length;
1524
+ const priorUpgrading = this.#upgrading;
1525
+ const priorNeedsPull = this.#needsPull;
1526
+ try {
1527
+ this.#applyBatch((batch) => {
1528
+ this.#upgrading = true;
1529
+ this.#needsPull = true;
1530
+ batch.status();
1531
+ dropAndRecreateSyncedTables(this.#db, this.#schema);
1532
+ resetSubscriptionsForBump(this.#db);
1533
+ for (const table of this.#schema.tables.values()) {
1534
+ batch.table(table.name);
1535
+ }
1536
+ for (const commit of pending) {
1537
+ this.#applyOperationsLocally(commit.operations, batch);
1538
+ }
1539
+ setMeta(this.#db, metaKey, 'v1');
1540
+ });
1541
+ }
1542
+ catch (error) {
1543
+ this.#upgrading = priorUpgrading;
1544
+ this.#needsPull = priorNeedsPull;
1545
+ throw error;
1546
+ }
1547
+ if (!priorUpgrading)
1548
+ this.#config.onUpgrading?.(true);
1549
+ this.#config.onSyncNeeded?.('startup');
1550
+ this.#config.onSyncIntent?.({ kind: 'interactive' });
1551
+ return {
1552
+ alreadyApplied: false,
1553
+ retainedCommits: pending.length,
1554
+ resetSubscriptions,
1555
+ };
1556
+ }
1497
1557
  #localPurgeTargetsByTable(purge) {
1498
1558
  const byTable = new Map();
1499
1559
  for (const target of purge.targets) {
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export * from './http.js';
22
22
  export * from './invalidation.js';
23
23
  export * from './leader-lock.js';
24
24
  export * from './local-purge.js';
25
+ export * from './local-rebootstrap.js';
25
26
  export * from './multi-tab.js';
26
27
  export * from './naming.js';
27
28
  export * from './outbox.js';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export * from './http.js';
22
22
  export * from './invalidation.js';
23
23
  export * from './leader-lock.js';
24
24
  export * from './local-purge.js';
25
+ export * from './local-rebootstrap.js';
25
26
  export * from './multi-tab.js';
26
27
  export * from './naming.js';
27
28
  export * from './outbox.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Application-authorized recovery of the replicated local projection.
3
+ *
4
+ * This is deliberately separate from `purgeLocalData`: rebootstrap keeps
5
+ * device identity, the outbox, commit outcomes, subscription registrations,
6
+ * lease state, and protected bookkeeping. It only discards server-derived
7
+ * projection state so the registered subscriptions can bootstrap it again.
8
+ */
9
+ /** A durable idempotency key supplied by the application repair coordinator. */
10
+ export interface LocalDataRebootstrapInput {
11
+ readonly rebootstrapId: string;
12
+ }
13
+ /** Privacy-safe acknowledgement; no row or subscription identifiers escape. */
14
+ export interface LocalDataRebootstrapResult {
15
+ readonly alreadyApplied: boolean;
16
+ readonly retainedCommits: number;
17
+ readonly resetSubscriptions: number;
18
+ }
19
+ /** Validate before entering the recovery transaction. */
20
+ export declare function compileLocalDataRebootstrap(input: LocalDataRebootstrapInput): string;
21
+ export declare function localDataRebootstrapMetaKey(rebootstrapId: string): string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Application-authorized recovery of the replicated local projection.
3
+ *
4
+ * This is deliberately separate from `purgeLocalData`: rebootstrap keeps
5
+ * device identity, the outbox, commit outcomes, subscription registrations,
6
+ * lease state, and protected bookkeeping. It only discards server-derived
7
+ * projection state so the registered subscriptions can bootstrap it again.
8
+ */
9
+ import { ClientSyncError } from './errors.js';
10
+ const CODE_LIKE_VALUE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
11
+ /** Validate before entering the recovery transaction. */
12
+ export function compileLocalDataRebootstrap(input) {
13
+ if (input.rebootstrapId.length === 0 ||
14
+ input.rebootstrapId.length > 128 ||
15
+ !CODE_LIKE_VALUE.test(input.rebootstrapId)) {
16
+ throw new ClientSyncError('sync.invalid_request', 'local rebootstrap rebootstrapId must be a 1–128 character code-like identifier');
17
+ }
18
+ return input.rebootstrapId;
19
+ }
20
+ export function localDataRebootstrapMetaKey(rebootstrapId) {
21
+ return `localRebootstrap:${rebootstrapId}`;
22
+ }
@@ -321,6 +321,7 @@ export function startSyncWorker(overrides = {}) {
321
321
  return result.value;
322
322
  },
323
323
  purgeLocalData: (input) => requireClient().purgeLocalData(input),
324
+ rebootstrapLocalData: (input) => requireClient().rebootstrapLocalData(input),
324
325
  sync: () => {
325
326
  const running = requireClient();
326
327
  return serializedSync(() => running.sync());
@@ -30,6 +30,7 @@ import type { EncryptionKeyringConfig } from './encryption.js';
30
30
  import { ChangeEmitter, type ClientChangeListener, InvalidationEmitter, type InvalidationListener, type LocalRevision, type SyncStatusSnapshot } from './invalidation.js';
31
31
  import { type LeaderLease, type LeaderLock } from './leader-lock.js';
32
32
  import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge.js';
33
+ import type { LocalDataRebootstrapInput, LocalDataRebootstrapResult } from './local-rebootstrap.js';
33
34
  import { type CrossTabChannel, FollowerLink, LeaderBridge, type LeadershipState } from './multi-tab.js';
34
35
  import type { OutboxCommit } from './outbox.js';
35
36
  import type { CommitOutcome, CommitOutcomeQuery, ResolveCommitOutcomeInput } from './outcomes.js';
@@ -189,6 +190,7 @@ export declare class SyncClientHandle {
189
190
  readonly baseVersion?: number;
190
191
  }): Promise<string>;
191
192
  purgeLocalData(input: LocalDataPurgeInput): Promise<LocalDataPurgeResult>;
193
+ rebootstrapLocalData(input: LocalDataRebootstrapInput): Promise<LocalDataRebootstrapResult>;
192
194
  sync(): Promise<SyncSummary>;
193
195
  syncUntilIdle(maxRounds?: number): Promise<SyncSummary>;
194
196
  query(sql: string, params?: readonly SqlValue[]): Promise<SqlRow[]>;
@@ -247,6 +247,9 @@ export class SyncClientHandle {
247
247
  purgeLocalData(input) {
248
248
  return this.#call('purgeLocalData', [input]);
249
249
  }
250
+ rebootstrapLocalData(input) {
251
+ return this.#call('rebootstrapLocalData', [input]);
252
+ }
250
253
  sync() {
251
254
  return this.#call('sync', []);
252
255
  }
@@ -24,6 +24,7 @@ import type { ClientDiagnosticsRequest, ClientDiagnosticsSnapshot } from './diag
24
24
  import type { EncryptionKeyringConfig } from './encryption.js';
25
25
  import type { ClientChangeBatch, LocalRevision, SyncStatusSnapshot } from './invalidation.js';
26
26
  import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge.js';
27
+ import type { LocalDataRebootstrapInput, LocalDataRebootstrapResult } from './local-rebootstrap.js';
27
28
  import type { OutboxCommit } from './outbox.js';
28
29
  import type { CommitOutcome, CommitOutcomeQuery, ResolveCommitOutcomeInput } from './outcomes.js';
29
30
  import type { ClientSchema } from './schema.js';
@@ -102,6 +103,8 @@ export interface WorkerApi {
102
103
  }): string;
103
104
  /** Application-authorized, idempotent local security purge. */
104
105
  purgeLocalData(input: LocalDataPurgeInput): LocalDataPurgeResult;
106
+ /** Application-authorized, outbox-preserving projection recovery. */
107
+ rebootstrapLocalData(input: LocalDataRebootstrapInput): LocalDataRebootstrapResult;
105
108
  sync(): Promise<SyncSummary>;
106
109
  syncUntilIdle(maxRounds?: number): Promise<SyncSummary>;
107
110
  query(sql: string, params?: readonly SqlValue[]): SqlRow[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/client",
3
- "version": "0.15.26",
3
+ "version": "0.15.28",
4
4
  "description": "Syncular TypeScript client core — offline-first sync over SQLite (WASM/OPFS, Bun, Node)",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  "dependencies": {
83
83
  "@sqlite.org/sqlite-wasm": "^3.53.0-build1",
84
- "@syncular/core": "0.15.26"
84
+ "@syncular/core": "0.15.28"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "better-sqlite3": ">=11"
@@ -92,7 +92,7 @@
92
92
  }
93
93
  },
94
94
  "devDependencies": {
95
- "@syncular/server": "0.15.26",
95
+ "@syncular/server": "0.15.28",
96
96
  "@types/better-sqlite3": "^7.6.13",
97
97
  "better-sqlite3": "^12.11.1"
98
98
  }
package/src/client.ts CHANGED
@@ -103,6 +103,12 @@ import {
103
103
  localDataPurgeMetaKey,
104
104
  localDataPurgeTargetMatches,
105
105
  } from './local-purge';
106
+ import {
107
+ compileLocalDataRebootstrap,
108
+ type LocalDataRebootstrapInput,
109
+ type LocalDataRebootstrapResult,
110
+ localDataRebootstrapMetaKey,
111
+ } from './local-rebootstrap';
106
112
  import {
107
113
  appendOutboxCommit,
108
114
  deleteOutboxCommit,
@@ -2222,6 +2228,71 @@ export class SyncClient {
2222
2228
  }
2223
2229
  }
2224
2230
 
2231
+ /**
2232
+ * Rebuild the server-derived local projection without sacrificing offline
2233
+ * work or device identity. The reset, subscription rewind, durable
2234
+ * idempotency marker, and optimistic outbox replay are one SQLite
2235
+ * transaction, so an interruption cannot expose a half-repaired replica.
2236
+ *
2237
+ * This is an application-authorized repair primitive, not a security purge.
2238
+ * It is unavailable during security preflight and while a schema-floor stop
2239
+ * is active: neither condition can be repaired by redownloading data.
2240
+ */
2241
+ rebootstrapLocalData(
2242
+ input: LocalDataRebootstrapInput,
2243
+ ): LocalDataRebootstrapResult {
2244
+ this.#requireActive();
2245
+ const rebootstrapId = compileLocalDataRebootstrap(input);
2246
+ const metaKey = localDataRebootstrapMetaKey(rebootstrapId);
2247
+ if (getMeta(this.#db, metaKey) !== undefined) {
2248
+ return {
2249
+ alreadyApplied: true,
2250
+ retainedCommits: 0,
2251
+ resetSubscriptions: 0,
2252
+ };
2253
+ }
2254
+ if (this.#schemaFloor !== undefined) {
2255
+ throw new ClientSyncError(
2256
+ 'sync.invalid_request',
2257
+ 'local rebootstrap cannot bypass an active schema-floor stop; update the application first',
2258
+ );
2259
+ }
2260
+
2261
+ const pending = listOutbox(this.#db);
2262
+ const resetSubscriptions = loadSubscriptions(this.#db).length;
2263
+ const priorUpgrading = this.#upgrading;
2264
+ const priorNeedsPull = this.#needsPull;
2265
+ try {
2266
+ this.#applyBatch((batch) => {
2267
+ this.#upgrading = true;
2268
+ this.#needsPull = true;
2269
+ batch.status();
2270
+ dropAndRecreateSyncedTables(this.#db, this.#schema);
2271
+ resetSubscriptionsForBump(this.#db);
2272
+ for (const table of this.#schema.tables.values()) {
2273
+ batch.table(table.name);
2274
+ }
2275
+ for (const commit of pending) {
2276
+ this.#applyOperationsLocally(commit.operations, batch);
2277
+ }
2278
+ setMeta(this.#db, metaKey, 'v1');
2279
+ });
2280
+ } catch (error) {
2281
+ this.#upgrading = priorUpgrading;
2282
+ this.#needsPull = priorNeedsPull;
2283
+ throw error;
2284
+ }
2285
+
2286
+ if (!priorUpgrading) this.#config.onUpgrading?.(true);
2287
+ this.#config.onSyncNeeded?.('startup');
2288
+ this.#config.onSyncIntent?.({ kind: 'interactive' });
2289
+ return {
2290
+ alreadyApplied: false,
2291
+ retainedCommits: pending.length,
2292
+ resetSubscriptions,
2293
+ };
2294
+ }
2295
+
2225
2296
  #localPurgeTargetsByTable(
2226
2297
  purge: CompiledLocalDataPurge,
2227
2298
  ): Map<string, CompiledLocalDataPurgeTarget[]> {
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export * from './http';
22
22
  export * from './invalidation';
23
23
  export * from './leader-lock';
24
24
  export * from './local-purge';
25
+ export * from './local-rebootstrap';
25
26
  export * from './multi-tab';
26
27
  export * from './naming';
27
28
  export * from './outbox';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Application-authorized recovery of the replicated local projection.
3
+ *
4
+ * This is deliberately separate from `purgeLocalData`: rebootstrap keeps
5
+ * device identity, the outbox, commit outcomes, subscription registrations,
6
+ * lease state, and protected bookkeeping. It only discards server-derived
7
+ * projection state so the registered subscriptions can bootstrap it again.
8
+ */
9
+
10
+ import { ClientSyncError } from './errors';
11
+
12
+ const CODE_LIKE_VALUE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
13
+
14
+ /** A durable idempotency key supplied by the application repair coordinator. */
15
+ export interface LocalDataRebootstrapInput {
16
+ readonly rebootstrapId: string;
17
+ }
18
+
19
+ /** Privacy-safe acknowledgement; no row or subscription identifiers escape. */
20
+ export interface LocalDataRebootstrapResult {
21
+ readonly alreadyApplied: boolean;
22
+ readonly retainedCommits: number;
23
+ readonly resetSubscriptions: number;
24
+ }
25
+
26
+ /** Validate before entering the recovery transaction. */
27
+ export function compileLocalDataRebootstrap(
28
+ input: LocalDataRebootstrapInput,
29
+ ): string {
30
+ if (
31
+ input.rebootstrapId.length === 0 ||
32
+ input.rebootstrapId.length > 128 ||
33
+ !CODE_LIKE_VALUE.test(input.rebootstrapId)
34
+ ) {
35
+ throw new ClientSyncError(
36
+ 'sync.invalid_request',
37
+ 'local rebootstrap rebootstrapId must be a 1–128 character code-like identifier',
38
+ );
39
+ }
40
+ return input.rebootstrapId;
41
+ }
42
+
43
+ export function localDataRebootstrapMetaKey(rebootstrapId: string): string {
44
+ return `localRebootstrap:${rebootstrapId}`;
45
+ }
@@ -419,6 +419,8 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
419
419
  return result.value;
420
420
  },
421
421
  purgeLocalData: (input) => requireClient().purgeLocalData(input),
422
+ rebootstrapLocalData: (input) =>
423
+ requireClient().rebootstrapLocalData(input),
422
424
  sync: () => {
423
425
  const running = requireClient();
424
426
  return serializedSync(() => running.sync());
@@ -65,6 +65,10 @@ import {
65
65
  webLocksLeaderLock,
66
66
  } from './leader-lock';
67
67
  import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge';
68
+ import type {
69
+ LocalDataRebootstrapInput,
70
+ LocalDataRebootstrapResult,
71
+ } from './local-rebootstrap';
68
72
  import {
69
73
  broadcastChannelFactory,
70
74
  type CrossTabChannel,
@@ -493,6 +497,12 @@ export class SyncClientHandle {
493
497
  return this.#call('purgeLocalData', [input]);
494
498
  }
495
499
 
500
+ rebootstrapLocalData(
501
+ input: LocalDataRebootstrapInput,
502
+ ): Promise<LocalDataRebootstrapResult> {
503
+ return this.#call('rebootstrapLocalData', [input]);
504
+ }
505
+
496
506
  sync(): Promise<SyncSummary> {
497
507
  return this.#call('sync', []);
498
508
  }
@@ -45,6 +45,10 @@ import type {
45
45
  SyncStatusSnapshot,
46
46
  } from './invalidation';
47
47
  import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge';
48
+ import type {
49
+ LocalDataRebootstrapInput,
50
+ LocalDataRebootstrapResult,
51
+ } from './local-rebootstrap';
48
52
  import type { OutboxCommit } from './outbox';
49
53
  import type {
50
54
  CommitOutcome,
@@ -151,6 +155,10 @@ export interface WorkerApi {
151
155
  ): string;
152
156
  /** Application-authorized, idempotent local security purge. */
153
157
  purgeLocalData(input: LocalDataPurgeInput): LocalDataPurgeResult;
158
+ /** Application-authorized, outbox-preserving projection recovery. */
159
+ rebootstrapLocalData(
160
+ input: LocalDataRebootstrapInput,
161
+ ): LocalDataRebootstrapResult;
154
162
  sync(): Promise<SyncSummary>;
155
163
  syncUntilIdle(maxRounds?: number): Promise<SyncSummary>;
156
164
  query(sql: string, params?: readonly SqlValue[]): SqlRow[];