hyperstack-react 0.3.14 → 0.3.15
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 +50 -69
- package/dist/index.esm.js +1105 -522
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1115 -521
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7500,6 +7500,69 @@ function createUpdateStream(storage, subscriptionRegistry, subscription, keyFilt
|
|
|
7500
7500
|
},
|
|
7501
7501
|
};
|
|
7502
7502
|
}
|
|
7503
|
+
function createEntityStream(storage, subscriptionRegistry, subscription, keyFilter) {
|
|
7504
|
+
return {
|
|
7505
|
+
[Symbol.asyncIterator]() {
|
|
7506
|
+
const queue = [];
|
|
7507
|
+
let waitingResolve = null;
|
|
7508
|
+
let unsubscribeStorage = null;
|
|
7509
|
+
let unsubscribeRegistry = null;
|
|
7510
|
+
let done = false;
|
|
7511
|
+
const handler = (viewPath, key, update) => {
|
|
7512
|
+
if (viewPath !== subscription.view)
|
|
7513
|
+
return;
|
|
7514
|
+
if (keyFilter !== undefined && key !== keyFilter)
|
|
7515
|
+
return;
|
|
7516
|
+
if (update.type === 'deleted')
|
|
7517
|
+
return;
|
|
7518
|
+
const entity = (update.type === 'created' ? update.data : update.after);
|
|
7519
|
+
if (waitingResolve) {
|
|
7520
|
+
const resolve = waitingResolve;
|
|
7521
|
+
waitingResolve = null;
|
|
7522
|
+
resolve({ value: entity, done: false });
|
|
7523
|
+
}
|
|
7524
|
+
else {
|
|
7525
|
+
if (queue.length >= MAX_QUEUE_SIZE) {
|
|
7526
|
+
queue.shift();
|
|
7527
|
+
}
|
|
7528
|
+
queue.push(entity);
|
|
7529
|
+
}
|
|
7530
|
+
};
|
|
7531
|
+
const start = () => {
|
|
7532
|
+
unsubscribeStorage = storage.onRichUpdate(handler);
|
|
7533
|
+
unsubscribeRegistry = subscriptionRegistry.subscribe(subscription);
|
|
7534
|
+
};
|
|
7535
|
+
const cleanup = () => {
|
|
7536
|
+
done = true;
|
|
7537
|
+
unsubscribeStorage?.();
|
|
7538
|
+
unsubscribeRegistry?.();
|
|
7539
|
+
};
|
|
7540
|
+
start();
|
|
7541
|
+
return {
|
|
7542
|
+
async next() {
|
|
7543
|
+
if (done) {
|
|
7544
|
+
return { value: undefined, done: true };
|
|
7545
|
+
}
|
|
7546
|
+
const queued = queue.shift();
|
|
7547
|
+
if (queued) {
|
|
7548
|
+
return { value: queued, done: false };
|
|
7549
|
+
}
|
|
7550
|
+
return new Promise((resolve) => {
|
|
7551
|
+
waitingResolve = resolve;
|
|
7552
|
+
});
|
|
7553
|
+
},
|
|
7554
|
+
async return() {
|
|
7555
|
+
cleanup();
|
|
7556
|
+
return { value: undefined, done: true };
|
|
7557
|
+
},
|
|
7558
|
+
async throw(error) {
|
|
7559
|
+
cleanup();
|
|
7560
|
+
throw error;
|
|
7561
|
+
},
|
|
7562
|
+
};
|
|
7563
|
+
},
|
|
7564
|
+
};
|
|
7565
|
+
}
|
|
7503
7566
|
function createRichUpdateStream(storage, subscriptionRegistry, subscription, keyFilter) {
|
|
7504
7567
|
return {
|
|
7505
7568
|
[Symbol.asyncIterator]() {
|
|
@@ -7567,11 +7630,14 @@ function createRichUpdateStream(storage, subscriptionRegistry, subscription, key
|
|
|
7567
7630
|
|
|
7568
7631
|
function createTypedStateView(viewDef, storage, subscriptionRegistry) {
|
|
7569
7632
|
return {
|
|
7570
|
-
|
|
7571
|
-
return
|
|
7633
|
+
use(key, options) {
|
|
7634
|
+
return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
|
|
7635
|
+
},
|
|
7636
|
+
watch(key, options) {
|
|
7637
|
+
return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
|
|
7572
7638
|
},
|
|
7573
|
-
watchRich(key) {
|
|
7574
|
-
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key }, key);
|
|
7639
|
+
watchRich(key, options) {
|
|
7640
|
+
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
|
|
7575
7641
|
},
|
|
7576
7642
|
async get(key) {
|
|
7577
7643
|
return storage.get(viewDef.view, key);
|
|
@@ -7583,11 +7649,14 @@ function createTypedStateView(viewDef, storage, subscriptionRegistry) {
|
|
|
7583
7649
|
}
|
|
7584
7650
|
function createTypedListView(viewDef, storage, subscriptionRegistry) {
|
|
7585
7651
|
return {
|
|
7586
|
-
|
|
7587
|
-
return
|
|
7652
|
+
use(options) {
|
|
7653
|
+
return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
|
|
7588
7654
|
},
|
|
7589
|
-
|
|
7590
|
-
return
|
|
7655
|
+
watch(options) {
|
|
7656
|
+
return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
|
|
7657
|
+
},
|
|
7658
|
+
watchRich(options) {
|
|
7659
|
+
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
|
|
7591
7660
|
},
|
|
7592
7661
|
async get() {
|
|
7593
7662
|
return storage.getAll(viewDef.view);
|
|
@@ -7599,20 +7668,512 @@ function createTypedListView(viewDef, storage, subscriptionRegistry) {
|
|
|
7599
7668
|
}
|
|
7600
7669
|
function createTypedViews(stack, storage, subscriptionRegistry) {
|
|
7601
7670
|
const views = {};
|
|
7602
|
-
for (const [
|
|
7671
|
+
for (const [entityName, viewGroup] of Object.entries(stack.views)) {
|
|
7603
7672
|
const group = viewGroup;
|
|
7604
7673
|
const typedGroup = {};
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7674
|
+
for (const [viewName, viewDef] of Object.entries(group)) {
|
|
7675
|
+
if (viewDef.mode === 'state') {
|
|
7676
|
+
typedGroup[viewName] = createTypedStateView(viewDef, storage, subscriptionRegistry);
|
|
7677
|
+
}
|
|
7678
|
+
else if (viewDef.mode === 'list') {
|
|
7679
|
+
typedGroup[viewName] = createTypedListView(viewDef, storage, subscriptionRegistry);
|
|
7680
|
+
}
|
|
7610
7681
|
}
|
|
7611
|
-
views[
|
|
7682
|
+
views[entityName] = typedGroup;
|
|
7612
7683
|
}
|
|
7613
7684
|
return views;
|
|
7614
7685
|
}
|
|
7615
7686
|
|
|
7687
|
+
/**
|
|
7688
|
+
* Resolves instruction accounts by categorizing and deriving addresses.
|
|
7689
|
+
*
|
|
7690
|
+
* @param accountMetas - Account metadata from the instruction definition
|
|
7691
|
+
* @param args - Instruction arguments (used for PDA derivation)
|
|
7692
|
+
* @param options - Resolution options including wallet and user-provided accounts
|
|
7693
|
+
* @returns Resolved accounts and any missing required accounts
|
|
7694
|
+
*/
|
|
7695
|
+
function resolveAccounts(accountMetas, args, options) {
|
|
7696
|
+
const resolved = [];
|
|
7697
|
+
const missing = [];
|
|
7698
|
+
for (const meta of accountMetas) {
|
|
7699
|
+
const resolvedAccount = resolveSingleAccount(meta, args, options);
|
|
7700
|
+
if (resolvedAccount) {
|
|
7701
|
+
resolved.push(resolvedAccount);
|
|
7702
|
+
}
|
|
7703
|
+
else if (!meta.isOptional) {
|
|
7704
|
+
missing.push(meta.name);
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
return {
|
|
7708
|
+
accounts: resolved,
|
|
7709
|
+
missingUserAccounts: missing,
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
function resolveSingleAccount(meta, args, options) {
|
|
7713
|
+
switch (meta.category) {
|
|
7714
|
+
case 'signer':
|
|
7715
|
+
return resolveSignerAccount(meta, options.wallet);
|
|
7716
|
+
case 'known':
|
|
7717
|
+
return resolveKnownAccount(meta);
|
|
7718
|
+
case 'pda':
|
|
7719
|
+
return resolvePdaAccount(meta);
|
|
7720
|
+
case 'userProvided':
|
|
7721
|
+
return resolveUserProvidedAccount(meta, options.accounts);
|
|
7722
|
+
default:
|
|
7723
|
+
return null;
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
function resolveSignerAccount(meta, wallet) {
|
|
7727
|
+
if (!wallet) {
|
|
7728
|
+
return null;
|
|
7729
|
+
}
|
|
7730
|
+
return {
|
|
7731
|
+
name: meta.name,
|
|
7732
|
+
address: wallet.publicKey,
|
|
7733
|
+
isSigner: true,
|
|
7734
|
+
isWritable: meta.isWritable,
|
|
7735
|
+
};
|
|
7736
|
+
}
|
|
7737
|
+
function resolveKnownAccount(meta) {
|
|
7738
|
+
if (!meta.knownAddress) {
|
|
7739
|
+
return null;
|
|
7740
|
+
}
|
|
7741
|
+
return {
|
|
7742
|
+
name: meta.name,
|
|
7743
|
+
address: meta.knownAddress,
|
|
7744
|
+
isSigner: meta.isSigner,
|
|
7745
|
+
isWritable: meta.isWritable,
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7748
|
+
function resolvePdaAccount(meta, args) {
|
|
7749
|
+
if (!meta.pdaConfig) {
|
|
7750
|
+
return null;
|
|
7751
|
+
}
|
|
7752
|
+
// PDA derivation will be implemented in pda.ts
|
|
7753
|
+
// For now, return a placeholder that will be resolved later
|
|
7754
|
+
return {
|
|
7755
|
+
name: meta.name,
|
|
7756
|
+
address: '', // Will be derived
|
|
7757
|
+
isSigner: meta.isSigner,
|
|
7758
|
+
isWritable: meta.isWritable,
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
function resolveUserProvidedAccount(meta, accounts) {
|
|
7762
|
+
const address = accounts?.[meta.name];
|
|
7763
|
+
if (!address) {
|
|
7764
|
+
return null;
|
|
7765
|
+
}
|
|
7766
|
+
return {
|
|
7767
|
+
name: meta.name,
|
|
7768
|
+
address,
|
|
7769
|
+
isSigner: meta.isSigner,
|
|
7770
|
+
isWritable: meta.isWritable,
|
|
7771
|
+
};
|
|
7772
|
+
}
|
|
7773
|
+
/**
|
|
7774
|
+
* Validates that all required accounts are present.
|
|
7775
|
+
*
|
|
7776
|
+
* @param result - Account resolution result
|
|
7777
|
+
* @throws Error if any required accounts are missing
|
|
7778
|
+
*/
|
|
7779
|
+
function validateAccountResolution(result) {
|
|
7780
|
+
if (result.missingUserAccounts.length > 0) {
|
|
7781
|
+
throw new Error(`Missing required accounts: ${result.missingUserAccounts.join(', ')}`);
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
|
|
7785
|
+
/**
|
|
7786
|
+
* Derives a Program-Derived Address (PDA) from seeds and program ID.
|
|
7787
|
+
*
|
|
7788
|
+
* This function implements PDA derivation using the Solana algorithm:
|
|
7789
|
+
* 1. Concatenate all seeds
|
|
7790
|
+
* 2. Hash with SHA-256
|
|
7791
|
+
* 3. Check if result is off-curve (valid PDA)
|
|
7792
|
+
*
|
|
7793
|
+
* Note: This is a placeholder implementation. In production, you would use
|
|
7794
|
+
* the actual Solana web3.js library's PDA derivation.
|
|
7795
|
+
*
|
|
7796
|
+
* @param seeds - Array of seed buffers
|
|
7797
|
+
* @param programId - The program ID (as base58 string)
|
|
7798
|
+
* @returns The derived PDA address (base58 string)
|
|
7799
|
+
*/
|
|
7800
|
+
async function derivePda(seeds, programId) {
|
|
7801
|
+
// In production, this would use:
|
|
7802
|
+
// PublicKey.findProgramAddressSync(seeds, new PublicKey(programId))
|
|
7803
|
+
// For now, return a placeholder that will be replaced with actual implementation
|
|
7804
|
+
const combined = Buffer.concat(seeds);
|
|
7805
|
+
// Simulate PDA derivation (this is NOT the actual algorithm)
|
|
7806
|
+
const hash = await simulateHash(combined);
|
|
7807
|
+
// Return base58-encoded address
|
|
7808
|
+
return bs58Encode(hash);
|
|
7809
|
+
}
|
|
7810
|
+
/**
|
|
7811
|
+
* Creates a seed buffer from various input types.
|
|
7812
|
+
*
|
|
7813
|
+
* @param value - The value to convert to a seed
|
|
7814
|
+
* @returns Buffer suitable for PDA derivation
|
|
7815
|
+
*/
|
|
7816
|
+
function createSeed(value) {
|
|
7817
|
+
if (Buffer.isBuffer(value)) {
|
|
7818
|
+
return value;
|
|
7819
|
+
}
|
|
7820
|
+
if (value instanceof Uint8Array) {
|
|
7821
|
+
return Buffer.from(value);
|
|
7822
|
+
}
|
|
7823
|
+
if (typeof value === 'string') {
|
|
7824
|
+
return Buffer.from(value, 'utf-8');
|
|
7825
|
+
}
|
|
7826
|
+
if (typeof value === 'bigint') {
|
|
7827
|
+
// Convert bigint to 8-byte buffer (u64)
|
|
7828
|
+
const buffer = Buffer.alloc(8);
|
|
7829
|
+
buffer.writeBigUInt64LE(value);
|
|
7830
|
+
return buffer;
|
|
7831
|
+
}
|
|
7832
|
+
throw new Error(`Cannot create seed from type: ${typeof value}`);
|
|
7833
|
+
}
|
|
7834
|
+
/**
|
|
7835
|
+
* Creates a public key seed from a base58-encoded address.
|
|
7836
|
+
*
|
|
7837
|
+
* @param address - Base58-encoded public key
|
|
7838
|
+
* @returns 32-byte buffer
|
|
7839
|
+
*/
|
|
7840
|
+
function createPublicKeySeed(address) {
|
|
7841
|
+
// In production, decode base58 to 32-byte buffer
|
|
7842
|
+
// For now, return placeholder
|
|
7843
|
+
return Buffer.alloc(32);
|
|
7844
|
+
}
|
|
7845
|
+
async function simulateHash(data) {
|
|
7846
|
+
// In production, use actual SHA-256
|
|
7847
|
+
// This is a placeholder
|
|
7848
|
+
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
7849
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
7850
|
+
return Buffer.from(hashBuffer);
|
|
7851
|
+
}
|
|
7852
|
+
// Fallback for Node.js
|
|
7853
|
+
return Buffer.alloc(32, 0);
|
|
7854
|
+
}
|
|
7855
|
+
function bs58Encode(buffer) {
|
|
7856
|
+
// In production, use actual base58 encoding
|
|
7857
|
+
// This is a placeholder
|
|
7858
|
+
return 'P' + buffer.toString('hex').slice(0, 31);
|
|
7859
|
+
}
|
|
7860
|
+
|
|
7861
|
+
/**
|
|
7862
|
+
* Borsh-compatible instruction data serializer.
|
|
7863
|
+
*
|
|
7864
|
+
* This module handles serializing instruction arguments into the binary format
|
|
7865
|
+
* expected by Solana programs using Borsh serialization.
|
|
7866
|
+
*/
|
|
7867
|
+
/**
|
|
7868
|
+
* Serializes instruction arguments into a Buffer using Borsh encoding.
|
|
7869
|
+
*
|
|
7870
|
+
* @param discriminator - The 8-byte instruction discriminator
|
|
7871
|
+
* @param args - Arguments to serialize
|
|
7872
|
+
* @param schema - Schema defining argument types
|
|
7873
|
+
* @returns Serialized instruction data
|
|
7874
|
+
*/
|
|
7875
|
+
function serializeInstructionData(discriminator, args, schema) {
|
|
7876
|
+
const buffers = [Buffer.from(discriminator)];
|
|
7877
|
+
for (const field of schema) {
|
|
7878
|
+
const value = args[field.name];
|
|
7879
|
+
const serialized = serializeValue(value, field.type);
|
|
7880
|
+
buffers.push(serialized);
|
|
7881
|
+
}
|
|
7882
|
+
return Buffer.concat(buffers);
|
|
7883
|
+
}
|
|
7884
|
+
function serializeValue(value, type) {
|
|
7885
|
+
if (typeof type === 'string') {
|
|
7886
|
+
return serializePrimitive(value, type);
|
|
7887
|
+
}
|
|
7888
|
+
if ('vec' in type) {
|
|
7889
|
+
return serializeVec(value, type.vec);
|
|
7890
|
+
}
|
|
7891
|
+
if ('option' in type) {
|
|
7892
|
+
return serializeOption(value, type.option);
|
|
7893
|
+
}
|
|
7894
|
+
if ('array' in type) {
|
|
7895
|
+
return serializeArray(value, type.array[0], type.array[1]);
|
|
7896
|
+
}
|
|
7897
|
+
throw new Error(`Unknown type: ${JSON.stringify(type)}`);
|
|
7898
|
+
}
|
|
7899
|
+
function serializePrimitive(value, type) {
|
|
7900
|
+
switch (type) {
|
|
7901
|
+
case 'u8':
|
|
7902
|
+
return Buffer.from([value]);
|
|
7903
|
+
case 'u16':
|
|
7904
|
+
const u16 = Buffer.alloc(2);
|
|
7905
|
+
u16.writeUInt16LE(value, 0);
|
|
7906
|
+
return u16;
|
|
7907
|
+
case 'u32':
|
|
7908
|
+
const u32 = Buffer.alloc(4);
|
|
7909
|
+
u32.writeUInt32LE(value, 0);
|
|
7910
|
+
return u32;
|
|
7911
|
+
case 'u64':
|
|
7912
|
+
const u64 = Buffer.alloc(8);
|
|
7913
|
+
u64.writeBigUInt64LE(BigInt(value), 0);
|
|
7914
|
+
return u64;
|
|
7915
|
+
case 'u128':
|
|
7916
|
+
// u128 is 16 bytes, little-endian
|
|
7917
|
+
const u128 = Buffer.alloc(16);
|
|
7918
|
+
const bigU128 = BigInt(value);
|
|
7919
|
+
u128.writeBigUInt64LE(bigU128 & BigInt('0xFFFFFFFFFFFFFFFF'), 0);
|
|
7920
|
+
u128.writeBigUInt64LE(bigU128 >> BigInt(64), 8);
|
|
7921
|
+
return u128;
|
|
7922
|
+
case 'i8':
|
|
7923
|
+
return Buffer.from([value]);
|
|
7924
|
+
case 'i16':
|
|
7925
|
+
const i16 = Buffer.alloc(2);
|
|
7926
|
+
i16.writeInt16LE(value, 0);
|
|
7927
|
+
return i16;
|
|
7928
|
+
case 'i32':
|
|
7929
|
+
const i32 = Buffer.alloc(4);
|
|
7930
|
+
i32.writeInt32LE(value, 0);
|
|
7931
|
+
return i32;
|
|
7932
|
+
case 'i64':
|
|
7933
|
+
const i64 = Buffer.alloc(8);
|
|
7934
|
+
i64.writeBigInt64LE(BigInt(value), 0);
|
|
7935
|
+
return i64;
|
|
7936
|
+
case 'i128':
|
|
7937
|
+
const i128 = Buffer.alloc(16);
|
|
7938
|
+
const bigI128 = BigInt(value);
|
|
7939
|
+
i128.writeBigInt64LE(bigI128 & BigInt('0xFFFFFFFFFFFFFFFF'), 0);
|
|
7940
|
+
i128.writeBigInt64LE(bigI128 >> BigInt(64), 8);
|
|
7941
|
+
return i128;
|
|
7942
|
+
case 'bool':
|
|
7943
|
+
return Buffer.from([value ? 1 : 0]);
|
|
7944
|
+
case 'string':
|
|
7945
|
+
const str = value;
|
|
7946
|
+
const strBytes = Buffer.from(str, 'utf-8');
|
|
7947
|
+
const strLen = Buffer.alloc(4);
|
|
7948
|
+
strLen.writeUInt32LE(strBytes.length, 0);
|
|
7949
|
+
return Buffer.concat([strLen, strBytes]);
|
|
7950
|
+
case 'pubkey':
|
|
7951
|
+
// In production, decode base58 to 32 bytes
|
|
7952
|
+
return Buffer.alloc(32, 0);
|
|
7953
|
+
default:
|
|
7954
|
+
throw new Error(`Unknown primitive type: ${type}`);
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7957
|
+
function serializeVec(values, elementType) {
|
|
7958
|
+
const len = Buffer.alloc(4);
|
|
7959
|
+
len.writeUInt32LE(values.length, 0);
|
|
7960
|
+
const elementBuffers = values.map(v => serializeValue(v, elementType));
|
|
7961
|
+
return Buffer.concat([len, ...elementBuffers]);
|
|
7962
|
+
}
|
|
7963
|
+
function serializeOption(value, innerType) {
|
|
7964
|
+
if (value === null || value === undefined) {
|
|
7965
|
+
return Buffer.from([0]); // None
|
|
7966
|
+
}
|
|
7967
|
+
const inner = serializeValue(value, innerType);
|
|
7968
|
+
return Buffer.concat([Buffer.from([1]), inner]); // Some
|
|
7969
|
+
}
|
|
7970
|
+
function serializeArray(values, elementType, length) {
|
|
7971
|
+
if (values.length !== length) {
|
|
7972
|
+
throw new Error(`Array length mismatch: expected ${length}, got ${values.length}`);
|
|
7973
|
+
}
|
|
7974
|
+
const elementBuffers = values.map(v => serializeValue(v, elementType));
|
|
7975
|
+
return Buffer.concat(elementBuffers);
|
|
7976
|
+
}
|
|
7977
|
+
|
|
7978
|
+
/**
|
|
7979
|
+
* Waits for transaction confirmation.
|
|
7980
|
+
*
|
|
7981
|
+
* @param signature - Transaction signature
|
|
7982
|
+
* @param level - Desired confirmation level
|
|
7983
|
+
* @param timeout - Maximum wait time in milliseconds
|
|
7984
|
+
* @returns Confirmation result
|
|
7985
|
+
*/
|
|
7986
|
+
async function waitForConfirmation(signature, level = 'confirmed', timeout = 60000) {
|
|
7987
|
+
const startTime = Date.now();
|
|
7988
|
+
while (Date.now() - startTime < timeout) {
|
|
7989
|
+
const status = await checkTransactionStatus();
|
|
7990
|
+
if (status.err) {
|
|
7991
|
+
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
7992
|
+
}
|
|
7993
|
+
if (isConfirmationLevelSufficient(status.confirmations, level)) {
|
|
7994
|
+
return {
|
|
7995
|
+
level,
|
|
7996
|
+
slot: status.slot,
|
|
7997
|
+
};
|
|
7998
|
+
}
|
|
7999
|
+
await sleep(1000);
|
|
8000
|
+
}
|
|
8001
|
+
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
|
|
8002
|
+
}
|
|
8003
|
+
async function checkTransactionStatus(signature) {
|
|
8004
|
+
// In production, query the Solana RPC
|
|
8005
|
+
return {
|
|
8006
|
+
err: null,
|
|
8007
|
+
confirmations: 32,
|
|
8008
|
+
slot: 123456789,
|
|
8009
|
+
};
|
|
8010
|
+
}
|
|
8011
|
+
function isConfirmationLevelSufficient(confirmations, level) {
|
|
8012
|
+
if (confirmations === null) {
|
|
8013
|
+
return false;
|
|
8014
|
+
}
|
|
8015
|
+
switch (level) {
|
|
8016
|
+
case 'processed':
|
|
8017
|
+
return confirmations >= 0;
|
|
8018
|
+
case 'confirmed':
|
|
8019
|
+
return confirmations >= 1;
|
|
8020
|
+
case 'finalized':
|
|
8021
|
+
return confirmations >= 32;
|
|
8022
|
+
default:
|
|
8023
|
+
return false;
|
|
8024
|
+
}
|
|
8025
|
+
}
|
|
8026
|
+
function sleep(ms) {
|
|
8027
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
8028
|
+
}
|
|
8029
|
+
|
|
8030
|
+
/**
|
|
8031
|
+
* Parses and handles instruction errors.
|
|
8032
|
+
*/
|
|
8033
|
+
/**
|
|
8034
|
+
* Parses an error returned from a Solana transaction.
|
|
8035
|
+
*
|
|
8036
|
+
* @param error - The error from the transaction
|
|
8037
|
+
* @param errorMetadata - Error definitions from the IDL
|
|
8038
|
+
* @returns Parsed program error or null if not a program error
|
|
8039
|
+
*/
|
|
8040
|
+
function parseInstructionError(error, errorMetadata) {
|
|
8041
|
+
if (!error) {
|
|
8042
|
+
return null;
|
|
8043
|
+
}
|
|
8044
|
+
const errorCode = extractErrorCode(error);
|
|
8045
|
+
if (errorCode === null) {
|
|
8046
|
+
return null;
|
|
8047
|
+
}
|
|
8048
|
+
const metadata = errorMetadata.find(e => e.code === errorCode);
|
|
8049
|
+
if (metadata) {
|
|
8050
|
+
return {
|
|
8051
|
+
code: metadata.code,
|
|
8052
|
+
name: metadata.name,
|
|
8053
|
+
message: metadata.msg,
|
|
8054
|
+
};
|
|
8055
|
+
}
|
|
8056
|
+
return {
|
|
8057
|
+
code: errorCode,
|
|
8058
|
+
name: `CustomError${errorCode}`,
|
|
8059
|
+
message: `Unknown error with code ${errorCode}`,
|
|
8060
|
+
};
|
|
8061
|
+
}
|
|
8062
|
+
function extractErrorCode(error) {
|
|
8063
|
+
if (typeof error !== 'object' || error === null) {
|
|
8064
|
+
return null;
|
|
8065
|
+
}
|
|
8066
|
+
const errorObj = error;
|
|
8067
|
+
// Check for InstructionError format
|
|
8068
|
+
if (errorObj.InstructionError) {
|
|
8069
|
+
const instructionError = errorObj.InstructionError;
|
|
8070
|
+
if (instructionError[1]?.Custom !== undefined) {
|
|
8071
|
+
return instructionError[1].Custom;
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
8074
|
+
// Check for direct code
|
|
8075
|
+
if (typeof errorObj.code === 'number') {
|
|
8076
|
+
return errorObj.code;
|
|
8077
|
+
}
|
|
8078
|
+
return null;
|
|
8079
|
+
}
|
|
8080
|
+
/**
|
|
8081
|
+
* Formats an error for display.
|
|
8082
|
+
*
|
|
8083
|
+
* @param error - The program error
|
|
8084
|
+
* @returns Human-readable error message
|
|
8085
|
+
*/
|
|
8086
|
+
function formatProgramError(error) {
|
|
8087
|
+
return `${error.name} (${error.code}): ${error.message}`;
|
|
8088
|
+
}
|
|
8089
|
+
|
|
8090
|
+
/**
|
|
8091
|
+
* Converts resolved account array to a map for the builder.
|
|
8092
|
+
*/
|
|
8093
|
+
function toResolvedAccountsMap(accounts) {
|
|
8094
|
+
const map = {};
|
|
8095
|
+
for (const account of accounts) {
|
|
8096
|
+
map[account.name] = account.address;
|
|
8097
|
+
}
|
|
8098
|
+
return map;
|
|
8099
|
+
}
|
|
8100
|
+
/**
|
|
8101
|
+
* Executes an instruction handler with the given arguments and options.
|
|
8102
|
+
*
|
|
8103
|
+
* This is the main function for executing Solana instructions. It handles:
|
|
8104
|
+
* 1. Account resolution (signer, PDA, user-provided)
|
|
8105
|
+
* 2. Calling the generated build() function
|
|
8106
|
+
* 3. Transaction signing and sending
|
|
8107
|
+
* 4. Confirmation waiting
|
|
8108
|
+
*
|
|
8109
|
+
* @param handler - Instruction handler from generated SDK
|
|
8110
|
+
* @param args - Instruction arguments
|
|
8111
|
+
* @param options - Execution options
|
|
8112
|
+
* @returns Execution result with signature
|
|
8113
|
+
*/
|
|
8114
|
+
async function executeInstruction(handler, args, options = {}) {
|
|
8115
|
+
// Step 1: Resolve accounts using handler's account metadata
|
|
8116
|
+
const resolutionOptions = {
|
|
8117
|
+
accounts: options.accounts,
|
|
8118
|
+
wallet: options.wallet,
|
|
8119
|
+
};
|
|
8120
|
+
const resolution = resolveAccounts(handler.accounts, args, resolutionOptions);
|
|
8121
|
+
validateAccountResolution(resolution);
|
|
8122
|
+
// Step 2: Call generated build() function
|
|
8123
|
+
const resolvedAccountsMap = toResolvedAccountsMap(resolution.accounts);
|
|
8124
|
+
const instruction = handler.build(args, resolvedAccountsMap);
|
|
8125
|
+
// Step 3: Build transaction from the built instruction
|
|
8126
|
+
const transaction = buildTransaction(instruction);
|
|
8127
|
+
// Step 4: Sign and send
|
|
8128
|
+
if (!options.wallet) {
|
|
8129
|
+
throw new Error('Wallet required to sign transaction');
|
|
8130
|
+
}
|
|
8131
|
+
const signature = await options.wallet.signAndSend(transaction);
|
|
8132
|
+
// Step 5: Wait for confirmation
|
|
8133
|
+
const confirmationLevel = options.confirmationLevel ?? 'confirmed';
|
|
8134
|
+
const timeout = options.timeout ?? 60000;
|
|
8135
|
+
const confirmation = await waitForConfirmation(signature, confirmationLevel, timeout);
|
|
8136
|
+
return {
|
|
8137
|
+
signature,
|
|
8138
|
+
confirmationLevel: confirmation.level,
|
|
8139
|
+
slot: confirmation.slot,
|
|
8140
|
+
};
|
|
8141
|
+
}
|
|
8142
|
+
/**
|
|
8143
|
+
* Creates a transaction object from a built instruction.
|
|
8144
|
+
*
|
|
8145
|
+
* @param instruction - Built instruction from handler
|
|
8146
|
+
* @returns Transaction object ready for signing
|
|
8147
|
+
*/
|
|
8148
|
+
function buildTransaction(instruction) {
|
|
8149
|
+
// This returns a framework-agnostic transaction representation.
|
|
8150
|
+
// The wallet adapter is responsible for converting this to the
|
|
8151
|
+
// appropriate format (@solana/web3.js Transaction, etc.)
|
|
8152
|
+
return {
|
|
8153
|
+
instructions: [{
|
|
8154
|
+
programId: instruction.programId,
|
|
8155
|
+
keys: instruction.keys,
|
|
8156
|
+
data: Array.from(instruction.data),
|
|
8157
|
+
}],
|
|
8158
|
+
};
|
|
8159
|
+
}
|
|
8160
|
+
/**
|
|
8161
|
+
* Creates an instruction executor bound to a specific wallet.
|
|
8162
|
+
*
|
|
8163
|
+
* @param wallet - Wallet adapter
|
|
8164
|
+
* @returns Bound executor function
|
|
8165
|
+
*/
|
|
8166
|
+
function createInstructionExecutor(wallet) {
|
|
8167
|
+
return {
|
|
8168
|
+
execute: async (handler, args, options) => {
|
|
8169
|
+
return executeInstruction(handler, args, {
|
|
8170
|
+
...options,
|
|
8171
|
+
wallet,
|
|
8172
|
+
});
|
|
8173
|
+
},
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
|
|
7616
8177
|
class HyperStack {
|
|
7617
8178
|
constructor(url, options) {
|
|
7618
8179
|
this.stack = options.stack;
|
|
@@ -7630,16 +8191,34 @@ class HyperStack {
|
|
|
7630
8191
|
this.processor.handleFrame(frame);
|
|
7631
8192
|
});
|
|
7632
8193
|
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
8194
|
+
this._instructions = this.buildInstructions();
|
|
8195
|
+
}
|
|
8196
|
+
buildInstructions() {
|
|
8197
|
+
const instructions = {};
|
|
8198
|
+
if (this.stack.instructions) {
|
|
8199
|
+
for (const [name, handler] of Object.entries(this.stack.instructions)) {
|
|
8200
|
+
instructions[name] = (args, options) => {
|
|
8201
|
+
return executeInstruction(handler, args, options);
|
|
8202
|
+
};
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8205
|
+
return instructions;
|
|
7633
8206
|
}
|
|
7634
|
-
static async connect(
|
|
8207
|
+
static async connect(stack, options) {
|
|
8208
|
+
const url = options?.url ?? stack.url;
|
|
7635
8209
|
if (!url) {
|
|
7636
|
-
throw new HyperStackError('URL is required', 'INVALID_CONFIG');
|
|
7637
|
-
}
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
8210
|
+
throw new HyperStackError('URL is required (provide url option or define url in stack)', 'INVALID_CONFIG');
|
|
8211
|
+
}
|
|
8212
|
+
const internalOptions = {
|
|
8213
|
+
stack,
|
|
8214
|
+
storage: options?.storage,
|
|
8215
|
+
maxEntriesPerView: options?.maxEntriesPerView,
|
|
8216
|
+
autoReconnect: options?.autoReconnect,
|
|
8217
|
+
reconnectIntervals: options?.reconnectIntervals,
|
|
8218
|
+
maxReconnectAttempts: options?.maxReconnectAttempts,
|
|
8219
|
+
};
|
|
8220
|
+
const client = new HyperStack(url, internalOptions);
|
|
8221
|
+
if (options?.autoReconnect !== false) {
|
|
7643
8222
|
await client.connection.connect();
|
|
7644
8223
|
}
|
|
7645
8224
|
return client;
|
|
@@ -7647,6 +8226,9 @@ class HyperStack {
|
|
|
7647
8226
|
get views() {
|
|
7648
8227
|
return this._views;
|
|
7649
8228
|
}
|
|
8229
|
+
get instructions() {
|
|
8230
|
+
return this._instructions;
|
|
8231
|
+
}
|
|
7650
8232
|
get connectionState() {
|
|
7651
8233
|
return this.connection.getState();
|
|
7652
8234
|
}
|
|
@@ -7686,6 +8268,504 @@ class HyperStack {
|
|
|
7686
8268
|
}
|
|
7687
8269
|
}
|
|
7688
8270
|
|
|
8271
|
+
const HyperstackContext = React.createContext(null);
|
|
8272
|
+
function resolveNetworkConfig(network, websocketUrl) {
|
|
8273
|
+
if (websocketUrl) {
|
|
8274
|
+
return {
|
|
8275
|
+
name: 'custom',
|
|
8276
|
+
websocketUrl
|
|
8277
|
+
};
|
|
8278
|
+
}
|
|
8279
|
+
if (typeof network === 'object') {
|
|
8280
|
+
return network;
|
|
8281
|
+
}
|
|
8282
|
+
if (network === 'mainnet') {
|
|
8283
|
+
return {
|
|
8284
|
+
name: 'mainnet',
|
|
8285
|
+
websocketUrl: 'wss://mainnet.hyperstack.xyz',
|
|
8286
|
+
};
|
|
8287
|
+
}
|
|
8288
|
+
if (network === 'devnet') {
|
|
8289
|
+
return {
|
|
8290
|
+
name: 'devnet',
|
|
8291
|
+
websocketUrl: 'ws://localhost:8080',
|
|
8292
|
+
};
|
|
8293
|
+
}
|
|
8294
|
+
if (network === 'localnet') {
|
|
8295
|
+
return {
|
|
8296
|
+
name: 'localnet',
|
|
8297
|
+
websocketUrl: 'ws://localhost:8080',
|
|
8298
|
+
};
|
|
8299
|
+
}
|
|
8300
|
+
throw new Error('Must provide either network or websocketUrl');
|
|
8301
|
+
}
|
|
8302
|
+
function HyperstackProvider({ children, fallback = null, ...config }) {
|
|
8303
|
+
const networkConfig = resolveNetworkConfig(config.network, config.websocketUrl);
|
|
8304
|
+
const clientsRef = React.useRef(new Map());
|
|
8305
|
+
const connectingRef = React.useRef(new Map());
|
|
8306
|
+
const getOrCreateClient = React.useCallback(async (stack) => {
|
|
8307
|
+
const existing = clientsRef.current.get(stack.name);
|
|
8308
|
+
if (existing) {
|
|
8309
|
+
return existing.client;
|
|
8310
|
+
}
|
|
8311
|
+
const connecting = connectingRef.current.get(stack.name);
|
|
8312
|
+
if (connecting) {
|
|
8313
|
+
return connecting;
|
|
8314
|
+
}
|
|
8315
|
+
const connectionPromise = HyperStack.connect(stack, {
|
|
8316
|
+
url: networkConfig.websocketUrl,
|
|
8317
|
+
autoReconnect: config.autoConnect,
|
|
8318
|
+
reconnectIntervals: config.reconnectIntervals,
|
|
8319
|
+
maxReconnectAttempts: config.maxReconnectAttempts,
|
|
8320
|
+
maxEntriesPerView: config.maxEntriesPerView,
|
|
8321
|
+
}).then((client) => {
|
|
8322
|
+
clientsRef.current.set(stack.name, {
|
|
8323
|
+
client,
|
|
8324
|
+
disconnect: () => client.disconnect()
|
|
8325
|
+
});
|
|
8326
|
+
connectingRef.current.delete(stack.name);
|
|
8327
|
+
return client;
|
|
8328
|
+
});
|
|
8329
|
+
connectingRef.current.set(stack.name, connectionPromise);
|
|
8330
|
+
return connectionPromise;
|
|
8331
|
+
}, [networkConfig.websocketUrl, config.autoConnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView]);
|
|
8332
|
+
const getClient = React.useCallback((stack) => {
|
|
8333
|
+
if (!stack)
|
|
8334
|
+
return null;
|
|
8335
|
+
const entry = clientsRef.current.get(stack.name);
|
|
8336
|
+
return entry ? entry.client : null;
|
|
8337
|
+
}, []);
|
|
8338
|
+
React.useEffect(() => {
|
|
8339
|
+
return () => {
|
|
8340
|
+
clientsRef.current.forEach((entry) => {
|
|
8341
|
+
entry.disconnect();
|
|
8342
|
+
});
|
|
8343
|
+
clientsRef.current.clear();
|
|
8344
|
+
connectingRef.current.clear();
|
|
8345
|
+
};
|
|
8346
|
+
}, []);
|
|
8347
|
+
const value = {
|
|
8348
|
+
getOrCreateClient,
|
|
8349
|
+
getClient,
|
|
8350
|
+
config: {
|
|
8351
|
+
websocketUrl: networkConfig.websocketUrl,
|
|
8352
|
+
autoConnect: config.autoConnect,
|
|
8353
|
+
reconnectIntervals: config.reconnectIntervals,
|
|
8354
|
+
maxReconnectAttempts: config.maxReconnectAttempts,
|
|
8355
|
+
maxEntriesPerView: config.maxEntriesPerView,
|
|
8356
|
+
}
|
|
8357
|
+
};
|
|
8358
|
+
return (React.createElement(HyperstackContext.Provider, { value: value }, children));
|
|
8359
|
+
}
|
|
8360
|
+
function useHyperstackContext() {
|
|
8361
|
+
const context = React.useContext(HyperstackContext);
|
|
8362
|
+
if (!context) {
|
|
8363
|
+
throw new Error('useHyperstackContext must be used within HyperstackProvider');
|
|
8364
|
+
}
|
|
8365
|
+
return context;
|
|
8366
|
+
}
|
|
8367
|
+
function useConnectionState(stack) {
|
|
8368
|
+
const { getClient } = useHyperstackContext();
|
|
8369
|
+
const client = stack ? getClient(stack) : null;
|
|
8370
|
+
return React.useSyncExternalStore((callback) => {
|
|
8371
|
+
if (!client)
|
|
8372
|
+
return () => { };
|
|
8373
|
+
return client.onConnectionStateChange(callback);
|
|
8374
|
+
}, () => client?.connectionState ?? 'disconnected');
|
|
8375
|
+
}
|
|
8376
|
+
function useView(stack, viewPath) {
|
|
8377
|
+
const { getClient } = useHyperstackContext();
|
|
8378
|
+
const client = getClient(stack);
|
|
8379
|
+
return React.useSyncExternalStore((callback) => {
|
|
8380
|
+
if (!client)
|
|
8381
|
+
return () => { };
|
|
8382
|
+
return client.store.onUpdate(callback);
|
|
8383
|
+
}, () => {
|
|
8384
|
+
if (!client)
|
|
8385
|
+
return [];
|
|
8386
|
+
const data = client.store.getAll(viewPath);
|
|
8387
|
+
return data;
|
|
8388
|
+
});
|
|
8389
|
+
}
|
|
8390
|
+
function useEntity(stack, viewPath, key) {
|
|
8391
|
+
const { getClient } = useHyperstackContext();
|
|
8392
|
+
const client = getClient(stack);
|
|
8393
|
+
return React.useSyncExternalStore((callback) => {
|
|
8394
|
+
if (!client)
|
|
8395
|
+
return () => { };
|
|
8396
|
+
return client.store.onUpdate(callback);
|
|
8397
|
+
}, () => {
|
|
8398
|
+
if (!client)
|
|
8399
|
+
return null;
|
|
8400
|
+
const data = client.store.get(viewPath, key);
|
|
8401
|
+
return data;
|
|
8402
|
+
});
|
|
8403
|
+
}
|
|
8404
|
+
|
|
8405
|
+
function shallowArrayEqual(a, b) {
|
|
8406
|
+
if (a === b)
|
|
8407
|
+
return true;
|
|
8408
|
+
if (!a || !b)
|
|
8409
|
+
return false;
|
|
8410
|
+
if (a.length !== b.length)
|
|
8411
|
+
return false;
|
|
8412
|
+
for (let i = 0; i < a.length; i++) {
|
|
8413
|
+
if (a[i] !== b[i])
|
|
8414
|
+
return false;
|
|
8415
|
+
}
|
|
8416
|
+
return true;
|
|
8417
|
+
}
|
|
8418
|
+
function useStateView(viewDef, client, key, options) {
|
|
8419
|
+
const [isLoading, setIsLoading] = React.useState(!options?.initialData);
|
|
8420
|
+
const [error, setError] = React.useState();
|
|
8421
|
+
const clientRef = React.useRef(client);
|
|
8422
|
+
clientRef.current = client;
|
|
8423
|
+
const cachedSnapshotRef = React.useRef(undefined);
|
|
8424
|
+
const keyString = key ? Object.values(key)[0] : undefined;
|
|
8425
|
+
const enabled = options?.enabled !== false;
|
|
8426
|
+
React.useEffect(() => {
|
|
8427
|
+
if (!enabled || !clientRef.current)
|
|
8428
|
+
return undefined;
|
|
8429
|
+
try {
|
|
8430
|
+
const registry = clientRef.current.getSubscriptionRegistry();
|
|
8431
|
+
const unsubscribe = registry.subscribe({ view: viewDef.view, key: keyString });
|
|
8432
|
+
setIsLoading(true);
|
|
8433
|
+
return () => {
|
|
8434
|
+
try {
|
|
8435
|
+
unsubscribe();
|
|
8436
|
+
}
|
|
8437
|
+
catch (err) {
|
|
8438
|
+
console.error('[Hyperstack] Error unsubscribing from view:', err);
|
|
8439
|
+
}
|
|
8440
|
+
};
|
|
8441
|
+
}
|
|
8442
|
+
catch (err) {
|
|
8443
|
+
setError(err instanceof Error ? err : new Error('Subscription failed'));
|
|
8444
|
+
setIsLoading(false);
|
|
8445
|
+
return undefined;
|
|
8446
|
+
}
|
|
8447
|
+
}, [viewDef.view, keyString, enabled, client]);
|
|
8448
|
+
const refresh = React.useCallback(() => {
|
|
8449
|
+
if (!enabled || !clientRef.current)
|
|
8450
|
+
return;
|
|
8451
|
+
try {
|
|
8452
|
+
const registry = clientRef.current.getSubscriptionRegistry();
|
|
8453
|
+
const unsubscribe = registry.subscribe({ view: viewDef.view, key: keyString });
|
|
8454
|
+
setIsLoading(true);
|
|
8455
|
+
setTimeout(() => {
|
|
8456
|
+
try {
|
|
8457
|
+
unsubscribe();
|
|
8458
|
+
}
|
|
8459
|
+
catch (err) {
|
|
8460
|
+
console.error('[Hyperstack] Error during refresh unsubscribe:', err);
|
|
8461
|
+
}
|
|
8462
|
+
}, 0);
|
|
8463
|
+
}
|
|
8464
|
+
catch (err) {
|
|
8465
|
+
setError(err instanceof Error ? err : new Error('Refresh failed'));
|
|
8466
|
+
setIsLoading(false);
|
|
8467
|
+
}
|
|
8468
|
+
}, [viewDef.view, keyString, enabled]);
|
|
8469
|
+
const subscribe = React.useCallback((callback) => {
|
|
8470
|
+
if (!clientRef.current)
|
|
8471
|
+
return () => { };
|
|
8472
|
+
return clientRef.current.store.onUpdate(callback);
|
|
8473
|
+
}, [client]);
|
|
8474
|
+
const getSnapshot = React.useCallback(() => {
|
|
8475
|
+
if (!clientRef.current)
|
|
8476
|
+
return cachedSnapshotRef.current;
|
|
8477
|
+
const entity = keyString
|
|
8478
|
+
? clientRef.current.store.get(viewDef.view, keyString)
|
|
8479
|
+
: clientRef.current.store.getAll(viewDef.view)[0];
|
|
8480
|
+
// Cache the result to return stable reference for useSyncExternalStore
|
|
8481
|
+
if (entity !== cachedSnapshotRef.current) {
|
|
8482
|
+
cachedSnapshotRef.current = entity;
|
|
8483
|
+
}
|
|
8484
|
+
return cachedSnapshotRef.current;
|
|
8485
|
+
}, [viewDef.view, keyString, client]);
|
|
8486
|
+
const data = React.useSyncExternalStore(subscribe, getSnapshot);
|
|
8487
|
+
React.useEffect(() => {
|
|
8488
|
+
if (data !== undefined && isLoading) {
|
|
8489
|
+
setIsLoading(false);
|
|
8490
|
+
}
|
|
8491
|
+
}, [data, isLoading]);
|
|
8492
|
+
return {
|
|
8493
|
+
data: (options?.initialData ?? data),
|
|
8494
|
+
isLoading: client === null || isLoading,
|
|
8495
|
+
error,
|
|
8496
|
+
refresh
|
|
8497
|
+
};
|
|
8498
|
+
}
|
|
8499
|
+
function useListView(viewDef, client, params, options) {
|
|
8500
|
+
const [isLoading, setIsLoading] = React.useState(!options?.initialData);
|
|
8501
|
+
const [error, setError] = React.useState();
|
|
8502
|
+
const clientRef = React.useRef(client);
|
|
8503
|
+
clientRef.current = client;
|
|
8504
|
+
const cachedSnapshotRef = React.useRef(undefined);
|
|
8505
|
+
const enabled = options?.enabled !== false;
|
|
8506
|
+
const key = params?.key;
|
|
8507
|
+
const take = params?.take;
|
|
8508
|
+
const skip = params?.skip;
|
|
8509
|
+
const whereJson = params?.where ? JSON.stringify(params.where) : undefined;
|
|
8510
|
+
const filtersJson = params?.filters ? JSON.stringify(params.filters) : undefined;
|
|
8511
|
+
const limit = params?.limit;
|
|
8512
|
+
React.useEffect(() => {
|
|
8513
|
+
if (!enabled || !clientRef.current)
|
|
8514
|
+
return undefined;
|
|
8515
|
+
try {
|
|
8516
|
+
const registry = clientRef.current.getSubscriptionRegistry();
|
|
8517
|
+
const unsubscribe = registry.subscribe({
|
|
8518
|
+
view: viewDef.view,
|
|
8519
|
+
key,
|
|
8520
|
+
filters: params?.filters,
|
|
8521
|
+
take,
|
|
8522
|
+
skip
|
|
8523
|
+
});
|
|
8524
|
+
setIsLoading(true);
|
|
8525
|
+
return () => {
|
|
8526
|
+
try {
|
|
8527
|
+
unsubscribe();
|
|
8528
|
+
}
|
|
8529
|
+
catch (err) {
|
|
8530
|
+
console.error('[Hyperstack] Error unsubscribing from list view:', err);
|
|
8531
|
+
}
|
|
8532
|
+
};
|
|
8533
|
+
}
|
|
8534
|
+
catch (err) {
|
|
8535
|
+
setError(err instanceof Error ? err : new Error('Subscription failed'));
|
|
8536
|
+
setIsLoading(false);
|
|
8537
|
+
return undefined;
|
|
8538
|
+
}
|
|
8539
|
+
}, [viewDef.view, enabled, key, filtersJson, take, skip, client]);
|
|
8540
|
+
const refresh = React.useCallback(() => {
|
|
8541
|
+
if (!enabled || !clientRef.current)
|
|
8542
|
+
return;
|
|
8543
|
+
try {
|
|
8544
|
+
const registry = clientRef.current.getSubscriptionRegistry();
|
|
8545
|
+
const unsubscribe = registry.subscribe({
|
|
8546
|
+
view: viewDef.view,
|
|
8547
|
+
key,
|
|
8548
|
+
filters: params?.filters,
|
|
8549
|
+
take,
|
|
8550
|
+
skip
|
|
8551
|
+
});
|
|
8552
|
+
setIsLoading(true);
|
|
8553
|
+
setTimeout(() => {
|
|
8554
|
+
try {
|
|
8555
|
+
unsubscribe();
|
|
8556
|
+
}
|
|
8557
|
+
catch (err) {
|
|
8558
|
+
console.error('[Hyperstack] Error during list refresh unsubscribe:', err);
|
|
8559
|
+
}
|
|
8560
|
+
}, 0);
|
|
8561
|
+
}
|
|
8562
|
+
catch (err) {
|
|
8563
|
+
setError(err instanceof Error ? err : new Error('Refresh failed'));
|
|
8564
|
+
setIsLoading(false);
|
|
8565
|
+
}
|
|
8566
|
+
}, [viewDef.view, enabled, key, filtersJson, take, skip]);
|
|
8567
|
+
const subscribe = React.useCallback((callback) => {
|
|
8568
|
+
if (!clientRef.current)
|
|
8569
|
+
return () => { };
|
|
8570
|
+
return clientRef.current.store.onUpdate(callback);
|
|
8571
|
+
}, [client]);
|
|
8572
|
+
const getSnapshot = React.useCallback(() => {
|
|
8573
|
+
if (!clientRef.current)
|
|
8574
|
+
return cachedSnapshotRef.current;
|
|
8575
|
+
const viewData = clientRef.current.store.getAll(viewDef.view);
|
|
8576
|
+
if (!viewData || viewData.length === 0) {
|
|
8577
|
+
if (cachedSnapshotRef.current !== undefined) {
|
|
8578
|
+
cachedSnapshotRef.current = undefined;
|
|
8579
|
+
}
|
|
8580
|
+
return cachedSnapshotRef.current;
|
|
8581
|
+
}
|
|
8582
|
+
let items = viewData;
|
|
8583
|
+
if (params?.where) {
|
|
8584
|
+
items = items.filter((item) => {
|
|
8585
|
+
return Object.entries(params.where).every(([fieldKey, condition]) => {
|
|
8586
|
+
const value = item[fieldKey];
|
|
8587
|
+
if (typeof condition === 'object' && condition !== null) {
|
|
8588
|
+
const cond = condition;
|
|
8589
|
+
if ('gte' in cond)
|
|
8590
|
+
return value >= cond.gte;
|
|
8591
|
+
if ('lte' in cond)
|
|
8592
|
+
return value <= cond.lte;
|
|
8593
|
+
if ('gt' in cond)
|
|
8594
|
+
return value > cond.gt;
|
|
8595
|
+
if ('lt' in cond)
|
|
8596
|
+
return value < cond.lt;
|
|
8597
|
+
}
|
|
8598
|
+
return value === condition;
|
|
8599
|
+
});
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8602
|
+
if (limit) {
|
|
8603
|
+
items = items.slice(0, limit);
|
|
8604
|
+
}
|
|
8605
|
+
const result = items;
|
|
8606
|
+
// Cache the result - only update if data actually changed
|
|
8607
|
+
if (!shallowArrayEqual(cachedSnapshotRef.current, result)) {
|
|
8608
|
+
cachedSnapshotRef.current = result;
|
|
8609
|
+
}
|
|
8610
|
+
return cachedSnapshotRef.current;
|
|
8611
|
+
}, [viewDef.view, whereJson, limit, client]);
|
|
8612
|
+
const data = React.useSyncExternalStore(subscribe, getSnapshot);
|
|
8613
|
+
React.useEffect(() => {
|
|
8614
|
+
if (data !== undefined && isLoading) {
|
|
8615
|
+
setIsLoading(false);
|
|
8616
|
+
}
|
|
8617
|
+
}, [data, isLoading]);
|
|
8618
|
+
return {
|
|
8619
|
+
data: (options?.initialData ?? data),
|
|
8620
|
+
isLoading: client === null || isLoading,
|
|
8621
|
+
error,
|
|
8622
|
+
refresh
|
|
8623
|
+
};
|
|
8624
|
+
}
|
|
8625
|
+
function createStateViewHook(viewDef, client) {
|
|
8626
|
+
return {
|
|
8627
|
+
use: (key, options) => {
|
|
8628
|
+
return useStateView(viewDef, client, key, options);
|
|
8629
|
+
}
|
|
8630
|
+
};
|
|
8631
|
+
}
|
|
8632
|
+
function createListViewHook(viewDef, client) {
|
|
8633
|
+
function use(params, options) {
|
|
8634
|
+
const result = useListView(viewDef, client, params, options);
|
|
8635
|
+
if (params?.take === 1) {
|
|
8636
|
+
return {
|
|
8637
|
+
data: result.data?.[0],
|
|
8638
|
+
isLoading: result.isLoading,
|
|
8639
|
+
error: result.error,
|
|
8640
|
+
refresh: result.refresh
|
|
8641
|
+
};
|
|
8642
|
+
}
|
|
8643
|
+
return result;
|
|
8644
|
+
}
|
|
8645
|
+
function useOne(params, options) {
|
|
8646
|
+
const paramsWithTake = params ? { ...params, take: 1 } : { take: 1 };
|
|
8647
|
+
const result = useListView(viewDef, client, paramsWithTake, options);
|
|
8648
|
+
return {
|
|
8649
|
+
data: result.data?.[0],
|
|
8650
|
+
isLoading: result.isLoading,
|
|
8651
|
+
error: result.error,
|
|
8652
|
+
refresh: result.refresh
|
|
8653
|
+
};
|
|
8654
|
+
}
|
|
8655
|
+
return { use, useOne };
|
|
8656
|
+
}
|
|
8657
|
+
|
|
8658
|
+
function useInstructionMutation(execute) {
|
|
8659
|
+
const [status, setStatus] = React.useState('idle');
|
|
8660
|
+
const [error, setError] = React.useState(null);
|
|
8661
|
+
const [signature, setSignature] = React.useState(null);
|
|
8662
|
+
const submit = React.useCallback(async (args, options) => {
|
|
8663
|
+
setStatus('pending');
|
|
8664
|
+
setError(null);
|
|
8665
|
+
setSignature(null);
|
|
8666
|
+
try {
|
|
8667
|
+
const result = await execute(args, options);
|
|
8668
|
+
setStatus('success');
|
|
8669
|
+
setSignature(result.signature);
|
|
8670
|
+
if (options?.onSuccess) {
|
|
8671
|
+
options.onSuccess(result);
|
|
8672
|
+
}
|
|
8673
|
+
return result;
|
|
8674
|
+
}
|
|
8675
|
+
catch (err) {
|
|
8676
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
8677
|
+
const programError = parseInstructionError(err, []);
|
|
8678
|
+
const displayError = programError
|
|
8679
|
+
? `${programError.name}: ${programError.message}`
|
|
8680
|
+
: errorMessage;
|
|
8681
|
+
setStatus('error');
|
|
8682
|
+
setError(displayError);
|
|
8683
|
+
if (options?.onError && err instanceof Error) {
|
|
8684
|
+
options.onError(err);
|
|
8685
|
+
}
|
|
8686
|
+
throw err;
|
|
8687
|
+
}
|
|
8688
|
+
}, [execute]);
|
|
8689
|
+
const reset = React.useCallback(() => {
|
|
8690
|
+
setStatus('idle');
|
|
8691
|
+
setError(null);
|
|
8692
|
+
setSignature(null);
|
|
8693
|
+
}, []);
|
|
8694
|
+
return {
|
|
8695
|
+
submit,
|
|
8696
|
+
status,
|
|
8697
|
+
error,
|
|
8698
|
+
signature,
|
|
8699
|
+
isLoading: status === 'pending',
|
|
8700
|
+
reset,
|
|
8701
|
+
};
|
|
8702
|
+
}
|
|
8703
|
+
|
|
8704
|
+
function useHyperstack(stack) {
|
|
8705
|
+
const { getOrCreateClient, getClient } = useHyperstackContext();
|
|
8706
|
+
const [client, setClient] = React.useState(getClient(stack));
|
|
8707
|
+
const [isLoading, setIsLoading] = React.useState(!client);
|
|
8708
|
+
const [error, setError] = React.useState(null);
|
|
8709
|
+
React.useEffect(() => {
|
|
8710
|
+
const existingClient = getClient(stack);
|
|
8711
|
+
if (existingClient) {
|
|
8712
|
+
setClient(existingClient);
|
|
8713
|
+
setIsLoading(false);
|
|
8714
|
+
return;
|
|
8715
|
+
}
|
|
8716
|
+
setIsLoading(true);
|
|
8717
|
+
setError(null);
|
|
8718
|
+
getOrCreateClient(stack)
|
|
8719
|
+
.then((newClient) => {
|
|
8720
|
+
setClient(newClient);
|
|
8721
|
+
setIsLoading(false);
|
|
8722
|
+
})
|
|
8723
|
+
.catch((err) => {
|
|
8724
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
8725
|
+
setIsLoading(false);
|
|
8726
|
+
});
|
|
8727
|
+
}, [stack, getOrCreateClient, getClient]);
|
|
8728
|
+
const views = React.useMemo(() => {
|
|
8729
|
+
const result = {};
|
|
8730
|
+
for (const [viewName, viewGroup] of Object.entries(stack.views)) {
|
|
8731
|
+
result[viewName] = {};
|
|
8732
|
+
if (typeof viewGroup === 'object' && viewGroup !== null) {
|
|
8733
|
+
for (const [subViewName, viewDef] of Object.entries(viewGroup)) {
|
|
8734
|
+
if (!viewDef || typeof viewDef !== 'object' || !('mode' in viewDef))
|
|
8735
|
+
continue;
|
|
8736
|
+
if (viewDef.mode === 'state') {
|
|
8737
|
+
result[viewName][subViewName] = createStateViewHook(viewDef, client);
|
|
8738
|
+
}
|
|
8739
|
+
else if (viewDef.mode === 'list') {
|
|
8740
|
+
result[viewName][subViewName] = createListViewHook(viewDef, client);
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
return result;
|
|
8746
|
+
}, [stack, client]);
|
|
8747
|
+
const instructions = React.useMemo(() => {
|
|
8748
|
+
const result = {};
|
|
8749
|
+
if (client?.instructions) {
|
|
8750
|
+
for (const [instructionName, executeFn] of Object.entries(client.instructions)) {
|
|
8751
|
+
result[instructionName] = {
|
|
8752
|
+
execute: executeFn,
|
|
8753
|
+
useMutation: () => useInstructionMutation(executeFn)
|
|
8754
|
+
};
|
|
8755
|
+
}
|
|
8756
|
+
}
|
|
8757
|
+
return result;
|
|
8758
|
+
}, [client]);
|
|
8759
|
+
return {
|
|
8760
|
+
views: views,
|
|
8761
|
+
instructions: instructions,
|
|
8762
|
+
zustandStore: client?.store,
|
|
8763
|
+
client: client,
|
|
8764
|
+
isLoading,
|
|
8765
|
+
error
|
|
8766
|
+
};
|
|
8767
|
+
}
|
|
8768
|
+
|
|
7689
8769
|
function getNestedValue(obj, path) {
|
|
7690
8770
|
let current = obj;
|
|
7691
8771
|
for (const segment of path) {
|
|
@@ -7981,503 +9061,6 @@ class ZustandAdapter {
|
|
|
7981
9061
|
}
|
|
7982
9062
|
}
|
|
7983
9063
|
|
|
7984
|
-
const DEFAULT_FLUSH_INTERVAL_MS = 16;
|
|
7985
|
-
|
|
7986
|
-
function createRuntime(config) {
|
|
7987
|
-
const adapter = new ZustandAdapter();
|
|
7988
|
-
const processor = new FrameProcessor(adapter, {
|
|
7989
|
-
maxEntriesPerView: config.maxEntriesPerView,
|
|
7990
|
-
flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
7991
|
-
});
|
|
7992
|
-
const connection = new ConnectionManager({
|
|
7993
|
-
websocketUrl: config.websocketUrl,
|
|
7994
|
-
reconnectIntervals: config.reconnectIntervals,
|
|
7995
|
-
maxReconnectAttempts: config.maxReconnectAttempts,
|
|
7996
|
-
});
|
|
7997
|
-
const subscriptionRegistry = new SubscriptionRegistry(connection);
|
|
7998
|
-
connection.onFrame((frame) => {
|
|
7999
|
-
processor.handleFrame(frame);
|
|
8000
|
-
});
|
|
8001
|
-
connection.onStateChange((state, error) => {
|
|
8002
|
-
adapter.setConnectionState(state, error);
|
|
8003
|
-
});
|
|
8004
|
-
return {
|
|
8005
|
-
zustandStore: adapter.store,
|
|
8006
|
-
adapter,
|
|
8007
|
-
connection,
|
|
8008
|
-
subscriptionRegistry,
|
|
8009
|
-
wallet: config.wallet,
|
|
8010
|
-
subscribe(view, key, filters, take, skip) {
|
|
8011
|
-
const subscription = { view, key, filters, take, skip };
|
|
8012
|
-
const unsubscribe = subscriptionRegistry.subscribe(subscription);
|
|
8013
|
-
return {
|
|
8014
|
-
view,
|
|
8015
|
-
key,
|
|
8016
|
-
filters,
|
|
8017
|
-
take,
|
|
8018
|
-
skip,
|
|
8019
|
-
unsubscribe,
|
|
8020
|
-
};
|
|
8021
|
-
},
|
|
8022
|
-
unsubscribe(handle) {
|
|
8023
|
-
handle.unsubscribe();
|
|
8024
|
-
},
|
|
8025
|
-
};
|
|
8026
|
-
}
|
|
8027
|
-
|
|
8028
|
-
const HyperstackContext = React.createContext(null);
|
|
8029
|
-
function resolveNetworkConfig(network, websocketUrl) {
|
|
8030
|
-
if (websocketUrl) {
|
|
8031
|
-
return {
|
|
8032
|
-
name: 'custom',
|
|
8033
|
-
websocketUrl
|
|
8034
|
-
};
|
|
8035
|
-
}
|
|
8036
|
-
if (typeof network === 'object') {
|
|
8037
|
-
return network;
|
|
8038
|
-
}
|
|
8039
|
-
if (network === 'mainnet') {
|
|
8040
|
-
return {
|
|
8041
|
-
name: 'mainnet',
|
|
8042
|
-
websocketUrl: 'wss://mainnet.hyperstack.xyz',
|
|
8043
|
-
};
|
|
8044
|
-
}
|
|
8045
|
-
if (network === 'devnet') {
|
|
8046
|
-
return {
|
|
8047
|
-
name: 'devnet',
|
|
8048
|
-
websocketUrl: 'ws://localhost:8080',
|
|
8049
|
-
};
|
|
8050
|
-
}
|
|
8051
|
-
if (network === 'localnet') {
|
|
8052
|
-
return {
|
|
8053
|
-
name: 'localnet',
|
|
8054
|
-
websocketUrl: 'ws://localhost:8080',
|
|
8055
|
-
};
|
|
8056
|
-
}
|
|
8057
|
-
throw new Error('Must provide either network or websocketUrl');
|
|
8058
|
-
}
|
|
8059
|
-
function HyperstackProvider({ children, ...config }) {
|
|
8060
|
-
const networkConfig = React.useMemo(() => {
|
|
8061
|
-
try {
|
|
8062
|
-
return resolveNetworkConfig(config.network, config.websocketUrl);
|
|
8063
|
-
}
|
|
8064
|
-
catch (error) {
|
|
8065
|
-
console.error('[Hyperstack] Invalid network configuration:', error);
|
|
8066
|
-
throw error;
|
|
8067
|
-
}
|
|
8068
|
-
}, [config.network, config.websocketUrl]);
|
|
8069
|
-
const runtimeRef = React.useRef(null);
|
|
8070
|
-
if (!runtimeRef.current) {
|
|
8071
|
-
try {
|
|
8072
|
-
runtimeRef.current = createRuntime({
|
|
8073
|
-
...config,
|
|
8074
|
-
websocketUrl: networkConfig.websocketUrl,
|
|
8075
|
-
network: networkConfig
|
|
8076
|
-
});
|
|
8077
|
-
}
|
|
8078
|
-
catch (error) {
|
|
8079
|
-
console.error('[Hyperstack] Failed to create runtime:', error);
|
|
8080
|
-
throw error;
|
|
8081
|
-
}
|
|
8082
|
-
}
|
|
8083
|
-
const runtime = runtimeRef.current;
|
|
8084
|
-
const isMountedRef = React.useRef(true);
|
|
8085
|
-
React.useEffect(() => {
|
|
8086
|
-
isMountedRef.current = true;
|
|
8087
|
-
if (config.autoConnect !== false) {
|
|
8088
|
-
try {
|
|
8089
|
-
runtime.connection.connect();
|
|
8090
|
-
}
|
|
8091
|
-
catch (error) {
|
|
8092
|
-
console.error('[Hyperstack] Failed to auto-connect:', error);
|
|
8093
|
-
}
|
|
8094
|
-
}
|
|
8095
|
-
return () => {
|
|
8096
|
-
isMountedRef.current = false;
|
|
8097
|
-
setTimeout(() => {
|
|
8098
|
-
if (!isMountedRef.current) {
|
|
8099
|
-
try {
|
|
8100
|
-
runtime.subscriptionRegistry.clear();
|
|
8101
|
-
runtime.connection.disconnect();
|
|
8102
|
-
}
|
|
8103
|
-
catch (error) {
|
|
8104
|
-
console.error('[Hyperstack] Failed to disconnect:', error);
|
|
8105
|
-
}
|
|
8106
|
-
}
|
|
8107
|
-
}, 100);
|
|
8108
|
-
};
|
|
8109
|
-
}, [runtime, config.autoConnect]);
|
|
8110
|
-
const value = {
|
|
8111
|
-
runtime,
|
|
8112
|
-
config: { ...config, network: networkConfig }
|
|
8113
|
-
};
|
|
8114
|
-
return (React.createElement(HyperstackContext.Provider, { value: value }, children));
|
|
8115
|
-
}
|
|
8116
|
-
function useHyperstackContext() {
|
|
8117
|
-
const context = React.useContext(HyperstackContext);
|
|
8118
|
-
if (!context) {
|
|
8119
|
-
throw new Error('useHyperstackContext must be used within HyperstackProvider');
|
|
8120
|
-
}
|
|
8121
|
-
return context;
|
|
8122
|
-
}
|
|
8123
|
-
function useConnectionState() {
|
|
8124
|
-
const { runtime } = useHyperstackContext();
|
|
8125
|
-
return React.useSyncExternalStore((callback) => {
|
|
8126
|
-
const unsubscribe = runtime.zustandStore.subscribe(callback);
|
|
8127
|
-
return unsubscribe;
|
|
8128
|
-
}, () => runtime.zustandStore.getState().connectionState);
|
|
8129
|
-
}
|
|
8130
|
-
function useView(viewPath) {
|
|
8131
|
-
const { runtime } = useHyperstackContext();
|
|
8132
|
-
return React.useSyncExternalStore((callback) => runtime.zustandStore.subscribe(callback), () => {
|
|
8133
|
-
const viewMap = runtime.zustandStore.getState().entities.get(viewPath);
|
|
8134
|
-
if (!viewMap)
|
|
8135
|
-
return [];
|
|
8136
|
-
return Array.from(viewMap.values());
|
|
8137
|
-
});
|
|
8138
|
-
}
|
|
8139
|
-
function useEntity(viewPath, key) {
|
|
8140
|
-
const { runtime } = useHyperstackContext();
|
|
8141
|
-
return React.useSyncExternalStore((callback) => runtime.zustandStore.subscribe(callback), () => {
|
|
8142
|
-
const viewMap = runtime.zustandStore.getState().entities.get(viewPath);
|
|
8143
|
-
if (!viewMap)
|
|
8144
|
-
return null;
|
|
8145
|
-
const value = viewMap.get(key);
|
|
8146
|
-
return value !== undefined ? value : null;
|
|
8147
|
-
});
|
|
8148
|
-
}
|
|
8149
|
-
|
|
8150
|
-
function createStateViewHook(viewDef, runtime) {
|
|
8151
|
-
return {
|
|
8152
|
-
use: (key, options) => {
|
|
8153
|
-
const [isLoading, setIsLoading] = React.useState(!options?.initialData);
|
|
8154
|
-
const [error, setError] = React.useState();
|
|
8155
|
-
const keyString = key ? Object.values(key)[0] : undefined;
|
|
8156
|
-
const enabled = options?.enabled !== false;
|
|
8157
|
-
React.useEffect(() => {
|
|
8158
|
-
if (!enabled)
|
|
8159
|
-
return undefined;
|
|
8160
|
-
try {
|
|
8161
|
-
const handle = runtime.subscribe(viewDef.view, keyString);
|
|
8162
|
-
setIsLoading(true);
|
|
8163
|
-
return () => {
|
|
8164
|
-
try {
|
|
8165
|
-
handle.unsubscribe();
|
|
8166
|
-
}
|
|
8167
|
-
catch (err) {
|
|
8168
|
-
console.error('[Hyperstack] Error unsubscribing from view:', err);
|
|
8169
|
-
}
|
|
8170
|
-
};
|
|
8171
|
-
}
|
|
8172
|
-
catch (err) {
|
|
8173
|
-
setError(err instanceof Error ? err : new Error('Subscription failed'));
|
|
8174
|
-
setIsLoading(false);
|
|
8175
|
-
return undefined;
|
|
8176
|
-
}
|
|
8177
|
-
}, [keyString, enabled]);
|
|
8178
|
-
const refresh = React.useCallback(() => {
|
|
8179
|
-
if (!enabled)
|
|
8180
|
-
return;
|
|
8181
|
-
try {
|
|
8182
|
-
const handle = runtime.subscribe(viewDef.view, keyString);
|
|
8183
|
-
setIsLoading(true);
|
|
8184
|
-
setTimeout(() => {
|
|
8185
|
-
try {
|
|
8186
|
-
handle.unsubscribe();
|
|
8187
|
-
}
|
|
8188
|
-
catch (err) {
|
|
8189
|
-
console.error('[Hyperstack] Error during refresh unsubscribe:', err);
|
|
8190
|
-
}
|
|
8191
|
-
}, 0);
|
|
8192
|
-
}
|
|
8193
|
-
catch (err) {
|
|
8194
|
-
setError(err instanceof Error ? err : new Error('Refresh failed'));
|
|
8195
|
-
setIsLoading(false);
|
|
8196
|
-
}
|
|
8197
|
-
}, [keyString, enabled]);
|
|
8198
|
-
const data = React.useSyncExternalStore((callback) => {
|
|
8199
|
-
const unsubscribe = runtime.zustandStore.subscribe(callback);
|
|
8200
|
-
return unsubscribe;
|
|
8201
|
-
}, () => {
|
|
8202
|
-
const viewMap = runtime.zustandStore.getState().entities.get(viewDef.view);
|
|
8203
|
-
if (!viewMap)
|
|
8204
|
-
return undefined;
|
|
8205
|
-
if (keyString) {
|
|
8206
|
-
return viewMap.get(keyString);
|
|
8207
|
-
}
|
|
8208
|
-
const firstEntry = viewMap.values().next();
|
|
8209
|
-
return firstEntry.done ? undefined : firstEntry.value;
|
|
8210
|
-
});
|
|
8211
|
-
React.useEffect(() => {
|
|
8212
|
-
if (data && isLoading) {
|
|
8213
|
-
setIsLoading(false);
|
|
8214
|
-
}
|
|
8215
|
-
}, [data, isLoading]);
|
|
8216
|
-
return {
|
|
8217
|
-
data: (options?.initialData ?? data),
|
|
8218
|
-
isLoading,
|
|
8219
|
-
error,
|
|
8220
|
-
refresh
|
|
8221
|
-
};
|
|
8222
|
-
}
|
|
8223
|
-
};
|
|
8224
|
-
}
|
|
8225
|
-
function useListViewInternal(viewDef, runtime, params, options) {
|
|
8226
|
-
const [isLoading, setIsLoading] = React.useState(!options?.initialData);
|
|
8227
|
-
const [error, setError] = React.useState();
|
|
8228
|
-
const cachedDataRef = React.useRef(undefined);
|
|
8229
|
-
const lastMapRef = React.useRef(undefined);
|
|
8230
|
-
const lastSortedKeysRef = React.useRef(undefined);
|
|
8231
|
-
const enabled = options?.enabled !== false;
|
|
8232
|
-
const key = params?.key;
|
|
8233
|
-
const take = params?.take;
|
|
8234
|
-
const skip = params?.skip;
|
|
8235
|
-
const filtersJson = params?.filters ? JSON.stringify(params.filters) : undefined;
|
|
8236
|
-
const filters = React.useMemo(() => params?.filters, [filtersJson]);
|
|
8237
|
-
React.useEffect(() => {
|
|
8238
|
-
if (!enabled)
|
|
8239
|
-
return undefined;
|
|
8240
|
-
try {
|
|
8241
|
-
const handle = runtime.subscribe(viewDef.view, key, filters, take, skip);
|
|
8242
|
-
setIsLoading(true);
|
|
8243
|
-
return () => {
|
|
8244
|
-
try {
|
|
8245
|
-
handle.unsubscribe();
|
|
8246
|
-
}
|
|
8247
|
-
catch (err) {
|
|
8248
|
-
console.error('[Hyperstack] Error unsubscribing from list view:', err);
|
|
8249
|
-
}
|
|
8250
|
-
};
|
|
8251
|
-
}
|
|
8252
|
-
catch (err) {
|
|
8253
|
-
setError(err instanceof Error ? err : new Error('Subscription failed'));
|
|
8254
|
-
setIsLoading(false);
|
|
8255
|
-
return undefined;
|
|
8256
|
-
}
|
|
8257
|
-
}, [enabled, key, filtersJson, take, skip]);
|
|
8258
|
-
const refresh = React.useCallback(() => {
|
|
8259
|
-
if (!enabled)
|
|
8260
|
-
return;
|
|
8261
|
-
try {
|
|
8262
|
-
const handle = runtime.subscribe(viewDef.view, key, filters, take, skip);
|
|
8263
|
-
setIsLoading(true);
|
|
8264
|
-
setTimeout(() => {
|
|
8265
|
-
try {
|
|
8266
|
-
handle.unsubscribe();
|
|
8267
|
-
}
|
|
8268
|
-
catch (err) {
|
|
8269
|
-
console.error('[Hyperstack] Error during list refresh unsubscribe:', err);
|
|
8270
|
-
}
|
|
8271
|
-
}, 0);
|
|
8272
|
-
}
|
|
8273
|
-
catch (err) {
|
|
8274
|
-
setError(err instanceof Error ? err : new Error('Refresh failed'));
|
|
8275
|
-
setIsLoading(false);
|
|
8276
|
-
}
|
|
8277
|
-
}, [enabled, key, filtersJson, take, skip]);
|
|
8278
|
-
const data = React.useSyncExternalStore((callback) => {
|
|
8279
|
-
const unsubscribe = runtime.zustandStore.subscribe(callback);
|
|
8280
|
-
return unsubscribe;
|
|
8281
|
-
}, () => {
|
|
8282
|
-
const state = runtime.zustandStore.getState();
|
|
8283
|
-
const baseMap = state.entities.get(viewDef.view);
|
|
8284
|
-
const sortedKeys = state.sortedKeys.get(viewDef.view);
|
|
8285
|
-
if (!baseMap) {
|
|
8286
|
-
if (cachedDataRef.current !== undefined) {
|
|
8287
|
-
cachedDataRef.current = undefined;
|
|
8288
|
-
lastMapRef.current = undefined;
|
|
8289
|
-
lastSortedKeysRef.current = undefined;
|
|
8290
|
-
}
|
|
8291
|
-
return undefined;
|
|
8292
|
-
}
|
|
8293
|
-
if (lastMapRef.current === baseMap && lastSortedKeysRef.current === sortedKeys && cachedDataRef.current !== undefined) {
|
|
8294
|
-
return cachedDataRef.current;
|
|
8295
|
-
}
|
|
8296
|
-
let items;
|
|
8297
|
-
if (sortedKeys && sortedKeys.length > 0) {
|
|
8298
|
-
items = sortedKeys.map(k => baseMap.get(k)).filter(v => v !== undefined);
|
|
8299
|
-
}
|
|
8300
|
-
else {
|
|
8301
|
-
items = Array.from(baseMap.values());
|
|
8302
|
-
}
|
|
8303
|
-
if (params?.where) {
|
|
8304
|
-
items = items.filter((item) => {
|
|
8305
|
-
return Object.entries(params.where).every(([fieldKey, condition]) => {
|
|
8306
|
-
const value = item[fieldKey];
|
|
8307
|
-
if (typeof condition === 'object' && condition !== null) {
|
|
8308
|
-
const cond = condition;
|
|
8309
|
-
if ('gte' in cond)
|
|
8310
|
-
return value >= cond.gte;
|
|
8311
|
-
if ('lte' in cond)
|
|
8312
|
-
return value <= cond.lte;
|
|
8313
|
-
if ('gt' in cond)
|
|
8314
|
-
return value > cond.gt;
|
|
8315
|
-
if ('lt' in cond)
|
|
8316
|
-
return value < cond.lt;
|
|
8317
|
-
}
|
|
8318
|
-
return value === condition;
|
|
8319
|
-
});
|
|
8320
|
-
});
|
|
8321
|
-
}
|
|
8322
|
-
if (params?.limit) {
|
|
8323
|
-
items = items.slice(0, params.limit);
|
|
8324
|
-
}
|
|
8325
|
-
lastMapRef.current = baseMap;
|
|
8326
|
-
lastSortedKeysRef.current = sortedKeys;
|
|
8327
|
-
cachedDataRef.current = items;
|
|
8328
|
-
return items;
|
|
8329
|
-
});
|
|
8330
|
-
React.useEffect(() => {
|
|
8331
|
-
if (data && isLoading) {
|
|
8332
|
-
setIsLoading(false);
|
|
8333
|
-
}
|
|
8334
|
-
}, [data, isLoading]);
|
|
8335
|
-
return {
|
|
8336
|
-
data: (options?.initialData ?? data),
|
|
8337
|
-
isLoading,
|
|
8338
|
-
error,
|
|
8339
|
-
refresh
|
|
8340
|
-
};
|
|
8341
|
-
}
|
|
8342
|
-
function createListViewHook(viewDef, runtime) {
|
|
8343
|
-
function use(params, options) {
|
|
8344
|
-
const result = useListViewInternal(viewDef, runtime, params, options);
|
|
8345
|
-
if (params?.take === 1) {
|
|
8346
|
-
return {
|
|
8347
|
-
data: result.data?.[0],
|
|
8348
|
-
isLoading: result.isLoading,
|
|
8349
|
-
error: result.error,
|
|
8350
|
-
refresh: result.refresh
|
|
8351
|
-
};
|
|
8352
|
-
}
|
|
8353
|
-
return result;
|
|
8354
|
-
}
|
|
8355
|
-
function useOne(params, options) {
|
|
8356
|
-
const paramsWithTake = params ? { ...params, take: 1 } : { take: 1 };
|
|
8357
|
-
const result = useListViewInternal(viewDef, runtime, paramsWithTake, options);
|
|
8358
|
-
return {
|
|
8359
|
-
data: result.data?.[0],
|
|
8360
|
-
isLoading: result.isLoading,
|
|
8361
|
-
error: result.error,
|
|
8362
|
-
refresh: result.refresh
|
|
8363
|
-
};
|
|
8364
|
-
}
|
|
8365
|
-
return { use, useOne };
|
|
8366
|
-
}
|
|
8367
|
-
|
|
8368
|
-
function createTxMutationHook(runtime, transactions) {
|
|
8369
|
-
return function useMutation() {
|
|
8370
|
-
const [status, setStatus] = React.useState('idle');
|
|
8371
|
-
const [error, setError] = React.useState();
|
|
8372
|
-
const [signature, setSignature] = React.useState();
|
|
8373
|
-
const submit = async (instructionOrTx) => {
|
|
8374
|
-
setStatus('pending');
|
|
8375
|
-
setError(undefined);
|
|
8376
|
-
setSignature(undefined);
|
|
8377
|
-
try {
|
|
8378
|
-
if (!instructionOrTx) {
|
|
8379
|
-
throw new Error('Transaction instruction or transaction object is required');
|
|
8380
|
-
}
|
|
8381
|
-
if (!runtime.wallet) {
|
|
8382
|
-
throw new Error('Wallet not connected. Please provide a wallet adapter to HyperstackProvider.');
|
|
8383
|
-
}
|
|
8384
|
-
let txSignature;
|
|
8385
|
-
let instructionsToRefresh = [];
|
|
8386
|
-
if (Array.isArray(instructionOrTx)) {
|
|
8387
|
-
txSignature = await runtime.wallet.signAndSend(instructionOrTx);
|
|
8388
|
-
instructionsToRefresh = instructionOrTx.filter(inst => inst && typeof inst === 'object' && inst.instruction && inst.params !== undefined);
|
|
8389
|
-
}
|
|
8390
|
-
else if (typeof instructionOrTx === 'object' &&
|
|
8391
|
-
instructionOrTx.instruction &&
|
|
8392
|
-
instructionOrTx.params !== undefined) {
|
|
8393
|
-
txSignature = await runtime.wallet.signAndSend(instructionOrTx);
|
|
8394
|
-
instructionsToRefresh = [instructionOrTx];
|
|
8395
|
-
}
|
|
8396
|
-
else {
|
|
8397
|
-
txSignature = await runtime.wallet.signAndSend(instructionOrTx);
|
|
8398
|
-
}
|
|
8399
|
-
setSignature(txSignature);
|
|
8400
|
-
setStatus('success');
|
|
8401
|
-
if (transactions && instructionsToRefresh.length > 0) {
|
|
8402
|
-
for (const inst of instructionsToRefresh) {
|
|
8403
|
-
const txDef = transactions[inst.instruction];
|
|
8404
|
-
if (txDef?.refresh) {
|
|
8405
|
-
for (const refreshTarget of txDef.refresh) {
|
|
8406
|
-
try {
|
|
8407
|
-
const key = typeof refreshTarget.key === 'function'
|
|
8408
|
-
? refreshTarget.key(inst.params)
|
|
8409
|
-
: refreshTarget.key;
|
|
8410
|
-
runtime.subscribe(refreshTarget.view, key);
|
|
8411
|
-
}
|
|
8412
|
-
catch (err) {
|
|
8413
|
-
console.error('[Hyperstack] Error refreshing view after transaction:', err);
|
|
8414
|
-
}
|
|
8415
|
-
}
|
|
8416
|
-
}
|
|
8417
|
-
}
|
|
8418
|
-
}
|
|
8419
|
-
return txSignature;
|
|
8420
|
-
}
|
|
8421
|
-
catch (err) {
|
|
8422
|
-
const errorMessage = err instanceof Error ? err.message : 'Transaction failed';
|
|
8423
|
-
console.error('[Hyperstack] Transaction error:', errorMessage, err);
|
|
8424
|
-
setStatus('error');
|
|
8425
|
-
setError(errorMessage);
|
|
8426
|
-
throw err;
|
|
8427
|
-
}
|
|
8428
|
-
};
|
|
8429
|
-
const reset = () => {
|
|
8430
|
-
setStatus('idle');
|
|
8431
|
-
setError(undefined);
|
|
8432
|
-
setSignature(undefined);
|
|
8433
|
-
};
|
|
8434
|
-
return {
|
|
8435
|
-
submit,
|
|
8436
|
-
status,
|
|
8437
|
-
error,
|
|
8438
|
-
signature,
|
|
8439
|
-
reset
|
|
8440
|
-
};
|
|
8441
|
-
};
|
|
8442
|
-
}
|
|
8443
|
-
|
|
8444
|
-
function useHyperstack(stack) {
|
|
8445
|
-
if (!stack) {
|
|
8446
|
-
throw new Error('[Hyperstack] Stack definition is required');
|
|
8447
|
-
}
|
|
8448
|
-
const { runtime } = useHyperstackContext();
|
|
8449
|
-
const views = {};
|
|
8450
|
-
for (const [viewName, viewGroup] of Object.entries(stack.views)) {
|
|
8451
|
-
views[viewName] = {};
|
|
8452
|
-
if (typeof viewGroup === 'object' && viewGroup !== null) {
|
|
8453
|
-
const group = viewGroup;
|
|
8454
|
-
for (const [subViewName, viewDef] of Object.entries(group)) {
|
|
8455
|
-
if (!viewDef || typeof viewDef !== 'object' || !('mode' in viewDef))
|
|
8456
|
-
continue;
|
|
8457
|
-
if (viewDef.mode === 'state') {
|
|
8458
|
-
views[viewName][subViewName] = createStateViewHook(viewDef, runtime);
|
|
8459
|
-
}
|
|
8460
|
-
else if (viewDef.mode === 'list') {
|
|
8461
|
-
views[viewName][subViewName] = createListViewHook(viewDef, runtime);
|
|
8462
|
-
}
|
|
8463
|
-
}
|
|
8464
|
-
}
|
|
8465
|
-
}
|
|
8466
|
-
const tx = {};
|
|
8467
|
-
if (stack.transactions) {
|
|
8468
|
-
for (const [txName, txDef] of Object.entries(stack.transactions)) {
|
|
8469
|
-
tx[txName] = txDef.build;
|
|
8470
|
-
}
|
|
8471
|
-
}
|
|
8472
|
-
tx.useMutation = createTxMutationHook(runtime, stack.transactions);
|
|
8473
|
-
return {
|
|
8474
|
-
views,
|
|
8475
|
-
tx,
|
|
8476
|
-
zustandStore: runtime.zustandStore,
|
|
8477
|
-
runtime
|
|
8478
|
-
};
|
|
8479
|
-
}
|
|
8480
|
-
|
|
8481
9064
|
exports.ConnectionManager = ConnectionManager;
|
|
8482
9065
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
8483
9066
|
exports.DEFAULT_MAX_ENTRIES_PER_VIEW = DEFAULT_MAX_ENTRIES_PER_VIEW;
|
|
@@ -8488,14 +9071,25 @@ exports.HyperstackProvider = HyperstackProvider;
|
|
|
8488
9071
|
exports.MemoryAdapter = MemoryAdapter;
|
|
8489
9072
|
exports.SubscriptionRegistry = SubscriptionRegistry;
|
|
8490
9073
|
exports.ZustandAdapter = ZustandAdapter;
|
|
8491
|
-
exports.
|
|
9074
|
+
exports.createInstructionExecutor = createInstructionExecutor;
|
|
9075
|
+
exports.createPublicKeySeed = createPublicKeySeed;
|
|
9076
|
+
exports.createSeed = createSeed;
|
|
9077
|
+
exports.derivePda = derivePda;
|
|
9078
|
+
exports.executeInstruction = executeInstruction;
|
|
9079
|
+
exports.formatProgramError = formatProgramError;
|
|
8492
9080
|
exports.isSnapshotFrame = isSnapshotFrame;
|
|
8493
9081
|
exports.isValidFrame = isValidFrame;
|
|
8494
9082
|
exports.parseFrame = parseFrame;
|
|
8495
9083
|
exports.parseFrameFromBlob = parseFrameFromBlob;
|
|
9084
|
+
exports.parseInstructionError = parseInstructionError;
|
|
9085
|
+
exports.resolveAccounts = resolveAccounts;
|
|
9086
|
+
exports.serializeInstructionData = serializeInstructionData;
|
|
8496
9087
|
exports.useConnectionState = useConnectionState;
|
|
8497
9088
|
exports.useEntity = useEntity;
|
|
8498
9089
|
exports.useHyperstack = useHyperstack;
|
|
8499
9090
|
exports.useHyperstackContext = useHyperstackContext;
|
|
9091
|
+
exports.useInstructionMutation = useInstructionMutation;
|
|
8500
9092
|
exports.useView = useView;
|
|
9093
|
+
exports.validateAccountResolution = validateAccountResolution;
|
|
9094
|
+
exports.waitForConfirmation = waitForConfirmation;
|
|
8501
9095
|
//# sourceMappingURL=index.js.map
|