@koolbase/react-native 9.2.0 → 10.0.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.
- package/CHANGELOG.md +1342 -0
- package/README.md +403 -568
- package/dist/{auth-storage.d.ts → cjs/auth-storage.d.ts} +1 -1
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +125 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform.d.ts +2 -0
- package/dist/cjs/platform.js +43 -0
- package/dist/esm/auth-storage.d.ts +26 -0
- package/dist/esm/auth-storage.js +100 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +106 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/platform.d.ts +2 -0
- package/dist/esm/platform.js +37 -0
- package/package.json +30 -30
- package/dist/analytics.d.ts +0 -24
- package/dist/analytics.js +0 -114
- package/dist/apple-auth.d.ts +0 -22
- package/dist/apple-auth.js +0 -74
- package/dist/auth-errors.d.ts +0 -117
- package/dist/auth-errors.js +0 -250
- package/dist/auth.d.ts +0 -213
- package/dist/auth.js +0 -810
- package/dist/cache-store.d.ts +0 -50
- package/dist/cache-store.js +0 -197
- package/dist/code-push.d.ts +0 -59
- package/dist/code-push.js +0 -255
- package/dist/conflict.d.ts +0 -80
- package/dist/conflict.js +0 -84
- package/dist/database-errors.d.ts +0 -101
- package/dist/database-errors.js +0 -200
- package/dist/database.d.ts +0 -298
- package/dist/database.js +0 -852
- package/dist/device-id.d.ts +0 -1
- package/dist/device-id.js +0 -60
- package/dist/device-metadata.d.ts +0 -36
- package/dist/device-metadata.js +0 -102
- package/dist/errors.d.ts +0 -64
- package/dist/errors.js +0 -85
- package/dist/flags.d.ts +0 -15
- package/dist/flags.js +0 -76
- package/dist/function-errors.d.ts +0 -51
- package/dist/function-errors.js +0 -103
- package/dist/functions.d.ts +0 -15
- package/dist/functions.js +0 -83
- package/dist/index.d.ts +0 -49
- package/dist/index.js +0 -204
- package/dist/logic-engine.d.ts +0 -17
- package/dist/logic-engine.js +0 -193
- package/dist/messaging.d.ts +0 -13
- package/dist/messaging.js +0 -36
- package/dist/offline-state.d.ts +0 -97
- package/dist/offline-state.js +0 -200
- package/dist/pending-write.d.ts +0 -47
- package/dist/pending-write.js +0 -22
- package/dist/realtime.d.ts +0 -44
- package/dist/realtime.js +0 -195
- package/dist/record.d.ts +0 -2
- package/dist/record.js +0 -23
- package/dist/storage-errors.d.ts +0 -163
- package/dist/storage-errors.js +0 -253
- package/dist/storage.d.ts +0 -198
- package/dist/storage.js +0 -451
- package/dist/sync-engine.d.ts +0 -30
- package/dist/sync-engine.js +0 -290
- package/dist/types.d.ts +0 -487
- package/dist/types.js +0 -40
- /package/dist/{auth-storage.js → cjs/auth-storage.js} +0 -0
package/dist/cache-store.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { KoolbaseRecord, LegacyPendingWrite, QueryResult } from './types';
|
|
2
|
-
export declare function hashQuery(collection: string, options: Record<string, unknown>): string;
|
|
3
|
-
export declare function getCached(userId: string, collection: string, queryHash: string): Promise<QueryResult | null>;
|
|
4
|
-
export declare function setCached(userId: string, collection: string, queryHash: string, result: QueryResult): Promise<void>;
|
|
5
|
-
export declare function invalidateCache(userId: string, collection: string): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Drops everything cached for a user.
|
|
8
|
-
*
|
|
9
|
-
* Deliberately spares the write queue. The prefix covers every key for this
|
|
10
|
-
* user, and the queue lives under one of them — so clearing the cache used to
|
|
11
|
-
* delete offline writes the user believes are saved, silently and
|
|
12
|
-
* irrecoverably. A cache is what can be refetched; queued writes are not that.
|
|
13
|
-
*/
|
|
14
|
-
export declare function clearUserCache(userId: string): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* A record as the SDK last saw it, with the revision it was read at.
|
|
17
|
-
*
|
|
18
|
-
* Separate from the query cache, which answers "what did this query return".
|
|
19
|
-
* This answers "what is the latest copy of this record" — and an offline
|
|
20
|
-
* mutation composes against the second. Scanning query blobs for one would mean
|
|
21
|
-
* the same record appearing in several snapshots at different revisions, with no
|
|
22
|
-
* principled way to choose.
|
|
23
|
-
*
|
|
24
|
-
* Keyed with `record` where a collection name would sit, so invalidateCache —
|
|
25
|
-
* which scopes to a collection and runs after every write — cannot reach these.
|
|
26
|
-
* A user's own edit must not remove the baseline they need for the next one.
|
|
27
|
-
*/
|
|
28
|
-
export interface CachedRecord {
|
|
29
|
-
collection: string;
|
|
30
|
-
data: Record<string, unknown>;
|
|
31
|
-
revision?: number;
|
|
32
|
-
cachedAt: string;
|
|
33
|
-
}
|
|
34
|
-
export declare function getCachedRecord(userId: string, recordId: string): Promise<CachedRecord | null>;
|
|
35
|
-
/**
|
|
36
|
-
* Stores a record, refusing to move it backwards.
|
|
37
|
-
*
|
|
38
|
-
* Query responses can arrive out of order — a slow request from an earlier
|
|
39
|
-
* screen resolving after a fresh one — and an older copy overwriting a newer
|
|
40
|
-
* would compose the next mutation against a stale revision, producing a conflict
|
|
41
|
-
* the user never caused. False conflicts teach people to force-overwrite, which
|
|
42
|
-
* is worse than none.
|
|
43
|
-
*/
|
|
44
|
-
export declare function cacheRecord(userId: string, collection: string, recordId: string, data: Record<string, unknown>, revision?: number): Promise<void>;
|
|
45
|
-
export declare function removeCachedRecord(userId: string, recordId: string): Promise<void>;
|
|
46
|
-
export declare function getWriteQueue(userId: string): Promise<LegacyPendingWrite[]>;
|
|
47
|
-
export declare function addToWriteQueue(userId: string, write: Omit<LegacyPendingWrite, 'retries' | 'createdAt'>): Promise<void>;
|
|
48
|
-
export declare function removeFromWriteQueue(userId: string, writeId: string): Promise<void>;
|
|
49
|
-
export declare function incrementWriteRetry(userId: string, writeId: string): Promise<void>;
|
|
50
|
-
export declare function optimisticallyInsert(userId: string, collection: string, record: KoolbaseRecord): Promise<void>;
|
package/dist/cache-store.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.hashQuery = hashQuery;
|
|
7
|
-
exports.getCached = getCached;
|
|
8
|
-
exports.setCached = setCached;
|
|
9
|
-
exports.invalidateCache = invalidateCache;
|
|
10
|
-
exports.clearUserCache = clearUserCache;
|
|
11
|
-
exports.getCachedRecord = getCachedRecord;
|
|
12
|
-
exports.cacheRecord = cacheRecord;
|
|
13
|
-
exports.removeCachedRecord = removeCachedRecord;
|
|
14
|
-
exports.getWriteQueue = getWriteQueue;
|
|
15
|
-
exports.addToWriteQueue = addToWriteQueue;
|
|
16
|
-
exports.removeFromWriteQueue = removeFromWriteQueue;
|
|
17
|
-
exports.incrementWriteRetry = incrementWriteRetry;
|
|
18
|
-
exports.optimisticallyInsert = optimisticallyInsert;
|
|
19
|
-
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
20
|
-
const CACHE_VERSION = 'v1';
|
|
21
|
-
function cacheKey(userId, collection, queryHash) {
|
|
22
|
-
return `koolbase:${CACHE_VERSION}:${userId}:${collection}:${queryHash}`;
|
|
23
|
-
}
|
|
24
|
-
function writeQueueKey(userId) {
|
|
25
|
-
return `koolbase:${CACHE_VERSION}:${userId}:write_queue`;
|
|
26
|
-
}
|
|
27
|
-
function hashQuery(collection, options) {
|
|
28
|
-
return `${collection}:${JSON.stringify(options)}`;
|
|
29
|
-
}
|
|
30
|
-
// ─── Cache ──────────────────────────────────────────────────────────────────
|
|
31
|
-
async function getCached(userId, collection, queryHash) {
|
|
32
|
-
try {
|
|
33
|
-
const raw = await async_storage_1.default.getItem(cacheKey(userId, collection, queryHash));
|
|
34
|
-
if (!raw)
|
|
35
|
-
return null;
|
|
36
|
-
return JSON.parse(raw);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function setCached(userId, collection, queryHash, result) {
|
|
43
|
-
try {
|
|
44
|
-
await async_storage_1.default.setItem(cacheKey(userId, collection, queryHash), JSON.stringify(result));
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// ignore storage errors
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async function invalidateCache(userId, collection) {
|
|
51
|
-
try {
|
|
52
|
-
const keys = await async_storage_1.default.getAllKeys();
|
|
53
|
-
const prefix = `koolbase:${CACHE_VERSION}:${userId}:${collection}:`;
|
|
54
|
-
const toDelete = keys.filter(k => k.startsWith(prefix));
|
|
55
|
-
for (const key of toDelete) {
|
|
56
|
-
await async_storage_1.default.removeItem(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// ignore
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Drops everything cached for a user.
|
|
65
|
-
*
|
|
66
|
-
* Deliberately spares the write queue. The prefix covers every key for this
|
|
67
|
-
* user, and the queue lives under one of them — so clearing the cache used to
|
|
68
|
-
* delete offline writes the user believes are saved, silently and
|
|
69
|
-
* irrecoverably. A cache is what can be refetched; queued writes are not that.
|
|
70
|
-
*/
|
|
71
|
-
async function clearUserCache(userId) {
|
|
72
|
-
try {
|
|
73
|
-
const keys = await async_storage_1.default.getAllKeys();
|
|
74
|
-
const prefix = `koolbase:${CACHE_VERSION}:${userId}:`;
|
|
75
|
-
const queueKey = writeQueueKey(userId);
|
|
76
|
-
const toDelete = keys.filter(k => k.startsWith(prefix) && k !== queueKey);
|
|
77
|
-
for (const key of toDelete) {
|
|
78
|
-
await async_storage_1.default.removeItem(key);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// ignore
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function recordCacheKey(userId, recordId) {
|
|
86
|
-
return `koolbase:${CACHE_VERSION}:${userId}:record:${recordId}`;
|
|
87
|
-
}
|
|
88
|
-
async function getCachedRecord(userId, recordId) {
|
|
89
|
-
try {
|
|
90
|
-
const raw = await async_storage_1.default.getItem(recordCacheKey(userId, recordId));
|
|
91
|
-
return raw ? JSON.parse(raw) : null;
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Stores a record, refusing to move it backwards.
|
|
99
|
-
*
|
|
100
|
-
* Query responses can arrive out of order — a slow request from an earlier
|
|
101
|
-
* screen resolving after a fresh one — and an older copy overwriting a newer
|
|
102
|
-
* would compose the next mutation against a stale revision, producing a conflict
|
|
103
|
-
* the user never caused. False conflicts teach people to force-overwrite, which
|
|
104
|
-
* is worse than none.
|
|
105
|
-
*/
|
|
106
|
-
async function cacheRecord(userId, collection, recordId, data, revision) {
|
|
107
|
-
try {
|
|
108
|
-
const existing = await getCachedRecord(userId, recordId);
|
|
109
|
-
if (existing?.revision !== undefined &&
|
|
110
|
-
revision !== undefined &&
|
|
111
|
-
revision < existing.revision) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const entry = {
|
|
115
|
-
collection,
|
|
116
|
-
data,
|
|
117
|
-
revision,
|
|
118
|
-
cachedAt: new Date().toISOString(),
|
|
119
|
-
};
|
|
120
|
-
await async_storage_1.default.setItem(recordCacheKey(userId, recordId), JSON.stringify(entry));
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// ignore
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function removeCachedRecord(userId, recordId) {
|
|
127
|
-
try {
|
|
128
|
-
await async_storage_1.default.removeItem(recordCacheKey(userId, recordId));
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// ignore
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// ─── Write Queue ────────────────────────────────────────────────────────────
|
|
135
|
-
async function getWriteQueue(userId) {
|
|
136
|
-
try {
|
|
137
|
-
const raw = await async_storage_1.default.getItem(writeQueueKey(userId));
|
|
138
|
-
if (!raw)
|
|
139
|
-
return [];
|
|
140
|
-
return JSON.parse(raw);
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async function addToWriteQueue(userId, write) {
|
|
147
|
-
try {
|
|
148
|
-
const queue = await getWriteQueue(userId);
|
|
149
|
-
queue.push({ ...write, retries: 0, createdAt: new Date().toISOString() });
|
|
150
|
-
await async_storage_1.default.setItem(writeQueueKey(userId), JSON.stringify(queue));
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
// ignore
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
async function removeFromWriteQueue(userId, writeId) {
|
|
157
|
-
try {
|
|
158
|
-
const queue = await getWriteQueue(userId);
|
|
159
|
-
const updated = queue.filter(w => w.id !== writeId);
|
|
160
|
-
await async_storage_1.default.setItem(writeQueueKey(userId), JSON.stringify(updated));
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
// ignore
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
async function incrementWriteRetry(userId, writeId) {
|
|
167
|
-
try {
|
|
168
|
-
const queue = await getWriteQueue(userId);
|
|
169
|
-
const updated = queue.map(w => w.id === writeId ? { ...w, retries: w.retries + 1 } : w);
|
|
170
|
-
// Drop writes that have exceeded 3 retries
|
|
171
|
-
const filtered = updated.filter(w => w.retries <= 3);
|
|
172
|
-
await async_storage_1.default.setItem(writeQueueKey(userId), JSON.stringify(filtered));
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
// ignore
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// ─── Optimistic cache update ─────────────────────────────────────────────────
|
|
179
|
-
async function optimisticallyInsert(userId, collection, record) {
|
|
180
|
-
try {
|
|
181
|
-
const keys = await async_storage_1.default.getAllKeys();
|
|
182
|
-
const prefix = `koolbase:${CACHE_VERSION}:${userId}:${collection}:`;
|
|
183
|
-
const collectionKeys = keys.filter(k => k.startsWith(prefix));
|
|
184
|
-
for (const key of collectionKeys) {
|
|
185
|
-
const raw = await async_storage_1.default.getItem(key);
|
|
186
|
-
if (!raw)
|
|
187
|
-
continue;
|
|
188
|
-
const cached = JSON.parse(raw);
|
|
189
|
-
cached.records = [record, ...cached.records];
|
|
190
|
-
cached.total = cached.total + 1;
|
|
191
|
-
await async_storage_1.default.setItem(key, JSON.stringify(cached));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
// ignore
|
|
196
|
-
}
|
|
197
|
-
}
|
package/dist/code-push.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { KoolbaseConfig } from './types';
|
|
2
|
-
export interface BundlePayload {
|
|
3
|
-
config: Record<string, unknown>;
|
|
4
|
-
flags: Record<string, boolean>;
|
|
5
|
-
directives: Record<string, unknown>;
|
|
6
|
-
assets: {
|
|
7
|
-
images: string[];
|
|
8
|
-
json: string[];
|
|
9
|
-
fonts: string[];
|
|
10
|
-
};
|
|
11
|
-
screens?: Record<string, string>;
|
|
12
|
-
flows?: Record<string, unknown>;
|
|
13
|
-
}
|
|
14
|
-
export interface BundleManifest {
|
|
15
|
-
bundle_id: string;
|
|
16
|
-
app_id: string;
|
|
17
|
-
version: number;
|
|
18
|
-
base_app_version: string;
|
|
19
|
-
max_app_version: string;
|
|
20
|
-
platform: string;
|
|
21
|
-
channel: string;
|
|
22
|
-
checksum: string;
|
|
23
|
-
signature: string;
|
|
24
|
-
size_bytes: number;
|
|
25
|
-
payload: BundlePayload;
|
|
26
|
-
mandatory?: boolean;
|
|
27
|
-
}
|
|
28
|
-
export declare class KoolbaseCodePush {
|
|
29
|
-
private config;
|
|
30
|
-
private channel;
|
|
31
|
-
private activeManifest;
|
|
32
|
-
private mandatoryPending;
|
|
33
|
-
constructor(config: KoolbaseConfig, channel?: string);
|
|
34
|
-
init(options: {
|
|
35
|
-
appVersion: string;
|
|
36
|
-
platform: string;
|
|
37
|
-
deviceId: string;
|
|
38
|
-
}): Promise<void>;
|
|
39
|
-
private promotePendingIfAvailable;
|
|
40
|
-
private loadActive;
|
|
41
|
-
private checkInBackground;
|
|
42
|
-
private download;
|
|
43
|
-
private handleRollback;
|
|
44
|
-
get hasActiveBundle(): boolean;
|
|
45
|
-
/**
|
|
46
|
-
* True when a mandatory bundle has been staged this session and is awaiting
|
|
47
|
-
* application (it activates on the next cold launch). Gate your UI on this to
|
|
48
|
-
* prompt the user to restart so the required update takes effect.
|
|
49
|
-
*/
|
|
50
|
-
get hasMandatoryUpdate(): boolean;
|
|
51
|
-
get manifest(): BundleManifest | null;
|
|
52
|
-
getBundleConfig(key: string): unknown | undefined;
|
|
53
|
-
getBundleFlag(key: string): boolean | undefined;
|
|
54
|
-
getBundleDirective(key: string): unknown | undefined;
|
|
55
|
-
private directiveHandlers;
|
|
56
|
-
onDirective(key: string, handler: (value: unknown) => void): void;
|
|
57
|
-
applyDirectives(): void;
|
|
58
|
-
clearBundle(): Promise<void>;
|
|
59
|
-
}
|
package/dist/code-push.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.KoolbaseCodePush = void 0;
|
|
40
|
-
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
41
|
-
const STORAGE_KEY_ACTIVE = 'koolbase:codepush:active';
|
|
42
|
-
const STORAGE_KEY_PENDING = 'koolbase:codepush:pending';
|
|
43
|
-
// ─── Pure JS sha256 for checksum verification ────────────────────────────────
|
|
44
|
-
async function sha256Hex(data) {
|
|
45
|
-
// Use SubtleCrypto if available (modern RN / Hermes)
|
|
46
|
-
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
47
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
48
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
49
|
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
50
|
-
}
|
|
51
|
-
// Fallback — skip verification (dev only)
|
|
52
|
-
return '';
|
|
53
|
-
}
|
|
54
|
-
// ─── Zip manifest extraction ─────────────────────────────────────────────────
|
|
55
|
-
async function extractManifestFromZip(data) {
|
|
56
|
-
try {
|
|
57
|
-
// Dynamically import JSZip — must be installed as a dependency
|
|
58
|
-
const JSZip = (await Promise.resolve().then(() => __importStar(require('jszip')))).default;
|
|
59
|
-
const zip = await JSZip.loadAsync(data);
|
|
60
|
-
const manifestFile = zip.file('manifest.json');
|
|
61
|
-
if (!manifestFile)
|
|
62
|
-
return null;
|
|
63
|
-
const json = await manifestFile.async('string');
|
|
64
|
-
return JSON.parse(json);
|
|
65
|
-
}
|
|
66
|
-
catch (e) {
|
|
67
|
-
console.warn('[KoolbaseCodePush] manifest extraction failed:', e);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// ─── KoolbaseCodePush ────────────────────────────────────────────────────────
|
|
72
|
-
class KoolbaseCodePush {
|
|
73
|
-
constructor(config, channel = 'stable') {
|
|
74
|
-
this.activeManifest = null;
|
|
75
|
-
this.mandatoryPending = false;
|
|
76
|
-
// ─── Directive handling ──────────────────────────────────────────────────
|
|
77
|
-
this.directiveHandlers = new Map();
|
|
78
|
-
this.config = config;
|
|
79
|
-
this.channel = channel;
|
|
80
|
-
}
|
|
81
|
-
// Called inside Koolbase.initialize() — loads cached bundle then checks in background
|
|
82
|
-
async init(options) {
|
|
83
|
-
// Step 1 — promote pending → active if available
|
|
84
|
-
await this.promotePendingIfAvailable();
|
|
85
|
-
// Step 2 — load active manifest into memory
|
|
86
|
-
this.activeManifest = await this.loadActive();
|
|
87
|
-
if (this.activeManifest) {
|
|
88
|
-
console.log(`[KoolbaseCodePush] bundle v${this.activeManifest.version} active`);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
console.log('[KoolbaseCodePush] no active bundle — using app defaults');
|
|
92
|
-
}
|
|
93
|
-
// Step 3 — check for updates in background (non-blocking)
|
|
94
|
-
this.checkInBackground(options);
|
|
95
|
-
}
|
|
96
|
-
async promotePendingIfAvailable() {
|
|
97
|
-
try {
|
|
98
|
-
const pending = await async_storage_1.default.getItem(STORAGE_KEY_PENDING);
|
|
99
|
-
if (!pending)
|
|
100
|
-
return;
|
|
101
|
-
// Archive current active → just overwrite, we keep one version
|
|
102
|
-
await async_storage_1.default.setItem(STORAGE_KEY_ACTIVE, pending);
|
|
103
|
-
await async_storage_1.default.removeItem(STORAGE_KEY_PENDING);
|
|
104
|
-
console.log('[KoolbaseCodePush] promoted pending bundle to active');
|
|
105
|
-
}
|
|
106
|
-
catch (e) {
|
|
107
|
-
console.warn('[KoolbaseCodePush] promotion failed:', e);
|
|
108
|
-
// One re-check will be triggered naturally by background check
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async loadActive() {
|
|
112
|
-
try {
|
|
113
|
-
const stored = await async_storage_1.default.getItem(STORAGE_KEY_ACTIVE);
|
|
114
|
-
if (!stored)
|
|
115
|
-
return null;
|
|
116
|
-
return JSON.parse(stored);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
checkInBackground(options) {
|
|
123
|
-
// Fire and forget
|
|
124
|
-
(async () => {
|
|
125
|
-
try {
|
|
126
|
-
const currentVersion = this.activeManifest?.version ?? 0;
|
|
127
|
-
const res = await fetch(`${this.config.baseUrl}/v1/code-push/check` +
|
|
128
|
-
`?app_version=${options.appVersion}` +
|
|
129
|
-
`&platform=${options.platform}` +
|
|
130
|
-
`&channel=${this.channel}` +
|
|
131
|
-
`&device_id=${options.deviceId}` +
|
|
132
|
-
`¤t_bundle=${currentVersion}`, { headers: { 'x-api-key': this.config.publicKey } });
|
|
133
|
-
if (!res.ok)
|
|
134
|
-
return;
|
|
135
|
-
const body = await res.json();
|
|
136
|
-
switch (body.status) {
|
|
137
|
-
case 'update_available':
|
|
138
|
-
await this.download(body.bundle);
|
|
139
|
-
break;
|
|
140
|
-
case 'rollback':
|
|
141
|
-
await this.handleRollback(body.revert_to ?? '0');
|
|
142
|
-
break;
|
|
143
|
-
case 'no_update':
|
|
144
|
-
console.log('[KoolbaseCodePush] no update available');
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch (e) {
|
|
149
|
-
// Server unreachable — continue silently
|
|
150
|
-
console.log('[KoolbaseCodePush] check failed silently:', e);
|
|
151
|
-
}
|
|
152
|
-
})();
|
|
153
|
-
}
|
|
154
|
-
async download(ref) {
|
|
155
|
-
try {
|
|
156
|
-
console.log(`[KoolbaseCodePush] downloading bundle v${ref.version}...`);
|
|
157
|
-
const res = await fetch(ref.download_url);
|
|
158
|
-
if (!res.ok) {
|
|
159
|
-
console.warn('[KoolbaseCodePush] download failed:', res.status);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const data = await res.arrayBuffer();
|
|
163
|
-
console.log(`[KoolbaseCodePush] downloaded ${data.byteLength} bytes`);
|
|
164
|
-
// Verify checksum
|
|
165
|
-
if (ref.checksum !== 'placeholder' && ref.checksum !== 'pending') {
|
|
166
|
-
const actual = `sha256:${await sha256Hex(data)}`;
|
|
167
|
-
if (actual !== ref.checksum && actual !== 'sha256:') {
|
|
168
|
-
console.warn('[KoolbaseCodePush] checksum mismatch — bundle rejected');
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
console.log('[KoolbaseCodePush] checksum verified');
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
console.log('[KoolbaseCodePush] checksum skipped (dev mode)');
|
|
175
|
-
}
|
|
176
|
-
// Extract manifest
|
|
177
|
-
const manifest = await extractManifestFromZip(data);
|
|
178
|
-
if (!manifest) {
|
|
179
|
-
console.warn('[KoolbaseCodePush] manifest extraction failed');
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
// Carry the mandatory flag from the check response onto the staged manifest
|
|
183
|
-
manifest.mandatory = ref.mandatory;
|
|
184
|
-
// Store as pending — activates on next launch
|
|
185
|
-
await async_storage_1.default.setItem(STORAGE_KEY_PENDING, JSON.stringify(manifest));
|
|
186
|
-
console.log(`[KoolbaseCodePush] bundle v${manifest.version} ready for next launch`);
|
|
187
|
-
if (ref.mandatory) {
|
|
188
|
-
this.mandatoryPending = true;
|
|
189
|
-
console.log('[KoolbaseCodePush] staged bundle is mandatory — notifying app');
|
|
190
|
-
try {
|
|
191
|
-
this.config.onMandatoryUpdate?.({ version: manifest.version, bundleId: manifest.bundle_id });
|
|
192
|
-
}
|
|
193
|
-
catch (cbErr) {
|
|
194
|
-
console.warn('[KoolbaseCodePush] onMandatoryUpdate handler threw:', cbErr);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
catch (e) {
|
|
199
|
-
console.warn('[KoolbaseCodePush] download error:', e);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async handleRollback(revertTo) {
|
|
203
|
-
console.log(`[KoolbaseCodePush] rollback to v${revertTo}`);
|
|
204
|
-
await async_storage_1.default.removeItem(STORAGE_KEY_ACTIVE);
|
|
205
|
-
await async_storage_1.default.removeItem(STORAGE_KEY_PENDING);
|
|
206
|
-
this.activeManifest = null;
|
|
207
|
-
console.log('[KoolbaseCodePush] reverted to app defaults');
|
|
208
|
-
}
|
|
209
|
-
// ─── Public accessors ────────────────────────────────────────────────────
|
|
210
|
-
get hasActiveBundle() {
|
|
211
|
-
return this.activeManifest !== null;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* True when a mandatory bundle has been staged this session and is awaiting
|
|
215
|
-
* application (it activates on the next cold launch). Gate your UI on this to
|
|
216
|
-
* prompt the user to restart so the required update takes effect.
|
|
217
|
-
*/
|
|
218
|
-
get hasMandatoryUpdate() {
|
|
219
|
-
return this.mandatoryPending;
|
|
220
|
-
}
|
|
221
|
-
get manifest() {
|
|
222
|
-
return this.activeManifest;
|
|
223
|
-
}
|
|
224
|
-
getBundleConfig(key) {
|
|
225
|
-
return this.activeManifest?.payload.config[key];
|
|
226
|
-
}
|
|
227
|
-
getBundleFlag(key) {
|
|
228
|
-
return this.activeManifest?.payload.flags[key];
|
|
229
|
-
}
|
|
230
|
-
getBundleDirective(key) {
|
|
231
|
-
return this.activeManifest?.payload.directives[key];
|
|
232
|
-
}
|
|
233
|
-
onDirective(key, handler) {
|
|
234
|
-
this.directiveHandlers.set(key, handler);
|
|
235
|
-
}
|
|
236
|
-
applyDirectives() {
|
|
237
|
-
if (!this.activeManifest?.payload.directives)
|
|
238
|
-
return;
|
|
239
|
-
for (const [key, value] of Object.entries(this.activeManifest.payload.directives)) {
|
|
240
|
-
const handler = this.directiveHandlers.get(key);
|
|
241
|
-
if (handler) {
|
|
242
|
-
console.log(`[KoolbaseCodePush] directive: ${key} = ${value}`);
|
|
243
|
-
handler(value);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// ─── Cache management ────────────────────────────────────────────────────
|
|
248
|
-
async clearBundle() {
|
|
249
|
-
await async_storage_1.default.removeItem(STORAGE_KEY_ACTIVE);
|
|
250
|
-
await async_storage_1.default.removeItem(STORAGE_KEY_PENDING);
|
|
251
|
-
this.activeManifest = null;
|
|
252
|
-
console.log('[KoolbaseCodePush] bundle cache cleared');
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
exports.KoolbaseCodePush = KoolbaseCodePush;
|
package/dist/conflict.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { ConflictReason } from './offline-state';
|
|
2
|
-
/** Resolves conflicts by id. */
|
|
3
|
-
export interface ConflictResolver {
|
|
4
|
-
resolveWithLocal(conflictId: string): Promise<void>;
|
|
5
|
-
resolveWithServer(conflictId: string): Promise<void>;
|
|
6
|
-
resolveWithMerge(conflictId: string, data: Record<string, unknown>): Promise<void>;
|
|
7
|
-
abandon(conflictId: string): Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* A queued offline write that could not be applied, waiting for a decision.
|
|
11
|
-
*
|
|
12
|
-
* Not an error to dismiss and not a write to retry: retrying cannot help, and
|
|
13
|
-
* discarding it would lose a change the user believes is saved. It waits, and
|
|
14
|
-
* keeps waiting across restarts, until the application decides.
|
|
15
|
-
*
|
|
16
|
-
* Only the application can decide. Whether a later edit should win depends on
|
|
17
|
-
* what the data means, and a platform that chooses for everyone is wrong for
|
|
18
|
-
* someone.
|
|
19
|
-
*/
|
|
20
|
-
export declare class KoolbaseConflict {
|
|
21
|
-
readonly id: string;
|
|
22
|
-
readonly reason: ConflictReason;
|
|
23
|
-
readonly operation: 'insert' | 'update' | 'delete';
|
|
24
|
-
readonly collection: string;
|
|
25
|
-
readonly recordId: string;
|
|
26
|
-
/** The change the user made, still unapplied. */
|
|
27
|
-
readonly local: Record<string, unknown> | undefined;
|
|
28
|
-
/** The record as it was when the change was composed, where that is known. */
|
|
29
|
-
readonly baseline: Record<string, unknown> | undefined;
|
|
30
|
-
/**
|
|
31
|
-
* The record as the server held it when the write was refused, captured with
|
|
32
|
-
* the refusal so deciding needs no fetch and cannot race one.
|
|
33
|
-
*
|
|
34
|
-
* Undefined when the reason is `baseline_unavailable` — nothing was ever
|
|
35
|
-
* sent, so the server never answered.
|
|
36
|
-
*/
|
|
37
|
-
readonly server: Record<string, unknown> | undefined;
|
|
38
|
-
readonly baseRevision: number | undefined;
|
|
39
|
-
readonly serverRevision: number | undefined;
|
|
40
|
-
readonly createdAt: string;
|
|
41
|
-
private readonly resolver;
|
|
42
|
-
constructor(id: string, reason: ConflictReason, operation: 'insert' | 'update' | 'delete', collection: string, recordId: string,
|
|
43
|
-
/** The change the user made, still unapplied. */
|
|
44
|
-
local: Record<string, unknown> | undefined,
|
|
45
|
-
/** The record as it was when the change was composed, where that is known. */
|
|
46
|
-
baseline: Record<string, unknown> | undefined,
|
|
47
|
-
/**
|
|
48
|
-
* The record as the server held it when the write was refused, captured with
|
|
49
|
-
* the refusal so deciding needs no fetch and cannot race one.
|
|
50
|
-
*
|
|
51
|
-
* Undefined when the reason is `baseline_unavailable` — nothing was ever
|
|
52
|
-
* sent, so the server never answered.
|
|
53
|
-
*/
|
|
54
|
-
server: Record<string, unknown> | undefined, baseRevision: number | undefined, serverRevision: number | undefined, createdAt: string, resolver: ConflictResolver);
|
|
55
|
-
/**
|
|
56
|
-
* Fields where the user's change and the server's version disagree.
|
|
57
|
-
*
|
|
58
|
-
* Only the fields the change touches: a record accumulates values the write
|
|
59
|
-
* never asserted, and listing those would bury the real disagreement. Empty
|
|
60
|
-
* when there is no server version to compare against.
|
|
61
|
-
*/
|
|
62
|
-
get divergentFields(): string[];
|
|
63
|
-
/** How long this has been waiting. Metadata, not a deletion rule. */
|
|
64
|
-
get ageMs(): number;
|
|
65
|
-
/**
|
|
66
|
-
* Reapplies the user's change to the record as it stands now.
|
|
67
|
-
*
|
|
68
|
-
* An explicit decision to overwrite the server's version of the fields that
|
|
69
|
-
* disagree. Conditional where a revision is known, so a record that moved
|
|
70
|
-
* again while someone was deciding produces a new conflict rather than an
|
|
71
|
-
* unnoticed overwrite.
|
|
72
|
-
*/
|
|
73
|
-
resolveWithLocal(): Promise<void>;
|
|
74
|
-
/** Keeps the server's version and discards the user's change, as a decision. */
|
|
75
|
-
resolveWithServer(): Promise<void>;
|
|
76
|
-
/** Applies something the application composed from both versions. */
|
|
77
|
-
resolveWithMerge(data: Record<string, unknown>): Promise<void>;
|
|
78
|
-
/** Drops the change without claiming either version won. */
|
|
79
|
-
abandon(): Promise<void>;
|
|
80
|
-
}
|