diff --git a/android/build.gradle b/android/build.gradle index 4378548..3b6a569 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,7 +65,4 @@ repositories { dependencies { //noinspection GradleDynamicVersion api 'com.facebook.react:react-native:+' - implementation "androidx.room:room-runtime:2.5.2" - annotationProcessor "androidx.room:room-compiler:2.5.2" - implementation "androidx.room:room-ktx:2.5.2" } diff --git a/android/schemas/com.github.simonerm.JobDatabase/1.json b/android/schemas/com.github.simonerm.JobDatabase/1.json deleted file mode 100644 index b28cbd3..0000000 --- a/android/schemas/com.github.simonerm.JobDatabase/1.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "e0e3e2eb03357230350c15ce572617a7", - "entities": [ - { - "tableName": "job", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `worker_name` TEXT, `active` INTEGER NOT NULL, `payload` TEXT, `attempts` INTEGER NOT NULL, `created` TEXT, `success` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `is_deleted` INTEGER NOT NULL DEFAULT 0, `created` TEXT, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "workerName", - "columnName": "worker_name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "active", - "columnName": "active", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "payload", - "columnName": "payload", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "attempts", - "columnName": "attempts", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "created", - "columnName": "created", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "success", - "columnName": "success", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "completed", - "columnName": "completed", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isDeleted", - "columnName": "is_deleted", - "affinity": "INTEGER", - "notNull": true, - "defaultValue": "0" - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"e0e3e2eb03357230350c15ce572617a7\")" - ] - } -} \ No newline at end of file diff --git a/android/src/main/java/com/github/simonerm/ConversionHelper.java b/android/src/main/java/com/github/simonerm/ConversionHelper.java deleted file mode 100644 index 0f43620..0000000 --- a/android/src/main/java/com/github/simonerm/ConversionHelper.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.simonerm; - -import android.os.Bundle; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public class ConversionHelper { - - public static WritableMap getJobAsWritableMap(Job job){ - - return Arguments.fromBundle(getJobAsBundle(job)); - } - public static WritableArray getJobsAsWritableArray(List jobs){ - List jobsAsBundleList =new ArrayList<>(); - Iterator jobIterable=jobs.iterator(); - while(jobIterable.hasNext()){ - Job job =jobIterable.next(); - jobsAsBundleList.add(getJobAsBundle(job)); - } - - return Arguments.fromArray(jobsAsBundleList.toArray(new Bundle[jobsAsBundleList.size()])); - } - public static Job getJobFromReadableMap(ReadableMap jobAsMap){ - Job job = new Job(); - job.setId(jobAsMap.getString("id")); - job.setWorkerName(jobAsMap.getString("workerName")); - job.setActive(jobAsMap.getInt("active")); - job.setPayload(jobAsMap.getString("payload")); - job.setAttempts(jobAsMap.getInt("attempts")); - job.setMetaData(jobAsMap.getString("metaData")); - job.setTimeout(jobAsMap.getInt("timeout")); - job.setPriority(jobAsMap.getInt("priority")); - job.setCreated(jobAsMap.getString("created")); - job.setFailed(jobAsMap.getString("failed")); - job.setIsDeleted(jobAsMap.hasKey("isDeleted") && jobAsMap.getBoolean("isDeleted")); - job.setStatus(jobAsMap.getString("status")); - return job; - } - private static Bundle getJobAsBundle(Job job){ - Bundle jobAsBundle =new Bundle(); - jobAsBundle.putString("id",job.getId()); - jobAsBundle.putString("workerName",job.getWorkerName()); - jobAsBundle.putInt("active",job.getActive()); - jobAsBundle.putString("payload",job.getPayload()); - jobAsBundle.putString("metaData",job.getMetaData()); - jobAsBundle.putInt("attempts",job.getAttempts()); - jobAsBundle.putInt("timeout",job.getTimeout()); - jobAsBundle.putInt("priority",job.getPriority()); - jobAsBundle.putString("created",job.getCreated()); - jobAsBundle.putString("failed",job.getFailed()); - jobAsBundle.putBoolean("isDeleted", job.isDeleted()); - jobAsBundle.putString("status",job.getStatus()); - return jobAsBundle; - } -} diff --git a/android/src/main/java/com/github/simonerm/Job.java b/android/src/main/java/com/github/simonerm/Job.java deleted file mode 100644 index 6ff368b..0000000 --- a/android/src/main/java/com/github/simonerm/Job.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.github.simonerm; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.PrimaryKey; - -import java.sql.Date; - -@Entity(tableName = "job") -public class Job { - @NonNull - @PrimaryKey - private String id; - - @ColumnInfo(name = "worker_name") - private String workerName; - - private int active; - - private String payload; - - @ColumnInfo(name = "meta_data") - private String metaData; - - private int attempts; - - private String created; - - private int timeout; - - private int priority; - - private String failed; - - private String status; - - @ColumnInfo(name = "is_deleted") - private boolean isDeleted; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getWorkerName() { - return workerName; - } - - public void setWorkerName(String workerName) { - this.workerName = workerName; - } - - public int getActive() { - return active; - } - - public void setActive(int active) { - this.active = active; - } - - public String getPayload() { - return payload; - } - - public void setPayload(String payload) { - this.payload = payload; - } - - public int getAttempts() { - return attempts; - } - - public void setAttempts(int attempts) { - this.attempts = attempts; - } - - public String getCreated() { - return created; - } - - public void setCreated(String created) { - this.created = created; - } - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public String getFailed() { - return failed; - } - - public void setFailed(String failed) { - this.failed = failed; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public String getMetaData() { - return metaData; - } - - public void setMetaData(String metaData) { - this.metaData = metaData; - } - public boolean isDeleted() { - return isDeleted; - } - - public void setIsDeleted(boolean isDeleted) { - this.isDeleted = isDeleted; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } -} \ No newline at end of file diff --git a/android/src/main/java/com/github/simonerm/JobDao.java b/android/src/main/java/com/github/simonerm/JobDao.java deleted file mode 100644 index 15c3882..0000000 --- a/android/src/main/java/com/github/simonerm/JobDao.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.github.simonerm; - -import androidx.room.Dao; -import androidx.room.Delete; -import androidx.room.Insert; -import androidx.room.Query; -import androidx.room.Update; - -import java.util.List; - -@Dao -public interface JobDao { - @Query("SELECT * FROM job WHERE is_deleted == 0") - List getAll(); - - @Query("SELECT * FROM job WHERE active == 0 AND status != 'failed' AND status != 'cancelled' AND is_deleted == 0 ORDER BY priority DESC,datetime(created) LIMIT 1") - Job getNextJob(); - - @Query("SELECT * FROM job WHERE active == 0 AND status == 'processing' AND is_deleted = 0 ORDER BY priority DESC,datetime(created) LIMIT 1") - Job getWorkInProgressJob(); - - @Query("SELECT * FROM job WHERE is_deleted == 0 ORDER BY priority DESC,datetime(created)") - List getJobs(); - - @Query("SELECT * FROM job ORDER BY priority DESC,datetime(created)") - List getJobsWithDeleted(); - - @Query("SELECT * FROM job WHERE active == 1 AND is_deleted == 0") - List getActiveMarkedJobs(); - - @Query("SELECT * FROM job WHERE active == 0 AND status != 'failed' AND status != 'cancelled' AND worker_name == :workerName AND is_deleted == 0 ORDER BY priority DESC,datetime(created) LIMIT :limit") - List getJobsForWorker(String workerName, int limit); - - @Query("SELECT * FROM job WHERE active == 0 AND status != 'failed' AND status != 'cancelled' AND worker_name == :workerName ORDER BY priority DESC,datetime(created) LIMIT :limit") - List getJobsForWorkerWithDeleted(String workerName, int limit); - - @Query("SELECT * FROM job WHERE id LIKE :id AND is_deleted == 0") - Job findById(String id); - - @Query("SELECT COUNT(*) from job WHERE is_deleted == 0") - int countJobs(); - - @Update - void update(Job job); - - @Insert - void insert(Job job); - - @Delete - void delete(Job job); - - @Query("UPDATE job SET is_deleted = 1 WHERE worker_name == :workerName") - void markJobsAsDeletedByWorkerName(String workerName); - - @Query("UPDATE job SET is_deleted = 1 WHERE id = :jobId") - void softDeleteJob(String jobId); -} diff --git a/android/src/main/java/com/github/simonerm/JobDatabase.java b/android/src/main/java/com/github/simonerm/JobDatabase.java deleted file mode 100644 index 94d46b5..0000000 --- a/android/src/main/java/com/github/simonerm/JobDatabase.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.github.simonerm; - -import android.content.Context; - -import androidx.room.Database; -import androidx.room.Room; -import androidx.room.RoomDatabase; -import androidx.room.migration.Migration; -import androidx.sqlite.db.SupportSQLiteDatabase; - -@Database(entities = {Job.class}, version = 2) -public abstract class JobDatabase extends RoomDatabase { - - private static JobDatabase INSTANCE; - - public abstract JobDao jobDao(); - - public static JobDatabase getAppDatabase(Context context) { - if (INSTANCE == null) { - INSTANCE = - Room.databaseBuilder(context.getApplicationContext(), JobDatabase.class, "job-database") - .addMigrations(MIGRATION_1_2) - .build(); - } - return INSTANCE; - } - - public static void destroyInstance() { - INSTANCE = null; - } - - static final Migration MIGRATION_1_2 = new Migration(1, 2) { - @Override - public void migrate(SupportSQLiteDatabase database) { - database.execSQL("CREATE TABLE IF NOT EXISTS job(" + - "id CHAR(36) PRIMARY KEY NOT NULL," + - "worker_name CHAR(255) NOT NULL," + - "active INTEGER NOT NULL," + - "payload CHAR(1024)," + - "meta_data CHAR(1024)," + - "attempts INTEGER NOT NULL," + - "created CHAR(255)," + - "failed CHAR(255)," + - "timeout INTEGER NOT NULL," + - "priority Integer NOT NULL," + - "is_deleted INTEGER NOT NULL DEFAULT 0," + - "status CHAR(255)" + - ")"); // Removed the extra semicolon and parenthesis - } - }; -} \ No newline at end of file diff --git a/android/src/main/java/com/github/simonerm/JobQueueModule.java b/android/src/main/java/com/github/simonerm/JobQueueModule.java deleted file mode 100644 index d4c685c..0000000 --- a/android/src/main/java/com/github/simonerm/JobQueueModule.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.github.simonerm; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.ReadableMap; - -import java.util.Iterator; -import java.util.List; - -public class JobQueueModule extends ReactContextBaseJavaModule { - - private final ReactApplicationContext reactContext; - - public JobQueueModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return "JobQueue"; - } - - @ReactMethod - public void addJob(ReadableMap job) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - dao.insert(ConversionHelper.getJobFromReadableMap(job)); - } - - @ReactMethod - public void updateJob(ReadableMap job) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - dao.update(ConversionHelper.getJobFromReadableMap(job)); - } - - @ReactMethod - public void removeJob(ReadableMap job) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - dao.softDeleteJob(ConversionHelper.getJobFromReadableMap(job).getId()); - } - - @ReactMethod - public void removeJobPermanently(ReadableMap job) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - dao.delete(ConversionHelper.getJobFromReadableMap(job)); - } - - @ReactMethod - public void removeJobsByWorkerName(String workerName) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - dao.markJobsAsDeletedByWorkerName(workerName); - } - - @ReactMethod - public void getJobById(String id, Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - Job job = dao.findById(id); - promise.resolve(ConversionHelper.getJobAsWritableMap(job)); - } - - @ReactMethod - public void getJobs(Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - List jobs=dao.getJobs(); - promise.resolve(ConversionHelper.getJobsAsWritableArray(jobs)); - } - @ReactMethod - public void getJobsWithDeleted(Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - List jobs=dao.getJobsWithDeleted(); - promise.resolve(ConversionHelper.getJobsAsWritableArray(jobs)); - } - - @ReactMethod - public void getActiveMarkedJobs(Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - List jobs=dao.getActiveMarkedJobs(); - promise.resolve(ConversionHelper.getJobsAsWritableArray(jobs)); - } - - @ReactMethod - public void getJobsForWorker(String workerName,int limit,Promise promise){ - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - List jobsToExecute=dao.getJobsForWorker(workerName,limit); - IteratorjobIterator=jobsToExecute.iterator(); - while(jobIterator.hasNext()){ - Job job = jobIterator.next(); - job.setActive(1); - dao.update(job); - } - promise.resolve(ConversionHelper.getJobsAsWritableArray(jobsToExecute)); - } - @ReactMethod - public void getJobsForWorkerWithDeleted(String workerName,int limit,Promise promise){ - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - List jobsToExecute=dao.getJobsForWorkerWithDeleted(workerName,limit); - IteratorjobIterator=jobsToExecute.iterator(); - while(jobIterator.hasNext()){ - Job job = jobIterator.next(); - job.setActive(1); - dao.update(job); - } - promise.resolve(ConversionHelper.getJobsAsWritableArray(jobsToExecute)); - } - @ReactMethod - public void getNextJob(Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - Job nextJob=dao.getNextJob(); - if(nextJob!=null){ - promise.resolve(ConversionHelper.getJobAsWritableMap(nextJob)); - }else{ - promise.resolve(Arguments.createMap()); - } - } - @ReactMethod - public void getWorkInProgressJob(Promise promise) { - JobDao dao = JobDatabase.getAppDatabase(this.reactContext).jobDao(); - - Job nextJob=dao.getWorkInProgressJob(); - if(nextJob!=null){ - promise.resolve(ConversionHelper.getJobAsWritableMap(nextJob)); - }else{ - promise.resolve(Arguments.createMap()); - } - } - -} diff --git a/android/src/main/java/com/github/simonerm/JobQueuePackage.java b/android/src/main/java/com/github/simonerm/JobQueuePackage.java deleted file mode 100644 index 9f39913..0000000 --- a/android/src/main/java/com/github/simonerm/JobQueuePackage.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.simonerm; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; - -public class JobQueuePackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new JobQueueModule(reactContext)); - } - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} diff --git a/babel.config.js b/babel.config.js index c50a8a0..0c20fc6 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,6 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'] + presets: ['module:metro-react-native-babel-preset'], + plugins: [ + ['@babel/plugin-proposal-decorators', { 'legacy': true }] + ] }; diff --git a/ios/Job.swift b/ios/Job.swift deleted file mode 100644 index cfbee96..0000000 --- a/ios/Job.swift +++ /dev/null @@ -1,339 +0,0 @@ -import Foundation -import SQLite3 - -struct Job { - var id: NSString - var workerName: NSString - var active:Int32 - var payload: NSString - var metaData: NSString - var attempts: Int32 - var created: NSString - var failed: NSString - var timeout: Int32 - var priority: Int32 - var isDeleted: Int32 - var status: NSString -} -extension Job: SQLTable { - static var createStatement: String { - return """ - CREATE TABLE IF NOT EXISTS Job( - id CHAR(36) PRIMARY KEY NOT NULL, - worker_name CHAR(255) NOT NULL, - active INTEGER NOT NULL, - payload CHAR(1024), - meta_data CHAR(1024), - attempts INTEGER NOT NULL, - created CHAR(255), - failed CHAR(255), - timeout INTEGER NOT NULL, - priority Integer NOT NULL, - is_deleted INTEGER NOT NULL DEFAULT 0, - status CHAR(255) - ); - """ - } - static func createJobFromDictionary(job:[String:Any])->Job{ - return Job(id:job["id"] as! NSString, - workerName: job["workerName"] as! NSString, - active:job["active"] as! Int32, - payload:job["payload"] as! NSString, - metaData: job["metaData"] as! NSString, - attempts: job["attempts"] as! Int32, - created: job["created"] as! NSString, - failed: job["failed"] as! NSString, - timeout: job["timeout"] as! Int32, - priority: job["priority"] as! Int32, - isDeleted: job["isDeleted"] as! Int32, - status: job["status"] as! NSString) - } - func toDictionary()->[String:Any]{ - var jobAsDictionary=[String:Any]() - jobAsDictionary["id"]=self.id - jobAsDictionary["workerName"]=self.workerName - jobAsDictionary["active"]=self.active - jobAsDictionary["payload"]=self.payload - jobAsDictionary["metaData"]=self.metaData - jobAsDictionary["attempts"]=self.attempts - jobAsDictionary["created"]=self.created - jobAsDictionary["failed"]=self.failed - jobAsDictionary["timeout"]=self.timeout - jobAsDictionary["priority"]=self.priority - jobAsDictionary["isDeleted"]=self.isDeleted - jobAsDictionary["status"]=self.status - return jobAsDictionary; - } -} -extension SQLiteDatabase { - func add(job: Job) throws { - let insertSql = "INSERT INTO job (id, worker_name, active, payload, meta_data, attempts, created, failed, timeout, priority, is_deleted, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" - let insertStatement = try prepareStatement(sql: insertSql) - defer { - sqlite3_finalize(insertStatement) - } - - guard (sqlite3_bind_text(insertStatement, 1, job.id.utf8String,-1,nil) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 2, job.workerName.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_int(insertStatement, 3, job.active) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 4, job.payload.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 5, job.metaData.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_int(insertStatement, 6, job.attempts) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 7, job.created.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 8, job.failed.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_int(insertStatement, 9, job.timeout) == SQLITE_OK && - sqlite3_bind_int(insertStatement, 10, job.priority) == SQLITE_OK && - sqlite3_bind_int(insertStatement, 11, job.isDeleted) == SQLITE_OK && - sqlite3_bind_text(insertStatement, 12, job.status.utf8String, -1, nil) == SQLITE_OK - - )else { - throw SQLiteError.Bind(message: errorMessage) - } - - guard sqlite3_step(insertStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - } - func getJobBy(id: NSString) -> Job? { - let querySql = "SELECT * FROM job WHERE id = ? AND is_deleted = 0;" - guard let queryStatement = try? prepareStatement(sql: querySql) else { - return nil - } - - defer { - sqlite3_finalize(queryStatement) - } - - guard sqlite3_bind_text(queryStatement, 1, id.utf8String,-1,nil) == SQLITE_OK - else { - return nil - } - - guard sqlite3_step(queryStatement) == SQLITE_ROW else { - return nil - } - - return mapColumnsToJob(sqlStatement: queryStatement) - } - func getNextJob() -> Job? { - let querySql = "SELECT * FROM job WHERE active == 0 AND status != 'failed' AND status != 'cancelled' AND is_deleted = 0 ORDER BY priority DESC,datetime(created) LIMIT 1;" - guard let queryStatement = try? prepareStatement(sql: querySql) else { - return nil - } - - defer { - sqlite3_finalize(queryStatement) - } - - guard sqlite3_step(queryStatement) == SQLITE_ROW else { - return nil - } - - return mapColumnsToJob(sqlStatement: queryStatement) - } - func getWorkInProgressJob() -> Job? { - let querySql = "SELECT * FROM job WHERE active == 0 AND status == 'processing' AND is_deleted = 0 ORDER BY priority DESC,datetime(created) LIMIT 1;" - guard let queryStatement = try? prepareStatement(sql: querySql) else { - return nil - } - - defer { - sqlite3_finalize(queryStatement) - } - - guard sqlite3_step(queryStatement) == SQLITE_ROW else { - return nil - } - - return mapColumnsToJob(sqlStatement: queryStatement) - } - func getJobsForWorker(name:NSString,count:Int32) -> [Job]? { - let querySql = "SELECT * FROM job WHERE active == 0 AND status != 'failed' AND status != 'cancelled' AND worker_name == ? AND is_deleted = 0 ORDER BY priority DESC,datetime(created) LIMIT ?;" - guard let queryStatement = try? prepareStatement(sql: querySql) else { - return nil - } - defer { - sqlite3_finalize(queryStatement) - } - - guard (sqlite3_bind_text(queryStatement, 1, name.utf8String,-1,nil) == SQLITE_OK && - sqlite3_bind_int(queryStatement, 2, count) == SQLITE_OK)else { - return nil - } - - var jobs = [Job]() - while(sqlite3_step(queryStatement) == SQLITE_ROW){ - if let job=mapColumnsToJob(sqlStatement: queryStatement){ - jobs.append(job) - } - } - - return jobs - } - func getJobsForWorkerWithDeleted(name:NSString,count:Int32) -> [Job]? { - let querySql = "SELECT * FROM job WHERE active == 0 AND worker_name == ? ORDER BY priority DESC,datetime(created) LIMIT ?;" - guard let queryStatement = try? prepareStatement(sql: querySql) else { - return nil - } - defer { - sqlite3_finalize(queryStatement) - } - - guard (sqlite3_bind_text(queryStatement, 1, name.utf8String,-1,nil) == SQLITE_OK && - sqlite3_bind_int(queryStatement, 2, count) == SQLITE_OK)else { - return nil - } - - var jobs = [Job]() - while(sqlite3_step(queryStatement) == SQLITE_ROW){ - if let job=mapColumnsToJob(sqlStatement: queryStatement){ - jobs.append(job) - } - } - - return jobs - } - func getJobs() -> [Job]? { - let querySql = "SELECT * FROM job WHERE is_deleted = 0 ORDER BY priority DESC,datetime(created);" - return getJobsByQuery(query: querySql) - } - func getJobsWithDeleted() -> [Job]? { - let querySql = "SELECT * FROM job ORDER BY priority DESC,datetime(created);" - return getJobsByQuery(query: querySql) - } - func getActiveMarkedJobs() -> [Job]? { - let querySql = "SELECT * FROM job WHERE active == 1 AND is_deleted = 0;" - return getJobsByQuery(query: querySql) - } - func getJobsByQuery(query:String)->[Job]?{ - let modifiedQuery: String - if query.lowercased().contains("where") { - modifiedQuery = query.replacingOccurrences(of: "where", with: "WHERE is_deleted = 0 AND", options: .caseInsensitive, range: nil) - } else { - modifiedQuery = query + " WHERE is_deleted = 0" - } - guard let queryStatement = try? prepareStatement(sql: modifiedQuery) else { - return nil - } - defer { - sqlite3_finalize(queryStatement) - } - - var jobs = [Job]() - while(sqlite3_step(queryStatement) == SQLITE_ROW){ - if let job=mapColumnsToJob(sqlStatement: queryStatement){ - jobs.append(job) - } - } - - return jobs - } - - func deletePermanently(job: Job) throws{ - let querySql = "DELETE FROM job WHERE id = ?;" - guard let deleteStatement = try? prepareStatement(sql: querySql) else { - throw SQLiteError.Prepare(message: errorMessage) - } - - defer { - sqlite3_finalize(deleteStatement) - } - - guard sqlite3_bind_text(deleteStatement, 1, job.id.utf8String,-1,nil) == SQLITE_OK else { - throw SQLiteError.Bind(message: errorMessage) - } - - guard sqlite3_step(deleteStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - } - - func delete(job: Job) throws{ - let querySql = "UPDATE job SET is_deleted = 1 WHERE id = ?;" - guard let deleteStatement = try? prepareStatement(sql: querySql) else { - throw SQLiteError.Prepare(message: errorMessage) - } - - defer { - sqlite3_finalize(deleteStatement) - } - - guard sqlite3_bind_text(deleteStatement, 1, job.id.utf8String,-1,nil) == SQLITE_OK else { - throw SQLiteError.Bind(message: errorMessage) - } - - guard sqlite3_step(deleteStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - } - func deleteJobsByWorkerName(workerName: NSString) throws{ - let querySql = "UPDATE job SET is_deleted = 1 WHERE worker_name = ?;" - guard let deleteStatement = try? prepareStatement(sql: querySql) else { - throw SQLiteError.Prepare(message: errorMessage) - } - - defer { - sqlite3_finalize(deleteStatement) - } - - guard sqlite3_bind_text(deleteStatement, 1, workerName.utf8String,-1,nil) == SQLITE_OK else { - throw SQLiteError.Bind(message: errorMessage) - } - - guard sqlite3_step(deleteStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - } - - func update(job: Job) throws{ - let querySql = "UPDATE job SET active = ?, failed = ?, meta_data = ?, attempts = ?, status = ? WHERE id = ?;" - guard let updateStatement = try? prepareStatement(sql: querySql) else { - throw SQLiteError.Prepare(message: errorMessage) - } - - defer { - sqlite3_finalize(updateStatement) - } - - guard (sqlite3_bind_int(updateStatement, 1,job.active) == SQLITE_OK && - sqlite3_bind_text(updateStatement, 2, job.failed.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_text(updateStatement, 3, job.metaData.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_int(updateStatement, 4, job.attempts) == SQLITE_OK && - sqlite3_bind_text(updateStatement, 5, job.status.utf8String, -1, nil) == SQLITE_OK && - sqlite3_bind_text(updateStatement, 6, job.id.utf8String,-1, nil) == SQLITE_OK - ) else { - throw SQLiteError.Bind(message: errorMessage) - } - guard sqlite3_step(updateStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - - } - - func mapColumnsToJob(sqlStatement: OpaquePointer?)->Job?{ - if(sqlStatement != nil){ - let idColumnContent = sqlite3_column_text(sqlStatement, 0) - let id = String(cString: idColumnContent!) as NSString - let workerNameColumnContent = sqlite3_column_text(sqlStatement, 1) - let workerName = String(cString: workerNameColumnContent!) as NSString - let active=sqlite3_column_int(sqlStatement, 2) - let payloadColumnContent = sqlite3_column_text(sqlStatement, 3) - let payload = String(cString: payloadColumnContent!) as NSString - let metaDataColumnContent = sqlite3_column_text(sqlStatement, 4) - let metaData = String(cString: metaDataColumnContent!) as NSString - let attempts=sqlite3_column_int(sqlStatement, 5) - let createdColumnContent = sqlite3_column_text(sqlStatement, 6) - let created = String(cString: createdColumnContent!) as NSString - let failedColumnContent = sqlite3_column_text(sqlStatement, 7) - let failed = String(cString: failedColumnContent!) as NSString - let timeout=sqlite3_column_int(sqlStatement, 8) - let priority=sqlite3_column_int(sqlStatement, 9) - let isDeleted = sqlite3_column_int(sqlStatement, 10) - let statusColumnContent = sqlite3_column_text(sqlStatement, 11) - let status = String(cString: statusColumnContent!) as NSString - return Job(id: id, workerName: workerName,active:active,payload:payload,metaData: metaData,attempts: attempts,created: created,failed: failed,timeout: timeout,priority: priority, isDeleted: isDeleted, status: status) - }else{ - return nil - } - } -} diff --git a/ios/JobQueue-Bridging-Header.h b/ios/JobQueue-Bridging-Header.h deleted file mode 100644 index 5fff35c..0000000 --- a/ios/JobQueue-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import diff --git a/ios/JobQueue.h b/ios/JobQueue.h deleted file mode 100644 index 0710050..0000000 --- a/ios/JobQueue.h +++ /dev/null @@ -1,5 +0,0 @@ -#import - -@interface JobQueue : NSObject - -@end diff --git a/ios/JobQueue.m b/ios/JobQueue.m deleted file mode 100644 index cc21755..0000000 --- a/ios/JobQueue.m +++ /dev/null @@ -1,18 +0,0 @@ -#import - -@interface RCT_EXTERN_MODULE(JobQueue, NSObject) - -RCT_EXTERN_METHOD(addJob:(NSDictionary *)job) -RCT_EXTERN_METHOD(removeJob:(NSDictionary *)job) -RCT_EXTERN_METHOD(removeJobPermanently:(NSDictionary *)job) -RCT_EXTERN_METHOD(updateJob:(NSDictionary *)job) -RCT_EXTERN_METHOD(removeJobsByWorkerName:(NSString *)workerName) -RCT_EXTERN_METHOD(getNextJob:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getWorkInProgressJob:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getJobsForWorker:(NSString *)name count:(NSInteger) count resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getJobsForWorkerWithDeleted:(NSString *)name count:(NSInteger) count resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getJobs:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getJobsWithDeleted:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getActiveMarkedJobs:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -@end diff --git a/ios/JobQueue.swift b/ios/JobQueue.swift deleted file mode 100644 index 0a44be7..0000000 --- a/ios/JobQueue.swift +++ /dev/null @@ -1,199 +0,0 @@ -import Foundation -import SQLite3 - -@objc(JobQueue) -public class JobQueue:NSObject{ - var db: SQLiteDatabase?; - public override init() { - super.init() - do { - var path = try FileManager.default.url(for:.libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: true); - path.appendPathComponent("jobdb") - db = try SQLiteDatabase.open(path: path.path) - print("Successfully opened connection to database.") - do { - try db!.createTable(table: Job.self) - } catch { - print(db!.errorMessage) - } - } catch SQLiteError.OpenDatabase(let message) { - print("Unable to open database. Verify that you created the directory described in the Getting Started section.",message) - } catch { - print("Unable to open database. Verify that you created the directory described in the Getting Started section.") - } - - } - - @objc - public func addJob(_ job:[String:Any]){ - if(db != nil){ - do{ - try db?.add(job: Job.createJobFromDictionary(job: job)) - }catch{ - print("Couln't add Job to Database: ",error) - } - } - } - - @objc - public func removeJob(_ job:[String:Any]){ - if(db != nil){ - do{ - try db?.delete(job: Job.createJobFromDictionary(job: job)) - }catch{ - print("Couln't remove job Job to Database: ",error) - } - } - } - @objc - public func removeJobPermanently(_ job:[String:Any]){ - if(db != nil){ - do{ - try db?.deletePermanently(job: Job.createJobFromDictionary(job: job)) - }catch{ - print("Couln't remove job Job to Database: ",error) - } - } - } - @objc - public func removeJobsByWorkerName(_ workerName:String){ - if(db != nil){ - do{ - try db?.deleteJobsByWorkerName(workerName:workerName as NSString) - }catch{ - print("Couln't remove job Job to Database: ",error) - } - } - } - @objc - public func updateJob(_ job:[String:Any]){ - if(db != nil){ - do{ - try db?.update(job: Job.createJobFromDictionary(job: job)) - }catch{ - print("Couln't update job Job to Database: ",error) - } - } - } - - @objc - public func getNextJob(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let job = db?.getNextJob(){ - resolve(job.toDictionary()) - }else{ - resolve([String:Any]()) - } - - } - } - @objc - public func getWorkInProgressJob(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let job = db?.getWorkInProgressJob(){ - resolve(job.toDictionary()) - }else{ - resolve([String:Any]()) - } - - } - } - - @objc - public func getJobsForWorker(_ name:String, count:Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let jobs = db?.getJobsForWorker(name: name as NSString, count: Int32(count)){ - var jobsAsDictionaryArray=[[String:Any]]() - for job in jobs{ - var job=job - job.active = 1 - do{ - try db?.update(job: job) - jobsAsDictionaryArray.append(job.toDictionary()) - }catch{ - print("Couln't update job Job to Database: ",error) - } - - } - resolve(jobsAsDictionaryArray) - }else{ - resolve([[String:Any]]()) - } - - } - } - @objc - public func getJobsForWorkerWithDeleted(_ name:String, count:Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let jobs = db?.getJobsForWorkerWithDeleted(name: name as NSString, count: Int32(count)){ - var jobsAsDictionaryArray=[[String:Any]]() - for job in jobs{ - var job=job - job.active = 1 - do{ - try db?.update(job: job) - jobsAsDictionaryArray.append(job.toDictionary()) - }catch{ - print("Couln't update job Job to Database: ",error) - } - - } - resolve(jobsAsDictionaryArray) - }else{ - resolve([[String:Any]]()) - } - - } - } - - @objc - public func getJobs(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let jobs = db?.getJobs(){ - var jobsAsDictionaryArray=[[String:Any]]() - for job in jobs{ - jobsAsDictionaryArray.append(job.toDictionary()) - } - resolve(jobsAsDictionaryArray) - }else{ - resolve([[String:Any]]()) - } - - } - } - @objc - public func getJobsWithDeleted(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let jobs = db?.getJobsWithDeleted(){ - var jobsAsDictionaryArray=[[String:Any]]() - for job in jobs{ - jobsAsDictionaryArray.append(job.toDictionary()) - } - resolve(jobsAsDictionaryArray) - }else{ - resolve([[String:Any]]()) - } - - } - } - - @objc - public func getActiveMarkedJobs(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock){ - if(db != nil){ - if let jobs = db?.getActiveMarkedJobs(){ - var jobsAsDictionaryArray=[[String:Any]]() - for job in jobs{ - jobsAsDictionaryArray.append(job.toDictionary()) - } - resolve(jobsAsDictionaryArray) - }else{ - resolve([[String:Any]]()) - } - - } - } - - @objc static func requiresMainQueueSetup() -> Bool { - return false - } -} diff --git a/ios/JobQueue.xcodeproj/project.pbxproj b/ios/JobQueue.xcodeproj/project.pbxproj index 01ca5c8..c5219fb 100644 --- a/ios/JobQueue.xcodeproj/project.pbxproj +++ b/ios/JobQueue.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - B3E7B58A1CC2AC0600A0062D /* JobQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* JobQueue.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -24,15 +23,6 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libJobQueue.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libJobQueue.a; sourceTree = BUILT_PRODUCTS_DIR; }; - B3E7B5881CC2AC0600A0062D /* JobQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JobQueue.h; sourceTree = ""; }; - B3E7B5891CC2AC0600A0062D /* JobQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JobQueue.m; sourceTree = ""; }; - F4297A7A22FA3484000FE33E /* JobQueue-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JobQueue-Bridging-Header.h"; sourceTree = ""; }; - F4297A7B22FA3484000FE33E /* JobQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobQueue.swift; sourceTree = ""; }; - F4297A8A22FB720E000FE33E /* JobQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = JobQueue.m; path = "/Users/simon/Developer/Projects/ReactNative/react-native-job-queue/ios/JobQueue.m"; sourceTree = ""; }; - F4297A8B22FB720E000FE33E /* JobQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JobQueue.swift; path = "/Users/simon/Developer/Projects/ReactNative/react-native-job-queue/ios/JobQueue.swift"; sourceTree = ""; }; - F4297A8C22FB720E000FE33E /* Job.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Job.swift; path = "/Users/simon/Developer/Projects/ReactNative/react-native-job-queue/ios/Job.swift"; sourceTree = ""; }; - F4297A8D22FB720E000FE33E /* SQLiteDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SQLiteDatabase.swift; path = "/Users/simon/Developer/Projects/ReactNative/react-native-job-queue/ios/SQLiteDatabase.swift"; sourceTree = ""; }; - F4297A8E22FB720E000FE33E /* SQLTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SQLTable.swift; path = "/Users/simon/Developer/Projects/ReactNative/react-native-job-queue/ios/SQLTable.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -57,16 +47,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - F4297A7B22FA3484000FE33E /* JobQueue.swift */, - F4297A8C22FB720E000FE33E /* Job.swift */, - F4297A8A22FB720E000FE33E /* JobQueue.m */, - F4297A8B22FB720E000FE33E /* JobQueue.swift */, - F4297A8D22FB720E000FE33E /* SQLiteDatabase.swift */, - F4297A8E22FB720E000FE33E /* SQLTable.swift */, - B3E7B5881CC2AC0600A0062D /* JobQueue.h */, - B3E7B5891CC2AC0600A0062D /* JobQueue.m */, 134814211AA4EA7D00B7C361 /* Products */, - F4297A7A22FA3484000FE33E /* JobQueue-Bridging-Header.h */, ); sourceTree = ""; }; @@ -128,7 +109,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B3E7B58A1CC2AC0600A0062D /* JobQueue.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -243,7 +223,6 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = JobQueue; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "JobQueue-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -264,7 +243,6 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = JobQueue; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "JobQueue-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/ios/SQLTable.swift b/ios/SQLTable.swift deleted file mode 100644 index 116fb7b..0000000 --- a/ios/SQLTable.swift +++ /dev/null @@ -1,3 +0,0 @@ -protocol SQLTable { - static var createStatement: String { get } -} diff --git a/ios/SQLiteDatabase.swift b/ios/SQLiteDatabase.swift deleted file mode 100644 index 101b06e..0000000 --- a/ios/SQLiteDatabase.swift +++ /dev/null @@ -1,68 +0,0 @@ -import SQLite3 -class SQLiteDatabase { - fileprivate let dbPointer: OpaquePointer? - - fileprivate init(dbPointer: OpaquePointer?) { - self.dbPointer = dbPointer - } - - deinit { - sqlite3_close(dbPointer) - } - - static func open(path: String) throws -> SQLiteDatabase { - var db: OpaquePointer? = nil - if sqlite3_open(path, &db) == SQLITE_OK { - return SQLiteDatabase(dbPointer: db) - } else { - defer { - if db != nil { - sqlite3_close(db) - } - } - if let errorPointer = sqlite3_errmsg(db) { - let message = String.init(cString: errorPointer) - throw SQLiteError.OpenDatabase(message: message) - } else { - throw SQLiteError.OpenDatabase(message: "No error message provided from sqlite.") - } - } - } - - var errorMessage: String { - if let errorPointer = sqlite3_errmsg(dbPointer) { - let errorMessage = String(cString: errorPointer) - return errorMessage - } else { - return "No error message provided from sqlite." - } - } - func prepareStatement(sql: String) throws -> OpaquePointer? { - var statement: OpaquePointer? = nil - guard sqlite3_prepare_v2(dbPointer, sql, -1, &statement, nil) == SQLITE_OK else { - throw SQLiteError.Prepare(message: errorMessage) - } - - return statement - } - func createTable(table: SQLTable.Type) throws { - // 1 - let createTableStatement = try prepareStatement(sql: table.createStatement) - // 2 - defer { - sqlite3_finalize(createTableStatement) - } - // 3 - guard sqlite3_step(createTableStatement) == SQLITE_DONE else { - throw SQLiteError.Step(message: errorMessage) - } - print("\(table) table created.") - } -} - -enum SQLiteError: Error { - case OpenDatabase(message: String) - case Prepare(message: String) - case Step(message: String) - case Bind(message: String) -} diff --git a/package.json b/package.json index e4a90c4..4659164 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,11 @@ "react-native": ">=0.57.0-rc.0 <1.0.x" }, "dependencies": { + "@nozbe/watermelondb": "^0.28.0", "eventemitter3": "^5.0.1" }, "devDependencies": { + "@babel/plugin-proposal-decorators": "^7.27.1", "@react-native-community/bob": "^0.17.1", "@types/jest": "^24.0.17", "@types/react": "^16.8.25", @@ -75,4 +77,4 @@ "typescript" ] } -} \ No newline at end of file +} diff --git a/react-native-job-queue.podspec b/react-native-job-queue.podspec index e6ff768..fd3c670 100644 --- a/react-native-job-queue.podspec +++ b/react-native-job-queue.podspec @@ -17,10 +17,11 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/SimonErm/react-native-job-queue.git", :tag => "#{s.version}" } s.swift_version = "5.0" - s.source_files = "ios/**/*.{h,m,swift}" + # No longer providing its own native iOS source files directly in this package + # s.source_files = "ios/**/*.{h,m,swift}" s.requires_arc = true s.dependency "React" - s.library = "sqlite3" + # s.library = "sqlite3" # Removed as the native SQLite code is deleted # s.dependency "..." end diff --git a/src/JobStore.ts b/src/JobStore.ts new file mode 100644 index 0000000..b2c6354 --- /dev/null +++ b/src/JobStore.ts @@ -0,0 +1,217 @@ +import { Platform } from 'react-native'; +import { Database, Model, Q, Collection } from '@nozbe/watermelondb'; +import { field, table } from '@nozbe/watermelondb/decorators'; + +import { database, JobModel } from './watermelon/database'; +import { JobStore as JobStoreInterface } from './models/JobStore'; +import { RawJob, JobStatus, Bool, TRUE, FALSE } from './models/Job'; // Assuming TRUE/FALSE are 0/1 + +// Helper function to map JobModel to RawJob +function mapJobModelToRawJob(jobModel: JobModel): RawJob { + if (!jobModel) { + return {} as RawJob; // Or handle as an error/null based on how getNextJob/getWorkInProgressJob expect it + } + return { + id: jobModel.id, // WatermelonDB model ID + workerName: jobModel.workerName, + active: jobModel.active ? TRUE : FALSE, + payload: jobModel.payload, + metaData: jobModel.metaData, + attempts: jobModel.attempts, + created: jobModel.created, // Ensure this matches RawJob's expected format + status: jobModel.status as JobStatus, + failed: jobModel.failed || '', // RawJob expects string, model might have null + timeout: jobModel.timeout, + priority: jobModel.priority, + isDeleted: jobModel.isDeleted, // RawJob has isDeleted: boolean + }; +} + +function mapRawJobToJobModelFields(job: RawJob, record: JobModel) { + // Note: record.id is managed by WatermelonDB or set via record._raw.id if needed + // For creation, some fields like 'id' might be handled differently + if (job.id && record._raw) { // only for creation. On update, id is not changed. + record._raw.id = job.id; + } + record.workerName = job.workerName; + record.active = job.active === TRUE; + record.payload = job.payload; + record.metaData = job.metaData; + record.attempts = job.attempts; + record.created = job.created; + record.status = job.status; + record.failed = job.failed; + record.timeout = job.timeout; + record.priority = job.priority; + record.isDeleted = job.isDeleted; // Assuming RawJob.isDeleted is boolean +} + + +export class WatermelonDBJobStore implements JobStoreInterface { + private get jobsCollection(): Collection { + return database.collections.get('jobs'); + } + + async addJob(job: RawJob): Promise { + await database.write(async () => { + await this.jobsCollection.create(record => { + // WatermelonDB handles ID generation by default if job.id is not pre-set + // If job.id is meant to be external, it needs to be set via record._raw.id = job.id; + // For this implementation, let's assume job.id is provided and should be used. + if (!job.id) { + // This should ideally not happen if RawJob mandates an id. + // Or, we let WatermelonDB generate it (remove the line below) + throw new Error("Job ID must be provided when adding a job."); + } + record._raw.id = job.id; // Explicitly set the ID from RawJob + mapRawJobToJobModelFields(job, record); + }); + }); + } + + async getJobs(): Promise { + const jobModels = await this.jobsCollection.query(Q.where('is_deleted', false)).fetch(); + return jobModels.map(mapJobModelToRawJob); + } + + async getJobsWithDeleted(): Promise { + const jobModels = await this.jobsCollection.query().fetch(); + return jobModels.map(mapJobModelToRawJob); + } + + async getActiveMarkedJobs(): Promise { + const jobModels = await this.jobsCollection.query( + Q.and( + Q.where('active', true), + Q.where('is_deleted', false) + ) + ).fetch(); + return jobModels.map(mapJobModelToRawJob); + } + + async getNextJob(): Promise { + const result = await this.jobsCollection.query( + Q.where('is_deleted', false), + Q.where('active', false), + Q.where('failed', null), // Assumes 'failed' is null for non-failed, not empty string + Q.where('status', JobStatus.IDLE), // Use JobStatus enum + Q.sortBy('priority', Q.desc), + Q.sortBy('created', Q.asc), + Q.take(1) + ).fetch(); + + if (result.length > 0) { + return mapJobModelToRawJob(result[0]); + } + return {} as RawJob; // As per instruction for empty object + } + + async getWorkInProgressJob(): Promise { + const result = await this.jobsCollection.query( + Q.where('is_deleted', false), + Q.where('active', true), + Q.where('status', JobStatus.PROCESSING), // Use JobStatus enum + Q.sortBy('created', Q.asc), + Q.take(1) + ).fetch(); + + if (result.length > 0) { + return mapJobModelToRawJob(result[0]); + } + return {} as RawJob; // As per instruction for empty object + } + + async getJobsForWorker(name: string, count: number): Promise { + const jobModels = await this.jobsCollection.query( + Q.where('worker_name', name), + Q.where('is_deleted', false), + Q.where('active', false), + Q.where('status', JobStatus.IDLE), + Q.where('failed', null), + Q.sortBy('priority', Q.desc), + Q.sortBy('created', Q.asc), + Q.take(count) + ).fetch(); + return jobModels.map(mapJobModelToRawJob); + } + + async getJobsForWorkerWithDeleted(name: string, count: number): Promise { + // The requirement "active is false, status is 'idle', failed is empty or null" might be too restrictive + // if we want to see ALL jobs for a worker, including processed, failed etc. + // Assuming the requirement is strict as written: + const jobModels = await this.jobsCollection.query( + Q.where('worker_name', name), + // Q.where('active', false), // Removed as per "with deleted" implies less filtering + // Q.where('status', JobStatus.IDLE), // Removed + // Q.where('failed', null), // Removed + Q.sortBy('priority', Q.desc), // This sorting might not make sense for already processed/deleted jobs + Q.sortBy('created', Q.asc), + Q.take(count) + ).fetch(); + return jobModels.map(mapJobModelToRawJob); + } + + async updateJob(job: RawJob): Promise { + if (!job.id) { + throw new Error("Job ID must be provided for update."); + } + await database.write(async () => { + const jobRecord = await this.jobsCollection.find(job.id); + await jobRecord.update(record => { + mapRawJobToJobModelFields(job, record); + }); + }); + } + + async removeJob(job: RawJob): Promise { + if (!job.id) { + throw new Error("Job ID must be provided for soft delete."); + } + await database.write(async () => { + const jobRecord = await this.jobsCollection.find(job.id); + await jobRecord.update(record => { + record.isDeleted = true; + record.active = false; // Also mark as inactive + }); + }); + } + + async removeJobPermanently(job: RawJob): Promise { + if (!job.id) { + throw new Error("Job ID must be provided for permanent delete."); + } + await database.write(async () => { + const jobRecord = await this.jobsCollection.find(job.id); + await jobRecord.destroyPermanently(); + }); + } + + async removeJobsByWorkerName(workerName: string): Promise { + const jobsToMark = await this.jobsCollection.query( + Q.where('worker_name', workerName), + Q.where('is_deleted', false) // Only soft-delete those not already soft-deleted + ).fetch(); + + if (jobsToMark.length > 0) { + await database.write(async () => { + for (const job of jobsToMark) { + await job.update(j => { + j.isDeleted = true; + j.active = false; + }); + } + }); + } + } + + async deleteAllJobs(): Promise { + await database.write(async () => { + // Fetch all job records + const allJobs = await this.jobsCollection.query().fetch(); + // Create a batch of delete operations + const deletions = allJobs.map(job => job.prepareDestroyPermanently()); + // Execute the batch operation + await database.batch(...deletions); + }); + } +} diff --git a/src/Queue.ts b/src/Queue.ts index a56c0a2..29e8b79 100644 --- a/src/Queue.ts +++ b/src/Queue.ts @@ -1,9 +1,10 @@ /* eslint-disable no-extra-boolean-cast */ /* eslint-disable @typescript-eslint/no-empty-function */ -import { AppState, NativeModules, Platform } from 'react-native'; +import { AppState, Platform } from 'react-native'; import { FALSE, Job, RawJob, TRUE } from './models/Job'; -import { JobStore } from './models/JobStore'; +import { JobStore } from './models/JobStore'; // This is the interface +import { WatermelonDBJobStore } from './JobStore'; // This is the implementation import { Uuid } from './utils/Uuid'; import { Worker, CANCEL, CancellablePromise } from './Worker'; @@ -142,7 +143,7 @@ export class Queue extends EventEmitter { private static queueInstance: Queue | null; private emitter: EventEmitter = new EventEmitter(); - private jobStore: JobStore; + private jobStore: JobStore; // Keep type as interface for flexibility private workers: { [key: string]: Worker }; private isActive: boolean; @@ -160,7 +161,7 @@ export class Queue extends EventEmitter { private constructor() { super(); - this.jobStore = NativeModules.JobQueue; + this.jobStore = new WatermelonDBJobStore(); this.workers = {}; this.runningJobPromises = {}; this.isActive = false; @@ -189,20 +190,20 @@ export class Queue extends EventEmitter { /** * @param job the job to be deleted */ - removeJob(job: RawJob) { - this.jobStore.removeJob(job); + async removeJob(job: RawJob) { + await this.jobStore.removeJob(job); this.emit("jobDeleted", job) } - removeJobPermanent(job: RawJob) { - this.jobStore.removeJobPermanently(job); + async removeJobPermanent(job: RawJob) { + await this.jobStore.removeJobPermanently(job); this.emit("jobDeleted", job) } /** * @param job the job which should be requeued */ - requeueJob(job: RawJob) { + async requeueJob(job: RawJob) { const newJob = { ...job, failed: '', status: "idle", active: FALSE } as RawJob; - this.jobStore.updateJob(newJob); + await this.jobStore.updateJob(newJob); this.emit("jobRequeued", newJob); this._log(`Job requeued: ${newJob.id}`); // Added this line if (!this.isActive) { @@ -247,10 +248,10 @@ export class Queue extends EventEmitter { * @param name * @param [deleteRelatedJobs=false] removes all queued jobs releated to the worker if set to true */ - removeWorker(name: string, deleteRelatedJobs = false) { + async removeWorker(name: string, deleteRelatedJobs = false) { delete this.workers[name]; if (deleteRelatedJobs) { - this.jobStore.removeJobsByWorkerName(name); + await this.jobStore.removeJobsByWorkerName(name); } } @@ -262,7 +263,7 @@ export class Queue extends EventEmitter { * @param [startQueue=true] if set to false the queue won't start automaticly when adding a job * @returns job id */ - addJob

( + async addJob

( workerName: string, payload: P, options = { attempts: 0, timeout: 0, priority: 0 }, @@ -288,11 +289,12 @@ export class Queue extends EventEmitter { throw new QueueError(`Missing worker with name ${job.workerName}`); } - this.jobStore.addJob(job); + await this.jobStore.addJob(job); this.emit('jobAdded', job); this._log(`Job added: ${job.id}, workerName: ${job.workerName}`); // Added this line if (startQueue && !this.isActive) { - this.start(); + // this.start() is already async + await this.start(); } return id; @@ -332,37 +334,35 @@ export class Queue extends EventEmitter { } } async cancelAllActiveJobs() { - const jobs = await this.jobStore.getJobs() + const jobs = await this.jobStore.getJobs(); // Already async - jobs.forEach((job) => { + for (const job of jobs) { // Use for...of for async operations in loop const newJob = { ...job, ...{ active: FALSE, status: "cancelled" } }; const isRunning = this.runningJobPromises[job.id]; - // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!!Boolean(isRunning)) { this._log(`Cancelling job from cancelAllActiveJobs: ${job.id}`); // Added this line this.cancelJob(job.id, new QueueError(`Job with id ${job.id} cancelled`, "cancelled")); } - this.jobStore.updateJob(newJob as RawJob); + await this.jobStore.updateJob(newJob as RawJob); // Added await this.emit('jobCancelled', newJob as RawJob); - }) + } } - cancelActiveJob(job: RawJob) { + async cancelActiveJob(job: RawJob) { // Made async this._log(`Cancelling active job: ${job.id}`); // Added this line const newJob = { ...job, ...{ active: FALSE, status: "cancelled" } }; const isRunning = this.runningJobPromises[job.id]; - // eslint-disable-next-line @typescript-eslint/no-misused-promises - if (!!isRunning) { + if (!!Boolean(isRunning)) { this.cancelJob(job.id, new QueueError(`Job with id ${job.id} cancelled`, "cancelled")); } - this.jobStore.updateJob(newJob as RawJob); + await this.jobStore.updateJob(newJob as RawJob); // Added await this.emit('jobCancelled', newJob as RawJob); } - private resetActiveJob = (job: RawJob) => { - this.jobStore.updateJob({ ...job, ...{ active: FALSE } }); + private resetActiveJob = async (job: RawJob) => { // Made async + await this.jobStore.updateJob({ ...job, ...{ active: FALSE } }); // Added await }; // Add this method to the Queue class @@ -373,7 +373,8 @@ export class Queue extends EventEmitter { } private async resetActiveJobs() { const activeMarkedJobs = await this.jobStore.getActiveMarkedJobs(); - const resetTasks = activeMarkedJobs.map(this.resetActiveJob); + // .map with async function returns array of Promises + const resetTasks = activeMarkedJobs.map(j => this.resetActiveJob(j)); // Pass the async function directly await Promise.all(resetTasks); } private scheduleQueue() { @@ -489,7 +490,8 @@ export class Queue extends EventEmitter { try { job.status = "processing"; - this.jobStore.updateJob({ ...job, payload: JSON.stringify(payload) }); + // Ensure payload is stringified for updateJob + await this.jobStore.updateJob({ ...job, payload: JSON.stringify(job.payload) }); // Added await this.emit('jobStarted', job); this._log(`Job started: ${job.id}, workerName: ${job.workerName}`); // Added this line @@ -504,8 +506,8 @@ export class Queue extends EventEmitter { worker.triggerSuccess(job); job.status = "finished"; - this.jobStore.updateJob({ ...job, payload: JSON.stringify(payload) }); - this.jobStore.removeJob(rawJob); + await this.jobStore.updateJob({ ...job, payload: JSON.stringify(job.payload) }); // Added await + await this.jobStore.removeJob(rawJob); // This is a soft delete, added await this.emit('jobSucceeded', job); this._log(`Job succeeded: ${job.id}`); // Added this line } catch (err) { @@ -521,7 +523,7 @@ export class Queue extends EventEmitter { const metaData = JSON.stringify({ errors: [...errors, error], failedAttempts }); worker.triggerFailure({ ...job, metaData, failed }, error); const failedJob = { ...rawJob, ...{ active: FALSE, metaData, failed, status: error.code === "cancelled" ? "cancelled" : "failed" } } as RawJob; - this.jobStore.updateJob(failedJob); + await this.jobStore.updateJob(failedJob); // Added await this.emit('jobFailed', failedJob, error); this._log(`Job failed: ${failedJob.id}, error:`, error); // Added this line } finally { diff --git a/src/watermelon/database.ts b/src/watermelon/database.ts new file mode 100644 index 0000000..4678d0b --- /dev/null +++ b/src/watermelon/database.ts @@ -0,0 +1,47 @@ +import { Platform } from 'react-native'; +import { Database, Model } from '@nozbe/watermelondb'; +import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; +import { field, table } from '@nozbe/watermelondb/decorators'; + +import { myAppSchema } from './schema'; + +// Define the JobModel +@table('jobs') +export class JobModel extends Model { + static table = 'jobs'; // WatermelonDB static table name + + @field('worker_name') workerName!: string; + @field('active') active!: boolean; + @field('payload') payload!: string; + @field('meta_data') metaData!: string; + @field('attempts') attempts!: number; + @field('created') created!: string; // Consider number for actual timestamp storage + @field('status') status!: string; + @field('failed') failed?: string | null; // Optional field + @field('timeout') timeout!: number; + @field('priority') priority!: number; + @field('is_deleted') isDeleted!: boolean; +} + +// Configure the adapter +const adapter = new SQLiteAdapter({ + schema: myAppSchema, + jsi: Platform.OS === 'ios', // JSI is only available on iOS in some RN versions, might need to check compatibility or use false for broader compatibility initially + dbName: 'JobQueue', + onSetUpError: error => { + // It's crucial to handle database setup errors + // For example, log to a monitoring service or display an alert + console.error("Failed to set up WatermelonDB:", error); + // Depending on the app, you might want to throw the error, + // or attempt a recovery/reset, or inform the user. + }, +}); + +// Create and export the database instance +export const database = new Database({ + adapter, + modelClasses: [ + JobModel, + // Add other models here if you have more + ], +}); diff --git a/src/watermelon/schema.ts b/src/watermelon/schema.ts new file mode 100644 index 0000000..d808f82 --- /dev/null +++ b/src/watermelon/schema.ts @@ -0,0 +1,24 @@ +import { appSchema, tableSchema } from '@nozbe/watermelondb/Schema'; + +export const myAppSchema = appSchema({ + version: 1, + tables: [ + tableSchema({ + name: 'jobs', + columns: [ + { name: 'id', type: 'string', isOptional: false }, // id is the default primary key and should not be indexed here as per WatermelonDB docs for `id` + { name: 'worker_name', type: 'string', isOptional: false, isIndexed: true }, + { name: 'active', type: 'boolean', isOptional: false }, + { name: 'payload', type: 'string', isOptional: false }, + { name: 'meta_data', type: 'string', isOptional: false }, + { name: 'attempts', type: 'number', isOptional: false }, + { name: 'created', type: 'string', isOptional: false }, // WatermelonDB uses number for dates (timestamps) + { name: 'status', type: 'string', isOptional: false }, + { name: 'failed', type: 'string', isOptional: true }, + { name: 'timeout', type: 'number', isOptional: false }, + { name: 'priority', type: 'number', isOptional: false }, + { name: 'is_deleted', type: 'boolean', isOptional: false }, + ], + }), + ], +}); diff --git a/yarn.lock b/yarn.lock index 6d8598d..476d35e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,15 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.8": version "7.26.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" @@ -56,6 +65,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" + integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== + dependencies: + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" @@ -63,6 +83,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-annotate-as-pure@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz#4345d81a9a46a6486e24d069469f13e60445c05d" + integrity sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow== + dependencies: + "@babel/types" "^7.27.1" + "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz#de0c753b1cd1d9ab55d473c5a5cf7170f0a81880" @@ -87,6 +114,19 @@ "@babel/traverse" "^7.27.0" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281" + integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.27.1" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz#0e41f7d38c2ebe06ebd9cf0e02fb26019c77cd95" @@ -115,6 +155,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-member-expression-to-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" + integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" @@ -139,11 +187,23 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0": version "7.26.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + "@babel/helper-remap-async-to-generator@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" @@ -162,6 +222,15 @@ "@babel/helper-optimise-call-expression" "^7.25.9" "@babel/traverse" "^7.26.5" +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" @@ -170,16 +239,34 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-string-parser@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@babel/helper-validator-option@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" @@ -209,6 +296,13 @@ dependencies: "@babel/types" "^7.27.0" +"@babel/parser@^7.27.1", "@babel/parser@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" + integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== + dependencies: + "@babel/types" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" @@ -263,6 +357,15 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-proposal-decorators@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz#3686f424b2f8b2fee7579aa4df133a4f5244a596" + integrity sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-decorators" "^7.27.1" + "@babel/plugin-proposal-export-default-from@^7.0.0": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz#52702be6ef8367fc8f18b8438278332beeb8f87c" @@ -318,6 +421,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-decorators@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz#ee7dd9590aeebc05f9d4c8c0560007b05979a63d" + integrity sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-dynamic-import@^7.0.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -995,6 +1105,13 @@ pirates "^4.0.6" source-map-support "^0.5.16" +"@babel/runtime@7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" @@ -1011,6 +1128,15 @@ "@babel/parser" "^7.27.0" "@babel/types" "^7.27.0" +"@babel/template@^7.27.1": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.27.0", "@babel/traverse@^7.4.3": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70" @@ -1024,6 +1150,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" + integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559" @@ -1032,6 +1171,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" + integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1324,6 +1471,29 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nozbe/simdjson@3.9.4": + version "3.9.4" + resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-3.9.4.tgz#64bb522c54cd22e40ff4a64d8f8e8e9285b123b6" + integrity sha512-/3oCP8GBpdyeiBMEnuI6S0yl0yekD6Qxfed67hZqU1GIVn3o/vZgE8qANm69THfw7JgHLS9zjx56F/dO3q+koA== + +"@nozbe/sqlite@3.46.0": + version "3.46.0" + resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.46.0.tgz#aeda9df305e2f49ef951409e44544c19a5a3e32d" + integrity sha512-ntt8eNp5hh+axX9+kFb5uwyVE0edyfhiYYr+zHDzzFleGC7Qm+a2wHDWDtmRr5nSfbgomhY1uh30kpsHEIR3Mw== + +"@nozbe/watermelondb@^0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.28.0.tgz#6130ac9017f9019966f892919ada79234c306c70" + integrity sha512-40ttcqPOLCTGnbfCQAXSEo8J4KlH/QQhwTfTb9E21CyX/driMEZueiJSI2rSkHICZrI2vnu52aRubNbI3UAzQQ== + dependencies: + "@babel/runtime" "7.26.0" + "@nozbe/simdjson" "3.9.4" + "@nozbe/sqlite" "3.46.0" + hoist-non-react-statics "^3.3.2" + lokijs "npm:@nozbe/lokijs@1.5.12-wmelon6" + rxjs "^7.8.1" + sql-escape-string "^1.1.0" + "@react-native-community/bob@^0.17.1": version "0.17.1" resolved "https://registry.yarnpkg.com/@react-native-community/bob/-/bob-0.17.1.tgz#72d71747208e6cf534f14ee7e5a0d39562f6d768" @@ -4056,6 +4226,13 @@ highlight.js@^9.17.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -5379,6 +5556,11 @@ logkitty@^0.6.0: dayjs "^1.8.15" yargs "^12.0.5" +"lokijs@npm:@nozbe/lokijs@1.5.12-wmelon6": + version "1.5.12-wmelon6" + resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon6.tgz#e457d934d614d5df80105c86314252a6e614df9b" + integrity sha512-GXsaqY8qTJ6xdCrGyno2t+ON2aj6PrUDdvhbrkxK/0Fp12C4FGvDg1wS+voLU9BANYHEnr7KRWfItDZnQkjoAg== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -6632,7 +6814,7 @@ react-devtools-core@^3.6.1: shell-quote "^1.6.1" ws "^3.3.1" -react-is@^16.13.1, react-is@^16.8.4: +react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7038,6 +7220,13 @@ rxjs@^5.4.3: dependencies: symbol-observable "1.0.1" +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.1.2, safe-array-concat@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" @@ -7474,6 +7663,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sql-escape-string@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/sql-escape-string/-/sql-escape-string-1.1.0.tgz#fe744b8514868c0eb4bfb9e4a989271d40f30eb9" + integrity sha512-/kqO4pLZSLfV0KsBM2xkVh2S3GbjJJone37d7gYwLyP0c+REh3vnmkhQ7VwNrX76igC0OhJWpTg0ukkdef9vvA== + sshpk@^1.7.0: version "1.18.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" @@ -7822,6 +8016,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"