@tryterra/react-native 0.0.0-stage → 2.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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -2
  3. package/TerraReactNative.podspec +48 -0
  4. package/android/build.gradle +87 -0
  5. package/android/src/bridgeCore/kotlin/README.md +1 -0
  6. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
  7. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
  8. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +468 -0
  9. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
  10. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
  11. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
  12. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
  13. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +258 -0
  14. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +52 -0
  15. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
  16. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
  17. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +493 -0
  18. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +897 -0
  19. package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
  20. package/android/src/main/AndroidManifest.xml +24 -0
  21. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
  22. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
  23. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
  24. package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
  25. package/docs/availability.md +246 -0
  26. package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
  27. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  28. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +81513 -0
  29. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2150 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2150 -0
  31. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2127 -0
  33. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  34. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  36. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  37. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6473 -0
  38. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  39. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +81513 -0
  40. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2150 -0
  41. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2150 -0
  42. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  43. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2127 -0
  44. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +81513 -0
  45. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2150 -0
  46. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2150 -0
  47. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  48. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2127 -0
  49. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  50. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  51. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
  52. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  53. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  54. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6474 -0
  55. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6392 -0
  56. package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
  57. package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
  58. package/ios/TerraBridgeCore/Conversions.swift +467 -0
  59. package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
  60. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +795 -0
  61. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +1317 -0
  62. package/ios/TerraBridgeCore/Locked.swift +15 -0
  63. package/ios/TerraBridgeCore/README.md +1 -0
  64. package/ios/TerraBridgeCore/Rejections.swift +130 -0
  65. package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
  66. package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
  67. package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
  68. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +254 -0
  69. package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
  70. package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
  71. package/ios/TerraBridgeModule.mm +79 -0
  72. package/ios/TerraBridgeModuleImpl.swift +49 -0
  73. package/ios/TerraReactNative.swift +19 -0
  74. package/lib/EndUser.d.ts +12 -0
  75. package/lib/EndUser.js +13 -0
  76. package/lib/NativeTerraBridge.d.ts +23 -0
  77. package/lib/NativeTerraBridge.js +13 -0
  78. package/lib/Terra.d.ts +154 -0
  79. package/lib/Terra.js +280 -0
  80. package/lib/TerraError.d.ts +42 -0
  81. package/lib/TerraError.js +95 -0
  82. package/lib/createTerra.d.ts +8 -0
  83. package/lib/createTerra.js +19 -0
  84. package/lib/generated/client.d.ts +92 -0
  85. package/lib/generated/client.js +196 -0
  86. package/lib/generated/codecs.d.ts +135 -0
  87. package/lib/generated/codecs.js +1358 -0
  88. package/lib/generated/errors.d.ts +13 -0
  89. package/lib/generated/errors.js +68 -0
  90. package/lib/generated/ops.d.ts +13 -0
  91. package/lib/generated/ops.js +137 -0
  92. package/lib/generated/streams.d.ts +23 -0
  93. package/lib/generated/streams.js +58 -0
  94. package/lib/generated/types.d.ts +481 -0
  95. package/lib/generated/types.js +92 -0
  96. package/lib/index.d.ts +29 -0
  97. package/lib/index.js +55 -0
  98. package/package.json +71 -4
  99. package/src/EndUser.ts +15 -0
  100. package/src/NativeTerraBridge.ts +27 -0
  101. package/src/Terra.ts +462 -0
  102. package/src/TerraError.ts +116 -0
  103. package/src/createTerra.ts +18 -0
  104. package/src/generated/client.ts +243 -0
  105. package/src/generated/codecs.ts +1287 -0
  106. package/src/generated/errors.ts +113 -0
  107. package/src/generated/ops.ts +197 -0
  108. package/src/generated/streams.ts +74 -0
  109. package/src/generated/types.ts +667 -0
  110. package/src/index.ts +52 -0
package/lib/Terra.js ADDED
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ /**
3
+ * The client an app holds: namespaces over the generated ops, the token provider, and the streams.
4
+ *
5
+ * Hand-written, deliberately thin. Every method is one generated `GeneratedClient` call; what lives
6
+ * here is only what the generated layer cannot know - which runtime the app is on, how a JS
7
+ * function answers a native token request, and how one native event emitter fans out to many
8
+ * listeners. `facade.test.ts` drives every method and fails when an op in the schema has no member
9
+ * here, so this file cannot quietly fall behind the surface the way the 1.x wrapper did.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Terra = void 0;
13
+ const TerraError_1 = require("./TerraError");
14
+ const client_1 = require("./generated/client");
15
+ const ops_1 = require("./generated/ops");
16
+ const streams_1 = require("./generated/streams");
17
+ class Terra {
18
+ constructor(native, platform, options = {}) {
19
+ this.platform = platform;
20
+ const invoker = native;
21
+ const ops = new client_1.GeneratedClient(invoker);
22
+ this.ops = ops;
23
+ this.streams = new StreamHub(native);
24
+ this.tokensReady = options.tokenProvider === undefined
25
+ ? Promise.resolve()
26
+ : this.answerTokens(options.tokenProvider, options.tokenTimeoutMs ?? 30000);
27
+ // The runtime can report an unhandled rejection before a later call awaits this promise.
28
+ this.tokensReady.catch(() => undefined);
29
+ const on = (op) => ops_1.availableOn[op].includes(platform);
30
+ const stream = (id) => (listener, onError) => this.streams.subscribe(id, listener, onError);
31
+ this.permissions = {
32
+ request: async (access, source) => {
33
+ const grant = await ops.permissionsRequest({
34
+ read: access.read ?? [],
35
+ write: access.write ?? [],
36
+ workouts: access.workouts ?? [],
37
+ source,
38
+ });
39
+ return grant.granted;
40
+ },
41
+ status: async (category, source) => (await ops.permissionsStatus({ category, source })).status,
42
+ writeStatus: async (category, source) => (await ops.permissionsWriteStatus({ category, source })).status,
43
+ requested: (source) => ops.permissionsRequested({ source }),
44
+ ...(on("permissions.statuses") && {
45
+ statuses: async (source) => (await ops.permissionsStatuses({ source })).statuses,
46
+ }),
47
+ ...(on("permissions.backgroundReadStatus") && {
48
+ backgroundReadStatus: async (source) => (await ops.permissionsBackgroundReadStatus({ source })).status,
49
+ }),
50
+ ...(on("permissions.historyReadStatus") && {
51
+ historyReadStatus: async (source) => (await ops.permissionsHistoryReadStatus({ source })).status,
52
+ }),
53
+ };
54
+ this.diagnostics = {
55
+ state: (source) => ops.diagnosticsState({ source }),
56
+ lastDelivery: async (source) => (await ops.diagnosticsLastDelivery({ source })).at,
57
+ report: (source) => ops.diagnosticsReport({ source }),
58
+ send: async (source) => {
59
+ await ops.diagnosticsSend({ source });
60
+ },
61
+ events: stream("diagnostics.events"),
62
+ };
63
+ this.background = {
64
+ isRegistered: async () => (await ops.backgroundIsRegistered({})).value,
65
+ ...(on("background.cancel") && { cancel: async () => (await ops.backgroundCancel({})).value }),
66
+ };
67
+ this.body = {
68
+ heartRate: async (range) => (await ops.bodyHeartRate({ range })).samples,
69
+ logHeartRate: async (entries) => (await ops.bodyLogHeartRate({ entries })).ids,
70
+ log: async (entries) => (await ops.bodyLog({ entries })).ids,
71
+ delete: async (id) => {
72
+ await ops.bodyDelete({ id });
73
+ },
74
+ changes: stream("body.changes"),
75
+ };
76
+ this.daily = {
77
+ steps: async (range) => (await ops.dailySteps({ range })).samples,
78
+ log: async (entries) => (await ops.dailyLog({ entries })).ids,
79
+ delete: async (id) => {
80
+ await ops.dailyDelete({ id });
81
+ },
82
+ changes: stream("daily.changes"),
83
+ };
84
+ this.sleep = {
85
+ segments: async (range) => (await ops.sleepSegments({ range })).samples,
86
+ log: async (entries) => (await ops.sleepLog({ entries })).ids,
87
+ delete: async (id) => {
88
+ await ops.sleepDelete({ id });
89
+ },
90
+ changes: stream("sleep.changes"),
91
+ };
92
+ this.activity = {
93
+ workouts: async (range) => (await ops.activityWorkouts({ range })).samples,
94
+ log: async (entries) => (await ops.activityLog({ entries })).ids,
95
+ delete: async (id) => {
96
+ await ops.activityDelete({ id });
97
+ },
98
+ changes: stream("activity.changes"),
99
+ };
100
+ this.nutrition = {
101
+ log: async (entries) => (await ops.nutritionLog({ entries })).ids,
102
+ delete: async (id) => {
103
+ await ops.nutritionDelete({ id });
104
+ },
105
+ };
106
+ this.plannedWorkouts = on("plannedWorkouts.planned")
107
+ ? {
108
+ planned: async () => (await ops.plannedWorkoutsPlanned({})).workouts,
109
+ markComplete: async (id, at) => {
110
+ await ops.plannedWorkoutsMarkComplete({ id, at });
111
+ },
112
+ delete: async (id) => {
113
+ await ops.plannedWorkoutsDelete({ id });
114
+ },
115
+ schedulingStatus: async () => (await ops.plannedWorkoutsSchedulingStatus({})).status,
116
+ requestScheduling: async () => (await ops.plannedWorkoutsRequestScheduling({})).status,
117
+ }
118
+ : undefined;
119
+ }
120
+ /**
121
+ * The native client this runtime is attached to. Rejects with `bridge.not_bootstrapped`, carrying
122
+ * the exact line to add in `details.line`, when the app's native launch never set Terra up.
123
+ */
124
+ attach() {
125
+ return this.ops.clientAttach({});
126
+ }
127
+ /**
128
+ * Links `source` for the person `endUser` names, or restores the link already made for them.
129
+ *
130
+ * Every connect names its person: a second account on the same phone must not be handed the
131
+ * first one's link. With `token` the grant in hand is spent (a QR code, a deep link); without it
132
+ * the `tokenProvider` is asked, and only when a new link is actually needed.
133
+ */
134
+ async connect(source, endUser, options = {}) {
135
+ await this.tokensReady;
136
+ return this.ops.clientConnect({ source, endUser, token: options.token });
137
+ }
138
+ async connections() {
139
+ return (await this.ops.clientConnections({})).connections;
140
+ }
141
+ async disconnect(source) {
142
+ await this.ops.clientDisconnect({ source });
143
+ }
144
+ /** Answers the native broker's token requests with `provider`, for the lifetime of this runtime. */
145
+ async answerTokens(provider, timeoutMs) {
146
+ await this.streams.open("bridge.tokenRequest", ({ requestId }) => {
147
+ void (async () => {
148
+ let token;
149
+ try {
150
+ token = await provider();
151
+ }
152
+ catch (failed) {
153
+ const message = failed instanceof Error ? failed.message : String(failed);
154
+ await this.ops.tokenReject({ requestId, message }).catch(() => undefined);
155
+ return;
156
+ }
157
+ await this.ops.tokenResolve({ requestId, token }).catch(() => undefined);
158
+ })();
159
+ });
160
+ await this.ops.tokenSetProviderPresent({ present: true, timeoutMs });
161
+ }
162
+ }
163
+ exports.Terra = Terra;
164
+ class StreamHub {
165
+ constructor(native) {
166
+ this.native = native;
167
+ this.handlers = new Map();
168
+ this.early = new Map();
169
+ this.pending = 0;
170
+ }
171
+ /** Subscribes and answers once the native stream is open. */
172
+ async open(stream, listener, onError) {
173
+ this.listen();
174
+ this.pending += 1;
175
+ let id;
176
+ try {
177
+ id = await this.native.subscribe(stream, "{}");
178
+ }
179
+ catch (thrown) {
180
+ this.settled();
181
+ throw TerraError_1.TerraError.from(thrown);
182
+ }
183
+ const handle = (message) => {
184
+ const delivery = (0, streams_1.decodeStreamDelivery)(stream, JSON.parse(message));
185
+ switch (delivery.kind) {
186
+ case "data":
187
+ listener(delivery.element);
188
+ return;
189
+ case "error":
190
+ this.ended(id);
191
+ onError?.(new TerraError_1.TerraError(delivery.error));
192
+ return;
193
+ case "complete":
194
+ this.ended(id);
195
+ return;
196
+ }
197
+ };
198
+ this.handlers.set(id, handle);
199
+ const held = this.early.get(id);
200
+ this.early.delete(id);
201
+ this.settled();
202
+ held?.forEach(handle);
203
+ return id;
204
+ }
205
+ settled() {
206
+ this.pending -= 1;
207
+ if (this.pending === 0)
208
+ this.early.clear();
209
+ this.releaseIfIdle();
210
+ }
211
+ ended(id) {
212
+ this.handlers.delete(id);
213
+ this.releaseIfIdle();
214
+ }
215
+ releaseIfIdle() {
216
+ if (this.handlers.size > 0 || this.pending > 0)
217
+ return;
218
+ this.listening?.remove();
219
+ this.listening = undefined;
220
+ }
221
+ /** Subscribes without waiting; the returned function cancels whether or not the stream has opened yet. */
222
+ subscribe(stream, listener, onError) {
223
+ let cancelled = false;
224
+ let opened;
225
+ this.open(stream, (element) => {
226
+ if (!cancelled)
227
+ listener(element);
228
+ }, (error) => {
229
+ if (!cancelled)
230
+ onError?.(error);
231
+ }).then((id) => {
232
+ opened = id;
233
+ if (cancelled)
234
+ this.close(id);
235
+ }, (error) => {
236
+ if (!cancelled)
237
+ onError?.(error);
238
+ });
239
+ return () => {
240
+ if (cancelled)
241
+ return;
242
+ cancelled = true;
243
+ if (opened !== undefined)
244
+ this.close(opened);
245
+ };
246
+ }
247
+ close(id) {
248
+ this.handlers.delete(id);
249
+ this.early.delete(id);
250
+ this.native.unsubscribe(id);
251
+ this.releaseIfIdle();
252
+ }
253
+ listen() {
254
+ if (this.listening !== undefined)
255
+ return;
256
+ this.listening = this.native.onMessage(({ subscriptionId, message }) => {
257
+ const handler = this.handlers.get(subscriptionId);
258
+ if (handler !== undefined) {
259
+ handler(message);
260
+ return;
261
+ }
262
+ if (this.pending === 0)
263
+ return;
264
+ const held = this.early.get(subscriptionId) ?? [];
265
+ if (held.length < EARLY_LIMIT || isTerminal(message))
266
+ held.push(message);
267
+ this.early.set(subscriptionId, held);
268
+ });
269
+ }
270
+ }
271
+ const EARLY_LIMIT = 64;
272
+ function isTerminal(message) {
273
+ try {
274
+ const kind = JSON.parse(message).kind;
275
+ return kind === "error" || kind === "complete";
276
+ }
277
+ catch {
278
+ return false;
279
+ }
280
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The error every rejected op carries, hydrated from the bridge's JSON.
3
+ *
4
+ * Hand-written, and one of only two files here that are: the shape is fixed, while the *vocabulary*
5
+ * it carries is generated. `code` is a `TerraErrorCode`, so a code renamed natively stops a
6
+ * `switch` over it compiling rather than silently never matching.
7
+ */
8
+ import { type TerraErrorCode } from "./generated/errors";
9
+ import type { Source, TerraErrorPayload } from "./generated/types";
10
+ /**
11
+ * The JSON a native rejection carries in its userInfo / message: the schema's own payload, except
12
+ * that `retryable` may be left out, in which case the per-code table answers.
13
+ */
14
+ export type TerraErrorJSON = Omit<TerraErrorPayload, "retryable"> & {
15
+ readonly retryable?: boolean;
16
+ };
17
+ export declare class TerraError extends Error {
18
+ readonly code: TerraErrorCode;
19
+ /**
20
+ * Whether the same call could succeed if repeated.
21
+ *
22
+ * Read from the rejection rather than from `retryableByCode`, because one code decides per
23
+ * instance: `network.server_error` is retryable for a 503 and not for a 400. The table is the
24
+ * fallback for a rejection that did not carry the flag at all.
25
+ */
26
+ readonly retryable: boolean;
27
+ readonly source: Source | undefined;
28
+ readonly details: Record<string, unknown> | undefined;
29
+ constructor(json: TerraErrorJSON, options?: {
30
+ readonly cause?: unknown;
31
+ });
32
+ /**
33
+ * Rebuilds a `TerraError` from whatever the native module rejected with.
34
+ *
35
+ * Anything unrecognisable becomes `platform` rather than being rethrown as-is: a caller that has
36
+ * to handle both `TerraError` and an arbitrary `Error` ends up handling neither properly, and the
37
+ * original is kept as `cause` and its text in `message`.
38
+ */
39
+ static from(thrown: unknown): TerraError;
40
+ /** A response the bridge resolved that this build cannot read: the native side and JS disagree. */
41
+ static invalidPayload(op: string, thrown: unknown): TerraError;
42
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * The error every rejected op carries, hydrated from the bridge's JSON.
4
+ *
5
+ * Hand-written, and one of only two files here that are: the shape is fixed, while the *vocabulary*
6
+ * it carries is generated. `code` is a `TerraErrorCode`, so a code renamed natively stops a
7
+ * `switch` over it compiling rather than silently never matching.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.TerraError = void 0;
11
+ const codecs_1 = require("./generated/codecs");
12
+ const errors_1 = require("./generated/errors");
13
+ class TerraError extends Error {
14
+ constructor(json, options) {
15
+ super(json.message, options);
16
+ this.name = "TerraError";
17
+ const known = isKnownCode(json.code);
18
+ this.code = known ? json.code : "platform";
19
+ this.retryable = known ? (json.retryable ?? fallbackRetryable(json.code)) : false;
20
+ this.source = json.source;
21
+ this.details = known ? json.details : { ...json.details, unrecognisedCode: json.code };
22
+ }
23
+ /**
24
+ * Rebuilds a `TerraError` from whatever the native module rejected with.
25
+ *
26
+ * Anything unrecognisable becomes `platform` rather than being rethrown as-is: a caller that has
27
+ * to handle both `TerraError` and an arbitrary `Error` ends up handling neither properly, and the
28
+ * original is kept as `cause` and its text in `message`.
29
+ */
30
+ static from(thrown) {
31
+ if (thrown instanceof TerraError)
32
+ return thrown;
33
+ const json = readJSON(thrown);
34
+ if (json !== undefined)
35
+ return new TerraError(json, { cause: thrown });
36
+ return new TerraError({ code: "platform", retryable: false, message: messageOf(thrown) }, { cause: thrown });
37
+ }
38
+ /** A response the bridge resolved that this build cannot read: the native side and JS disagree. */
39
+ static invalidPayload(op, thrown) {
40
+ return new TerraError({ code: "bridge.invalid_payload", message: `${op}: ${messageOf(thrown)}`, details: { op } }, { cause: thrown });
41
+ }
42
+ }
43
+ exports.TerraError = TerraError;
44
+ function isKnownCode(code) {
45
+ return Object.prototype.hasOwnProperty.call(errors_1.retryableByCode, code);
46
+ }
47
+ function messageOf(thrown) {
48
+ if (thrown instanceof Error)
49
+ return thrown.message;
50
+ if (isObject(thrown) && typeof thrown.message === "string")
51
+ return thrown.message;
52
+ return String(thrown);
53
+ }
54
+ function fallbackRetryable(code) {
55
+ return errors_1.retryableByCode[code] === true;
56
+ }
57
+ /** A JSON string preserves payload types across `NSError` and `WritableMap`, unlike a nested map. */
58
+ function readJSON(thrown) {
59
+ if (typeof thrown !== "object" || thrown === null)
60
+ return undefined;
61
+ const candidate = thrown;
62
+ const userInfo = candidate.userInfo;
63
+ const encoded = isObject(userInfo) ? userInfo.payload : undefined;
64
+ return (typeof encoded === "string" ? decode(parse(encoded)) : undefined) ?? decode(userInfo) ?? decode(candidate);
65
+ }
66
+ function parse(encoded) {
67
+ try {
68
+ return JSON.parse(encoded);
69
+ }
70
+ catch {
71
+ return undefined;
72
+ }
73
+ }
74
+ /** The generated decoder does the checking: a string code, a boolean flag, a known source. */
75
+ function decode(raw) {
76
+ if (!isObject(raw) || typeof raw.code !== "string")
77
+ return undefined;
78
+ try {
79
+ // Read field by field: an Error's `message` is not enumerable, so spreading one drops it.
80
+ const { retryable, ...rest } = (0, codecs_1.decodeTerraErrorPayload)({
81
+ code: raw.code,
82
+ retryable: raw.retryable ?? false,
83
+ message: raw.message,
84
+ source: raw.source,
85
+ details: raw.details,
86
+ });
87
+ return raw.retryable === undefined ? rest : { ...rest, retryable };
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ function isObject(value) {
94
+ return typeof value === "object" && value !== null;
95
+ }
@@ -0,0 +1,8 @@
1
+ import { Terra, type TerraOptions } from "./Terra";
2
+ /**
3
+ * Attaches this JavaScript runtime to the native client the app built at launch.
4
+ *
5
+ * Call it once, at module scope. The native client outlives a reload, so a reload attaches again
6
+ * rather than building a second client.
7
+ */
8
+ export declare function createTerra(options?: TerraOptions): Terra;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTerra = createTerra;
4
+ const react_native_1 = require("react-native");
5
+ const NativeTerraBridge_1 = require("./NativeTerraBridge");
6
+ const Terra_1 = require("./Terra");
7
+ /**
8
+ * Attaches this JavaScript runtime to the native client the app built at launch.
9
+ *
10
+ * Call it once, at module scope. The native client outlives a reload, so a reload attaches again
11
+ * rather than building a second client.
12
+ */
13
+ function createTerra(options = {}) {
14
+ const platform = react_native_1.Platform.OS;
15
+ if (platform !== "ios" && platform !== "android") {
16
+ throw new Error(`@tryterra/react-native runs on iOS and Android, not ${platform}`);
17
+ }
18
+ return new Terra_1.Terra(NativeTerraBridge_1.default, platform, options);
19
+ }
@@ -0,0 +1,92 @@
1
+ import type { BodyEntryList, BoolResult, BootstrapState, CategoryRequest, ConnectRequest, Connection, ConnectionList, DiagnosticsState, Empty, HealthAccess, HeartRateEntryList, HeartRateList, LastDeliveryResult, MarkCompleteRequest, NutritionEntryList, PermissionGrant, PermissionRequest, PlannedWorkoutList, RangeRequest, RecordIdList, RecordIdRequest, SleepEntryList, SleepSegmentList, SourceRequest, StatusResult, StatusesResult, StepCountList, StepEntryList, SupportReport, TokenProviderRequest, TokenRejectRequest, TokenResolveRequest, WorkoutEntryList, WorkoutList, WorkoutScheduling } from "./types";
2
+ import type { OpId } from "./ops";
3
+ /** What the facade needs from the native module. The TurboModule spec satisfies it; so does a test double. */
4
+ export interface Invoker {
5
+ invoke(op: OpId, payload: string): Promise<string>;
6
+ }
7
+ /**
8
+ * One generated method per op, and nothing else.
9
+ *
10
+ * The `op` string exists only between this file and the generated native dispatch, and a vector
11
+ * in `vectors/bridge` pins the two to each other. A caller never sees it.
12
+ */
13
+ export declare class GeneratedClient {
14
+ protected readonly invoker: Invoker;
15
+ constructor(invoker: Invoker);
16
+ private call;
17
+ /**
18
+ * Reports the native client the JS side is attaching to. Idempotent, and safe to call after a
19
+ * JS reload.
20
+ */
21
+ clientAttach(request: Empty): Promise<BootstrapState>;
22
+ clientConnect(request: ConnectRequest): Promise<Connection>;
23
+ clientConnections(request: Empty): Promise<ConnectionList>;
24
+ clientDisconnect(request: SourceRequest): Promise<Empty>;
25
+ /**
26
+ * Tells the broker whether JS can answer at all. Without it a link asks a provider that is not
27
+ * there and waits out the timeout.
28
+ */
29
+ tokenSetProviderPresent(request: TokenProviderRequest): Promise<Empty>;
30
+ /**
31
+ * An unknown or already-settled requestId is ignored rather than rejected, so a reload cannot
32
+ * fail a link that has already moved on.
33
+ */
34
+ tokenResolve(request: TokenResolveRequest): Promise<Empty>;
35
+ tokenReject(request: TokenRejectRequest): Promise<Empty>;
36
+ permissionsRequest(request: PermissionRequest): Promise<PermissionGrant>;
37
+ permissionsStatus(request: CategoryRequest): Promise<StatusResult>;
38
+ permissionsWriteStatus(request: CategoryRequest): Promise<StatusResult>;
39
+ permissionsRequested(request: SourceRequest): Promise<HealthAccess>;
40
+ /** Available on android only. */
41
+ permissionsStatuses(request: SourceRequest): Promise<StatusesResult>;
42
+ /** Available on android only. */
43
+ permissionsBackgroundReadStatus(request: SourceRequest): Promise<StatusResult>;
44
+ /** Available on android only. */
45
+ permissionsHistoryReadStatus(request: SourceRequest): Promise<StatusResult>;
46
+ diagnosticsState(request: SourceRequest): Promise<DiagnosticsState>;
47
+ diagnosticsLastDelivery(request: SourceRequest): Promise<LastDeliveryResult>;
48
+ diagnosticsReport(request: SourceRequest): Promise<SupportReport>;
49
+ diagnosticsSend(request: SourceRequest): Promise<Empty>;
50
+ /**
51
+ * False means bootstrap did not run. The SDK reports the same thing independently as
52
+ * backgroundRefreshUnscheduled.
53
+ */
54
+ backgroundIsRegistered(request: Empty): Promise<BoolResult>;
55
+ /** Available on android only. */
56
+ backgroundCancel(request: Empty): Promise<BoolResult>;
57
+ bodyHeartRate(request: RangeRequest): Promise<HeartRateList>;
58
+ /** Its own op rather than an overload: Swift overloads log by element type and JS cannot. */
59
+ bodyLogHeartRate(request: HeartRateEntryList): Promise<RecordIdList>;
60
+ bodyLog(request: BodyEntryList): Promise<RecordIdList>;
61
+ bodyDelete(request: RecordIdRequest): Promise<Empty>;
62
+ dailySteps(request: RangeRequest): Promise<StepCountList>;
63
+ dailyLog(request: StepEntryList): Promise<RecordIdList>;
64
+ dailyDelete(request: RecordIdRequest): Promise<Empty>;
65
+ sleepSegments(request: RangeRequest): Promise<SleepSegmentList>;
66
+ sleepLog(request: SleepEntryList): Promise<RecordIdList>;
67
+ sleepDelete(request: RecordIdRequest): Promise<Empty>;
68
+ activityWorkouts(request: RangeRequest): Promise<WorkoutList>;
69
+ activityLog(request: WorkoutEntryList): Promise<RecordIdList>;
70
+ activityDelete(request: RecordIdRequest): Promise<Empty>;
71
+ nutritionLog(request: NutritionEntryList): Promise<RecordIdList>;
72
+ nutritionDelete(request: RecordIdRequest): Promise<Empty>;
73
+ /** Available on ios only. */
74
+ plannedWorkoutsPlanned(request: Empty): Promise<PlannedWorkoutList>;
75
+ /** Available on ios only. */
76
+ plannedWorkoutsMarkComplete(request: MarkCompleteRequest): Promise<Empty>;
77
+ /** Available on ios only. */
78
+ plannedWorkoutsDelete(request: RecordIdRequest): Promise<Empty>;
79
+ /**
80
+ * Never prompts.
81
+ *
82
+ * Available on ios only.
83
+ */
84
+ plannedWorkoutsSchedulingStatus(request: Empty): Promise<WorkoutScheduling>;
85
+ /**
86
+ * Shows WorkoutKit's sheet if the person has not been asked, and reconciles straight away once
87
+ * authorized.
88
+ *
89
+ * Available on ios only.
90
+ */
91
+ plannedWorkoutsRequestScheduling(request: Empty): Promise<WorkoutScheduling>;
92
+ }