hyperstack-react 0.4.3 → 0.5.1

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.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: entity, done: false });
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(entity);
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
- return {
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
- return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
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
- return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
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
- return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
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
- return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
7893
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
7894
+ return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions }, options);
7844
7895
  },
7845
7896
  watch(options) {
7846
- return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
7897
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
7898
+ return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions });
7847
7899
  },
7848
7900
  watchRich(options) {
7849
- return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
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 and user-provided accounts
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
- const resolved = [];
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 accountMetas) {
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
- resolved.push(resolvedAccount);
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: resolved,
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
- // PDA derivation will be implemented in pda.ts
7943
- // For now, return a placeholder that will be resolved later
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: '', // Will be derived
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(`Missing required accounts: ${result.missingUserAccounts.join(', ')}`);
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(signature) {
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 client = stack ? getClient(stack) : null;
8701
- return useSyncExternalStore((callback) => {
8702
- if (!client)
8703
- return () => { };
8704
- return client.onConnectionStateChange(callback);
8705
- }, () => client?.connectionState ?? 'disconnected');
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
- // Cache the result to return stable reference for useSyncExternalStore
8812
- if (entity !== cachedSnapshotRef.current) {
8813
- cachedSnapshotRef.current = entity;
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