@expo-kit/cloudkit 0.0.0-stage → 0.1.1

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.
Files changed (45) hide show
  1. package/README.md +145 -2
  2. package/app.plugin.js +1 -0
  3. package/build/codec.d.ts +6 -0
  4. package/build/codec.d.ts.map +1 -0
  5. package/build/codec.js +34 -0
  6. package/build/codec.js.map +1 -0
  7. package/build/index.d.ts +7 -0
  8. package/build/index.d.ts.map +1 -0
  9. package/build/index.js +9 -0
  10. package/build/index.js.map +1 -0
  11. package/build/native.d.ts +86 -0
  12. package/build/native.d.ts.map +1 -0
  13. package/build/native.js +7 -0
  14. package/build/native.js.map +1 -0
  15. package/build/schema.d.ts +98 -0
  16. package/build/schema.d.ts.map +1 -0
  17. package/build/schema.js +83 -0
  18. package/build/schema.js.map +1 -0
  19. package/build/sync.d.ts +78 -0
  20. package/build/sync.d.ts.map +1 -0
  21. package/build/sync.js +236 -0
  22. package/build/sync.js.map +1 -0
  23. package/build/use-cloud-sync.d.ts +5 -0
  24. package/build/use-cloud-sync.d.ts.map +1 -0
  25. package/build/use-cloud-sync.js +6 -0
  26. package/build/use-cloud-sync.js.map +1 -0
  27. package/expo-module.config.json +7 -0
  28. package/ios/CloudSyncEngineObject.swift +62 -0
  29. package/ios/ExpoKitCloudKit.podspec +28 -0
  30. package/ios/ExpoKitCloudKitModule.swift +50 -0
  31. package/ios/LocalStore.swift +351 -0
  32. package/ios/PushSubscriber.swift +14 -0
  33. package/ios/RecordMapper.swift +237 -0
  34. package/ios/Records.swift +60 -0
  35. package/ios/SchemaModel.swift +68 -0
  36. package/ios/SyncCore.swift +586 -0
  37. package/package.json +61 -4
  38. package/plugin/build/index.d.ts +7 -0
  39. package/plugin/build/index.js +68 -0
  40. package/src/codec.ts +36 -0
  41. package/src/index.ts +26 -0
  42. package/src/native.ts +83 -0
  43. package/src/schema.ts +169 -0
  44. package/src/sync.ts +322 -0
  45. package/src/use-cloud-sync.ts +9 -0
package/build/sync.js ADDED
@@ -0,0 +1,236 @@
1
+ import { reportError } from '@expo-kit/core';
2
+ import { AppState } from 'react-native';
3
+ import { decodeRecord, encodeFields } from './codec';
4
+ import { createNativeEngine, } from './native';
5
+ import { toSchemaSpec } from './schema';
6
+ const BATCH = 200;
7
+ /**
8
+ * Creates the sync for one schema. Create it once (module level) and call `start()`.
9
+ * In Expo Go (no native module) every method is a safe no-op.
10
+ */
11
+ export function createCloudSync(options) {
12
+ return createCloudSyncWithEngine(options, createNativeEngine);
13
+ }
14
+ /** Internal: lets tests pass a fake engine. */
15
+ export function createCloudSyncWithEngine(options, makeEngine) {
16
+ const { schema, apply, conflict = 'newest', zone = 'AppData', syncOnForeground = true } = options;
17
+ let engine = null;
18
+ let status = toStatus(null, false);
19
+ const listeners = new Set();
20
+ const subscriptions = [];
21
+ let draining = null;
22
+ let drainAgain = false;
23
+ try {
24
+ engine = makeEngine({ containerId: options.container, zoneName: zone, conflict, schema: toSchemaSpec(schema) });
25
+ }
26
+ catch (error) {
27
+ reportError(error, { fn: 'createCloudSync' });
28
+ }
29
+ if (engine)
30
+ status = toStatus(safeNativeStatus(engine), true);
31
+ function setStatus(next) {
32
+ status = next;
33
+ listeners.forEach((listener) => listener(status));
34
+ }
35
+ /**
36
+ * Applies changes from iCloud, then confirms them. iCloud doesn't keep any order between
37
+ * records (a note can arrive before its folder), so a group that fails is skipped and tried
38
+ * again after the others. Stops when a full pass makes no progress; the rest waits for the next sync.
39
+ */
40
+ function drain() {
41
+ if (!engine)
42
+ return Promise.resolve();
43
+ if (draining) {
44
+ drainAgain = true;
45
+ return draining;
46
+ }
47
+ const current = engine;
48
+ draining = (async () => {
49
+ let progressSinceRetry = false;
50
+ do {
51
+ drainAgain = false;
52
+ let failed = new Set();
53
+ while (true) {
54
+ // Failed changes stay in the inbox; skip them in this pass so later ones get their turn.
55
+ const changes = (await current.pendingChanges(BATCH + failed.size)).filter((change) => !failed.has(change.sequence));
56
+ if (changes.length === 0) {
57
+ if (failed.size === 0 || !progressSinceRetry)
58
+ break;
59
+ failed = new Set(); // Something else was applied since: try the failed ones again.
60
+ progressSinceRetry = false;
61
+ continue;
62
+ }
63
+ const { done, failedSequences } = await applyInOrder(changes.slice(0, BATCH));
64
+ if (done.length > 0) {
65
+ await current.ack(done);
66
+ progressSinceRetry = true;
67
+ }
68
+ failedSequences.forEach((sequence) => failed.add(sequence));
69
+ }
70
+ } while (drainAgain);
71
+ })()
72
+ .catch((error) => reportError(error, { fn: 'cloudSync.drain' }))
73
+ .finally(() => {
74
+ draining = null;
75
+ });
76
+ return draining;
77
+ }
78
+ /** Groups consecutive changes of the same type and operation, keeping their order. */
79
+ async function applyInOrder(changes) {
80
+ const done = [];
81
+ const failedSequences = [];
82
+ let index = 0;
83
+ while (index < changes.length) {
84
+ const first = changes[index];
85
+ const group = [];
86
+ while (index < changes.length && changes[index].type === first.type && changes[index].operation === first.operation) {
87
+ group.push(changes[index]);
88
+ index += 1;
89
+ }
90
+ const handler = apply[first.type];
91
+ if (!handler) {
92
+ // A type no longer in the schema: nothing to apply it to.
93
+ done.push(...group.map((change) => change.sequence));
94
+ continue;
95
+ }
96
+ try {
97
+ if (first.operation === 'delete') {
98
+ await handler.delete(group.map((change) => change.id));
99
+ }
100
+ else {
101
+ const records = group
102
+ .map((change) => decodeRecord(schema, change.type, change.id, change.fields))
103
+ .filter((record) => record !== null);
104
+ await handler.save(records);
105
+ }
106
+ done.push(...group.map((change) => change.sequence));
107
+ }
108
+ catch (error) {
109
+ reportError(error, { fn: 'cloudSync.apply', type: first.type, operation: first.operation });
110
+ failedSequences.push(...group.map((change) => change.sequence));
111
+ }
112
+ }
113
+ return { done, failedSequences };
114
+ }
115
+ if (engine) {
116
+ const current = engine;
117
+ subscriptions.push(current.addListener('status', (next) => setStatus(toStatus(next, true))), current.addListener('changes', () => void drain()), current.addListener('account', (event) => {
118
+ try {
119
+ options.onAccountChange?.(event);
120
+ }
121
+ catch (error) {
122
+ reportError(error, { fn: 'cloudSync.onAccountChange' });
123
+ }
124
+ }), current.addListener('error', (event) => reportError(new Error(event.message), { fn: 'cloudSync', context: event.context })));
125
+ }
126
+ let foreground = null;
127
+ let lastState = AppState.currentState;
128
+ const sync = {
129
+ async start() {
130
+ if (!engine)
131
+ return;
132
+ try {
133
+ await drain(); // Changes saved before the last app kill.
134
+ await engine.start();
135
+ await drain();
136
+ }
137
+ catch (error) {
138
+ reportError(error, { fn: 'cloudSync.start' });
139
+ }
140
+ if (syncOnForeground && !foreground) {
141
+ foreground = AppState.addEventListener('change', (next) => {
142
+ if (lastState !== 'active' && next === 'active')
143
+ void sync.syncNow();
144
+ lastState = next;
145
+ });
146
+ }
147
+ },
148
+ save(type, record) {
149
+ if (!engine)
150
+ return;
151
+ const current = engine;
152
+ guard(() => current.save({ type, id: record.id, fields: encodeFields(schema, type, record) }), {
153
+ fn: 'cloudSync.save',
154
+ type,
155
+ });
156
+ },
157
+ delete(type, id) {
158
+ if (!engine)
159
+ return;
160
+ const current = engine;
161
+ guard(() => current.delete({ type, id }), { fn: 'cloudSync.delete', type });
162
+ },
163
+ importAll(type, records) {
164
+ for (const record of records)
165
+ sync.save(type, record);
166
+ },
167
+ async syncNow() {
168
+ if (!engine)
169
+ return;
170
+ try {
171
+ await engine.syncNow();
172
+ }
173
+ catch (error) {
174
+ reportError(error, { fn: 'cloudSync.syncNow' });
175
+ }
176
+ await drain();
177
+ },
178
+ getStatus: () => status,
179
+ subscribe(listener) {
180
+ listeners.add(listener);
181
+ return () => {
182
+ listeners.delete(listener);
183
+ };
184
+ },
185
+ async stop() {
186
+ foreground?.remove();
187
+ foreground = null;
188
+ subscriptions.splice(0).forEach((subscription) => subscription.remove());
189
+ const current = engine;
190
+ engine = null;
191
+ setStatus(toStatus(null, false));
192
+ try {
193
+ await current?.stop();
194
+ }
195
+ catch (error) {
196
+ reportError(error, { fn: 'cloudSync.stop' });
197
+ }
198
+ },
199
+ };
200
+ return sync;
201
+ }
202
+ /**
203
+ * A rejected record is a bug in the app: throw in development so it's seen right away,
204
+ * report it in production instead of breaking the app's own save.
205
+ */
206
+ function guard(body, context) {
207
+ try {
208
+ body();
209
+ }
210
+ catch (error) {
211
+ if (__DEV__)
212
+ throw error;
213
+ reportError(error, context);
214
+ }
215
+ }
216
+ function safeNativeStatus(engine) {
217
+ try {
218
+ return engine.status();
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ }
224
+ function toStatus(native, supported) {
225
+ return {
226
+ supported,
227
+ account: native?.account ?? 'unknown',
228
+ syncing: native?.syncing ?? false,
229
+ initialSyncDone: native?.initialSyncDone ?? false,
230
+ pendingUploads: native?.pendingUploads ?? 0,
231
+ pendingChanges: native?.pendingChanges ?? 0,
232
+ lastSyncedAt: native?.lastSyncedAt != null ? new Date(native.lastSyncedAt) : null,
233
+ error: native?.error ?? null,
234
+ };
235
+ }
236
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CloudSchema } from './schema';
2
+ import type { CloudSync, CloudSyncStatus } from './sync';
3
+ /** The sync status. Re-renders when it changes. */
4
+ export declare function useCloudSync<S extends CloudSchema>(sync: CloudSync<S>): CloudSyncStatus;
5
+ //# sourceMappingURL=use-cloud-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-cloud-sync.d.ts","sourceRoot":"","sources":["../src/use-cloud-sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEzD,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,eAAe,CAEvF"}
@@ -0,0 +1,6 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ /** The sync status. Re-renders when it changes. */
3
+ export function useCloudSync(sync) {
4
+ return useSyncExternalStore(sync.subscribe, sync.getStatus, sync.getStatus);
5
+ }
6
+ //# sourceMappingURL=use-cloud-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-cloud-sync.js","sourceRoot":"","sources":["../src/use-cloud-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAK7C,mDAAmD;AACnD,MAAM,UAAU,YAAY,CAAwB,IAAkB;IACpE,OAAO,oBAAoB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,7 @@
1
+ {
2
+ "platforms": ["apple"],
3
+ "apple": {
4
+ "modules": ["ExpoKitCloudKitModule"],
5
+ "appDelegateSubscribers": ["ExpoKitCloudKitAppDelegateSubscriber"]
6
+ }
7
+ }
@@ -0,0 +1,62 @@
1
+ import CloudKit
2
+ import ExpoModulesCore
3
+
4
+ /// The sync engine as JS sees it. One instance per container + zone.
5
+ final class CloudSyncEngineObject: SharedObject, SyncCoreDelegate {
6
+ let core: SyncCore
7
+
8
+ init(options: EngineOptionsRecord) throws {
9
+ guard !options.containerId.isEmpty else {
10
+ throw CloudKitSyncError.invalidSchema("containerId is empty.")
11
+ }
12
+ let configured = Bundle.main.object(forInfoDictionaryKey: "ExpoKitCloudKitContainers") as? [String] ?? []
13
+ guard configured.contains(options.containerId) else {
14
+ throw CloudKitSyncError.invalidSchema(
15
+ "iCloud container \"\(options.containerId)\" isn't set up in this build. Add it to the @expo-kit/cloudkit plugin in app.json and rebuild."
16
+ )
17
+ }
18
+ guard let policy = ConflictPolicy(rawValue: options.conflict) else {
19
+ throw CloudKitSyncError.invalidSchema("Unknown conflict rule \"\(options.conflict)\". Use newest, server or device.")
20
+ }
21
+ let schema = try options.schema.toModel()
22
+ let fileManager = FileManager.default
23
+ let folder = "\(options.containerId)/\(options.zoneName)"
24
+ let support = try fileManager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
25
+ let documents = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
26
+ let store = try LocalStore(url: support.appendingPathComponent("expo-kit-cloudkit/\(folder)/sync.sqlite"))
27
+
28
+ core = SyncCore(
29
+ container: CKContainer(identifier: options.containerId),
30
+ zoneName: options.zoneName,
31
+ schema: schema,
32
+ policy: policy,
33
+ store: store,
34
+ assetsDirectory: documents.appendingPathComponent("expo-kit-cloudkit/\(folder)")
35
+ )
36
+ super.init()
37
+ core.delegate = self
38
+ }
39
+
40
+ override func sharedObjectWillRelease() {
41
+ let core = self.core
42
+ Task { await core.stop() }
43
+ }
44
+
45
+ // MARK: SyncCoreDelegate
46
+
47
+ func syncCore(_ core: SyncCore, didChangeStatus status: [String: Any]) {
48
+ emit(event: "status", payload: status)
49
+ }
50
+
51
+ func syncCoreHasNewChanges(_ core: SyncCore) {
52
+ emit(event: "changes", payload: [String: Any]())
53
+ }
54
+
55
+ func syncCore(_ core: SyncCore, didChangeAccount change: [String: Any]) {
56
+ emit(event: "account", payload: change)
57
+ }
58
+
59
+ func syncCore(_ core: SyncCore, didFail error: Error, context: String) {
60
+ emit(event: "error", payload: ["message": error.localizedDescription, "context": context] as [String: Any])
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoKitCloudKit'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = 'Ashraful Islam'
12
+ s.homepage = 'https://github.com/islamashraful/expo-kit'
13
+ # CKSyncEngine needs iOS 17.
14
+ s.platforms = { :ios => '17.0' }
15
+ s.swift_version = '5.9'
16
+ s.source = { git: 'https://github.com/islamashraful/expo-kit.git' }
17
+ s.static_framework = true
18
+
19
+ s.dependency 'ExpoModulesCore'
20
+ s.frameworks = 'CloudKit'
21
+ s.libraries = 'sqlite3'
22
+
23
+ s.pod_target_xcconfig = {
24
+ 'DEFINES_MODULE' => 'YES',
25
+ }
26
+
27
+ s.source_files = '**/*.{h,m,swift}'
28
+ end
@@ -0,0 +1,50 @@
1
+ import CloudKit
2
+ import ExpoModulesCore
3
+
4
+ public class ExpoKitCloudKitModule: Module {
5
+ public func definition() -> ModuleDefinition {
6
+ Name("ExpoKitCloudKit")
7
+
8
+ Function("isSupported") { () -> Bool in
9
+ return true
10
+ }
11
+
12
+ Class("CloudSyncEngine", CloudSyncEngineObject.self) {
13
+ Constructor { (options: EngineOptionsRecord) throws -> CloudSyncEngineObject in
14
+ try CloudSyncEngineObject(options: options)
15
+ }
16
+
17
+ AsyncFunction("start") { (engine: CloudSyncEngineObject) in
18
+ await engine.core.start()
19
+ }
20
+
21
+ AsyncFunction("syncNow") { (engine: CloudSyncEngineObject) in
22
+ await engine.core.syncNow()
23
+ }
24
+
25
+ AsyncFunction("stop") { (engine: CloudSyncEngineObject) in
26
+ await engine.core.stop()
27
+ }
28
+
29
+ Function("save") { (engine: CloudSyncEngineObject, input: SaveInputRecord) throws in
30
+ try engine.core.save(type: input.type, id: input.id, fields: input.fields)
31
+ }
32
+
33
+ Function("delete") { (engine: CloudSyncEngineObject, input: DeleteInputRecord) throws in
34
+ try engine.core.delete(type: input.type, id: input.id)
35
+ }
36
+
37
+ AsyncFunction("pendingChanges") { (engine: CloudSyncEngineObject, limit: Int) throws -> [[String: Any]] in
38
+ try engine.core.pendingChanges(limit: limit)
39
+ }
40
+
41
+ AsyncFunction("ack") { (engine: CloudSyncEngineObject, sequences: [Int]) throws in
42
+ try engine.core.ack(sequences.map(Int64.init))
43
+ }
44
+
45
+ Function("status") { (engine: CloudSyncEngineObject) -> [String: Any] in
46
+ engine.core.status()
47
+ }
48
+ }
49
+ }
50
+ }