hyperstack-typescript 0.4.2 → 0.5.0

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