diff --git a/.changeset/mean-numbers-remember.md b/.changeset/mean-numbers-remember.md new file mode 100644 index 00000000..a614afa5 --- /dev/null +++ b/.changeset/mean-numbers-remember.md @@ -0,0 +1,6 @@ +--- +"@journeyapps/powersync-sdk-common": patch +"@journeyapps/powersync-sdk-web": patch +--- + +Reduce JS bundle size diff --git a/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts b/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts index 0ddd0768..7dec6d10 100644 --- a/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts @@ -1,25 +1,25 @@ -import _ from 'lodash'; import { Mutex } from 'async-mutex'; +import { EventIterator } from 'event-iterator'; import Logger, { ILogger } from 'js-logger'; +import throttle from 'lodash/throttle'; import { DBAdapter, QueryResult, Transaction, isBatchedUpdateNotification } from '../db/DBAdapter'; -import { Schema } from '../db/schema/Schema'; import { SyncStatus } from '../db/crud/SyncStatus'; import { UploadQueueStats } from '../db/crud/UploadQueueStatus'; +import { Schema } from '../db/schema/Schema'; +import { BaseObserver } from '../utils/BaseObserver'; +import { mutexRunExclusive } from '../utils/mutex'; +import { quoteIdentifier } from '../utils/strings'; import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector'; +import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter'; +import { CrudBatch } from './sync/bucket/CrudBatch'; +import { CrudEntry, CrudEntryJSON } from './sync/bucket/CrudEntry'; +import { CrudTransaction } from './sync/bucket/CrudTransaction'; import { AbstractStreamingSyncImplementation, DEFAULT_CRUD_UPLOAD_THROTTLE_MS, StreamingSyncImplementationListener, StreamingSyncImplementation } from './sync/stream/AbstractStreamingSyncImplementation'; -import { CrudBatch } from './sync/bucket/CrudBatch'; -import { CrudTransaction } from './sync/bucket/CrudTransaction'; -import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter'; -import { CrudEntry, CrudEntryJSON } from './sync/bucket/CrudEntry'; -import { mutexRunExclusive } from '../utils/mutex'; -import { BaseObserver } from '../utils/BaseObserver'; -import { EventIterator } from 'event-iterator'; -import { quoteIdentifier } from '../utils/strings'; export interface DisconnectAndClearOptions { /** When set to false, data in local-only tables is preserved. */ @@ -548,25 +548,26 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver { - //Fetch initial data + // Fetch initial data yield await this.executeReadOnly(sql, parameters); - const resolvedTables = options?.tables ?? []; + const resolvedTables = options?.tables ? [...options.tables] : []; if (!options?.tables) { const explained = await this.getAll<{ opcode: string; p3: number; p2: number }>(`EXPLAIN ${sql}`, parameters); - const rootPages = _.chain(explained) - .filter((row) => row['opcode'] == 'OpenRead' && row['p3'] == 0 && _.isNumber(row['p2'])) - .map((row) => row['p2']) - .value(); + const rootPages = explained + .filter((row) => row.opcode == 'OpenRead' && row.p3 == 0 && typeof row.p2 == 'number') + .map((row) => row.p2); const tables = await this.getAll<{ tbl_name: string }>( - `SELECT tbl_name FROM sqlite_master WHERE rootpage IN (SELECT json_each.value FROM json_each(?))`, + `SELECT DISTINCT tbl_name FROM sqlite_master WHERE rootpage IN (SELECT json_each.value FROM json_each(?))`, [JSON.stringify(rootPages)] ); - tables.forEach((t) => resolvedTables.push(t.tbl_name.replace(POWERSYNC_TABLE_MATCH, ''))); + for (let table of tables) { + resolvedTables.push(table.tbl_name.replace(POWERSYNC_TABLE_MATCH, '')); + } } for await (const event of this.onChange({ ...(options ?? {}), - tables: _.uniq(resolvedTables) + tables: resolvedTables })) { yield await this.executeReadOnly(sql, parameters); } @@ -582,21 +583,20 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver { const resolvedOptions = options ?? {}; - const watchedTables = resolvedOptions.tables ?? []; + const watchedTables = new Set(resolvedOptions.tables ?? []); - let throttledTableUpdates: string[] = []; + let changedTables = new Set(); const throttleMs = resolvedOptions.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS; return new EventIterator((eventOptions) => { - const flushTableUpdates = _.throttle( + const flushTableUpdates = throttle( () => { - const intersection = _.intersection(watchedTables, throttledTableUpdates); - if (intersection.length) { + if (changedTables.size > 0) { eventOptions.push({ - changedTables: intersection + changedTables: [...changedTables] }); } - throttledTableUpdates = []; + changedTables.clear(); }, throttleMs, { leading: false, trailing: true } @@ -618,7 +618,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver t.replace(POWERSYNC_TABLE_MATCH, '')); - throttledTableUpdates.push(...mappedTableNames); + for (let table of mappedTableNames) { + changedTables.add(table); + } flushTableUpdates(); } diff --git a/packages/powersync-sdk-common/src/client/sync/bucket/CrudEntry.ts b/packages/powersync-sdk-common/src/client/sync/bucket/CrudEntry.ts index 64ef48ea..cb3c1cd1 100644 --- a/packages/powersync-sdk-common/src/client/sync/bucket/CrudEntry.ts +++ b/packages/powersync-sdk-common/src/client/sync/bucket/CrudEntry.ts @@ -1,5 +1,3 @@ -import _ from 'lodash'; - /** * 64-bit unsigned integer stored as a string in base-10. * @@ -109,7 +107,7 @@ export class CrudEntry { } equals(entry: CrudEntry) { - return _.isEqual(this.toComparisonArray(), entry.toComparisonArray()); + return JSON.stringify(this.toComparisonArray()) == JSON.stringify(entry.toComparisonArray()); } /** diff --git a/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index cee983e2..ec681dc1 100644 --- a/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -1,4 +1,5 @@ -import _ from 'lodash'; +import throttle from 'lodash/throttle'; + import Logger, { ILogger } from 'js-logger'; import { @@ -102,7 +103,7 @@ export abstract class AbstractStreamingSyncImplementation }); this.abortController = null; - this.triggerCrudUpload = _.throttle( + this.triggerCrudUpload = throttle( () => { if (!this.syncStatus.connected || this.syncStatus.dataFlowStatus.uploading) { return; @@ -296,6 +297,7 @@ export abstract class AbstractStreamingSyncImplementation after: after })); + // These are compared by reference let targetCheckpoint: Checkpoint | null = null; let validatedCheckpoint: Checkpoint | null = null; let appliedCheckpoint: Checkpoint | null = null; @@ -313,7 +315,7 @@ export abstract class AbstractStreamingSyncImplementation // A connection is active and messages are being received if (!this.syncStatus.connected) { // There is a connection now - _.defer(() => this.triggerCrudUpload()); + Promise.resolve().then(() => this.triggerCrudUpload()); this.updateSyncStatus({ connected: true }); @@ -346,7 +348,7 @@ export abstract class AbstractStreamingSyncImplementation // Continue waiting. // landing here the whole time } else { - appliedCheckpoint = _.clone(targetCheckpoint); + appliedCheckpoint = targetCheckpoint; this.logger.debug('validated checkpoint', appliedCheckpoint); this.updateSyncStatus({ connected: true, @@ -357,7 +359,7 @@ export abstract class AbstractStreamingSyncImplementation }); } - validatedCheckpoint = _.clone(targetCheckpoint); + validatedCheckpoint = targetCheckpoint; } else if (isStreamingSyncCheckpointDiff(line)) { // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint if (targetCheckpoint == null) { @@ -409,12 +411,12 @@ export abstract class AbstractStreamingSyncImplementation } else { this.logger.debug('Sync complete'); - if (_.isEqual(targetCheckpoint, appliedCheckpoint)) { + if (targetCheckpoint === appliedCheckpoint) { this.updateSyncStatus({ connected: true, lastSyncedAt: new Date() }); - } else if (_.isEqual(validatedCheckpoint, targetCheckpoint)) { + } else if (validatedCheckpoint === targetCheckpoint) { const result = await this.options.adapter.syncLocalDatabase(targetCheckpoint!); if (!result.checkpointValid) { // This means checksums failed. Start again with a new checkpoint. @@ -425,7 +427,7 @@ export abstract class AbstractStreamingSyncImplementation // Checksums valid, but need more data for a consistent checkpoint. // Continue waiting. } else { - appliedCheckpoint = _.clone(targetCheckpoint); + appliedCheckpoint = targetCheckpoint; this.updateSyncStatus({ connected: true, lastSyncedAt: new Date(), @@ -471,7 +473,10 @@ export abstract class AbstractStreamingSyncImplementation const updatedStatus = new SyncStatus({ connected: options.connected ?? this.syncStatus.connected, lastSyncedAt: options.lastSyncedAt ?? this.syncStatus.lastSyncedAt, - dataFlow: _.merge(_.clone(this.syncStatus.dataFlowStatus), options.dataFlow ?? {}) + dataFlow: { + ...this.syncStatus.dataFlowStatus, + ...options.dataFlow + } }); if (!this.syncStatus.isEqual(updatedStatus)) { diff --git a/packages/powersync-sdk-common/src/db/crud/SyncStatus.ts b/packages/powersync-sdk-common/src/db/crud/SyncStatus.ts index c0cb4c6e..1b4f0849 100644 --- a/packages/powersync-sdk-common/src/db/crud/SyncStatus.ts +++ b/packages/powersync-sdk-common/src/db/crud/SyncStatus.ts @@ -1,5 +1,3 @@ -import _ from 'lodash'; - export type SyncDataFlowStatus = Partial<{ downloading: boolean; uploading: boolean; @@ -49,7 +47,7 @@ export class SyncStatus { } isEqual(status: SyncStatus) { - return _.isEqual(this.options, status.options); + return JSON.stringify(this.options) == JSON.stringify(status.options); } getMessage() { diff --git a/packages/powersync-sdk-common/src/db/schema/Table.ts b/packages/powersync-sdk-common/src/db/schema/Table.ts index 4f4102ce..371045f9 100644 --- a/packages/powersync-sdk-common/src/db/schema/Table.ts +++ b/packages/powersync-sdk-common/src/db/schema/Table.ts @@ -1,4 +1,3 @@ -import _, { indexOf } from 'lodash'; import { Column } from '../Column'; import type { Index } from './Index'; import { TableV2 } from './TableV2'; @@ -86,10 +85,13 @@ export class Table { } get validName() { - return _.chain([this.name, this.viewNameOverride]) - .compact() - .every((name) => !InvalidSQLCharacters.test(name)) - .value(); + if (InvalidSQLCharacters.test(this.name)) { + return false; + } + if (this.viewNameOverride != null && InvalidSQLCharacters.test(this.viewNameOverride)) { + return false; + } + return true; } validate() { diff --git a/packages/powersync-sdk-web/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts b/packages/powersync-sdk-web/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts index e4caf2c7..e3e3f95c 100644 --- a/packages/powersync-sdk-web/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts +++ b/packages/powersync-sdk-web/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { AbstractPowerSyncDatabase, AbstractPowerSyncDatabaseOpenFactory, @@ -73,10 +72,14 @@ export abstract class AbstractWebPowerSyncDatabaseOpenFactory extends AbstractPo } protected resolveDBFlags(): WebPowerSyncFlags { - return _.merge(_.clone(DEFAULT_POWERSYNC_FLAGS), { - ssrMode: this.isServerSide(), - enableMultiTabs: this.options.flags?.enableMultiTabs - }); + let flags = { + ...DEFAULT_POWERSYNC_FLAGS, + ssrMode: this.isServerSide() + }; + if (typeof this.options.flags?.enableMultiTabs != 'undefined') { + flags.enableMultiTabs = this.options.flags.enableMultiTabs; + } + return flags; } generateInstance(options: PowerSyncDatabaseOptions): AbstractPowerSyncDatabase { diff --git a/packages/powersync-sdk-web/src/db/sync/SSRWebStreamingSyncImplementation.ts b/packages/powersync-sdk-web/src/db/sync/SSRWebStreamingSyncImplementation.ts index 6b084bc6..484718f7 100644 --- a/packages/powersync-sdk-web/src/db/sync/SSRWebStreamingSyncImplementation.ts +++ b/packages/powersync-sdk-web/src/db/sync/SSRWebStreamingSyncImplementation.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { AbstractStreamingSyncImplementation, AbstractStreamingSyncImplementationOptions, diff --git a/packages/powersync-sdk-web/src/db/sync/WebStreamingSyncImplementation.ts b/packages/powersync-sdk-web/src/db/sync/WebStreamingSyncImplementation.ts index 076e02cd..1fb7d1c2 100644 --- a/packages/powersync-sdk-web/src/db/sync/WebStreamingSyncImplementation.ts +++ b/packages/powersync-sdk-web/src/db/sync/WebStreamingSyncImplementation.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { AbstractStreamingSyncImplementation, AbstractStreamingSyncImplementationOptions, diff --git a/packages/powersync-sdk-web/src/worker/db/open-db.ts b/packages/powersync-sdk-web/src/worker/db/open-db.ts index 7d0cf531..269ad748 100644 --- a/packages/powersync-sdk-web/src/worker/db/open-db.ts +++ b/packages/powersync-sdk-web/src/worker/db/open-db.ts @@ -1,6 +1,5 @@ import * as SQLite from '@journeyapps/wa-sqlite'; import '@journeyapps/wa-sqlite'; -import _ from 'lodash'; import * as Comlink from 'comlink'; import { v4 as uuid } from 'uuid'; import { QueryResult } from '@journeyapps/powersync-sdk-common'; @@ -93,21 +92,16 @@ export async function _openDB(dbFileName: string): Promise { } } - const rows = _.chain(results) - .filter(({ rows }) => !!rows.length) - .flatMap(({ columns, rows }) => - _.map(rows, (row) => - _.reduce( - columns, - (out: Record, key: string, index) => { - out[key] = row[index]; - return out; - }, - {} - ) - ) - ) - .value(); + let rows: Record[] = []; + for (let resultset of results) { + for (let row of resultset.rows) { + let outRow: Record = {}; + resultset.columns.forEach((key, index) => { + outRow[key] = row[index]; + }); + rows.push(outRow); + } + } const result = { insertId: sqlite3.last_insert_id(db), diff --git a/packages/powersync-sdk-web/tests/crud.test.ts b/packages/powersync-sdk-web/tests/crud.test.ts index e6dbee26..95687a9b 100644 --- a/packages/powersync-sdk-web/tests/crud.test.ts +++ b/packages/powersync-sdk-web/tests/crud.test.ts @@ -163,8 +163,8 @@ describe('CRUD Tests', () => { const tx = (await powersync.getNextCrudTransaction())!; expect(tx.transactionId).equals(1); const expectedCrudEntry = new CrudEntry(1, UpdateType.PUT, 'logs', testId, 1, { - level: 'INFO', - content: 'test log' + content: 'test log', + level: 'INFO' }); expect(tx.crud[0].equals(expectedCrudEntry)).true; });