@powersync/common 1.29.0 → 1.30.0

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.
@@ -51,6 +51,11 @@ export declare class CrudEntry {
51
51
  * Data associated with the change.
52
52
  */
53
53
  opData?: Record<string, any>;
54
+ /**
55
+ * For tables where the `trackPreviousValues` option has been enabled, this tracks previous values for
56
+ * `UPDATE` and `DELETE` statements.
57
+ */
58
+ previousValues?: Record<string, any>;
54
59
  /**
55
60
  * Table that contained the change.
56
61
  */
@@ -59,8 +64,15 @@ export declare class CrudEntry {
59
64
  * Auto-incrementing transaction id. This is the same for all operations within the same transaction.
60
65
  */
61
66
  transactionId?: number;
67
+ /**
68
+ * Client-side metadata attached with this write.
69
+ *
70
+ * This field is only available when the `trackMetadata` option was set to `true` when creating a table
71
+ * and the insert or update statement set the `_metadata` column.
72
+ */
73
+ metadata?: string;
62
74
  static fromRow(dbRow: CrudEntryJSON): CrudEntry;
63
- constructor(clientId: number, op: UpdateType, table: string, id: string, transactionId?: number, opData?: Record<string, any>);
75
+ constructor(clientId: number, op: UpdateType, table: string, id: string, transactionId?: number, opData?: Record<string, any>, previousValues?: Record<string, any>, metadata?: string);
64
76
  /**
65
77
  * Converts the change to JSON format.
66
78
  */
@@ -30,6 +30,11 @@ export class CrudEntry {
30
30
  * Data associated with the change.
31
31
  */
32
32
  opData;
33
+ /**
34
+ * For tables where the `trackPreviousValues` option has been enabled, this tracks previous values for
35
+ * `UPDATE` and `DELETE` statements.
36
+ */
37
+ previousValues;
33
38
  /**
34
39
  * Table that contained the change.
35
40
  */
@@ -38,17 +43,26 @@ export class CrudEntry {
38
43
  * Auto-incrementing transaction id. This is the same for all operations within the same transaction.
39
44
  */
40
45
  transactionId;
46
+ /**
47
+ * Client-side metadata attached with this write.
48
+ *
49
+ * This field is only available when the `trackMetadata` option was set to `true` when creating a table
50
+ * and the insert or update statement set the `_metadata` column.
51
+ */
52
+ metadata;
41
53
  static fromRow(dbRow) {
42
54
  const data = JSON.parse(dbRow.data);
43
- return new CrudEntry(parseInt(dbRow.id), data.op, data.type, data.id, dbRow.tx_id, data.data);
55
+ return new CrudEntry(parseInt(dbRow.id), data.op, data.type, data.id, dbRow.tx_id, data.data, data.old, data.metadata);
44
56
  }
45
- constructor(clientId, op, table, id, transactionId, opData) {
57
+ constructor(clientId, op, table, id, transactionId, opData, previousValues, metadata) {
46
58
  this.clientId = clientId;
47
59
  this.id = id;
48
60
  this.op = op;
49
61
  this.opData = opData;
50
62
  this.table = table;
51
63
  this.transactionId = transactionId;
64
+ this.previousValues = previousValues;
65
+ this.metadata = metadata;
52
66
  }
53
67
  /**
54
68
  * Converts the change to JSON format.
@@ -119,9 +119,9 @@ export class SqliteBucketStorage extends BaseObserver {
119
119
  }
120
120
  return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
121
121
  }
122
- const buckets = checkpoint.buckets;
122
+ let buckets = checkpoint.buckets;
123
123
  if (priority !== undefined) {
124
- buckets.filter((b) => hasMatchingPriority(priority, b));
124
+ buckets = buckets.filter((b) => hasMatchingPriority(priority, b));
125
125
  }
126
126
  const bucketNames = buckets.map((b) => b.bucket);
127
127
  await this.writeTransaction(async (tx) => {
@@ -44,7 +44,7 @@ export interface ProgressWithOperations {
44
44
  * Additionally, the {@link SyncProgress.untilPriority} method can be used to otbain progress towards
45
45
  * a specific priority (instead of the progress for the entire download).
46
46
  *
47
- * The reported progress always reflects the status towards th end of a sync iteration (after
47
+ * The reported progress always reflects the status towards the end of a sync iteration (after
48
48
  * which a consistent snapshot of all buckets is available locally).
49
49
  *
50
50
  * In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
@@ -10,7 +10,7 @@ export const FULL_SYNC_PRIORITY = 2147483647;
10
10
  * Additionally, the {@link SyncProgress.untilPriority} method can be used to otbain progress towards
11
11
  * a specific priority (instead of the progress for the entire download).
12
12
  *
13
- * The reported progress always reflects the status towards th end of a sync iteration (after
13
+ * The reported progress always reflects the status towards the end of a sync iteration (after
14
14
  * which a consistent snapshot of all buckets is available locally).
15
15
  *
16
16
  * In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
@@ -18,6 +18,10 @@ export declare class Schema<S extends SchemaType = SchemaType> {
18
18
  view_name: string;
19
19
  local_only: boolean;
20
20
  insert_only: boolean;
21
+ include_old: any;
22
+ include_old_only_when_changed: boolean;
23
+ include_metadata: boolean;
24
+ ignore_empty_update: boolean;
21
25
  columns: {
22
26
  name: string;
23
27
  type: import("./Column.js").ColumnType | undefined;
@@ -1,4 +1,3 @@
1
- import { Table } from './Table.js';
2
1
  /**
3
2
  * A schema is a collection of tables. It is used to define the structure of a database.
4
3
  */
@@ -41,15 +40,7 @@ export class Schema {
41
40
  }
42
41
  convertToClassicTables(props) {
43
42
  return Object.entries(props).map(([name, table]) => {
44
- const convertedTable = new Table({
45
- name,
46
- columns: table.columns,
47
- indexes: table.indexes,
48
- localOnly: table.localOnly,
49
- insertOnly: table.insertOnly,
50
- viewName: table.viewNameOverride || name
51
- });
52
- return convertedTable;
43
+ return table.copyWithName(name);
53
44
  });
54
45
  }
55
46
  }
@@ -1,16 +1,32 @@
1
1
  import { Column, ColumnsType, ColumnType, ExtractColumnValueType } from './Column.js';
2
2
  import { Index } from './Index.js';
3
3
  import { TableV2 } from './TableV2.js';
4
- export interface TableOptions {
4
+ interface SharedTableOptions {
5
+ localOnly?: boolean;
6
+ insertOnly?: boolean;
7
+ viewName?: string;
8
+ trackPrevious?: boolean | TrackPreviousOptions;
9
+ trackMetadata?: boolean;
10
+ ignoreEmptyUpdates?: boolean;
11
+ }
12
+ /** Whether to include previous column values when PowerSync tracks local changes.
13
+ *
14
+ * Including old values may be helpful for some backend connector implementations, which is
15
+ * why it can be enabled on per-table or per-columm basis.
16
+ */
17
+ export interface TrackPreviousOptions {
18
+ /** When defined, a list of column names for which old values should be tracked. */
19
+ columns?: string[];
20
+ /** When enabled, only include values that have actually been changed by an update. */
21
+ onlyWhenChanged?: boolean;
22
+ }
23
+ export interface TableOptions extends SharedTableOptions {
5
24
  /**
6
25
  * The synced table name, matching sync rules
7
26
  */
8
27
  name: string;
9
28
  columns: Column[];
10
29
  indexes?: Index[];
11
- localOnly?: boolean;
12
- insertOnly?: boolean;
13
- viewName?: string;
14
30
  }
15
31
  export type RowType<T extends TableV2<any>> = {
16
32
  [K in keyof T['columnMap']]: ExtractColumnValueType<T['columnMap'][K]>;
@@ -18,16 +34,16 @@ export type RowType<T extends TableV2<any>> = {
18
34
  id: string;
19
35
  };
20
36
  export type IndexShorthand = Record<string, string[]>;
21
- export interface TableV2Options {
37
+ export interface TableV2Options extends SharedTableOptions {
22
38
  indexes?: IndexShorthand;
23
- localOnly?: boolean;
24
- insertOnly?: boolean;
25
- viewName?: string;
26
39
  }
27
40
  export declare const DEFAULT_TABLE_OPTIONS: {
28
41
  indexes: never[];
29
42
  insertOnly: boolean;
30
43
  localOnly: boolean;
44
+ trackPrevious: boolean;
45
+ trackMetadata: boolean;
46
+ ignoreEmptyUpdates: boolean;
31
47
  };
32
48
  export declare const InvalidSQLCharacters: RegExp;
33
49
  export declare class Table<Columns extends ColumnsType = ColumnsType> {
@@ -96,9 +112,11 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
96
112
  *```
97
113
  */
98
114
  constructor(options: TableOptions);
115
+ copyWithName(name: string): Table;
99
116
  private isTableV1;
100
117
  private initTableV1;
101
118
  private initTableV2;
119
+ private applyDefaultOptions;
102
120
  get name(): string;
103
121
  get viewNameOverride(): string | undefined;
104
122
  get viewName(): string;
@@ -107,6 +125,9 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
107
125
  get indexes(): Index[];
108
126
  get localOnly(): boolean;
109
127
  get insertOnly(): boolean;
128
+ get trackPrevious(): boolean | TrackPreviousOptions;
129
+ get trackMetadata(): boolean;
130
+ get ignoreEmptyUpdates(): boolean;
110
131
  get internalName(): string;
111
132
  get validName(): boolean;
112
133
  validate(): void;
@@ -115,6 +136,10 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
115
136
  view_name: string;
116
137
  local_only: boolean;
117
138
  insert_only: boolean;
139
+ include_old: any;
140
+ include_old_only_when_changed: boolean;
141
+ include_metadata: boolean;
142
+ ignore_empty_update: boolean;
118
143
  columns: {
119
144
  name: string;
120
145
  type: ColumnType | undefined;
@@ -129,3 +154,4 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
129
154
  }[];
130
155
  };
131
156
  }
157
+ export {};
@@ -4,7 +4,10 @@ import { IndexedColumn } from './IndexedColumn.js';
4
4
  export const DEFAULT_TABLE_OPTIONS = {
5
5
  indexes: [],
6
6
  insertOnly: false,
7
- localOnly: false
7
+ localOnly: false,
8
+ trackPrevious: false,
9
+ trackMetadata: false,
10
+ ignoreEmptyUpdates: false
8
11
  };
9
12
  export const InvalidSQLCharacters = /["'%,.#\s[\]]/;
10
13
  export class Table {
@@ -41,16 +44,21 @@ export class Table {
41
44
  this.initTableV2(optionsOrColumns, v2Options);
42
45
  }
43
46
  }
47
+ copyWithName(name) {
48
+ return new Table({
49
+ ...this.options,
50
+ name
51
+ });
52
+ }
44
53
  isTableV1(arg) {
45
54
  return 'columns' in arg && Array.isArray(arg.columns);
46
55
  }
47
56
  initTableV1(options) {
48
57
  this.options = {
49
58
  ...options,
50
- indexes: options.indexes || [],
51
- insertOnly: options.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly,
52
- localOnly: options.localOnly ?? DEFAULT_TABLE_OPTIONS.localOnly
59
+ indexes: options.indexes || []
53
60
  };
61
+ this.applyDefaultOptions();
54
62
  }
55
63
  initTableV2(columns, options) {
56
64
  const convertedColumns = Object.entries(columns).map(([name, columnInfo]) => new Column({ name, type: columnInfo.type }));
@@ -65,12 +73,23 @@ export class Table {
65
73
  name: '',
66
74
  columns: convertedColumns,
67
75
  indexes: convertedIndexes,
68
- insertOnly: options?.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly,
69
- localOnly: options?.localOnly ?? DEFAULT_TABLE_OPTIONS.localOnly,
70
- viewName: options?.viewName
76
+ viewName: options?.viewName,
77
+ insertOnly: options?.insertOnly,
78
+ localOnly: options?.localOnly,
79
+ trackPrevious: options?.trackPrevious,
80
+ trackMetadata: options?.trackMetadata,
81
+ ignoreEmptyUpdates: options?.ignoreEmptyUpdates
71
82
  };
83
+ this.applyDefaultOptions();
72
84
  this._mappedColumns = columns;
73
85
  }
86
+ applyDefaultOptions() {
87
+ this.options.insertOnly ??= DEFAULT_TABLE_OPTIONS.insertOnly;
88
+ this.options.localOnly ??= DEFAULT_TABLE_OPTIONS.localOnly;
89
+ this.options.trackPrevious ??= DEFAULT_TABLE_OPTIONS.trackPrevious;
90
+ this.options.trackMetadata ??= DEFAULT_TABLE_OPTIONS.trackMetadata;
91
+ this.options.ignoreEmptyUpdates ??= DEFAULT_TABLE_OPTIONS.ignoreEmptyUpdates;
92
+ }
74
93
  get name() {
75
94
  return this.options.name;
76
95
  }
@@ -94,10 +113,19 @@ export class Table {
94
113
  return this.options.indexes ?? [];
95
114
  }
96
115
  get localOnly() {
97
- return this.options.localOnly ?? false;
116
+ return this.options.localOnly;
98
117
  }
99
118
  get insertOnly() {
100
- return this.options.insertOnly ?? false;
119
+ return this.options.insertOnly;
120
+ }
121
+ get trackPrevious() {
122
+ return this.options.trackPrevious;
123
+ }
124
+ get trackMetadata() {
125
+ return this.options.trackMetadata;
126
+ }
127
+ get ignoreEmptyUpdates() {
128
+ return this.options.ignoreEmptyUpdates;
101
129
  }
102
130
  get internalName() {
103
131
  if (this.options.localOnly) {
@@ -124,6 +152,12 @@ export class Table {
124
152
  if (this.columns.length > MAX_AMOUNT_OF_COLUMNS) {
125
153
  throw new Error(`Table has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.`);
126
154
  }
155
+ if (this.trackMetadata && this.localOnly) {
156
+ throw new Error(`Can't include metadata for local-only tables.`);
157
+ }
158
+ if (this.trackPrevious != false && this.localOnly) {
159
+ throw new Error(`Can't include old values for local-only tables.`);
160
+ }
127
161
  const columnNames = new Set();
128
162
  columnNames.add('id');
129
163
  for (const column of this.columns) {
@@ -156,11 +190,16 @@ export class Table {
156
190
  }
157
191
  }
158
192
  toJSON() {
193
+ const trackPrevious = this.trackPrevious;
159
194
  return {
160
195
  name: this.name,
161
196
  view_name: this.viewName,
162
197
  local_only: this.localOnly,
163
198
  insert_only: this.insertOnly,
199
+ include_old: trackPrevious && (trackPrevious.columns ?? true),
200
+ include_old_only_when_changed: typeof trackPrevious == 'object' && trackPrevious.onlyWhenChanged == true,
201
+ include_metadata: this.trackMetadata,
202
+ ignore_empty_update: this.ignoreEmptyUpdates,
164
203
  columns: this.columns.map((c) => c.toJSON()),
165
204
  indexes: this.indexes.map((e) => e.toJSON(this))
166
205
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"