@parallax-rn/sdk 0.0.0-stage → 0.1.0-beta.2

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,25 @@
1
+ import type { HostGlobal } from './host.js';
2
+ import type { SdkLimits } from './limits.js';
3
+ export interface StoreOptions {
4
+ /** Redux only: wraps `dispatch` to put the last action's `type` on the snapshot. Off by default because it changes the store object. */
5
+ readonly actions?: boolean;
6
+ }
7
+ export declare class StoreAdapters {
8
+ private readonly deps;
9
+ private readonly tracked;
10
+ private started;
11
+ constructor(deps: {
12
+ readonly emit: (source: string, data: unknown) => void;
13
+ readonly host: HostGlobal;
14
+ readonly limits: SdkLimits;
15
+ });
16
+ registerStore(name: string, store: unknown, options?: StoreOptions): () => void;
17
+ registerQueryClient(name: string, client: unknown): () => void;
18
+ /** Begins reading the registered stores (the SDK was started). */
19
+ start(): void;
20
+ stop(): void;
21
+ private checkName;
22
+ private queryCacheOf;
23
+ private add;
24
+ }
25
+ //# sourceMappingURL=stores.d.ts.map
package/dist/stores.js ADDED
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StoreAdapters = void 0;
4
+ // Store adapters (plan 0013, 13.9): `registerStore` takes anything with `getState()` and `subscribe(listener)` (Redux, Zustand, a hand-made store) and `registerQueryClient` a TanStack
5
+ // Query client (`getQueryCache().getAll()` and `.subscribe`). Duck-typed: no store library is imported. Each becomes the source `state:<name>` / `query:<name>`; an entry is a masked, size-bounded
6
+ // snapshot, at most one per `snapshotIntervalMs`, and none when nothing changed. A registration reads the app only while the SDK is started (never in a release build) and nothing it does throws into the app.
7
+ const protocol_1 = require("@parallax-rn/protocol");
8
+ const snapshot_js_1 = require("./snapshot.js");
9
+ const isObjectLike = (value) => (typeof value === 'object' || typeof value === 'function') && value !== null;
10
+ /** Calls whatever a `subscribe` returned to end the subscription: a function (Redux, Zustand) or an object with `unsubscribe` (observable style). */
11
+ function unsubscribe(handle) {
12
+ try {
13
+ if (typeof handle === 'function')
14
+ handle();
15
+ else if (isObjectLike(handle) && typeof handle.unsubscribe === 'function')
16
+ handle.unsubscribe();
17
+ }
18
+ catch {
19
+ /* ending a subscription must never break the app */
20
+ }
21
+ }
22
+ class StoreAdapters {
23
+ constructor(deps) {
24
+ this.deps = deps;
25
+ this.tracked = new Map();
26
+ this.started = false;
27
+ }
28
+ registerStore(name, store, options = {}) {
29
+ const source = `state:${this.checkName(name)}`;
30
+ if (!isObjectLike(store) || typeof store.getState !== 'function' || typeof store.subscribe !== 'function') {
31
+ throw new TypeError(`registerStore("${name}"): expected a store with getState() and subscribe(listener), like Redux's store or Zustand's hook`);
32
+ }
33
+ const target = store;
34
+ let lastAction = '';
35
+ let restore = null;
36
+ return this.add(source, 'snapshot', () => target.getState(), listener => target.subscribe(listener), () => {
37
+ const original = target.dispatch;
38
+ if (!options.actions || typeof original !== 'function')
39
+ return;
40
+ const wrapper = function (action) {
41
+ if (isObjectLike(action) && typeof action.type === 'string')
42
+ lastAction = action.type;
43
+ return original.call(target, action);
44
+ };
45
+ target.dispatch = wrapper;
46
+ restore = () => {
47
+ if (target.dispatch === wrapper)
48
+ target.dispatch = original;
49
+ };
50
+ }, () => {
51
+ if (restore)
52
+ restore();
53
+ restore = null;
54
+ }, () => {
55
+ const action = lastAction;
56
+ lastAction = '';
57
+ return action;
58
+ });
59
+ }
60
+ registerQueryClient(name, client) {
61
+ const source = `query:${this.checkName(name)}`;
62
+ const cache = isObjectLike(client) && typeof client.getQueryCache === 'function' ? this.queryCacheOf(client) : null;
63
+ if (!isObjectLike(client) || cache === null) {
64
+ throw new TypeError(`registerQueryClient("${name}"): expected a TanStack Query client with getQueryCache().getAll() and .subscribe()`);
65
+ }
66
+ const { limits } = this.deps;
67
+ return this.add(source, 'query', () => {
68
+ const queries = client.getQueryCache().getAll();
69
+ return queries.map(query => queryItem(query, limits));
70
+ }, listener => client.getQueryCache().subscribe(listener), null, null, null);
71
+ }
72
+ /** Begins reading the registered stores (the SDK was started). */
73
+ start() {
74
+ if (this.started)
75
+ return;
76
+ this.started = true;
77
+ this.tracked.forEach(tracked => tracked.start());
78
+ }
79
+ stop() {
80
+ if (!this.started)
81
+ return;
82
+ this.started = false;
83
+ this.tracked.forEach(tracked => tracked.stop());
84
+ }
85
+ checkName(name) {
86
+ if (!(0, protocol_1.isSourceName)(`state:${name}`))
87
+ throw new TypeError(`a store name is 1 to 64 letters, digits, ".", "_" or "-", not "${String(name)}"`);
88
+ return name;
89
+ }
90
+ queryCacheOf(client) {
91
+ try {
92
+ const cache = client.getQueryCache();
93
+ return isObjectLike(cache) && typeof cache.getAll === 'function' && typeof cache.subscribe === 'function' ? cache : null;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ add(source, kind, read, subscribe, onStart, onStop, takeAction) {
100
+ var _a;
101
+ const { emit, host, limits } = this.deps;
102
+ let timer = null;
103
+ let handle = null;
104
+ let active = false;
105
+ let lastJson = '';
106
+ let last;
107
+ let lastItems = {};
108
+ const take = () => {
109
+ timer = null;
110
+ if (!active)
111
+ return;
112
+ try {
113
+ const snapshot = (0, snapshot_js_1.snapshotOf)(read(), limits.stateMaxChars, kind === 'query' ? limits.stateMaxDepth + 2 : limits.stateMaxDepth);
114
+ const json = JSON.stringify(snapshot.value);
115
+ const action = takeAction ? takeAction() : '';
116
+ if (json === lastJson && action === '')
117
+ return;
118
+ const data = { kind, state: snapshot.value };
119
+ if (lastJson !== '') {
120
+ if (kind === 'query') {
121
+ const items = byHash(snapshot.value);
122
+ data.changedKeys = Object.keys(items).filter(hash => items[hash] !== lastItems[hash]).slice(0, 30);
123
+ lastItems = items;
124
+ }
125
+ else {
126
+ data.changedKeys = (0, snapshot_js_1.changedPaths)(last, snapshot.value);
127
+ }
128
+ }
129
+ else if (kind === 'query') {
130
+ lastItems = byHash(snapshot.value);
131
+ }
132
+ if (action !== '')
133
+ data.action = action.slice(0, 100);
134
+ if (snapshot.truncated)
135
+ data.truncated = true;
136
+ lastJson = json;
137
+ last = snapshot.value;
138
+ emit(source, data);
139
+ }
140
+ catch {
141
+ /* a store that throws gives no snapshot this time; the next change tries again */
142
+ }
143
+ };
144
+ const schedule = () => {
145
+ if (timer !== null || !active)
146
+ return;
147
+ if (typeof host.setTimeout !== 'function')
148
+ take();
149
+ else
150
+ timer = host.setTimeout(take, limits.snapshotIntervalMs);
151
+ };
152
+ const tracked = {
153
+ start: () => {
154
+ if (active)
155
+ return;
156
+ active = true;
157
+ try {
158
+ if (onStart)
159
+ onStart();
160
+ handle = subscribe(schedule);
161
+ }
162
+ catch {
163
+ /* a store whose subscribe throws is not followed */
164
+ }
165
+ take();
166
+ },
167
+ stop: () => {
168
+ if (!active)
169
+ return;
170
+ active = false;
171
+ if (timer !== null && typeof host.clearTimeout === 'function')
172
+ host.clearTimeout(timer);
173
+ timer = null;
174
+ unsubscribe(handle);
175
+ handle = null;
176
+ if (onStop)
177
+ onStop();
178
+ },
179
+ };
180
+ (_a = this.tracked.get(source)) === null || _a === void 0 ? void 0 : _a.stop();
181
+ this.tracked.set(source, tracked);
182
+ if (this.started)
183
+ tracked.start();
184
+ return () => {
185
+ tracked.stop();
186
+ if (this.tracked.get(source) === tracked)
187
+ this.tracked.delete(source);
188
+ };
189
+ }
190
+ }
191
+ exports.StoreAdapters = StoreAdapters;
192
+ /** One TanStack Query `Query` as a plain item: no function of the app is called, `data` is cut to its own budget. */
193
+ function queryItem(query, limits) {
194
+ const q = isObjectLike(query) ? query : {};
195
+ const state = isObjectLike(q.state) ? q.state : {};
196
+ const error = isObjectLike(state.error) && typeof state.error.message === 'string' ? state.error.message : state.error;
197
+ const data = (0, snapshot_js_1.snapshotOf)(state.data, limits.queryDataMaxChars, limits.stateMaxDepth);
198
+ return {
199
+ hash: q.queryHash,
200
+ queryKey: q.queryKey,
201
+ status: state.status,
202
+ fetchStatus: state.fetchStatus,
203
+ dataUpdatedAt: state.dataUpdatedAt,
204
+ errorUpdateCount: state.errorUpdateCount,
205
+ ...(error === undefined || error === null ? {} : { error }),
206
+ ...(state.data === undefined ? {} : { data: data.value }),
207
+ ...(data.truncated ? { dataTruncated: true } : {}),
208
+ };
209
+ }
210
+ /** The per-query JSON of a snapshot's array, by hash, to tell which queries changed. */
211
+ function byHash(state) {
212
+ const out = {};
213
+ if (!Array.isArray(state))
214
+ return out;
215
+ for (let i = 0; i < state.length; i++) {
216
+ const item = state[i];
217
+ out[typeof (item === null || item === void 0 ? void 0 : item.hash) === 'string' ? item.hash : String(i)] = JSON.stringify(item);
218
+ }
219
+ return out;
220
+ }
@@ -0,0 +1,3 @@
1
+ /** Version of this package, reported in the handshake for diagnostics. A test keeps it equal to package.json. */
2
+ export declare const SDK_VERSION = "0.1.0-beta.2";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SDK_VERSION = void 0;
4
+ /** Version of this package, reported in the handshake for diagnostics. A test keeps it equal to package.json. */
5
+ exports.SDK_VERSION = '0.1.0-beta.2';
package/package.json CHANGED
@@ -1,6 +1,21 @@
1
1
  {
2
2
  "name": "@parallax-rn/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-beta.2",
4
+ "description": "Dev-only in-app SDK for Parallax for React Native: ring buffers for logs and errors, push through a CDP binding, command registry. Tested on React Native 0.87.1 (no third-party dependencies).",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "!dist/**/*.test.*",
11
+ "!dist/testing",
12
+ "!dist/*.tsbuildinfo",
13
+ "!dist/**/*.map"
14
+ ],
15
+ "engines": {
16
+ "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >=25.0.0"
17
+ },
18
+ "dependencies": {
19
+ "@parallax-rn/protocol": "0.1.0-beta.2"
20
+ }
21
+ }