hyperstack-react 0.4.2 → 0.5.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/dist/index.d.ts +24 -17
- package/dist/index.esm.js +492 -117
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +492 -117
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7075,6 +7075,31 @@ class FrameProcessor {
|
|
|
7075
7075
|
? DEFAULT_MAX_ENTRIES_PER_VIEW
|
|
7076
7076
|
: config.maxEntriesPerView;
|
|
7077
7077
|
this.flushIntervalMs = config.flushIntervalMs ?? 0;
|
|
7078
|
+
this.schemas = config.schemas;
|
|
7079
|
+
}
|
|
7080
|
+
getSchema(viewPath) {
|
|
7081
|
+
const schemas = this.schemas;
|
|
7082
|
+
if (!schemas)
|
|
7083
|
+
return null;
|
|
7084
|
+
const entityName = viewPath.split('/')[0];
|
|
7085
|
+
if (typeof entityName !== 'string' || entityName.length === 0)
|
|
7086
|
+
return null;
|
|
7087
|
+
const entityKey = entityName;
|
|
7088
|
+
return schemas[entityKey] ?? null;
|
|
7089
|
+
}
|
|
7090
|
+
validateEntity(viewPath, data) {
|
|
7091
|
+
const schema = this.getSchema(viewPath);
|
|
7092
|
+
if (!schema)
|
|
7093
|
+
return true;
|
|
7094
|
+
const result = schema.safeParse(data);
|
|
7095
|
+
if (!result.success) {
|
|
7096
|
+
console.warn('[Hyperstack] Frame validation failed:', {
|
|
7097
|
+
view: viewPath,
|
|
7098
|
+
error: result.error,
|
|
7099
|
+
});
|
|
7100
|
+
return false;
|
|
7101
|
+
}
|
|
7102
|
+
return true;
|
|
7078
7103
|
}
|
|
7079
7104
|
handleFrame(frame) {
|
|
7080
7105
|
if (this.flushIntervalMs === 0) {
|
|
@@ -7170,6 +7195,9 @@ class FrameProcessor {
|
|
|
7170
7195
|
handleSnapshotFrameWithoutEnforce(frame) {
|
|
7171
7196
|
const viewPath = frame.entity;
|
|
7172
7197
|
for (const entity of frame.data) {
|
|
7198
|
+
if (!this.validateEntity(viewPath, entity.data)) {
|
|
7199
|
+
continue;
|
|
7200
|
+
}
|
|
7173
7201
|
const previousValue = this.storage.get(viewPath, entity.key);
|
|
7174
7202
|
this.storage.set(viewPath, entity.key, entity.data);
|
|
7175
7203
|
this.storage.notifyUpdate(viewPath, entity.key, {
|
|
@@ -7190,6 +7218,9 @@ class FrameProcessor {
|
|
|
7190
7218
|
switch (frame.op) {
|
|
7191
7219
|
case 'create':
|
|
7192
7220
|
case 'upsert':
|
|
7221
|
+
if (!this.validateEntity(viewPath, frame.data)) {
|
|
7222
|
+
break;
|
|
7223
|
+
}
|
|
7193
7224
|
this.storage.set(viewPath, frame.key, frame.data);
|
|
7194
7225
|
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
7195
7226
|
type: 'upsert',
|
|
@@ -7204,6 +7235,9 @@ class FrameProcessor {
|
|
|
7204
7235
|
const merged = existing
|
|
7205
7236
|
? deepMergeWithAppend$1(existing, frame.data, appendPaths)
|
|
7206
7237
|
: frame.data;
|
|
7238
|
+
if (!this.validateEntity(viewPath, merged)) {
|
|
7239
|
+
break;
|
|
7240
|
+
}
|
|
7207
7241
|
this.storage.set(viewPath, frame.key, merged);
|
|
7208
7242
|
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
7209
7243
|
type: 'patch',
|
|
@@ -7692,7 +7726,8 @@ function createUpdateStream(storage, subscriptionRegistry, subscription, keyFilt
|
|
|
7692
7726
|
},
|
|
7693
7727
|
};
|
|
7694
7728
|
}
|
|
7695
|
-
function createEntityStream(storage, subscriptionRegistry, subscription, keyFilter) {
|
|
7729
|
+
function createEntityStream(storage, subscriptionRegistry, subscription, options, keyFilter) {
|
|
7730
|
+
const schema = options?.schema;
|
|
7696
7731
|
return {
|
|
7697
7732
|
[Symbol.asyncIterator]() {
|
|
7698
7733
|
const queue = [];
|
|
@@ -7708,16 +7743,27 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
|
|
|
7708
7743
|
if (update.type === 'deleted')
|
|
7709
7744
|
return;
|
|
7710
7745
|
const entity = (update.type === 'created' ? update.data : update.after);
|
|
7746
|
+
let output;
|
|
7747
|
+
if (schema) {
|
|
7748
|
+
const parsed = schema.safeParse(entity);
|
|
7749
|
+
if (!parsed.success) {
|
|
7750
|
+
return;
|
|
7751
|
+
}
|
|
7752
|
+
output = parsed.data;
|
|
7753
|
+
}
|
|
7754
|
+
else {
|
|
7755
|
+
output = entity;
|
|
7756
|
+
}
|
|
7711
7757
|
if (waitingResolve) {
|
|
7712
7758
|
const resolve = waitingResolve;
|
|
7713
7759
|
waitingResolve = null;
|
|
7714
|
-
resolve({ value:
|
|
7760
|
+
resolve({ value: output, done: false });
|
|
7715
7761
|
}
|
|
7716
7762
|
else {
|
|
7717
7763
|
if (queue.length >= MAX_QUEUE_SIZE) {
|
|
7718
7764
|
queue.shift();
|
|
7719
7765
|
}
|
|
7720
|
-
queue.push(
|
|
7766
|
+
queue.push(output);
|
|
7721
7767
|
}
|
|
7722
7768
|
};
|
|
7723
7769
|
const start = () => {
|
|
@@ -7730,7 +7776,7 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
|
|
|
7730
7776
|
unsubscribeRegistry?.();
|
|
7731
7777
|
};
|
|
7732
7778
|
start();
|
|
7733
|
-
|
|
7779
|
+
const iterator = {
|
|
7734
7780
|
async next() {
|
|
7735
7781
|
if (done) {
|
|
7736
7782
|
return { value: undefined, done: true };
|
|
@@ -7752,6 +7798,7 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
|
|
|
7752
7798
|
throw error;
|
|
7753
7799
|
},
|
|
7754
7800
|
};
|
|
7801
|
+
return iterator;
|
|
7755
7802
|
},
|
|
7756
7803
|
};
|
|
7757
7804
|
}
|
|
@@ -7823,13 +7870,16 @@ function createRichUpdateStream(storage, subscriptionRegistry, subscription, key
|
|
|
7823
7870
|
function createTypedStateView(viewDef, storage, subscriptionRegistry) {
|
|
7824
7871
|
return {
|
|
7825
7872
|
use(key, options) {
|
|
7826
|
-
|
|
7873
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7874
|
+
return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, options, key);
|
|
7827
7875
|
},
|
|
7828
7876
|
watch(key, options) {
|
|
7829
|
-
|
|
7877
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7878
|
+
return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, key);
|
|
7830
7879
|
},
|
|
7831
7880
|
watchRich(key, options) {
|
|
7832
|
-
|
|
7881
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7882
|
+
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, key);
|
|
7833
7883
|
},
|
|
7834
7884
|
async get(key) {
|
|
7835
7885
|
return storage.get(viewDef.view, key);
|
|
@@ -7842,13 +7892,16 @@ function createTypedStateView(viewDef, storage, subscriptionRegistry) {
|
|
|
7842
7892
|
function createTypedListView(viewDef, storage, subscriptionRegistry) {
|
|
7843
7893
|
return {
|
|
7844
7894
|
use(options) {
|
|
7845
|
-
|
|
7895
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7896
|
+
return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions }, options);
|
|
7846
7897
|
},
|
|
7847
7898
|
watch(options) {
|
|
7848
|
-
|
|
7899
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7900
|
+
return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions });
|
|
7849
7901
|
},
|
|
7850
7902
|
watchRich(options) {
|
|
7851
|
-
|
|
7903
|
+
const { schema: _schema, ...subscriptionOptions } = options ?? {};
|
|
7904
|
+
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions });
|
|
7852
7905
|
},
|
|
7853
7906
|
async get() {
|
|
7854
7907
|
return storage.getAll(viewDef.view);
|
|
@@ -7876,39 +7929,335 @@ function createTypedViews(stack, storage, subscriptionRegistry) {
|
|
|
7876
7929
|
return views;
|
|
7877
7930
|
}
|
|
7878
7931
|
|
|
7932
|
+
/**
|
|
7933
|
+
* PDA (Program Derived Address) derivation utilities.
|
|
7934
|
+
*
|
|
7935
|
+
* Implements Solana's PDA derivation algorithm without depending on @solana/web3.js.
|
|
7936
|
+
*/
|
|
7937
|
+
// Base58 alphabet (Bitcoin/Solana style)
|
|
7938
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
7939
|
+
/**
|
|
7940
|
+
* Decode base58 string to Uint8Array.
|
|
7941
|
+
*/
|
|
7942
|
+
function decodeBase58(str) {
|
|
7943
|
+
if (str.length === 0) {
|
|
7944
|
+
return new Uint8Array(0);
|
|
7945
|
+
}
|
|
7946
|
+
const bytes = [0];
|
|
7947
|
+
for (const char of str) {
|
|
7948
|
+
const value = BASE58_ALPHABET.indexOf(char);
|
|
7949
|
+
if (value === -1) {
|
|
7950
|
+
throw new Error('Invalid base58 character: ' + char);
|
|
7951
|
+
}
|
|
7952
|
+
let carry = value;
|
|
7953
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
7954
|
+
carry += (bytes[i] ?? 0) * 58;
|
|
7955
|
+
bytes[i] = carry & 0xff;
|
|
7956
|
+
carry >>= 8;
|
|
7957
|
+
}
|
|
7958
|
+
while (carry > 0) {
|
|
7959
|
+
bytes.push(carry & 0xff);
|
|
7960
|
+
carry >>= 8;
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
// Add leading zeros for each leading '1' in input
|
|
7964
|
+
for (const char of str) {
|
|
7965
|
+
if (char !== '1')
|
|
7966
|
+
break;
|
|
7967
|
+
bytes.push(0);
|
|
7968
|
+
}
|
|
7969
|
+
return new Uint8Array(bytes.reverse());
|
|
7970
|
+
}
|
|
7971
|
+
/**
|
|
7972
|
+
* Encode Uint8Array to base58 string.
|
|
7973
|
+
*/
|
|
7974
|
+
function encodeBase58(bytes) {
|
|
7975
|
+
if (bytes.length === 0) {
|
|
7976
|
+
return '';
|
|
7977
|
+
}
|
|
7978
|
+
const digits = [0];
|
|
7979
|
+
for (const byte of bytes) {
|
|
7980
|
+
let carry = byte;
|
|
7981
|
+
for (let i = 0; i < digits.length; i++) {
|
|
7982
|
+
carry += (digits[i] ?? 0) << 8;
|
|
7983
|
+
digits[i] = carry % 58;
|
|
7984
|
+
carry = (carry / 58) | 0;
|
|
7985
|
+
}
|
|
7986
|
+
while (carry > 0) {
|
|
7987
|
+
digits.push(carry % 58);
|
|
7988
|
+
carry = (carry / 58) | 0;
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
// Add leading zeros for each leading 0 byte in input
|
|
7992
|
+
for (const byte of bytes) {
|
|
7993
|
+
if (byte !== 0)
|
|
7994
|
+
break;
|
|
7995
|
+
digits.push(0);
|
|
7996
|
+
}
|
|
7997
|
+
return digits.reverse().map(d => BASE58_ALPHABET[d]).join('');
|
|
7998
|
+
}
|
|
7999
|
+
/**
|
|
8000
|
+
* SHA-256 hash function (synchronous, Node.js).
|
|
8001
|
+
*/
|
|
8002
|
+
function sha256Sync(data) {
|
|
8003
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8004
|
+
const { createHash } = require('crypto');
|
|
8005
|
+
return new Uint8Array(createHash('sha256').update(Buffer.from(data)).digest());
|
|
8006
|
+
}
|
|
8007
|
+
/**
|
|
8008
|
+
* SHA-256 hash function (async, works in browser and Node.js).
|
|
8009
|
+
*/
|
|
8010
|
+
async function sha256Async(data) {
|
|
8011
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) {
|
|
8012
|
+
// Create a copy of the data to ensure we have an ArrayBuffer
|
|
8013
|
+
const copy = new Uint8Array(data);
|
|
8014
|
+
const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', copy);
|
|
8015
|
+
return new Uint8Array(hashBuffer);
|
|
8016
|
+
}
|
|
8017
|
+
return sha256Sync(data);
|
|
8018
|
+
}
|
|
8019
|
+
/**
|
|
8020
|
+
* PDA marker bytes appended to seeds before hashing.
|
|
8021
|
+
*/
|
|
8022
|
+
const PDA_MARKER = new TextEncoder().encode('ProgramDerivedAddress');
|
|
8023
|
+
/**
|
|
8024
|
+
* Build the hash input buffer for PDA derivation.
|
|
8025
|
+
*/
|
|
8026
|
+
function buildPdaBuffer(seeds, programIdBytes, bump) {
|
|
8027
|
+
const totalLength = seeds.reduce((sum, s) => sum + s.length, 0)
|
|
8028
|
+
+ 1 // bump
|
|
8029
|
+
+ 32 // programId
|
|
8030
|
+
+ PDA_MARKER.length;
|
|
8031
|
+
const buffer = new Uint8Array(totalLength);
|
|
8032
|
+
let offset = 0;
|
|
8033
|
+
// Copy seeds
|
|
8034
|
+
for (const seed of seeds) {
|
|
8035
|
+
buffer.set(seed, offset);
|
|
8036
|
+
offset += seed.length;
|
|
8037
|
+
}
|
|
8038
|
+
// Add bump seed
|
|
8039
|
+
buffer[offset++] = bump;
|
|
8040
|
+
// Add program ID
|
|
8041
|
+
buffer.set(programIdBytes, offset);
|
|
8042
|
+
offset += 32;
|
|
8043
|
+
// Add PDA marker
|
|
8044
|
+
buffer.set(PDA_MARKER, offset);
|
|
8045
|
+
return buffer;
|
|
8046
|
+
}
|
|
8047
|
+
/**
|
|
8048
|
+
* Validate seeds before PDA derivation.
|
|
8049
|
+
*/
|
|
8050
|
+
function validateSeeds(seeds) {
|
|
8051
|
+
if (seeds.length > 16) {
|
|
8052
|
+
throw new Error('Maximum of 16 seeds allowed');
|
|
8053
|
+
}
|
|
8054
|
+
for (let i = 0; i < seeds.length; i++) {
|
|
8055
|
+
const seed = seeds[i];
|
|
8056
|
+
if (seed && seed.length > 32) {
|
|
8057
|
+
throw new Error('Seed ' + i + ' exceeds maximum length of 32 bytes');
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
/**
|
|
8062
|
+
* Derives a Program-Derived Address (PDA) from seeds and program ID.
|
|
8063
|
+
*
|
|
8064
|
+
* Algorithm:
|
|
8065
|
+
* 1. For bump = 255 down to 0:
|
|
8066
|
+
* a. Concatenate: seeds + [bump] + programId + "ProgramDerivedAddress"
|
|
8067
|
+
* b. SHA-256 hash the concatenation
|
|
8068
|
+
* c. If result is off the ed25519 curve, return it
|
|
8069
|
+
* 2. If no valid PDA found after 256 attempts, throw error
|
|
8070
|
+
*
|
|
8071
|
+
* @param seeds - Array of seed buffers (max 32 bytes each, max 16 seeds)
|
|
8072
|
+
* @param programId - The program ID (base58 string)
|
|
8073
|
+
* @returns Tuple of [derivedAddress (base58), bumpSeed]
|
|
8074
|
+
*/
|
|
8075
|
+
async function findProgramAddress(seeds, programId) {
|
|
8076
|
+
validateSeeds(seeds);
|
|
8077
|
+
const programIdBytes = decodeBase58(programId);
|
|
8078
|
+
if (programIdBytes.length !== 32) {
|
|
8079
|
+
throw new Error('Program ID must be 32 bytes');
|
|
8080
|
+
}
|
|
8081
|
+
// Try bump seeds from 255 down to 0
|
|
8082
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
8083
|
+
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
8084
|
+
const hash = await sha256Async(buffer);
|
|
8085
|
+
{
|
|
8086
|
+
return [encodeBase58(hash), bump];
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
throw new Error('Unable to find a valid PDA');
|
|
8090
|
+
}
|
|
8091
|
+
/**
|
|
8092
|
+
* Synchronous version of findProgramAddress.
|
|
8093
|
+
* Uses synchronous SHA-256 (Node.js crypto module).
|
|
8094
|
+
*/
|
|
8095
|
+
function findProgramAddressSync(seeds, programId) {
|
|
8096
|
+
validateSeeds(seeds);
|
|
8097
|
+
const programIdBytes = decodeBase58(programId);
|
|
8098
|
+
if (programIdBytes.length !== 32) {
|
|
8099
|
+
throw new Error('Program ID must be 32 bytes');
|
|
8100
|
+
}
|
|
8101
|
+
// Try bump seeds from 255 down to 0
|
|
8102
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
8103
|
+
const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
|
|
8104
|
+
const hash = sha256Sync(buffer);
|
|
8105
|
+
{
|
|
8106
|
+
return [encodeBase58(hash), bump];
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
throw new Error('Unable to find a valid PDA');
|
|
8110
|
+
}
|
|
8111
|
+
/**
|
|
8112
|
+
* Creates a seed buffer from various input types.
|
|
8113
|
+
*
|
|
8114
|
+
* @param value - The value to convert to a seed
|
|
8115
|
+
* @returns Uint8Array suitable for PDA derivation
|
|
8116
|
+
*/
|
|
8117
|
+
function createSeed(value) {
|
|
8118
|
+
if (value instanceof Uint8Array) {
|
|
8119
|
+
return value;
|
|
8120
|
+
}
|
|
8121
|
+
if (typeof value === 'string') {
|
|
8122
|
+
return new TextEncoder().encode(value);
|
|
8123
|
+
}
|
|
8124
|
+
if (typeof value === 'bigint') {
|
|
8125
|
+
// Convert bigint to 8-byte buffer (u64 little-endian)
|
|
8126
|
+
const buffer = new Uint8Array(8);
|
|
8127
|
+
let n = value;
|
|
8128
|
+
for (let i = 0; i < 8; i++) {
|
|
8129
|
+
buffer[i] = Number(n & BigInt(0xff));
|
|
8130
|
+
n >>= BigInt(8);
|
|
8131
|
+
}
|
|
8132
|
+
return buffer;
|
|
8133
|
+
}
|
|
8134
|
+
if (typeof value === 'number') {
|
|
8135
|
+
// Assume u64
|
|
8136
|
+
return createSeed(BigInt(value));
|
|
8137
|
+
}
|
|
8138
|
+
throw new Error('Cannot create seed from value');
|
|
8139
|
+
}
|
|
8140
|
+
/**
|
|
8141
|
+
* Creates a public key seed from a base58-encoded address.
|
|
8142
|
+
*
|
|
8143
|
+
* @param address - Base58-encoded public key
|
|
8144
|
+
* @returns 32-byte Uint8Array
|
|
8145
|
+
*/
|
|
8146
|
+
function createPublicKeySeed(address) {
|
|
8147
|
+
const decoded = decodeBase58(address);
|
|
8148
|
+
if (decoded.length !== 32) {
|
|
8149
|
+
throw new Error('Invalid public key length: expected 32, got ' + decoded.length);
|
|
8150
|
+
}
|
|
8151
|
+
return decoded;
|
|
8152
|
+
}
|
|
8153
|
+
|
|
8154
|
+
/**
|
|
8155
|
+
* Topologically sort accounts so that dependencies (accountRef) are resolved first.
|
|
8156
|
+
* Non-PDA accounts come first, then PDAs in dependency order.
|
|
8157
|
+
*/
|
|
8158
|
+
function sortAccountsByDependency(accountMetas) {
|
|
8159
|
+
// Separate non-PDA and PDA accounts
|
|
8160
|
+
const nonPda = [];
|
|
8161
|
+
const pda = [];
|
|
8162
|
+
for (const meta of accountMetas) {
|
|
8163
|
+
if (meta.category === 'pda') {
|
|
8164
|
+
pda.push(meta);
|
|
8165
|
+
}
|
|
8166
|
+
else {
|
|
8167
|
+
nonPda.push(meta);
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
// Build dependency graph for PDAs
|
|
8171
|
+
const pdaDeps = new Map();
|
|
8172
|
+
for (const meta of pda) {
|
|
8173
|
+
const deps = new Set();
|
|
8174
|
+
if (meta.pdaConfig) {
|
|
8175
|
+
for (const seed of meta.pdaConfig.seeds) {
|
|
8176
|
+
if (seed.type === 'accountRef') {
|
|
8177
|
+
deps.add(seed.accountName);
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
pdaDeps.set(meta.name, deps);
|
|
8182
|
+
}
|
|
8183
|
+
// Topological sort PDAs
|
|
8184
|
+
const sortedPda = [];
|
|
8185
|
+
const visited = new Set();
|
|
8186
|
+
const visiting = new Set();
|
|
8187
|
+
function visit(name) {
|
|
8188
|
+
if (visited.has(name))
|
|
8189
|
+
return;
|
|
8190
|
+
if (visiting.has(name)) {
|
|
8191
|
+
throw new Error('Circular dependency in PDA accounts: ' + name);
|
|
8192
|
+
}
|
|
8193
|
+
const meta = pda.find(m => m.name === name);
|
|
8194
|
+
if (!meta)
|
|
8195
|
+
return; // Not a PDA, skip
|
|
8196
|
+
visiting.add(name);
|
|
8197
|
+
const deps = pdaDeps.get(name) || new Set();
|
|
8198
|
+
for (const dep of deps) {
|
|
8199
|
+
// Only visit if dep is also a PDA
|
|
8200
|
+
if (pda.some(m => m.name === dep)) {
|
|
8201
|
+
visit(dep);
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
8204
|
+
visiting.delete(name);
|
|
8205
|
+
visited.add(name);
|
|
8206
|
+
sortedPda.push(meta);
|
|
8207
|
+
}
|
|
8208
|
+
for (const meta of pda) {
|
|
8209
|
+
visit(meta.name);
|
|
8210
|
+
}
|
|
8211
|
+
return [...nonPda, ...sortedPda];
|
|
8212
|
+
}
|
|
7879
8213
|
/**
|
|
7880
8214
|
* Resolves instruction accounts by categorizing and deriving addresses.
|
|
7881
8215
|
*
|
|
8216
|
+
* Resolution order:
|
|
8217
|
+
* 1. Non-PDA accounts (signer, known, userProvided) are resolved first
|
|
8218
|
+
* 2. PDA accounts are resolved in dependency order (accounts they reference come first)
|
|
8219
|
+
*
|
|
7882
8220
|
* @param accountMetas - Account metadata from the instruction definition
|
|
7883
|
-
* @param args - Instruction arguments (used for PDA derivation)
|
|
7884
|
-
* @param options - Resolution options including wallet
|
|
8221
|
+
* @param args - Instruction arguments (used for PDA derivation with argRef seeds)
|
|
8222
|
+
* @param options - Resolution options including wallet, user-provided accounts, and programId
|
|
7885
8223
|
* @returns Resolved accounts and any missing required accounts
|
|
7886
8224
|
*/
|
|
7887
8225
|
function resolveAccounts(accountMetas, args, options) {
|
|
7888
|
-
|
|
8226
|
+
// Sort accounts by dependency
|
|
8227
|
+
const sorted = sortAccountsByDependency(accountMetas);
|
|
8228
|
+
// Track resolved accounts for PDA accountRef lookups
|
|
8229
|
+
const resolvedMap = {};
|
|
7889
8230
|
const missing = [];
|
|
7890
|
-
for (const meta of
|
|
7891
|
-
const resolvedAccount = resolveSingleAccount(meta, args, options);
|
|
8231
|
+
for (const meta of sorted) {
|
|
8232
|
+
const resolvedAccount = resolveSingleAccount(meta, args, options, resolvedMap);
|
|
7892
8233
|
if (resolvedAccount) {
|
|
7893
|
-
|
|
8234
|
+
resolvedMap[meta.name] = resolvedAccount;
|
|
7894
8235
|
}
|
|
7895
8236
|
else if (!meta.isOptional) {
|
|
7896
8237
|
missing.push(meta.name);
|
|
7897
8238
|
}
|
|
7898
8239
|
}
|
|
8240
|
+
// Return accounts in original order (as defined in accountMetas)
|
|
8241
|
+
const orderedAccounts = [];
|
|
8242
|
+
for (const meta of accountMetas) {
|
|
8243
|
+
const resolved = resolvedMap[meta.name];
|
|
8244
|
+
if (resolved) {
|
|
8245
|
+
orderedAccounts.push(resolved);
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
7899
8248
|
return {
|
|
7900
|
-
accounts:
|
|
8249
|
+
accounts: orderedAccounts,
|
|
7901
8250
|
missingUserAccounts: missing,
|
|
7902
8251
|
};
|
|
7903
8252
|
}
|
|
7904
|
-
function resolveSingleAccount(meta, args, options) {
|
|
8253
|
+
function resolveSingleAccount(meta, args, options, resolvedMap) {
|
|
7905
8254
|
switch (meta.category) {
|
|
7906
8255
|
case 'signer':
|
|
7907
8256
|
return resolveSignerAccount(meta, options.wallet);
|
|
7908
8257
|
case 'known':
|
|
7909
8258
|
return resolveKnownAccount(meta);
|
|
7910
8259
|
case 'pda':
|
|
7911
|
-
return resolvePdaAccount(meta);
|
|
8260
|
+
return resolvePdaAccount(meta, args, resolvedMap, options.programId);
|
|
7912
8261
|
case 'userProvided':
|
|
7913
8262
|
return resolveUserProvidedAccount(meta, options.accounts);
|
|
7914
8263
|
default:
|
|
@@ -7937,15 +8286,51 @@ function resolveKnownAccount(meta) {
|
|
|
7937
8286
|
isWritable: meta.isWritable,
|
|
7938
8287
|
};
|
|
7939
8288
|
}
|
|
7940
|
-
function resolvePdaAccount(meta, args) {
|
|
8289
|
+
function resolvePdaAccount(meta, args, resolvedMap, programId) {
|
|
7941
8290
|
if (!meta.pdaConfig) {
|
|
7942
8291
|
return null;
|
|
7943
8292
|
}
|
|
7944
|
-
//
|
|
7945
|
-
|
|
8293
|
+
// Determine which program to derive against
|
|
8294
|
+
const pdaProgramId = meta.pdaConfig.programId || programId;
|
|
8295
|
+
if (!pdaProgramId) {
|
|
8296
|
+
throw new Error('Cannot derive PDA for "' + meta.name + '": no programId specified. ' +
|
|
8297
|
+
'Either set pdaConfig.programId or pass programId in options.');
|
|
8298
|
+
}
|
|
8299
|
+
// Build seeds array
|
|
8300
|
+
const seeds = [];
|
|
8301
|
+
for (const seed of meta.pdaConfig.seeds) {
|
|
8302
|
+
switch (seed.type) {
|
|
8303
|
+
case 'literal':
|
|
8304
|
+
seeds.push(createSeed(seed.value));
|
|
8305
|
+
break;
|
|
8306
|
+
case 'argRef': {
|
|
8307
|
+
const argValue = args[seed.argName];
|
|
8308
|
+
if (argValue === undefined) {
|
|
8309
|
+
throw new Error('PDA seed references missing argument: ' + seed.argName +
|
|
8310
|
+
' (for account "' + meta.name + '")');
|
|
8311
|
+
}
|
|
8312
|
+
seeds.push(createSeed(argValue));
|
|
8313
|
+
break;
|
|
8314
|
+
}
|
|
8315
|
+
case 'accountRef': {
|
|
8316
|
+
const refAccount = resolvedMap[seed.accountName];
|
|
8317
|
+
if (!refAccount) {
|
|
8318
|
+
throw new Error('PDA seed references unresolved account: ' + seed.accountName +
|
|
8319
|
+
' (for account "' + meta.name + '")');
|
|
8320
|
+
}
|
|
8321
|
+
// Account addresses are 32 bytes
|
|
8322
|
+
seeds.push(decodeBase58(refAccount.address));
|
|
8323
|
+
break;
|
|
8324
|
+
}
|
|
8325
|
+
default:
|
|
8326
|
+
throw new Error('Unknown seed type');
|
|
8327
|
+
}
|
|
8328
|
+
}
|
|
8329
|
+
// Derive the PDA
|
|
8330
|
+
const [derivedAddress] = findProgramAddressSync(seeds, pdaProgramId);
|
|
7946
8331
|
return {
|
|
7947
8332
|
name: meta.name,
|
|
7948
|
-
address:
|
|
8333
|
+
address: derivedAddress,
|
|
7949
8334
|
isSigner: meta.isSigner,
|
|
7950
8335
|
isWritable: meta.isWritable,
|
|
7951
8336
|
};
|
|
@@ -7970,86 +8355,10 @@ function resolveUserProvidedAccount(meta, accounts) {
|
|
|
7970
8355
|
*/
|
|
7971
8356
|
function validateAccountResolution(result) {
|
|
7972
8357
|
if (result.missingUserAccounts.length > 0) {
|
|
7973
|
-
throw new Error(
|
|
8358
|
+
throw new Error('Missing required accounts: ' + result.missingUserAccounts.join(', '));
|
|
7974
8359
|
}
|
|
7975
8360
|
}
|
|
7976
8361
|
|
|
7977
|
-
/**
|
|
7978
|
-
* Derives a Program-Derived Address (PDA) from seeds and program ID.
|
|
7979
|
-
*
|
|
7980
|
-
* This function implements PDA derivation using the Solana algorithm:
|
|
7981
|
-
* 1. Concatenate all seeds
|
|
7982
|
-
* 2. Hash with SHA-256
|
|
7983
|
-
* 3. Check if result is off-curve (valid PDA)
|
|
7984
|
-
*
|
|
7985
|
-
* Note: This is a placeholder implementation. In production, you would use
|
|
7986
|
-
* the actual Solana web3.js library's PDA derivation.
|
|
7987
|
-
*
|
|
7988
|
-
* @param seeds - Array of seed buffers
|
|
7989
|
-
* @param programId - The program ID (as base58 string)
|
|
7990
|
-
* @returns The derived PDA address (base58 string)
|
|
7991
|
-
*/
|
|
7992
|
-
async function derivePda(seeds, programId) {
|
|
7993
|
-
// In production, this would use:
|
|
7994
|
-
// PublicKey.findProgramAddressSync(seeds, new PublicKey(programId))
|
|
7995
|
-
// For now, return a placeholder that will be replaced with actual implementation
|
|
7996
|
-
const combined = Buffer.concat(seeds);
|
|
7997
|
-
// Simulate PDA derivation (this is NOT the actual algorithm)
|
|
7998
|
-
const hash = await simulateHash(combined);
|
|
7999
|
-
// Return base58-encoded address
|
|
8000
|
-
return bs58Encode(hash);
|
|
8001
|
-
}
|
|
8002
|
-
/**
|
|
8003
|
-
* Creates a seed buffer from various input types.
|
|
8004
|
-
*
|
|
8005
|
-
* @param value - The value to convert to a seed
|
|
8006
|
-
* @returns Buffer suitable for PDA derivation
|
|
8007
|
-
*/
|
|
8008
|
-
function createSeed(value) {
|
|
8009
|
-
if (Buffer.isBuffer(value)) {
|
|
8010
|
-
return value;
|
|
8011
|
-
}
|
|
8012
|
-
if (value instanceof Uint8Array) {
|
|
8013
|
-
return Buffer.from(value);
|
|
8014
|
-
}
|
|
8015
|
-
if (typeof value === 'string') {
|
|
8016
|
-
return Buffer.from(value, 'utf-8');
|
|
8017
|
-
}
|
|
8018
|
-
if (typeof value === 'bigint') {
|
|
8019
|
-
// Convert bigint to 8-byte buffer (u64)
|
|
8020
|
-
const buffer = Buffer.alloc(8);
|
|
8021
|
-
buffer.writeBigUInt64LE(value);
|
|
8022
|
-
return buffer;
|
|
8023
|
-
}
|
|
8024
|
-
throw new Error(`Cannot create seed from type: ${typeof value}`);
|
|
8025
|
-
}
|
|
8026
|
-
/**
|
|
8027
|
-
* Creates a public key seed from a base58-encoded address.
|
|
8028
|
-
*
|
|
8029
|
-
* @param address - Base58-encoded public key
|
|
8030
|
-
* @returns 32-byte buffer
|
|
8031
|
-
*/
|
|
8032
|
-
function createPublicKeySeed(address) {
|
|
8033
|
-
// In production, decode base58 to 32-byte buffer
|
|
8034
|
-
// For now, return placeholder
|
|
8035
|
-
return Buffer.alloc(32);
|
|
8036
|
-
}
|
|
8037
|
-
async function simulateHash(data) {
|
|
8038
|
-
// In production, use actual SHA-256
|
|
8039
|
-
// This is a placeholder
|
|
8040
|
-
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
8041
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
8042
|
-
return Buffer.from(hashBuffer);
|
|
8043
|
-
}
|
|
8044
|
-
// Fallback for Node.js
|
|
8045
|
-
return Buffer.alloc(32, 0);
|
|
8046
|
-
}
|
|
8047
|
-
function bs58Encode(buffer) {
|
|
8048
|
-
// In production, use actual base58 encoding
|
|
8049
|
-
// This is a placeholder
|
|
8050
|
-
return 'P' + buffer.toString('hex').slice(0, 31);
|
|
8051
|
-
}
|
|
8052
|
-
|
|
8053
8362
|
/**
|
|
8054
8363
|
* Borsh-compatible instruction data serializer.
|
|
8055
8364
|
*
|
|
@@ -8140,6 +8449,7 @@ function serializePrimitive(value, type) {
|
|
|
8140
8449
|
strLen.writeUInt32LE(strBytes.length, 0);
|
|
8141
8450
|
return Buffer.concat([strLen, strBytes]);
|
|
8142
8451
|
case 'pubkey':
|
|
8452
|
+
// Public key is 32 bytes
|
|
8143
8453
|
// In production, decode base58 to 32 bytes
|
|
8144
8454
|
return Buffer.alloc(32, 0);
|
|
8145
8455
|
default:
|
|
@@ -8192,7 +8502,7 @@ async function waitForConfirmation(signature, level = 'confirmed', timeout = 600
|
|
|
8192
8502
|
}
|
|
8193
8503
|
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
|
|
8194
8504
|
}
|
|
8195
|
-
async function checkTransactionStatus(
|
|
8505
|
+
async function checkTransactionStatus(_signature) {
|
|
8196
8506
|
// In production, query the Solana RPC
|
|
8197
8507
|
return {
|
|
8198
8508
|
err: null,
|
|
@@ -8308,6 +8618,7 @@ async function executeInstruction(handler, args, options = {}) {
|
|
|
8308
8618
|
const resolutionOptions = {
|
|
8309
8619
|
accounts: options.accounts,
|
|
8310
8620
|
wallet: options.wallet,
|
|
8621
|
+
programId: handler.programId, // Pass programId for PDA derivation
|
|
8311
8622
|
};
|
|
8312
8623
|
const resolution = resolveAccounts(handler.accounts, args, resolutionOptions);
|
|
8313
8624
|
validateAccountResolution(resolution);
|
|
@@ -8373,6 +8684,7 @@ class HyperStack {
|
|
|
8373
8684
|
this.processor = new FrameProcessor(this.storage, {
|
|
8374
8685
|
maxEntriesPerView: options.maxEntriesPerView,
|
|
8375
8686
|
flushIntervalMs: options.flushIntervalMs,
|
|
8687
|
+
schemas: options.validateFrames ? this.stack.schemas : undefined,
|
|
8376
8688
|
});
|
|
8377
8689
|
this.connection = new ConnectionManager({
|
|
8378
8690
|
websocketUrl: url,
|
|
@@ -8410,6 +8722,7 @@ class HyperStack {
|
|
|
8410
8722
|
autoReconnect: options?.autoReconnect,
|
|
8411
8723
|
reconnectIntervals: options?.reconnectIntervals,
|
|
8412
8724
|
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
8725
|
+
validateFrames: options?.validateFrames,
|
|
8413
8726
|
};
|
|
8414
8727
|
const client = new HyperStack(url, internalOptions);
|
|
8415
8728
|
if (options?.autoReconnect !== false) {
|
|
@@ -8634,6 +8947,16 @@ const HyperstackContext = React.createContext(null);
|
|
|
8634
8947
|
function HyperstackProvider({ children, fallback = null, ...config }) {
|
|
8635
8948
|
const clientsRef = React.useRef(new Map());
|
|
8636
8949
|
const connectingRef = React.useRef(new Map());
|
|
8950
|
+
const clientChangeListenersRef = React.useRef(new Set());
|
|
8951
|
+
const notifyClientChange = React.useCallback(() => {
|
|
8952
|
+
clientChangeListenersRef.current.forEach(cb => { cb(); });
|
|
8953
|
+
}, []);
|
|
8954
|
+
const subscribeToClientChanges = React.useCallback((callback) => {
|
|
8955
|
+
clientChangeListenersRef.current.add(callback);
|
|
8956
|
+
return () => {
|
|
8957
|
+
clientChangeListenersRef.current.delete(callback);
|
|
8958
|
+
};
|
|
8959
|
+
}, []);
|
|
8637
8960
|
const getOrCreateClient = React.useCallback(async (stack, urlOverride) => {
|
|
8638
8961
|
const cacheKey = urlOverride ? `${stack.name}:${urlOverride}` : stack.name;
|
|
8639
8962
|
const existing = clientsRef.current.get(cacheKey);
|
|
@@ -8663,14 +8986,20 @@ function HyperstackProvider({ children, fallback = null, ...config }) {
|
|
|
8663
8986
|
disconnect: () => client.disconnect()
|
|
8664
8987
|
});
|
|
8665
8988
|
connectingRef.current.delete(cacheKey);
|
|
8989
|
+
notifyClientChange();
|
|
8666
8990
|
return client;
|
|
8667
8991
|
});
|
|
8668
8992
|
connectingRef.current.set(cacheKey, connectionPromise);
|
|
8669
8993
|
return connectionPromise;
|
|
8670
|
-
}, [config.autoConnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView]);
|
|
8994
|
+
}, [config.autoConnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView, notifyClientChange]);
|
|
8671
8995
|
const getClient = React.useCallback((stack) => {
|
|
8672
|
-
if (!stack)
|
|
8996
|
+
if (!stack) {
|
|
8997
|
+
if (clientsRef.current.size === 1) {
|
|
8998
|
+
const firstEntry = clientsRef.current.values().next().value;
|
|
8999
|
+
return firstEntry ? firstEntry.client : null;
|
|
9000
|
+
}
|
|
8673
9001
|
return null;
|
|
9002
|
+
}
|
|
8674
9003
|
const entry = clientsRef.current.get(stack.name);
|
|
8675
9004
|
return entry ? entry.client : null;
|
|
8676
9005
|
}, []);
|
|
@@ -8686,6 +9015,7 @@ function HyperstackProvider({ children, fallback = null, ...config }) {
|
|
|
8686
9015
|
const value = {
|
|
8687
9016
|
getOrCreateClient,
|
|
8688
9017
|
getClient,
|
|
9018
|
+
subscribeToClientChanges,
|
|
8689
9019
|
config,
|
|
8690
9020
|
};
|
|
8691
9021
|
return (React.createElement(HyperstackContext.Provider, { value: value }, children));
|
|
@@ -8698,13 +9028,38 @@ function useHyperstackContext() {
|
|
|
8698
9028
|
return context;
|
|
8699
9029
|
}
|
|
8700
9030
|
function useConnectionState(stack) {
|
|
8701
|
-
const { getClient } = useHyperstackContext();
|
|
8702
|
-
const
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
9031
|
+
const { getClient, subscribeToClientChanges } = useHyperstackContext();
|
|
9032
|
+
const [state, setState] = React.useState(() => {
|
|
9033
|
+
const client = getClient(stack);
|
|
9034
|
+
return client?.connectionState ?? 'disconnected';
|
|
9035
|
+
});
|
|
9036
|
+
const unsubscribeRef = React.useRef(undefined);
|
|
9037
|
+
React.useEffect(() => {
|
|
9038
|
+
let mounted = true;
|
|
9039
|
+
const setupClientSubscription = () => {
|
|
9040
|
+
unsubscribeRef.current?.();
|
|
9041
|
+
unsubscribeRef.current = undefined;
|
|
9042
|
+
const client = getClient(stack);
|
|
9043
|
+
if (client && mounted) {
|
|
9044
|
+
setState(client.connectionState);
|
|
9045
|
+
unsubscribeRef.current = client.onConnectionStateChange((newState) => {
|
|
9046
|
+
if (mounted)
|
|
9047
|
+
setState(newState);
|
|
9048
|
+
});
|
|
9049
|
+
}
|
|
9050
|
+
else if (mounted) {
|
|
9051
|
+
setState('disconnected');
|
|
9052
|
+
}
|
|
9053
|
+
};
|
|
9054
|
+
const unsubscribeFromClientChanges = subscribeToClientChanges(setupClientSubscription);
|
|
9055
|
+
setupClientSubscription();
|
|
9056
|
+
return () => {
|
|
9057
|
+
mounted = false;
|
|
9058
|
+
unsubscribeFromClientChanges();
|
|
9059
|
+
unsubscribeRef.current?.();
|
|
9060
|
+
};
|
|
9061
|
+
}, [getClient, subscribeToClientChanges, stack]);
|
|
9062
|
+
return state;
|
|
8708
9063
|
}
|
|
8709
9064
|
function useView(stack, viewPath) {
|
|
8710
9065
|
const { getClient } = useHyperstackContext();
|
|
@@ -8756,6 +9111,7 @@ function useStateView(viewDef, client, key, options) {
|
|
|
8756
9111
|
const cachedSnapshotRef = React.useRef(undefined);
|
|
8757
9112
|
const keyString = key ? Object.values(key)[0] : undefined;
|
|
8758
9113
|
const enabled = options?.enabled !== false;
|
|
9114
|
+
const schema = options?.schema;
|
|
8759
9115
|
React.useEffect(() => {
|
|
8760
9116
|
if (!enabled || !clientRef.current)
|
|
8761
9117
|
return undefined;
|
|
@@ -8810,12 +9166,14 @@ function useStateView(viewDef, client, key, options) {
|
|
|
8810
9166
|
const entity = keyString
|
|
8811
9167
|
? clientRef.current.store.get(viewDef.view, keyString)
|
|
8812
9168
|
: clientRef.current.store.getAll(viewDef.view)[0];
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
9169
|
+
const validated = entity && schema
|
|
9170
|
+
? (schema.safeParse(entity).success ? entity : undefined)
|
|
9171
|
+
: entity;
|
|
9172
|
+
if (validated !== cachedSnapshotRef.current) {
|
|
9173
|
+
cachedSnapshotRef.current = validated;
|
|
8816
9174
|
}
|
|
8817
9175
|
return cachedSnapshotRef.current;
|
|
8818
|
-
}, [viewDef.view, keyString, client]);
|
|
9176
|
+
}, [viewDef.view, keyString, schema, client]);
|
|
8819
9177
|
const data = React.useSyncExternalStore(subscribe, getSnapshot);
|
|
8820
9178
|
React.useEffect(() => {
|
|
8821
9179
|
if (data !== undefined && isLoading) {
|
|
@@ -8842,6 +9200,7 @@ function useListView(viewDef, client, params, options) {
|
|
|
8842
9200
|
const whereJson = params?.where ? JSON.stringify(params.where) : undefined;
|
|
8843
9201
|
const filtersJson = params?.filters ? JSON.stringify(params.filters) : undefined;
|
|
8844
9202
|
const limit = params?.limit;
|
|
9203
|
+
const schema = params?.schema;
|
|
8845
9204
|
React.useEffect(() => {
|
|
8846
9205
|
if (!enabled || !clientRef.current)
|
|
8847
9206
|
return undefined;
|
|
@@ -8932,16 +9291,18 @@ function useListView(viewDef, client, params, options) {
|
|
|
8932
9291
|
});
|
|
8933
9292
|
});
|
|
8934
9293
|
}
|
|
9294
|
+
if (schema) {
|
|
9295
|
+
items = items.filter((item) => schema.safeParse(item).success);
|
|
9296
|
+
}
|
|
8935
9297
|
if (limit) {
|
|
8936
9298
|
items = items.slice(0, limit);
|
|
8937
9299
|
}
|
|
8938
9300
|
const result = items;
|
|
8939
|
-
// Cache the result - only update if data actually changed
|
|
8940
9301
|
if (!shallowArrayEqual(cachedSnapshotRef.current, result)) {
|
|
8941
9302
|
cachedSnapshotRef.current = result;
|
|
8942
9303
|
}
|
|
8943
9304
|
return cachedSnapshotRef.current;
|
|
8944
|
-
}, [viewDef.view, whereJson, limit, client]);
|
|
9305
|
+
}, [viewDef.view, whereJson, limit, schema, client]);
|
|
8945
9306
|
const data = React.useSyncExternalStore(subscribe, getSnapshot);
|
|
8946
9307
|
React.useEffect(() => {
|
|
8947
9308
|
if (data !== undefined && isLoading) {
|
|
@@ -9040,6 +9401,7 @@ function useHyperstack(stack, options) {
|
|
|
9040
9401
|
const [client, setClient] = React.useState(getClient(stack));
|
|
9041
9402
|
const [isLoading, setIsLoading] = React.useState(!client);
|
|
9042
9403
|
const [error, setError] = React.useState(null);
|
|
9404
|
+
const [connectionState, setConnectionState] = React.useState(() => client?.connectionState ?? 'disconnected');
|
|
9043
9405
|
React.useEffect(() => {
|
|
9044
9406
|
const existingClient = getClient(stack);
|
|
9045
9407
|
if (existingClient && !urlOverride) {
|
|
@@ -9059,6 +9421,17 @@ function useHyperstack(stack, options) {
|
|
|
9059
9421
|
setIsLoading(false);
|
|
9060
9422
|
});
|
|
9061
9423
|
}, [stack, getOrCreateClient, getClient, urlOverride]);
|
|
9424
|
+
React.useEffect(() => {
|
|
9425
|
+
if (!client) {
|
|
9426
|
+
setConnectionState('disconnected');
|
|
9427
|
+
return;
|
|
9428
|
+
}
|
|
9429
|
+
setConnectionState(client.connectionState);
|
|
9430
|
+
const unsubscribe = client.onConnectionStateChange((state) => {
|
|
9431
|
+
setConnectionState(state);
|
|
9432
|
+
});
|
|
9433
|
+
return unsubscribe;
|
|
9434
|
+
}, [client]);
|
|
9062
9435
|
const views = React.useMemo(() => {
|
|
9063
9436
|
const result = {};
|
|
9064
9437
|
for (const [viewName, viewGroup] of Object.entries(stack.views)) {
|
|
@@ -9095,6 +9468,8 @@ function useHyperstack(stack, options) {
|
|
|
9095
9468
|
instructions: instructions,
|
|
9096
9469
|
zustandStore: client?.store?.store,
|
|
9097
9470
|
client: client,
|
|
9471
|
+
connectionState,
|
|
9472
|
+
isConnected: connectionState === 'connected',
|
|
9098
9473
|
isLoading,
|
|
9099
9474
|
error
|
|
9100
9475
|
};
|
|
@@ -9113,7 +9488,7 @@ exports.ZustandAdapter = ZustandAdapter;
|
|
|
9113
9488
|
exports.createInstructionExecutor = createInstructionExecutor;
|
|
9114
9489
|
exports.createPublicKeySeed = createPublicKeySeed;
|
|
9115
9490
|
exports.createSeed = createSeed;
|
|
9116
|
-
exports.derivePda =
|
|
9491
|
+
exports.derivePda = findProgramAddress;
|
|
9117
9492
|
exports.executeInstruction = executeInstruction;
|
|
9118
9493
|
exports.formatProgramError = formatProgramError;
|
|
9119
9494
|
exports.isSnapshotFrame = isSnapshotFrame;
|