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