sqlite-replication
Advanced tools
Comparing version 0.0.22-b to 0.0.23-b
export * from './replicationHelpers'; | ||
export * from './replicationService'; | ||
export * from './SQLHelpers'; | ||
export * from './replicationSQLiteStorage'; |
export * from './replicationHelpers'; | ||
export * from './replicationService'; | ||
export * from './SQLHelpers'; | ||
export * from './replicationSQLiteStorage'; |
@@ -8,6 +8,2 @@ import { SQLiteDBConnection } from '@capacitor-community/sqlite'; | ||
}): ReplicationCollectionOptions; | ||
static getUpsertStatement(collectionName: string, document: any, option?: { | ||
ignoreUndefinedProperties: boolean; | ||
}): string | Promise<void>; | ||
static safeValue(value: any): any; | ||
static ensureRequiredColumns(db: ReplicationStorage, collections: ReplicationCollectionOptions[]): Promise<void>; | ||
@@ -14,0 +10,0 @@ static ensureFetchPull(config: ReplicationOptions): void; |
@@ -0,1 +1,2 @@ | ||
import { SQLHelpers } from './SQLHelpers'; | ||
const EXPECTED_COLUMNS = ['id', '_forkParent', 'updatedAt', 'deletedAt']; | ||
@@ -30,3 +31,3 @@ export class ReplicationHelpers { | ||
const keys = Object.keys(documents[0]); | ||
const values = documents.map((document) => `(${keys.map((key) => ReplicationHelpers.safeValue(document[key])).join()})`); | ||
const values = documents.map((document) => `(${keys.map((key) => SQLHelpers.safeValue(document[key])).join()})`); | ||
const conflictUpdate = keys.map((key) => `"${key}"=excluded."${key}"`).join(); | ||
@@ -40,3 +41,3 @@ return db.execute(`INSERT INTO "${collectionName}" (${keys.map((key) => `"${key}"`).join()}) values ${values.join()} | ||
return db.execute(`UPDATE "${collectionName}" SET "deletedAt"=unixepoch(), "updatedAt"=unixepoch() WHERE id IN (${documents | ||
.map((document) => ReplicationHelpers.safeValue(document.id)) | ||
.map((document) => SQLHelpers.safeValue(document.id)) | ||
.join()});`, false); | ||
@@ -46,31 +47,2 @@ }, | ||
} | ||
static getUpsertStatement(collectionName, document, option = { ignoreUndefinedProperties: true }) { | ||
if (!document) | ||
return Promise.resolve(); | ||
const keys = Object.keys(document).filter((key) => typeof document[key] !== 'undefined' || !option.ignoreUndefinedProperties); | ||
const values = keys.map((key) => ReplicationHelpers.safeValue(document[key])).join(); | ||
const conflictUpdate = keys.map((key) => `"${key}"=excluded."${key}"`).join(); | ||
return `INSERT INTO "${collectionName}" (${keys.map((key) => `"${key}"`).join()}) values (${values}) | ||
ON CONFLICT DO UPDATE SET ${conflictUpdate}`; | ||
} | ||
static safeValue(value) { | ||
if (value === null || typeof value === 'undefined') { | ||
return 'NULL'; | ||
} | ||
else if (value.toISOString) { | ||
return value.getTime(); | ||
} | ||
else if (Array.isArray(value) || typeof value === 'object') { | ||
return `'${JSON.stringify(value).replaceAll("'", "''")}'`; | ||
} | ||
else if (typeof value === 'string') { | ||
return `'${value.replaceAll("'", "''")}'`; | ||
} | ||
else if (typeof value === 'boolean') { | ||
return value ? '1' : '0'; | ||
} | ||
else { | ||
return value.toString(); | ||
} | ||
} | ||
static async ensureRequiredColumns(db, collections) { | ||
@@ -77,0 +49,0 @@ for (const collection of collections) { |
{ | ||
"name": "sqlite-replication", | ||
"version": "0.0.22b", | ||
"version": "0.0.23b", | ||
"description": "A Typescript module to replicate SQLite DB with server.", | ||
@@ -5,0 +5,0 @@ "scripts": { |
export * from './replicationHelpers'; | ||
export * from './replicationService'; | ||
export * from './SQLHelpers'; | ||
export * from './replicationSQLiteStorage'; |
@@ -73,84 +73,2 @@ import { ReplicationHelpers } from './replicationHelpers'; | ||
}); | ||
describe('upsert', () => { | ||
it('should generate SQL from random object', async () => { | ||
const resultingSQL = await ReplicationHelpers.getUpsertStatement('users', { id: 123, firstName: 'Andrew' }); | ||
expect(resultingSQL).toEqual( | ||
`INSERT INTO "users" ("id","firstName") values (123,'Andrew') | ||
ON CONFLICT DO UPDATE SET "id"=excluded."id","firstName"=excluded."firstName"`, | ||
); | ||
}); | ||
it('should generate SQL from random object changing undefined to null', async () => { | ||
const resultingSQL = await ReplicationHelpers.getUpsertStatement( | ||
'users', | ||
{ | ||
id: 123, | ||
firstName: 'Andrew', | ||
lastName: undefined, | ||
}, | ||
{ ignoreUndefinedProperties: false }, | ||
); | ||
expect(resultingSQL).toEqual( | ||
`INSERT INTO "users" ("id","firstName","lastName") values (123,'Andrew',NULL) | ||
ON CONFLICT DO UPDATE SET "id"=excluded."id","firstName"=excluded."firstName","lastName"=excluded."lastName"`, | ||
); | ||
}); | ||
it('should generate SQL from random object ignored undefined properties', async () => { | ||
const resultingSQL = await ReplicationHelpers.getUpsertStatement( | ||
'users', | ||
{ | ||
id: 123, | ||
firstName: 'Andrew', | ||
lastName: undefined, | ||
}, | ||
{ ignoreUndefinedProperties: true }, | ||
); | ||
expect(resultingSQL).toEqual( | ||
`INSERT INTO "users" ("id","firstName") values (123,'Andrew') | ||
ON CONFLICT DO UPDATE SET "id"=excluded."id","firstName"=excluded."firstName"`, | ||
); | ||
}); | ||
}); | ||
describe('safeValue', () => { | ||
it('should wrap strings with single quotes', async () => { | ||
expect(ReplicationHelpers.safeValue('value')).toEqual(`'value'`); | ||
}); | ||
it('should escape single quote, replaced by two single quotes', async () => { | ||
expect(ReplicationHelpers.safeValue("value with single quote in : 'here' ")).toEqual( | ||
"'value with single quote in : ''here'' '", | ||
); | ||
}); | ||
it('should not wrap numbers', async () => { | ||
expect(ReplicationHelpers.safeValue(5.2)).toEqual('5.2'); | ||
}); | ||
it('should convert dates to timestamp', async () => { | ||
expect(ReplicationHelpers.safeValue(new Date('2023-01-01'))).toEqual(1672531200000); | ||
}); | ||
it('should keep null', async () => { | ||
expect(ReplicationHelpers.safeValue(null)).toEqual('NULL'); | ||
}); | ||
it('should map undefined to null', async () => { | ||
let val; | ||
expect(ReplicationHelpers.safeValue(val)).toEqual('NULL'); | ||
}); | ||
it('should convert boolean to 0/1', async () => { | ||
expect(ReplicationHelpers.safeValue(true)).toEqual('1'); | ||
}); | ||
it('should convert boolean to 0/1', async () => { | ||
expect(ReplicationHelpers.safeValue(false)).toEqual('0'); | ||
}); | ||
it('should stringify object', async () => { | ||
expect(ReplicationHelpers.safeValue({ id: 1, toto: 2 })).toEqual(`'{"id":1,"toto":2}'`); | ||
}); | ||
it("should stringify object with '", async () => { | ||
expect(ReplicationHelpers.safeValue({ id: 1, toto: "coucou o'bryan" })).toEqual( | ||
`'{"id":1,"toto":"coucou o''bryan"}'`, | ||
); | ||
}); | ||
it('should stringify array', async () => { | ||
expect(ReplicationHelpers.safeValue([1, 2, 3])).toEqual(`'[1,2,3]'`); | ||
}); | ||
it("should stringify array with '", async () => { | ||
expect(ReplicationHelpers.safeValue(["o'connor", { id: 5 }, 3])).toEqual(`'["o''connor",{"id":5},3]'`); | ||
}); | ||
}); | ||
}); |
import { SQLiteDBConnection } from '@capacitor-community/sqlite'; | ||
import { ReplicationCollectionOptions, ReplicationConfig, ReplicationOptions, ReplicationStorage } from './replication'; | ||
import { SQLHelpers } from './SQLHelpers'; | ||
const EXPECTED_COLUMNS = ['id', '_forkParent', 'updatedAt', 'deletedAt']; | ||
@@ -37,3 +38,3 @@ | ||
const values = documents.map( | ||
(document) => `(${keys.map((key) => ReplicationHelpers.safeValue(document[key])).join()})`, | ||
(document) => `(${keys.map((key) => SQLHelpers.safeValue(document[key])).join()})`, | ||
); | ||
@@ -51,3 +52,3 @@ const conflictUpdate = keys.map((key) => `"${key}"=excluded."${key}"`).join(); | ||
`UPDATE "${collectionName}" SET "deletedAt"=unixepoch(), "updatedAt"=unixepoch() WHERE id IN (${documents | ||
.map((document) => ReplicationHelpers.safeValue(document.id)) | ||
.map((document) => SQLHelpers.safeValue(document.id)) | ||
.join()});`, | ||
@@ -60,33 +61,2 @@ false, | ||
static getUpsertStatement( | ||
collectionName: string, | ||
document: any, | ||
option: { ignoreUndefinedProperties: boolean } = { ignoreUndefinedProperties: true }, | ||
) { | ||
if (!document) return Promise.resolve(); | ||
const keys = Object.keys(document).filter( | ||
(key) => typeof document[key] !== 'undefined' || !option.ignoreUndefinedProperties, | ||
); | ||
const values = keys.map((key) => ReplicationHelpers.safeValue(document[key])).join(); | ||
const conflictUpdate = keys.map((key) => `"${key}"=excluded."${key}"`).join(); | ||
return `INSERT INTO "${collectionName}" (${keys.map((key) => `"${key}"`).join()}) values (${values}) | ||
ON CONFLICT DO UPDATE SET ${conflictUpdate}`; | ||
} | ||
static safeValue(value: any) { | ||
if (value === null || typeof value === 'undefined') { | ||
return 'NULL'; | ||
} else if (value.toISOString) { | ||
return value.getTime(); | ||
} else if (Array.isArray(value) || typeof value === 'object') { | ||
return `'${JSON.stringify(value).replaceAll("'", "''")}'`; | ||
} else if (typeof value === 'string') { | ||
return `'${(value as string).replaceAll("'", "''")}'`; | ||
} else if (typeof value === 'boolean') { | ||
return value ? '1' : '0'; | ||
} else { | ||
return value.toString(); | ||
} | ||
} | ||
static async ensureRequiredColumns(db: ReplicationStorage, collections: ReplicationCollectionOptions[]) { | ||
@@ -93,0 +63,0 @@ for (const collection of collections) { |
@@ -9,7 +9,7 @@ import { ReplicationService } from './replicationService'; | ||
async function testReplicationPush( | ||
documents, | ||
documents: any[], | ||
replicationPushState = { users: { offset: 0, cursor: 0 } }, | ||
batchSize = 10, | ||
) { | ||
const stateUpdates = []; | ||
const stateUpdates: any[] = []; | ||
const storage = new ReplicationTestStorage(null, replicationPushState, (collectionName, offset, cursor) => { | ||
@@ -26,3 +26,3 @@ stateUpdates.push({ collectionName, offset, cursor }); | ||
batchSize, | ||
getDocumentOffset: (cursor, id) => | ||
getDocumentOffset: (cursor: number, id: string) => | ||
Promise.resolve( | ||
@@ -29,0 +29,0 @@ documents.filter((document) => document.updatedAt === cursor && document.id <= id) |
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
96015
31
1832