Skip to content
Merged
7 changes: 7 additions & 0 deletions .changeset/dirty-buttons-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@powersync/attachments': patch
'@powersync/react': patch
'@powersync/vue': patch
---

Using newly exposed logger from AbstractPowerSyncDatabase to have controlled logging instead of using console based logging.
5 changes: 5 additions & 0 deletions .changeset/fresh-jobs-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/common': minor
---

Exposing logger on AbstractPowerSyncDatabase.
5 changes: 5 additions & 0 deletions .changeset/green-rings-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/web': patch
---

Updated db and sync workers to respect log levels.
42 changes: 23 additions & 19 deletions packages/attachments/src/AbstractAttachmentQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return this.options.powersync;
}

get logger() {
return this.powersync.logger ?? console;
}

protected get storage() {
return this.options.storage;
}
Expand Down Expand Up @@ -123,7 +127,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
async watchAttachmentIds() {
this.onAttachmentIdsChange(async (ids) => {
const _ids = `${ids.map((id) => `'${id}'`).join(',')}`;
console.debug(`Queuing for sync, attachment IDs: [${_ids}]`);
this.logger.debug(`Queuing for sync, attachment IDs: [${_ids}]`);

if (this.initialSync) {
this.initialSync = false;
Expand Down Expand Up @@ -151,11 +155,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
id: id,
state: AttachmentState.QUEUED_SYNC
});
console.debug(`Attachment (${id}) not found in database, creating new record`);
this.logger.debug(`Attachment (${id}) not found in database, creating new record`);
await this.saveToQueue(newRecord);
} else if (record.local_uri == null || !(await this.storage.fileExists(this.getLocalUri(record.local_uri)))) {
// 2. Attachment in database but no local file, mark as queued download
console.debug(`Attachment (${id}) found in database but no local file, marking as queued download`);
this.logger.debug(`Attachment (${id}) found in database but no local file, marking as queued download`);
await this.update({
...record,
state: AttachmentState.QUEUED_DOWNLOAD
Expand Down Expand Up @@ -241,7 +245,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
filename: record.filename
});
} catch (e) {
console.error(e);
this.logger.error(e);
}
}

Expand All @@ -267,7 +271,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
const localFilePathUri = this.getLocalUri(record.local_uri);
try {
if (!(await this.storage.fileExists(localFilePathUri))) {
console.warn(`File for ${record.id} does not exist, skipping upload`);
this.logger.warn(`File for ${record.id} does not exist, skipping upload`);
await this.update({
...record,
state: AttachmentState.QUEUED_DOWNLOAD
Expand All @@ -285,11 +289,11 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
});
// Mark as uploaded
await this.update({ ...record, state: AttachmentState.SYNCED });
console.debug(`Uploaded attachment "${record.id}" to Cloud Storage`);
this.logger.debug(`Uploaded attachment "${record.id}" to Cloud Storage`);
return true;
} catch (e: any) {
if (e.error == 'Duplicate') {
console.debug(`File already uploaded, marking ${record.id} as synced`);
this.logger.debug(`File already uploaded, marking ${record.id} as synced`);
await this.update({ ...record, state: AttachmentState.SYNCED });
return false;
}
Expand All @@ -300,7 +304,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return true;
}
}
console.error(`UploadAttachment error for record ${JSON.stringify(record, null, 2)}`);
this.logger.error(`UploadAttachment error for record ${JSON.stringify(record, null, 2)}`);
return false;
}
}
Expand All @@ -314,7 +318,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
const localFilePathUri = this.getLocalUri(record.local_uri);
if (await this.storage.fileExists(localFilePathUri)) {
console.debug(`Local file already downloaded, marking "${record.id}" as synced`);
this.logger.debug(`Local file already downloaded, marking "${record.id}" as synced`);
await this.update({ ...record, state: AttachmentState.SYNCED });
return true;
}
Expand Down Expand Up @@ -345,7 +349,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
media_type: fileBlob.type,
state: AttachmentState.SYNCED
});
console.debug(`Downloaded attachment "${record.id}"`);
this.logger.debug(`Downloaded attachment "${record.id}"`);
return true;
} catch (e) {
if (this.options.onDownloadError) {
Expand All @@ -355,7 +359,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return true;
}
}
console.error(`Download attachment error for record ${JSON.stringify(record, null, 2)}`, e);
this.logger.error(`Download attachment error for record ${JSON.stringify(record, null, 2)}`, e);
}
return false;
}
Expand Down Expand Up @@ -396,7 +400,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
if (!record) {
return;
}
console.debug(`Uploading attachments...`);
this.logger.debug(`Uploading attachments...`);
while (record) {
const uploaded = await this.uploadAttachment(record);
if (!uploaded) {
Expand All @@ -405,9 +409,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
record = await this.getNextUploadRecord();
}
console.debug('Finished uploading attachments');
this.logger.debug('Finished uploading attachments');
} catch (error) {
console.error('Upload failed:', error);
this.logger.error('Upload failed:', error);
} finally {
this.uploading = false;
}
Expand Down Expand Up @@ -464,7 +468,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =

this.downloading = true;
try {
console.debug(`Downloading ${this.downloadQueue.size} attachments...`);
this.logger.debug(`Downloading ${this.downloadQueue.size} attachments...`);
while (this.downloadQueue.size > 0) {
const id = this.downloadQueue.values().next().value;
this.downloadQueue.delete(id);
Expand All @@ -474,9 +478,9 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}
await this.downloadRecord(record);
}
console.debug('Finished downloading attachments');
this.logger.debug('Finished downloading attachments');
} catch (e) {
console.error('Downloads failed:', e);
this.logger.error('Downloads failed:', e);
} finally {
this.downloading = false;
}
Expand Down Expand Up @@ -518,7 +522,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
return;
}

console.debug(`Deleting ${res.length} attachments from cache...`);
this.logger.debug(`Deleting ${res.length} attachments from cache...`);
await this.powersync.writeTransaction(async (tx) => {
for (const record of res) {
await this.delete(record, tx);
Expand All @@ -527,7 +531,7 @@ export abstract class AbstractAttachmentQueue<T extends AttachmentQueueOptions =
}

async clearQueue(): Promise<void> {
console.debug(`Clearing attachment queue...`);
this.logger.debug(`Clearing attachment queue...`);
await this.powersync.writeTransaction(async (tx) => {
await tx.execute(`DELETE FROM ${this.table}`);
});
Expand Down
34 changes: 19 additions & 15 deletions packages/common/src/client/AbstractPowerSyncDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
}

get logger() {
return this.options.logger!;
}

/**
* Wait for initialization to complete.
* While initializing is automatic, this helps to catch and report initialization errors.
Expand Down Expand Up @@ -555,7 +559,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* This method does include transaction ids in the result, but does not group
* data by transaction. One batch may contain data from multiple transactions,
* and a single transaction may be split over multiple batches.
*
*
* @param limit Maximum number of CRUD entries to include in the batch
* @returns A batch of CRUD operations to upload, or null if there are none
*/
Expand Down Expand Up @@ -594,7 +598,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
*
* Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
* All data for the transaction is loaded into memory.
*
*
* @returns A transaction of CRUD operations to upload, or null if there are none
*/
async getNextCrudTransaction(): Promise<CrudTransaction | null> {
Expand Down Expand Up @@ -633,7 +637,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Get an unique client id for this database.
*
* The id is not reset when the database is cleared, only when the database is deleted.
*
*
* @returns A unique identifier for the database instance
*/
async getClientId(): Promise<string> {
Expand Down Expand Up @@ -661,7 +665,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
/**
* Execute a SQL write (INSERT/UPDATE/DELETE) query
* and optionally return results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The query result as an object with structured key-value pairs
Expand All @@ -674,7 +678,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
/**
* Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
* This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The raw query result from the underlying database as a nested array of raw values, where each row is
Expand All @@ -689,7 +693,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
* and optionally return results.
* This is faster than executing separately with each parameter set.
*
*
* @param sql The SQL query to execute
* @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
* @returns The query result
Expand All @@ -701,7 +705,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return results.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns An array of results
Expand All @@ -713,7 +717,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The first result if found, or null if no results are returned
Expand All @@ -725,7 +729,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

/**
* Execute a read-only query and return the first result, error if the ResultSet is empty.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @returns The first result matching the query
Expand Down Expand Up @@ -761,7 +765,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Open a read-only transaction.
* Read transactions can run concurrently to a write transaction.
* Changes from any write transaction are not visible to read transactions started before it.
*
*
* @param callback Function to execute within the transaction
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
* @returns The result of the callback
Expand All @@ -786,7 +790,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Open a read-write transaction.
* This takes a global lock - only one write transaction can execute against the database at a time.
* Statements within the transaction must be done on the provided {@link Transaction} interface.
*
*
* @param callback Function to execute within the transaction
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
* @returns The result of the callback
Expand Down Expand Up @@ -865,7 +869,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
*
* Note that the `onChange` callback member of the handler is required.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @param handler Callbacks for handling results and errors
Expand Down Expand Up @@ -915,7 +919,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Execute a read query every time the source tables are modified.
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
*
*
* @param sql The SQL query to execute
* @param parameters Optional array of parameters to bind to the query
* @param options Options for configuring watch behavior
Expand Down Expand Up @@ -944,7 +948,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Resolves the list of tables that are used in a SQL query.
* If tables are specified in the options, those are used directly.
* Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
*
*
* @param sql The SQL query to analyze
* @param parameters Optional parameters for the SQL query
* @param options Optional watch options that may contain explicit table list
Expand Down Expand Up @@ -1077,7 +1081,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
*
* This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
* together when data is changed.
*
*
* Note: do not declare this as `async *onChange` as it will not work in React Native.
*
* @param options Options for configuring watch behavior
Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/WatchedQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
});
}

get logger() {
return this.db.logger ?? console;
}

addTemporaryHold() {
const ref = new Object();
this.temporaryHolds.add(ref);
Expand Down Expand Up @@ -88,7 +92,7 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
try {
this.tables = await this.db.resolveTables(this.query.sqlStatement, this.query.queryParameters, this.options);
} catch (e) {
console.error('Failed to fetch tables:', e);
this.logger.error('Failed to fetch tables:', e);
this.setError(e);
}
}
Expand All @@ -103,7 +107,7 @@ export class WatchedQuery extends BaseObserver<WatchedQueryListener> implements
const data = result ?? [];
this.setData(data);
} catch (e) {
console.error('Failed to fetch data:', e);
this.logger.error('Failed to fetch data:', e);
this.setError(e);
}
}
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/hooks/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const useQuery = <T = any>(
options: AdditionalOptions = { runQueryOnce: false }
): QueryResult<T> => {
const powerSync = usePowerSync();
const logger = powerSync?.logger ?? console;
if (!powerSync) {
return { isLoading: false, isFetching: false, data: [], error: new Error('PowerSync not configured.') };
}
Expand All @@ -50,7 +51,7 @@ export const useQuery = <T = any>(
try {
parsedQuery = parseQuery(query, parameters);
} catch (error) {
console.error('Failed to parse query:', error);
logger.error('Failed to parse query:', error);
return { isLoading: false, isFetching: false, data: [], error };
}

Expand Down Expand Up @@ -107,7 +108,7 @@ export const useQuery = <T = any>(

handleResult(result);
} catch (e) {
console.error('Failed to fetch data:', e);
logger.error('Failed to fetch data:', e);
handleError(e);
}
};
Expand All @@ -122,7 +123,7 @@ export const useQuery = <T = any>(

setTables(tables);
} catch (e) {
console.error('Failed to fetch tables:', e);
logger.error('Failed to fetch tables:', e);
handleError(e);
}
};
Expand Down
Loading