hyperstack-typescript 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -314,6 +314,31 @@ class FrameProcessor {
314
314
  ? DEFAULT_MAX_ENTRIES_PER_VIEW
315
315
  : config.maxEntriesPerView;
316
316
  this.flushIntervalMs = config.flushIntervalMs ?? 0;
317
+ this.schemas = config.schemas;
318
+ }
319
+ getSchema(viewPath) {
320
+ const schemas = this.schemas;
321
+ if (!schemas)
322
+ return null;
323
+ const entityName = viewPath.split('/')[0];
324
+ if (typeof entityName !== 'string' || entityName.length === 0)
325
+ return null;
326
+ const entityKey = entityName;
327
+ return schemas[entityKey] ?? null;
328
+ }
329
+ validateEntity(viewPath, data) {
330
+ const schema = this.getSchema(viewPath);
331
+ if (!schema)
332
+ return true;
333
+ const result = schema.safeParse(data);
334
+ if (!result.success) {
335
+ console.warn('[Hyperstack] Frame validation failed:', {
336
+ view: viewPath,
337
+ error: result.error,
338
+ });
339
+ return false;
340
+ }
341
+ return true;
317
342
  }
318
343
  handleFrame(frame) {
319
344
  if (this.flushIntervalMs === 0) {
@@ -409,6 +434,9 @@ class FrameProcessor {
409
434
  handleSnapshotFrameWithoutEnforce(frame) {
410
435
  const viewPath = frame.entity;
411
436
  for (const entity of frame.data) {
437
+ if (!this.validateEntity(viewPath, entity.data)) {
438
+ continue;
439
+ }
412
440
  const previousValue = this.storage.get(viewPath, entity.key);
413
441
  this.storage.set(viewPath, entity.key, entity.data);
414
442
  this.storage.notifyUpdate(viewPath, entity.key, {
@@ -429,6 +457,9 @@ class FrameProcessor {
429
457
  switch (frame.op) {
430
458
  case 'create':
431
459
  case 'upsert':
460
+ if (!this.validateEntity(viewPath, frame.data)) {
461
+ break;
462
+ }
432
463
  this.storage.set(viewPath, frame.key, frame.data);
433
464
  this.storage.notifyUpdate(viewPath, frame.key, {
434
465
  type: 'upsert',
@@ -443,6 +474,9 @@ class FrameProcessor {
443
474
  const merged = existing
444
475
  ? deepMergeWithAppend$1(existing, frame.data, appendPaths)
445
476
  : frame.data;
477
+ if (!this.validateEntity(viewPath, merged)) {
478
+ break;
479
+ }
446
480
  this.storage.set(viewPath, frame.key, merged);
447
481
  this.storage.notifyUpdate(viewPath, frame.key, {
448
482
  type: 'patch',
@@ -931,7 +965,8 @@ function createUpdateStream(storage, subscriptionRegistry, subscription, keyFilt
931
965
  },
932
966
  };
933
967
  }
934
- function createEntityStream(storage, subscriptionRegistry, subscription, keyFilter) {
968
+ function createEntityStream(storage, subscriptionRegistry, subscription, options, keyFilter) {
969
+ const schema = options?.schema;
935
970
  return {
936
971
  [Symbol.asyncIterator]() {
937
972
  const queue = [];
@@ -947,16 +982,27 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
947
982
  if (update.type === 'deleted')
948
983
  return;
949
984
  const entity = (update.type === 'created' ? update.data : update.after);
985
+ let output;
986
+ if (schema) {
987
+ const parsed = schema.safeParse(entity);
988
+ if (!parsed.success) {
989
+ return;
990
+ }
991
+ output = parsed.data;
992
+ }
993
+ else {
994
+ output = entity;
995
+ }
950
996
  if (waitingResolve) {
951
997
  const resolve = waitingResolve;
952
998
  waitingResolve = null;
953
- resolve({ value: entity, done: false });
999
+ resolve({ value: output, done: false });
954
1000
  }
955
1001
  else {
956
1002
  if (queue.length >= MAX_QUEUE_SIZE) {
957
1003
  queue.shift();
958
1004
  }
959
- queue.push(entity);
1005
+ queue.push(output);
960
1006
  }
961
1007
  };
962
1008
  const start = () => {
@@ -969,7 +1015,7 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
969
1015
  unsubscribeRegistry?.();
970
1016
  };
971
1017
  start();
972
- return {
1018
+ const iterator = {
973
1019
  async next() {
974
1020
  if (done) {
975
1021
  return { value: undefined, done: true };
@@ -991,6 +1037,7 @@ function createEntityStream(storage, subscriptionRegistry, subscription, keyFilt
991
1037
  throw error;
992
1038
  },
993
1039
  };
1040
+ return iterator;
994
1041
  },
995
1042
  };
996
1043
  }
@@ -1062,13 +1109,16 @@ function createRichUpdateStream(storage, subscriptionRegistry, subscription, key
1062
1109
  function createTypedStateView(viewDef, storage, subscriptionRegistry) {
1063
1110
  return {
1064
1111
  use(key, options) {
1065
- return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
1112
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1113
+ return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, options, key);
1066
1114
  },
1067
1115
  watch(key, options) {
1068
- return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
1116
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1117
+ return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, key);
1069
1118
  },
1070
1119
  watchRich(key, options) {
1071
- return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...options }, key);
1120
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1121
+ return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key, ...subscriptionOptions }, key);
1072
1122
  },
1073
1123
  async get(key) {
1074
1124
  return storage.get(viewDef.view, key);
@@ -1081,13 +1131,16 @@ function createTypedStateView(viewDef, storage, subscriptionRegistry) {
1081
1131
  function createTypedListView(viewDef, storage, subscriptionRegistry) {
1082
1132
  return {
1083
1133
  use(options) {
1084
- return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
1134
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1135
+ return createEntityStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions }, options);
1085
1136
  },
1086
1137
  watch(options) {
1087
- return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
1138
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1139
+ return createUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions });
1088
1140
  },
1089
1141
  watchRich(options) {
1090
- return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...options });
1142
+ const { schema: _schema, ...subscriptionOptions } = options ?? {};
1143
+ return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...subscriptionOptions });
1091
1144
  },
1092
1145
  async get() {
1093
1146
  return storage.getAll(viewDef.view);
@@ -1115,39 +1168,335 @@ function createTypedViews(stack, storage, subscriptionRegistry) {
1115
1168
  return views;
1116
1169
  }
1117
1170
 
1171
+ /**
1172
+ * PDA (Program Derived Address) derivation utilities.
1173
+ *
1174
+ * Implements Solana's PDA derivation algorithm without depending on @solana/web3.js.
1175
+ */
1176
+ // Base58 alphabet (Bitcoin/Solana style)
1177
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
1178
+ /**
1179
+ * Decode base58 string to Uint8Array.
1180
+ */
1181
+ function decodeBase58(str) {
1182
+ if (str.length === 0) {
1183
+ return new Uint8Array(0);
1184
+ }
1185
+ const bytes = [0];
1186
+ for (const char of str) {
1187
+ const value = BASE58_ALPHABET.indexOf(char);
1188
+ if (value === -1) {
1189
+ throw new Error('Invalid base58 character: ' + char);
1190
+ }
1191
+ let carry = value;
1192
+ for (let i = 0; i < bytes.length; i++) {
1193
+ carry += (bytes[i] ?? 0) * 58;
1194
+ bytes[i] = carry & 0xff;
1195
+ carry >>= 8;
1196
+ }
1197
+ while (carry > 0) {
1198
+ bytes.push(carry & 0xff);
1199
+ carry >>= 8;
1200
+ }
1201
+ }
1202
+ // Add leading zeros for each leading '1' in input
1203
+ for (const char of str) {
1204
+ if (char !== '1')
1205
+ break;
1206
+ bytes.push(0);
1207
+ }
1208
+ return new Uint8Array(bytes.reverse());
1209
+ }
1210
+ /**
1211
+ * Encode Uint8Array to base58 string.
1212
+ */
1213
+ function encodeBase58(bytes) {
1214
+ if (bytes.length === 0) {
1215
+ return '';
1216
+ }
1217
+ const digits = [0];
1218
+ for (const byte of bytes) {
1219
+ let carry = byte;
1220
+ for (let i = 0; i < digits.length; i++) {
1221
+ carry += (digits[i] ?? 0) << 8;
1222
+ digits[i] = carry % 58;
1223
+ carry = (carry / 58) | 0;
1224
+ }
1225
+ while (carry > 0) {
1226
+ digits.push(carry % 58);
1227
+ carry = (carry / 58) | 0;
1228
+ }
1229
+ }
1230
+ // Add leading zeros for each leading 0 byte in input
1231
+ for (const byte of bytes) {
1232
+ if (byte !== 0)
1233
+ break;
1234
+ digits.push(0);
1235
+ }
1236
+ return digits.reverse().map(d => BASE58_ALPHABET[d]).join('');
1237
+ }
1238
+ /**
1239
+ * SHA-256 hash function (synchronous, Node.js).
1240
+ */
1241
+ function sha256Sync(data) {
1242
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1243
+ const { createHash } = require('crypto');
1244
+ return new Uint8Array(createHash('sha256').update(Buffer.from(data)).digest());
1245
+ }
1246
+ /**
1247
+ * SHA-256 hash function (async, works in browser and Node.js).
1248
+ */
1249
+ async function sha256Async(data) {
1250
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) {
1251
+ // Create a copy of the data to ensure we have an ArrayBuffer
1252
+ const copy = new Uint8Array(data);
1253
+ const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', copy);
1254
+ return new Uint8Array(hashBuffer);
1255
+ }
1256
+ return sha256Sync(data);
1257
+ }
1258
+ /**
1259
+ * PDA marker bytes appended to seeds before hashing.
1260
+ */
1261
+ const PDA_MARKER = new TextEncoder().encode('ProgramDerivedAddress');
1262
+ /**
1263
+ * Build the hash input buffer for PDA derivation.
1264
+ */
1265
+ function buildPdaBuffer(seeds, programIdBytes, bump) {
1266
+ const totalLength = seeds.reduce((sum, s) => sum + s.length, 0)
1267
+ + 1 // bump
1268
+ + 32 // programId
1269
+ + PDA_MARKER.length;
1270
+ const buffer = new Uint8Array(totalLength);
1271
+ let offset = 0;
1272
+ // Copy seeds
1273
+ for (const seed of seeds) {
1274
+ buffer.set(seed, offset);
1275
+ offset += seed.length;
1276
+ }
1277
+ // Add bump seed
1278
+ buffer[offset++] = bump;
1279
+ // Add program ID
1280
+ buffer.set(programIdBytes, offset);
1281
+ offset += 32;
1282
+ // Add PDA marker
1283
+ buffer.set(PDA_MARKER, offset);
1284
+ return buffer;
1285
+ }
1286
+ /**
1287
+ * Validate seeds before PDA derivation.
1288
+ */
1289
+ function validateSeeds(seeds) {
1290
+ if (seeds.length > 16) {
1291
+ throw new Error('Maximum of 16 seeds allowed');
1292
+ }
1293
+ for (let i = 0; i < seeds.length; i++) {
1294
+ const seed = seeds[i];
1295
+ if (seed && seed.length > 32) {
1296
+ throw new Error('Seed ' + i + ' exceeds maximum length of 32 bytes');
1297
+ }
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Derives a Program-Derived Address (PDA) from seeds and program ID.
1302
+ *
1303
+ * Algorithm:
1304
+ * 1. For bump = 255 down to 0:
1305
+ * a. Concatenate: seeds + [bump] + programId + "ProgramDerivedAddress"
1306
+ * b. SHA-256 hash the concatenation
1307
+ * c. If result is off the ed25519 curve, return it
1308
+ * 2. If no valid PDA found after 256 attempts, throw error
1309
+ *
1310
+ * @param seeds - Array of seed buffers (max 32 bytes each, max 16 seeds)
1311
+ * @param programId - The program ID (base58 string)
1312
+ * @returns Tuple of [derivedAddress (base58), bumpSeed]
1313
+ */
1314
+ async function findProgramAddress(seeds, programId) {
1315
+ validateSeeds(seeds);
1316
+ const programIdBytes = decodeBase58(programId);
1317
+ if (programIdBytes.length !== 32) {
1318
+ throw new Error('Program ID must be 32 bytes');
1319
+ }
1320
+ // Try bump seeds from 255 down to 0
1321
+ for (let bump = 255; bump >= 0; bump--) {
1322
+ const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
1323
+ const hash = await sha256Async(buffer);
1324
+ {
1325
+ return [encodeBase58(hash), bump];
1326
+ }
1327
+ }
1328
+ throw new Error('Unable to find a valid PDA');
1329
+ }
1330
+ /**
1331
+ * Synchronous version of findProgramAddress.
1332
+ * Uses synchronous SHA-256 (Node.js crypto module).
1333
+ */
1334
+ function findProgramAddressSync(seeds, programId) {
1335
+ validateSeeds(seeds);
1336
+ const programIdBytes = decodeBase58(programId);
1337
+ if (programIdBytes.length !== 32) {
1338
+ throw new Error('Program ID must be 32 bytes');
1339
+ }
1340
+ // Try bump seeds from 255 down to 0
1341
+ for (let bump = 255; bump >= 0; bump--) {
1342
+ const buffer = buildPdaBuffer(seeds, programIdBytes, bump);
1343
+ const hash = sha256Sync(buffer);
1344
+ {
1345
+ return [encodeBase58(hash), bump];
1346
+ }
1347
+ }
1348
+ throw new Error('Unable to find a valid PDA');
1349
+ }
1350
+ /**
1351
+ * Creates a seed buffer from various input types.
1352
+ *
1353
+ * @param value - The value to convert to a seed
1354
+ * @returns Uint8Array suitable for PDA derivation
1355
+ */
1356
+ function createSeed(value) {
1357
+ if (value instanceof Uint8Array) {
1358
+ return value;
1359
+ }
1360
+ if (typeof value === 'string') {
1361
+ return new TextEncoder().encode(value);
1362
+ }
1363
+ if (typeof value === 'bigint') {
1364
+ // Convert bigint to 8-byte buffer (u64 little-endian)
1365
+ const buffer = new Uint8Array(8);
1366
+ let n = value;
1367
+ for (let i = 0; i < 8; i++) {
1368
+ buffer[i] = Number(n & BigInt(0xff));
1369
+ n >>= BigInt(8);
1370
+ }
1371
+ return buffer;
1372
+ }
1373
+ if (typeof value === 'number') {
1374
+ // Assume u64
1375
+ return createSeed(BigInt(value));
1376
+ }
1377
+ throw new Error('Cannot create seed from value');
1378
+ }
1379
+ /**
1380
+ * Creates a public key seed from a base58-encoded address.
1381
+ *
1382
+ * @param address - Base58-encoded public key
1383
+ * @returns 32-byte Uint8Array
1384
+ */
1385
+ function createPublicKeySeed(address) {
1386
+ const decoded = decodeBase58(address);
1387
+ if (decoded.length !== 32) {
1388
+ throw new Error('Invalid public key length: expected 32, got ' + decoded.length);
1389
+ }
1390
+ return decoded;
1391
+ }
1392
+
1393
+ /**
1394
+ * Topologically sort accounts so that dependencies (accountRef) are resolved first.
1395
+ * Non-PDA accounts come first, then PDAs in dependency order.
1396
+ */
1397
+ function sortAccountsByDependency(accountMetas) {
1398
+ // Separate non-PDA and PDA accounts
1399
+ const nonPda = [];
1400
+ const pda = [];
1401
+ for (const meta of accountMetas) {
1402
+ if (meta.category === 'pda') {
1403
+ pda.push(meta);
1404
+ }
1405
+ else {
1406
+ nonPda.push(meta);
1407
+ }
1408
+ }
1409
+ // Build dependency graph for PDAs
1410
+ const pdaDeps = new Map();
1411
+ for (const meta of pda) {
1412
+ const deps = new Set();
1413
+ if (meta.pdaConfig) {
1414
+ for (const seed of meta.pdaConfig.seeds) {
1415
+ if (seed.type === 'accountRef') {
1416
+ deps.add(seed.accountName);
1417
+ }
1418
+ }
1419
+ }
1420
+ pdaDeps.set(meta.name, deps);
1421
+ }
1422
+ // Topological sort PDAs
1423
+ const sortedPda = [];
1424
+ const visited = new Set();
1425
+ const visiting = new Set();
1426
+ function visit(name) {
1427
+ if (visited.has(name))
1428
+ return;
1429
+ if (visiting.has(name)) {
1430
+ throw new Error('Circular dependency in PDA accounts: ' + name);
1431
+ }
1432
+ const meta = pda.find(m => m.name === name);
1433
+ if (!meta)
1434
+ return; // Not a PDA, skip
1435
+ visiting.add(name);
1436
+ const deps = pdaDeps.get(name) || new Set();
1437
+ for (const dep of deps) {
1438
+ // Only visit if dep is also a PDA
1439
+ if (pda.some(m => m.name === dep)) {
1440
+ visit(dep);
1441
+ }
1442
+ }
1443
+ visiting.delete(name);
1444
+ visited.add(name);
1445
+ sortedPda.push(meta);
1446
+ }
1447
+ for (const meta of pda) {
1448
+ visit(meta.name);
1449
+ }
1450
+ return [...nonPda, ...sortedPda];
1451
+ }
1118
1452
  /**
1119
1453
  * Resolves instruction accounts by categorizing and deriving addresses.
1120
1454
  *
1455
+ * Resolution order:
1456
+ * 1. Non-PDA accounts (signer, known, userProvided) are resolved first
1457
+ * 2. PDA accounts are resolved in dependency order (accounts they reference come first)
1458
+ *
1121
1459
  * @param accountMetas - Account metadata from the instruction definition
1122
- * @param args - Instruction arguments (used for PDA derivation)
1123
- * @param options - Resolution options including wallet and user-provided accounts
1460
+ * @param args - Instruction arguments (used for PDA derivation with argRef seeds)
1461
+ * @param options - Resolution options including wallet, user-provided accounts, and programId
1124
1462
  * @returns Resolved accounts and any missing required accounts
1125
1463
  */
1126
1464
  function resolveAccounts(accountMetas, args, options) {
1127
- const resolved = [];
1465
+ // Sort accounts by dependency
1466
+ const sorted = sortAccountsByDependency(accountMetas);
1467
+ // Track resolved accounts for PDA accountRef lookups
1468
+ const resolvedMap = {};
1128
1469
  const missing = [];
1129
- for (const meta of accountMetas) {
1130
- const resolvedAccount = resolveSingleAccount(meta, args, options);
1470
+ for (const meta of sorted) {
1471
+ const resolvedAccount = resolveSingleAccount(meta, args, options, resolvedMap);
1131
1472
  if (resolvedAccount) {
1132
- resolved.push(resolvedAccount);
1473
+ resolvedMap[meta.name] = resolvedAccount;
1133
1474
  }
1134
1475
  else if (!meta.isOptional) {
1135
1476
  missing.push(meta.name);
1136
1477
  }
1137
1478
  }
1479
+ // Return accounts in original order (as defined in accountMetas)
1480
+ const orderedAccounts = [];
1481
+ for (const meta of accountMetas) {
1482
+ const resolved = resolvedMap[meta.name];
1483
+ if (resolved) {
1484
+ orderedAccounts.push(resolved);
1485
+ }
1486
+ }
1138
1487
  return {
1139
- accounts: resolved,
1488
+ accounts: orderedAccounts,
1140
1489
  missingUserAccounts: missing,
1141
1490
  };
1142
1491
  }
1143
- function resolveSingleAccount(meta, args, options) {
1492
+ function resolveSingleAccount(meta, args, options, resolvedMap) {
1144
1493
  switch (meta.category) {
1145
1494
  case 'signer':
1146
1495
  return resolveSignerAccount(meta, options.wallet);
1147
1496
  case 'known':
1148
1497
  return resolveKnownAccount(meta);
1149
1498
  case 'pda':
1150
- return resolvePdaAccount(meta);
1499
+ return resolvePdaAccount(meta, args, resolvedMap, options.programId);
1151
1500
  case 'userProvided':
1152
1501
  return resolveUserProvidedAccount(meta, options.accounts);
1153
1502
  default:
@@ -1176,15 +1525,51 @@ function resolveKnownAccount(meta) {
1176
1525
  isWritable: meta.isWritable,
1177
1526
  };
1178
1527
  }
1179
- function resolvePdaAccount(meta, args) {
1528
+ function resolvePdaAccount(meta, args, resolvedMap, programId) {
1180
1529
  if (!meta.pdaConfig) {
1181
1530
  return null;
1182
1531
  }
1183
- // PDA derivation will be implemented in pda.ts
1184
- // For now, return a placeholder that will be resolved later
1532
+ // Determine which program to derive against
1533
+ const pdaProgramId = meta.pdaConfig.programId || programId;
1534
+ if (!pdaProgramId) {
1535
+ throw new Error('Cannot derive PDA for "' + meta.name + '": no programId specified. ' +
1536
+ 'Either set pdaConfig.programId or pass programId in options.');
1537
+ }
1538
+ // Build seeds array
1539
+ const seeds = [];
1540
+ for (const seed of meta.pdaConfig.seeds) {
1541
+ switch (seed.type) {
1542
+ case 'literal':
1543
+ seeds.push(createSeed(seed.value));
1544
+ break;
1545
+ case 'argRef': {
1546
+ const argValue = args[seed.argName];
1547
+ if (argValue === undefined) {
1548
+ throw new Error('PDA seed references missing argument: ' + seed.argName +
1549
+ ' (for account "' + meta.name + '")');
1550
+ }
1551
+ seeds.push(createSeed(argValue));
1552
+ break;
1553
+ }
1554
+ case 'accountRef': {
1555
+ const refAccount = resolvedMap[seed.accountName];
1556
+ if (!refAccount) {
1557
+ throw new Error('PDA seed references unresolved account: ' + seed.accountName +
1558
+ ' (for account "' + meta.name + '")');
1559
+ }
1560
+ // Account addresses are 32 bytes
1561
+ seeds.push(decodeBase58(refAccount.address));
1562
+ break;
1563
+ }
1564
+ default:
1565
+ throw new Error('Unknown seed type');
1566
+ }
1567
+ }
1568
+ // Derive the PDA
1569
+ const [derivedAddress] = findProgramAddressSync(seeds, pdaProgramId);
1185
1570
  return {
1186
1571
  name: meta.name,
1187
- address: '', // Will be derived
1572
+ address: derivedAddress,
1188
1573
  isSigner: meta.isSigner,
1189
1574
  isWritable: meta.isWritable,
1190
1575
  };
@@ -1209,86 +1594,10 @@ function resolveUserProvidedAccount(meta, accounts) {
1209
1594
  */
1210
1595
  function validateAccountResolution(result) {
1211
1596
  if (result.missingUserAccounts.length > 0) {
1212
- throw new Error(`Missing required accounts: ${result.missingUserAccounts.join(', ')}`);
1597
+ throw new Error('Missing required accounts: ' + result.missingUserAccounts.join(', '));
1213
1598
  }
1214
1599
  }
1215
1600
 
1216
- /**
1217
- * Derives a Program-Derived Address (PDA) from seeds and program ID.
1218
- *
1219
- * This function implements PDA derivation using the Solana algorithm:
1220
- * 1. Concatenate all seeds
1221
- * 2. Hash with SHA-256
1222
- * 3. Check if result is off-curve (valid PDA)
1223
- *
1224
- * Note: This is a placeholder implementation. In production, you would use
1225
- * the actual Solana web3.js library's PDA derivation.
1226
- *
1227
- * @param seeds - Array of seed buffers
1228
- * @param programId - The program ID (as base58 string)
1229
- * @returns The derived PDA address (base58 string)
1230
- */
1231
- async function derivePda(seeds, programId) {
1232
- // In production, this would use:
1233
- // PublicKey.findProgramAddressSync(seeds, new PublicKey(programId))
1234
- // For now, return a placeholder that will be replaced with actual implementation
1235
- const combined = Buffer.concat(seeds);
1236
- // Simulate PDA derivation (this is NOT the actual algorithm)
1237
- const hash = await simulateHash(combined);
1238
- // Return base58-encoded address
1239
- return bs58Encode(hash);
1240
- }
1241
- /**
1242
- * Creates a seed buffer from various input types.
1243
- *
1244
- * @param value - The value to convert to a seed
1245
- * @returns Buffer suitable for PDA derivation
1246
- */
1247
- function createSeed(value) {
1248
- if (Buffer.isBuffer(value)) {
1249
- return value;
1250
- }
1251
- if (value instanceof Uint8Array) {
1252
- return Buffer.from(value);
1253
- }
1254
- if (typeof value === 'string') {
1255
- return Buffer.from(value, 'utf-8');
1256
- }
1257
- if (typeof value === 'bigint') {
1258
- // Convert bigint to 8-byte buffer (u64)
1259
- const buffer = Buffer.alloc(8);
1260
- buffer.writeBigUInt64LE(value);
1261
- return buffer;
1262
- }
1263
- throw new Error(`Cannot create seed from type: ${typeof value}`);
1264
- }
1265
- /**
1266
- * Creates a public key seed from a base58-encoded address.
1267
- *
1268
- * @param address - Base58-encoded public key
1269
- * @returns 32-byte buffer
1270
- */
1271
- function createPublicKeySeed(address) {
1272
- // In production, decode base58 to 32-byte buffer
1273
- // For now, return placeholder
1274
- return Buffer.alloc(32);
1275
- }
1276
- async function simulateHash(data) {
1277
- // In production, use actual SHA-256
1278
- // This is a placeholder
1279
- if (typeof crypto !== 'undefined' && crypto.subtle) {
1280
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
1281
- return Buffer.from(hashBuffer);
1282
- }
1283
- // Fallback for Node.js
1284
- return Buffer.alloc(32, 0);
1285
- }
1286
- function bs58Encode(buffer) {
1287
- // In production, use actual base58 encoding
1288
- // This is a placeholder
1289
- return 'P' + buffer.toString('hex').slice(0, 31);
1290
- }
1291
-
1292
1601
  /**
1293
1602
  * Borsh-compatible instruction data serializer.
1294
1603
  *
@@ -1379,6 +1688,7 @@ function serializePrimitive(value, type) {
1379
1688
  strLen.writeUInt32LE(strBytes.length, 0);
1380
1689
  return Buffer.concat([strLen, strBytes]);
1381
1690
  case 'pubkey':
1691
+ // Public key is 32 bytes
1382
1692
  // In production, decode base58 to 32 bytes
1383
1693
  return Buffer.alloc(32, 0);
1384
1694
  default:
@@ -1431,7 +1741,7 @@ async function waitForConfirmation(signature, level = 'confirmed', timeout = 600
1431
1741
  }
1432
1742
  throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
1433
1743
  }
1434
- async function checkTransactionStatus(signature) {
1744
+ async function checkTransactionStatus(_signature) {
1435
1745
  // In production, query the Solana RPC
1436
1746
  return {
1437
1747
  err: null,
@@ -1547,6 +1857,7 @@ async function executeInstruction(handler, args, options = {}) {
1547
1857
  const resolutionOptions = {
1548
1858
  accounts: options.accounts,
1549
1859
  wallet: options.wallet,
1860
+ programId: handler.programId, // Pass programId for PDA derivation
1550
1861
  };
1551
1862
  const resolution = resolveAccounts(handler.accounts, args, resolutionOptions);
1552
1863
  validateAccountResolution(resolution);
@@ -1605,6 +1916,108 @@ function createInstructionExecutor(wallet) {
1605
1916
  };
1606
1917
  }
1607
1918
 
1919
+ function literal(value) {
1920
+ return { type: 'literal', value };
1921
+ }
1922
+ function account(name) {
1923
+ return { type: 'accountRef', accountName: name };
1924
+ }
1925
+ function arg(name, type) {
1926
+ return { type: 'argRef', argName: name, argType: type };
1927
+ }
1928
+ function bytes(value) {
1929
+ return { type: 'bytes', value };
1930
+ }
1931
+ function resolveSeeds(seeds, context) {
1932
+ return seeds.map((seed) => {
1933
+ switch (seed.type) {
1934
+ case 'literal':
1935
+ return new TextEncoder().encode(seed.value);
1936
+ case 'bytes':
1937
+ return seed.value;
1938
+ case 'argRef': {
1939
+ const value = context.args?.[seed.argName];
1940
+ if (value === undefined) {
1941
+ throw new Error(`Missing arg for PDA seed: ${seed.argName}`);
1942
+ }
1943
+ return serializeArgForSeed(value, seed.argType);
1944
+ }
1945
+ case 'accountRef': {
1946
+ const address = context.accounts?.[seed.accountName];
1947
+ if (!address) {
1948
+ throw new Error(`Missing account for PDA seed: ${seed.accountName}`);
1949
+ }
1950
+ return decodeBase58(address);
1951
+ }
1952
+ }
1953
+ });
1954
+ }
1955
+ function serializeArgForSeed(value, argType) {
1956
+ if (value instanceof Uint8Array) {
1957
+ return value;
1958
+ }
1959
+ if (typeof value === 'string') {
1960
+ if (value.length === 43 || value.length === 44) {
1961
+ try {
1962
+ return decodeBase58(value);
1963
+ }
1964
+ catch {
1965
+ return new TextEncoder().encode(value);
1966
+ }
1967
+ }
1968
+ return new TextEncoder().encode(value);
1969
+ }
1970
+ if (typeof value === 'bigint' || typeof value === 'number') {
1971
+ const size = getArgSize(argType);
1972
+ return serializeNumber(value, size);
1973
+ }
1974
+ throw new Error(`Cannot serialize value for PDA seed: ${typeof value}`);
1975
+ }
1976
+ function getArgSize(argType) {
1977
+ if (!argType)
1978
+ return 8;
1979
+ const match = argType.match(/^[ui](\d+)$/);
1980
+ if (match && match[1]) {
1981
+ return parseInt(match[1], 10) / 8;
1982
+ }
1983
+ if (argType === 'pubkey')
1984
+ return 32;
1985
+ return 8;
1986
+ }
1987
+ function serializeNumber(value, size) {
1988
+ const buffer = new Uint8Array(size);
1989
+ let n = typeof value === 'bigint' ? value : BigInt(value);
1990
+ for (let i = 0; i < size; i++) {
1991
+ buffer[i] = Number(n & BigInt(0xff));
1992
+ n >>= BigInt(8);
1993
+ }
1994
+ return buffer;
1995
+ }
1996
+ function pda(programId, ...seeds) {
1997
+ return {
1998
+ seeds,
1999
+ programId,
2000
+ program(newProgramId) {
2001
+ return pda(newProgramId, ...seeds);
2002
+ },
2003
+ async derive(context) {
2004
+ const resolvedSeeds = resolveSeeds(this.seeds, context);
2005
+ const pid = context.programId ?? this.programId;
2006
+ const [address] = await findProgramAddress(resolvedSeeds, pid);
2007
+ return address;
2008
+ },
2009
+ deriveSync(context) {
2010
+ const resolvedSeeds = resolveSeeds(this.seeds, context);
2011
+ const pid = context.programId ?? this.programId;
2012
+ const [address] = findProgramAddressSync(resolvedSeeds, pid);
2013
+ return address;
2014
+ },
2015
+ };
2016
+ }
2017
+ function createProgramPdas(pdas) {
2018
+ return pdas;
2019
+ }
2020
+
1608
2021
  class HyperStack {
1609
2022
  constructor(url, options) {
1610
2023
  this.stack = options.stack;
@@ -1612,6 +2025,7 @@ class HyperStack {
1612
2025
  this.processor = new FrameProcessor(this.storage, {
1613
2026
  maxEntriesPerView: options.maxEntriesPerView,
1614
2027
  flushIntervalMs: options.flushIntervalMs,
2028
+ schemas: options.validateFrames ? this.stack.schemas : undefined,
1615
2029
  });
1616
2030
  this.connection = new ConnectionManager({
1617
2031
  websocketUrl: url,
@@ -1649,6 +2063,7 @@ class HyperStack {
1649
2063
  autoReconnect: options?.autoReconnect,
1650
2064
  reconnectIntervals: options?.reconnectIntervals,
1651
2065
  maxReconnectAttempts: options?.maxReconnectAttempts,
2066
+ validateFrames: options?.validateFrames,
1652
2067
  };
1653
2068
  const client = new HyperStack(url, internalOptions);
1654
2069
  if (options?.autoReconnect !== false) {
@@ -1773,6 +2188,8 @@ class ViewData {
1773
2188
  while (low < high) {
1774
2189
  const mid = Math.floor((low + high) / 2);
1775
2190
  const midKey = this.sortedKeys[mid];
2191
+ if (midKey === undefined)
2192
+ break;
1776
2193
  const midEntity = this.entities.get(midKey);
1777
2194
  const midValue = getNestedValue(midEntity, this.sortConfig.field);
1778
2195
  let cmp = compareSortValues(sortValue, midValue);
@@ -2121,5 +2538,5 @@ class EntityStore {
2121
2538
  }
2122
2539
  }
2123
2540
 
2124
- export { ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, EntityStore, FrameProcessor, HyperStack, HyperStackError, MemoryAdapter, SubscriptionRegistry, createEntityStream, createInstructionExecutor, createPublicKeySeed, createRichUpdateStream, createSeed, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, derivePda, executeInstruction, formatProgramError, isEntityFrame, isSnapshotFrame, isSubscribedFrame, isValidFrame, parseFrame, parseFrameFromBlob, parseInstructionError, resolveAccounts, serializeInstructionData, validateAccountResolution, waitForConfirmation };
2541
+ export { ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, EntityStore, FrameProcessor, HyperStack, HyperStackError, MemoryAdapter, SubscriptionRegistry, account, arg, bytes, createEntityStream, createInstructionExecutor, createProgramPdas, createPublicKeySeed, createRichUpdateStream, createSeed, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, decodeBase58, findProgramAddress as derivePda, encodeBase58, executeInstruction, findProgramAddress, findProgramAddressSync, formatProgramError, isEntityFrame, isSnapshotFrame, isSubscribedFrame, isValidFrame, literal, parseFrame, parseFrameFromBlob, parseInstructionError, pda, resolveAccounts, serializeInstructionData, validateAccountResolution, waitForConfirmation };
2125
2542
  //# sourceMappingURL=index.esm.js.map