@offlinejs/queue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ import { EntityRecord, MutationOperation, PartialEntity, StorageAdapter, QueuedMutation, QueueProcessingOptions, RetryOptions } from '@offlinejs/types';
2
+
3
+ interface MutationQueueOptions {
4
+ collectionName?: string;
5
+ storage: StorageAdapter;
6
+ }
7
+ interface AddMutationInput<TRecord extends EntityRecord = EntityRecord> {
8
+ base?: TRecord | null;
9
+ collection: string;
10
+ operation: MutationOperation;
11
+ payload?: PartialEntity<TRecord>;
12
+ priority?: number;
13
+ recordId: string;
14
+ }
15
+ declare const defaultRetryOptions: RetryOptions;
16
+ declare const defaultQueueProcessingOptions: QueueProcessingOptions;
17
+ declare class MutationQueue {
18
+ private readonly collectionName;
19
+ private readonly storage;
20
+ private paused;
21
+ constructor(options: MutationQueueOptions);
22
+ add<TRecord extends EntityRecord>(input: AddMutationInput<TRecord>): Promise<QueuedMutation<TRecord>>;
23
+ all(): Promise<QueuedMutation[]>;
24
+ due(options?: QueueProcessingOptions): Promise<QueuedMutation[]>;
25
+ remove(id: string): Promise<void>;
26
+ markAttempt(id: string, status?: QueuedMutation["status"]): Promise<QueuedMutation | null>;
27
+ pause(): void;
28
+ resume(): void;
29
+ isPaused(): boolean;
30
+ clear(): Promise<void>;
31
+ }
32
+ declare const createMutationQueue: (options: MutationQueueOptions) => MutationQueue;
33
+
34
+ export { type AddMutationInput, MutationQueue, type MutationQueueOptions, createMutationQueue, defaultQueueProcessingOptions, defaultRetryOptions };
package/dist/index.js ADDED
@@ -0,0 +1,100 @@
1
+ import { now, createId, backoffDelay } from '@offlinejs/utils';
2
+
3
+ // src/index.ts
4
+ var defaultRetryOptions = {
5
+ baseDelayMs: 500,
6
+ factor: 2,
7
+ jitter: true,
8
+ maxAttempts: 5,
9
+ maxDelayMs: 3e4
10
+ };
11
+ var defaultQueueProcessingOptions = {
12
+ batchSize: 25,
13
+ retry: defaultRetryOptions
14
+ };
15
+ var MutationQueue = class {
16
+ collectionName;
17
+ storage;
18
+ paused = false;
19
+ constructor(options) {
20
+ this.collectionName = options.collectionName ?? "__offline_queue";
21
+ this.storage = options.storage;
22
+ }
23
+ async add(input) {
24
+ const mutation = {
25
+ id: createId(),
26
+ collection: input.collection,
27
+ operation: input.operation,
28
+ recordId: input.recordId,
29
+ createdAt: now(),
30
+ priority: input.priority ?? 0,
31
+ retries: 0,
32
+ status: "pending"
33
+ };
34
+ if (input.payload !== void 0) {
35
+ mutation.payload = input.payload;
36
+ }
37
+ if (input.base !== void 0) {
38
+ mutation.base = input.base;
39
+ }
40
+ await this.storage.set(this.collectionName, mutation);
41
+ return mutation;
42
+ }
43
+ async all() {
44
+ const mutations = await this.storage.find(this.collectionName);
45
+ return mutations.sort((left, right) => {
46
+ if (left.priority !== right.priority) {
47
+ return right.priority - left.priority;
48
+ }
49
+ return left.createdAt - right.createdAt;
50
+ });
51
+ }
52
+ async due(options = defaultQueueProcessingOptions) {
53
+ if (this.paused) {
54
+ return [];
55
+ }
56
+ const timestamp = now();
57
+ const mutations = await this.all();
58
+ return mutations.filter((mutation) => mutation.status !== "processing").filter((mutation) => mutation.retries < options.retry.maxAttempts).filter((mutation) => {
59
+ if (!mutation.lastAttemptAt) {
60
+ return true;
61
+ }
62
+ const delay = backoffDelay(mutation.retries, options.retry);
63
+ return mutation.lastAttemptAt + delay <= timestamp;
64
+ }).slice(0, options.batchSize);
65
+ }
66
+ async remove(id) {
67
+ await this.storage.delete(this.collectionName, id);
68
+ }
69
+ async markAttempt(id, status = "failed") {
70
+ const mutation = await this.storage.get(this.collectionName, id);
71
+ if (!mutation) {
72
+ return null;
73
+ }
74
+ const updated = {
75
+ ...mutation,
76
+ lastAttemptAt: now(),
77
+ retries: mutation.retries + 1,
78
+ status
79
+ };
80
+ await this.storage.set(this.collectionName, updated);
81
+ return updated;
82
+ }
83
+ pause() {
84
+ this.paused = true;
85
+ }
86
+ resume() {
87
+ this.paused = false;
88
+ }
89
+ isPaused() {
90
+ return this.paused;
91
+ }
92
+ async clear() {
93
+ await this.storage.clear(this.collectionName);
94
+ }
95
+ };
96
+ var createMutationQueue = (options) => new MutationQueue(options);
97
+
98
+ export { MutationQueue, createMutationQueue, defaultQueueProcessingOptions, defaultRetryOptions };
99
+ //# sourceMappingURL=index.js.map
100
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n EntityRecord,\n MutationOperation,\n PartialEntity,\n QueueProcessingOptions,\n QueuedMutation,\n RetryOptions,\n StorageAdapter\n} from \"@offlinejs/types\";\nimport { backoffDelay, createId, now } from \"@offlinejs/utils\";\n\nexport interface MutationQueueOptions {\n collectionName?: string;\n storage: StorageAdapter;\n}\n\nexport interface AddMutationInput<TRecord extends EntityRecord = EntityRecord> {\n base?: TRecord | null;\n collection: string;\n operation: MutationOperation;\n payload?: PartialEntity<TRecord>;\n priority?: number;\n recordId: string;\n}\n\nexport const defaultRetryOptions: RetryOptions = {\n baseDelayMs: 500,\n factor: 2,\n jitter: true,\n maxAttempts: 5,\n maxDelayMs: 30_000\n};\n\nexport const defaultQueueProcessingOptions: QueueProcessingOptions = {\n batchSize: 25,\n retry: defaultRetryOptions\n};\n\nexport class MutationQueue {\n private readonly collectionName: string;\n private readonly storage: StorageAdapter;\n private paused = false;\n\n constructor(options: MutationQueueOptions) {\n this.collectionName = options.collectionName ?? \"__offline_queue\";\n this.storage = options.storage;\n }\n\n async add<TRecord extends EntityRecord>(\n input: AddMutationInput<TRecord>\n ): Promise<QueuedMutation<TRecord>> {\n const mutation: QueuedMutation<TRecord> = {\n id: createId(),\n collection: input.collection,\n operation: input.operation,\n recordId: input.recordId,\n createdAt: now(),\n priority: input.priority ?? 0,\n retries: 0,\n status: \"pending\"\n };\n\n if (input.payload !== undefined) {\n mutation.payload = input.payload;\n }\n\n if (input.base !== undefined) {\n mutation.base = input.base;\n }\n\n await this.storage.set(this.collectionName, mutation);\n return mutation;\n }\n\n async all(): Promise<QueuedMutation[]> {\n const mutations = await this.storage.find<QueuedMutation>(this.collectionName);\n\n return mutations.sort((left, right) => {\n if (left.priority !== right.priority) {\n return right.priority - left.priority;\n }\n\n return left.createdAt - right.createdAt;\n });\n }\n\n async due(\n options: QueueProcessingOptions = defaultQueueProcessingOptions\n ): Promise<QueuedMutation[]> {\n if (this.paused) {\n return [];\n }\n\n const timestamp = now();\n const mutations = await this.all();\n\n return mutations\n .filter((mutation) => mutation.status !== \"processing\")\n .filter((mutation) => mutation.retries < options.retry.maxAttempts)\n .filter((mutation) => {\n if (!mutation.lastAttemptAt) {\n return true;\n }\n\n const delay = backoffDelay(mutation.retries, options.retry);\n return mutation.lastAttemptAt + delay <= timestamp;\n })\n .slice(0, options.batchSize);\n }\n\n async remove(id: string): Promise<void> {\n await this.storage.delete(this.collectionName, id);\n }\n\n async markAttempt(\n id: string,\n status: QueuedMutation[\"status\"] = \"failed\"\n ): Promise<QueuedMutation | null> {\n const mutation = await this.storage.get<QueuedMutation>(this.collectionName, id);\n\n if (!mutation) {\n return null;\n }\n\n const updated: QueuedMutation = {\n ...mutation,\n lastAttemptAt: now(),\n retries: mutation.retries + 1,\n status\n };\n\n await this.storage.set(this.collectionName, updated);\n return updated;\n }\n\n pause(): void {\n this.paused = true;\n }\n\n resume(): void {\n this.paused = false;\n }\n\n isPaused(): boolean {\n return this.paused;\n }\n\n async clear(): Promise<void> {\n await this.storage.clear(this.collectionName);\n }\n}\n\nexport const createMutationQueue = (options: MutationQueueOptions): MutationQueue =>\n new MutationQueue(options);\n"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@offlinejs/queue",
3
+ "version": "0.1.0",
4
+ "description": "Persistent mutation queue with priority, retries, pause/resume, and batching.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "dependencies": {
17
+ "@offlinejs/utils": "0.1.0",
18
+ "@offlinejs/types": "0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsup": "latest",
22
+ "typescript": "latest"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "typecheck": "tsc --noEmit"
31
+ }
32
+ }