@wishknish/knishio-client-js 0.7.6 → 0.7.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-js",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
5
  "productName": "Knish.IO Javascript SDK Client",
6
6
  "description": "JavaScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs.",
@@ -96,6 +96,7 @@ import QueryAtom from './query/QueryAtom.js'
96
96
  import QueryPolicy from './query/QueryPolicy.js'
97
97
  import QueryMetaTypeViaAtom from './query/QueryMetaTypeViaAtom.js'
98
98
  import QueryMetaTypeViaMolecule from './query/QueryMetaTypeViaMolecule.js'
99
+ import QueryEmbeddingStatus from './query/QueryEmbeddingStatus.js'
99
100
  import MutationCreateRule from './mutation/MutationCreateRule.js'
100
101
  import MutationDepositBufferToken from './mutation/MutationDepositBufferToken.js'
101
102
  import MutationWithdrawBufferToken from './mutation/MutationWithdrawBufferToken.js'
@@ -253,6 +254,7 @@ export default class KnishIOClient {
253
254
  this.$__secret = ''
254
255
  this.$__bundle = ''
255
256
  this.remainderWallet = null
257
+ this.$__capabilityCache = {}
256
258
  }
257
259
 
258
260
  /**
@@ -288,6 +290,21 @@ export default class KnishIOClient {
288
290
  }
289
291
  }
290
292
 
293
+ /**
294
+ * Sets the WebSocket (subscription) endpoint for this session.
295
+ *
296
+ * `setUri()` only updates the HTTP endpoint; the subscription socket is built once
297
+ * at construction from the `socket.socketUri` passed in. This lets a caller re-point
298
+ * the socket when the endpoint changes (F-8a, E2E gauntlet 2026-06-02).
299
+ *
300
+ * @param {string} socketUri
301
+ */
302
+ setSocketUri (socketUri) {
303
+ if (this.$__client && typeof this.$__client.setSocketUri === 'function') {
304
+ this.$__client.setSocketUri({ socketUri, appKey: 'knishio' })
305
+ }
306
+ }
307
+
291
308
  /**
292
309
  * Retrieves the endpoint URI for this session
293
310
  *
@@ -457,6 +474,26 @@ export default class KnishIOClient {
457
474
  secret = secret || this.getSecret()
458
475
  bundle = bundle || this.getBundle()
459
476
 
477
+ // For non-USER source wallets (V/B isotopes), capture the current USER
478
+ // ContinuID position BEFORE overwriting the client's remainder wallet.
479
+ // This position is needed by addContinuIdAtom() for previousPosition metadata.
480
+ let continuIdPosition = null
481
+ if (sourceWallet && sourceWallet.token !== 'USER') {
482
+ if (this.lastMoleculeQuery &&
483
+ this.getRemainderWallet() &&
484
+ this.getRemainderWallet().token === 'USER' &&
485
+ this.lastMoleculeQuery.response() &&
486
+ this.lastMoleculeQuery.response().success()
487
+ ) {
488
+ // Carry-forward: use last successful USER remainder wallet position
489
+ continuIdPosition = this.getRemainderWallet().position
490
+ } else {
491
+ // Query server for current ContinuID position
492
+ const userWallet = await this.getSourceWallet()
493
+ continuIdPosition = userWallet ? userWallet.position : null
494
+ }
495
+ }
496
+
460
497
  // Sets the source wallet as the last remainder wallet (to maintain ContinuID)
461
498
  if (!sourceWallet &&
462
499
  this.lastMoleculeQuery &&
@@ -486,7 +523,8 @@ export default class KnishIOClient {
486
523
  sourceWallet,
487
524
  remainderWallet: this.getRemainderWallet(),
488
525
  cellSlug: this.getCellSlug(),
489
- version: this.getServerSdkVersion()
526
+ version: this.getServerSdkVersion(),
527
+ continuIdPosition
490
528
  })
491
529
  }
492
530
 
@@ -885,6 +923,74 @@ export default class KnishIOClient {
885
923
  return response
886
924
  }
887
925
 
926
+ /**
927
+ * Probes the connected server to check whether it supports a named root query field.
928
+ * Result is cached per URI so the network round-trip happens at most once per URI.
929
+ *
930
+ * Uses GraphQL introspection which is universally supported by spec-compliant servers.
931
+ *
932
+ * @param {string} fieldName - The root Query field name to check (e.g. 'embeddingStatus')
933
+ * @return {Promise<boolean>}
934
+ */
935
+ async hasQueryField (fieldName) {
936
+ const uri = this.getUri()
937
+ const cacheKey = `${ uri }::${ fieldName }`
938
+
939
+ if (typeof this.$__capabilityCache[cacheKey] === 'boolean') {
940
+ return this.$__capabilityCache[cacheKey]
941
+ }
942
+
943
+ try {
944
+ const result = await this.$__client.query({
945
+ query: '{ __schema { queryType { fields { name } } } }',
946
+ variables: {}
947
+ })
948
+
949
+ const fields = result?.data?.__schema?.queryType?.fields || []
950
+ const supported = fields.some(f => f.name === fieldName)
951
+ this.$__capabilityCache[cacheKey] = supported
952
+ return supported
953
+ } catch (err) {
954
+ this.log('warn', `KnishIOClient::hasQueryField() - Capability probe for '${ fieldName }' failed: ${ err.message }`)
955
+ this.$__capabilityCache[cacheKey] = false
956
+ return false
957
+ }
958
+ }
959
+
960
+ /**
961
+ * Queries embedding status for one or more meta instances (DataBraid observability).
962
+ *
963
+ * If the connected server does not support the embeddingStatus query,
964
+ * returns null without throwing an error (graceful degradation).
965
+ *
966
+ * Single mode: queryEmbeddingStatus({ metaType: 'product', metaId: 'SKU-001' })
967
+ * Bulk mode: queryEmbeddingStatus({ instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] })
968
+ *
969
+ * @param {string|null} metaType - Metadata type (single-instance mode)
970
+ * @param {string|null} metaId - Instance identifier (single-instance mode)
971
+ * @param {Array<{metaType: string, metaId: string}>|null} instances - Bulk mode input
972
+ * @return {Promise<ResponseEmbeddingStatus|null>} Response with payload(), or null if unsupported
973
+ */
974
+ async queryEmbeddingStatus ({
975
+ metaType = null,
976
+ metaId = null,
977
+ instances = null
978
+ }) {
979
+ this.log('info', `KnishIOClient::queryEmbeddingStatus() - Checking embedding status for metaType: ${ metaType || '(bulk)' }...`)
980
+
981
+ const supported = await this.hasQueryField('embeddingStatus')
982
+
983
+ if (!supported) {
984
+ this.log('warn', 'KnishIOClient::queryEmbeddingStatus() - Server does not support embeddingStatus query. Returning null.')
985
+ return null
986
+ }
987
+
988
+ const query = this.createQuery(QueryEmbeddingStatus)
989
+ const variables = QueryEmbeddingStatus.createVariables({ metaType, metaId, instances })
990
+
991
+ return this.executeQuery(query, variables)
992
+ }
993
+
888
994
  /**
889
995
  * Query batch to get cascading meta instances by batchID
890
996
  *
@@ -1425,7 +1531,12 @@ export default class KnishIOClient {
1425
1531
  metaId,
1426
1532
  policy = {}
1427
1533
  }) {
1428
- // Create a molecule
1534
+ // Create a molecule. F-3 (E2E gauntlet 2026-06-02): the ContinuID-rejection bug
1535
+ // here was NOT this construction — it was molecule.addPolicyAtom() signing the
1536
+ // R-isotope atom from a fresh random wallet (now fixed in Molecule.addPolicyAtom
1537
+ // to use this.sourceWallet). A policy is a policy-only R-atom (no rules), so this
1538
+ // path is kept as-is: routing through createRule with an empty rule list would
1539
+ // fail the SDK's own `Check::isotopeR() - No rules!` validation.
1429
1540
  const molecule = await this.createMolecule({})
1430
1541
  molecule.addPolicyAtom({
1431
1542
  metaType,
@@ -1964,7 +2075,7 @@ export default class KnishIOClient {
1964
2075
  molecule.sign({
1965
2076
  bundle: this.getBundle()
1966
2077
  })
1967
- molecule.check()
2078
+ molecule.check(sourceWallet)
1968
2079
 
1969
2080
  // Create & execute a mutation
1970
2081
  const query = await this.createMoleculeMutation({
package/src/Molecule.js CHANGED
@@ -86,7 +86,8 @@ export default class Molecule {
86
86
  sourceWallet = null,
87
87
  remainderWallet = null,
88
88
  cellSlug = null,
89
- version = null
89
+ version = null,
90
+ continuIdPosition = null
90
91
  }) {
91
92
  this.status = null
92
93
  this.molecularHash = null
@@ -95,6 +96,7 @@ export default class Molecule {
95
96
  this.secret = secret
96
97
  this.bundle = bundle
97
98
  this.sourceWallet = sourceWallet
99
+ this.continuIdPosition = continuIdPosition
98
100
  this.atoms = []
99
101
  this.parentHashes = []
100
102
  if (version !== null && Object.prototype.hasOwnProperty.call(versions, version)) {
@@ -343,8 +345,12 @@ export default class Molecule {
343
345
  // These are atom meta fields (not core fields), so they don't affect molecular hash.
344
346
  const continuIdMeta = {}
345
347
 
346
- // previousPosition: the source wallet's position being consumed
347
- if (this.sourceWallet && this.sourceWallet.position) {
348
+ // previousPosition: the current USER ContinuID position being consumed.
349
+ // For non-USER source wallets (V/B isotopes), use the explicitly passed
350
+ // continuIdPosition from createMolecule() instead of the TOKEN wallet position.
351
+ if (this.continuIdPosition) {
352
+ continuIdMeta.previousPosition = this.continuIdPosition
353
+ } else if (this.sourceWallet && this.sourceWallet.position) {
348
354
  continuIdMeta.previousPosition = this.sourceWallet.position
349
355
  }
350
356
 
@@ -387,14 +393,14 @@ export default class Molecule {
387
393
  const atomMeta = new AtomMeta(meta)
388
394
  atomMeta.addPolicy(policy)
389
395
 
390
- const wallet = Wallet.create({
391
- secret: this.secret,
392
- bundle: this.sourceWallet.bundle,
393
- token: 'USER'
394
- })
395
-
396
+ // F-3 (E2E gauntlet 2026-06-02): sign the policy (R-isotope) atom from the
397
+ // established ContinuID source wallet, NOT a freshly-derived Wallet.create() at a
398
+ // random position. As the signing (first) atom in createPolicy(), a fresh-wallet
399
+ // position is unknown to the validator → "ContinuID chain validation failed:
400
+ // signing position … not found as wallet" (a different position each retry). This
401
+ // mirrors createRule(), which adds its R-atom at this.sourceWallet.
396
402
  this.addAtom(Atom.create({
397
- wallet,
403
+ wallet: this.sourceWallet,
398
404
  isotope: 'R',
399
405
  metaType,
400
406
  metaId,
@@ -464,11 +470,29 @@ export default class Molecule {
464
470
  throw new BalanceInsufficientException()
465
471
  }
466
472
 
473
+ // Create burn address wallet (null bundle = token destruction)
474
+ const burnWallet = new Wallet({
475
+ bundle: '0000000000000000000000000000000000000000000000000000000000000000',
476
+ token: this.sourceWallet.token
477
+ })
478
+
479
+ // V-atom 1: Debit full balance from source
467
480
  this.addAtom(Atom.create({
468
481
  isotope: 'V',
469
482
  wallet: this.sourceWallet,
470
- value: -amount
483
+ value: -this.sourceWallet.balance
471
484
  }))
485
+
486
+ // V-atom 2: Credit burn amount to burn address
487
+ this.addAtom(Atom.create({
488
+ isotope: 'V',
489
+ wallet: burnWallet,
490
+ value: amount,
491
+ metaType: 'walletBundle',
492
+ metaId: burnWallet.bundle
493
+ }))
494
+
495
+ // V-atom 3: Remainder back to source identity
472
496
  this.addAtom(Atom.create({
473
497
  isotope: 'V',
474
498
  wallet: this.remainderWallet,
package/src/index.js CHANGED
@@ -85,6 +85,7 @@ import QueryBalance from './query/QueryBalance.js'
85
85
  import QueryBatch from './query/QueryBatch.js'
86
86
  import QueryBatchHistory from './query/QueryBatchHistory.js'
87
87
  import QueryContinuId from './query/QueryContinuId.js'
88
+ import QueryEmbeddingStatus from './query/QueryEmbeddingStatus.js'
88
89
  import QueryMetaType from './query/QueryMetaType.js'
89
90
  import QueryMetaTypeViaAtom from './query/QueryMetaTypeViaAtom.js'
90
91
  import QueryMetaTypeViaMolecule from './query/QueryMetaTypeViaMolecule.js'
@@ -132,6 +133,7 @@ import ResponseCreateMeta from './response/ResponseCreateMeta.js'
132
133
  import ResponseCreateRule from './response/ResponseCreateRule.js'
133
134
  import ResponseCreateToken from './response/ResponseCreateToken.js'
134
135
  import ResponseCreateWallet from './response/ResponseCreateWallet.js'
136
+ import ResponseEmbeddingStatus from './response/ResponseEmbeddingStatus.js'
135
137
  import ResponseLinkIdentifier from './response/ResponseLinkIdentifier.js'
136
138
  import ResponseMetaType from './response/ResponseMetaType.js'
137
139
  import ResponseMetaTypeViaAtom from './response/ResponseMetaTypeViaAtom.js'
@@ -245,6 +247,7 @@ export {
245
247
  QueryBatch,
246
248
  QueryBatchHistory,
247
249
  QueryContinuId,
250
+ QueryEmbeddingStatus,
248
251
  QueryMetaType,
249
252
  QueryMetaTypeViaAtom,
250
253
  QueryMetaTypeViaMolecule,
@@ -286,6 +289,7 @@ export {
286
289
  ResponseCreateRule,
287
290
  ResponseCreateToken,
288
291
  ResponseCreateWallet,
292
+ ResponseEmbeddingStatus,
289
293
  ResponseLinkIdentifier,
290
294
  ResponseMetaType,
291
295
  ResponseMetaTypeViaAtom,
@@ -5,7 +5,7 @@ import {
5
5
  fetchExchange
6
6
  } from '@urql/core'
7
7
  import { createClient as createWSClient } from 'graphql-ws'
8
- import { pipe, map } from 'wonka'
8
+ import { pipe, map, subscribe } from 'wonka'
9
9
 
10
10
  class UrqlClientWrapper {
11
11
  constructor ({ serverUri, socket = null, encrypt = false }) {
@@ -82,15 +82,19 @@ class UrqlClientWrapper {
82
82
  subscribe (request, closure) {
83
83
  const { query, variables, operationName } = request
84
84
 
85
- const { unsubscribe } = pipe(
85
+ // F-22 fix: wonka v3 — `subscribe` is a function applied via pipe, not a
86
+ // method on the result. The pipe-with-subscribe expression returns the
87
+ // subscription object directly (already shaped as `{ unsubscribe }`).
88
+ const subscription = pipe(
86
89
  this.$__client.subscription(query, variables),
87
90
  map(result => {
88
91
  closure(this.formatResponse(result))
89
- })
90
- ).subscribe(() => {})
92
+ }),
93
+ subscribe(() => {})
94
+ )
91
95
 
92
96
  // Store subscription for later cleanup
93
- this.$__subscriptionManager.set(operationName, { unsubscribe })
97
+ this.$__subscriptionManager.set(operationName, subscription)
94
98
 
95
99
  return {
96
100
  unsubscribe: () => this.unsubscribe(operationName)
@@ -0,0 +1,132 @@
1
+ /*
2
+ (
3
+ (/(
4
+ (//(
5
+ (///(
6
+ (/////(
7
+ (//////( )
8
+ (////////( (/)
9
+ (////////( (///)
10
+ (//////////( (////)
11
+ (//////////( (//////)
12
+ (////////////( (///////)
13
+ (/////////////( (/////////)
14
+ (//////////////( (///////////)
15
+ (///////////////( (/////////////)
16
+ (////////////////( (//////////////)
17
+ ((((((((((((((((((( (((((((((((((((
18
+ ((((((((((((((((((( ((((((((((((((
19
+ ((((((((((((((((((( ((((((((((((((
20
+ (((((((((((((((((((( (((((((((((((
21
+ (((((((((((((((((((( ((((((((((((
22
+ ((((((((((((((((((( ((((((((((((
23
+ ((((((((((((((((((( ((((((((((
24
+ ((((((((((((((((((/ (((((((((
25
+ (((((((((((((((((( ((((((((
26
+ ((((((((((((((((( (((((((
27
+ (((((((((((((((((( (((((
28
+ ################# ##
29
+ ################ #
30
+ ################# ##
31
+ %################ ###
32
+ ###############( ####
33
+ ############### ####
34
+ ############### ######
35
+ %#############( (#######
36
+ %############# #########
37
+ ############( ##########
38
+ ########### #############
39
+ ######### ##############
40
+ %######
41
+
42
+ Powered by Knish.IO: Connecting a Decentralized World
43
+
44
+ Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
45
+
46
+ License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
47
+ */
48
+ import Query from './Query.js'
49
+ import ResponseEmbeddingStatus from '../response/ResponseEmbeddingStatus.js'
50
+ import { gql } from '@urql/core'
51
+
52
+ /**
53
+ * Query for retrieving DataBraid embedding status for meta instances.
54
+ *
55
+ * Supports both single-instance and bulk modes:
56
+ * - Single: { metaType: 'product', metaId: 'SKU-001' }
57
+ * - Bulk: { instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] }
58
+ *
59
+ * Returns embedding state (PENDING, STALE, COMPLETE) for each instance,
60
+ * allowing apps to render spinner badges and completion indicators.
61
+ *
62
+ * NOTE: Only available on servers with EMBEDDING_ENABLED=true.
63
+ * Use KnishIOClient.queryEmbeddingStatus() which handles capability
64
+ * detection and returns null for unsupported servers.
65
+ */
66
+ export default class QueryEmbeddingStatus extends Query {
67
+ /**
68
+ * @param {UrqlClientWrapper} graphQLClient
69
+ * @param {KnishIOClient} knishIOClient
70
+ */
71
+ constructor (graphQLClient, knishIOClient) {
72
+ super(graphQLClient, knishIOClient)
73
+
74
+ this.$__query = gql`query( $metaType: String, $metaId: String, $instances: [EmbeddingStatusInput!] ) {
75
+ embeddingStatus( metaType: $metaType, metaId: $metaId, instances: $instances ) {
76
+ metaType,
77
+ metaId,
78
+ state,
79
+ totalMetas,
80
+ embeddedCount,
81
+ embeddedAt,
82
+ model
83
+ }
84
+ }`
85
+ }
86
+
87
+ /**
88
+ * Builds a GraphQL-friendly variables object for embedding status queries.
89
+ *
90
+ * Single mode: createVariables({ metaType: 'product', metaId: 'SKU-001' })
91
+ * Bulk mode: createVariables({ instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] })
92
+ *
93
+ * @param {string|null} metaType
94
+ * @param {string|null} metaId
95
+ * @param {Array<{metaType: string, metaId: string}>|null} instances
96
+ * @return {{}}
97
+ */
98
+ static createVariables ({
99
+ metaType = null,
100
+ metaId = null,
101
+ instances = null
102
+ }) {
103
+ const variables = {}
104
+
105
+ if (instances && instances.length > 0) {
106
+ variables.instances = instances
107
+ }
108
+
109
+ if (metaType) {
110
+ variables.metaType = metaType
111
+ }
112
+
113
+ if (metaId) {
114
+ variables.metaId = metaId
115
+ }
116
+
117
+ return variables
118
+ }
119
+
120
+ /**
121
+ * Returns a Response object
122
+ *
123
+ * @param {object} json
124
+ * @return {ResponseEmbeddingStatus}
125
+ */
126
+ createResponse (json) {
127
+ return new ResponseEmbeddingStatus({
128
+ query: this,
129
+ json
130
+ })
131
+ }
132
+ }
@@ -0,0 +1,100 @@
1
+ /*
2
+ (
3
+ (/(
4
+ (//(
5
+ (///(
6
+ (/////(
7
+ (//////( )
8
+ (////////( (/)
9
+ (////////( (///)
10
+ (//////////( (////)
11
+ (//////////( (//////)
12
+ (////////////( (///////)
13
+ (/////////////( (/////////)
14
+ (//////////////( (///////////)
15
+ (///////////////( (/////////////)
16
+ (////////////////( (//////////////)
17
+ ((((((((((((((((((( (((((((((((((((
18
+ ((((((((((((((((((( ((((((((((((((
19
+ ((((((((((((((((((( ((((((((((((((
20
+ (((((((((((((((((((( (((((((((((((
21
+ (((((((((((((((((((( ((((((((((((
22
+ ((((((((((((((((((( ((((((((((((
23
+ ((((((((((((((((((( ((((((((((
24
+ ((((((((((((((((((/ (((((((((
25
+ (((((((((((((((((( ((((((((
26
+ ((((((((((((((((( (((((((
27
+ (((((((((((((((((( (((((
28
+ ################# ##
29
+ ################ #
30
+ ################# ##
31
+ %################ ###
32
+ ###############( ####
33
+ ############### ####
34
+ ############### ######
35
+ %#############( (#######
36
+ %############# #########
37
+ ############( ##########
38
+ ########### #############
39
+ ######### ##############
40
+ %######
41
+
42
+ Powered by Knish.IO: Connecting a Decentralized World
43
+
44
+ Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
45
+
46
+ License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
47
+ */
48
+
49
+ import Response from './Response.js'
50
+
51
+ /**
52
+ * Response for EmbeddingStatus Query
53
+ *
54
+ * Payload is an array of EmbeddingStatusItem objects:
55
+ * { metaType, metaId, state, totalMetas, embeddedCount, embeddedAt, model }
56
+ *
57
+ * state is one of: 'PENDING' | 'STALE' | 'COMPLETE'
58
+ */
59
+ export default class ResponseEmbeddingStatus extends Response {
60
+ /**
61
+ * Class constructor
62
+ *
63
+ * @param {Query} query
64
+ * @param {object} json
65
+ */
66
+ constructor ({
67
+ query,
68
+ json
69
+ }) {
70
+ super({
71
+ query,
72
+ json,
73
+ dataKey: 'data.embeddingStatus'
74
+ })
75
+ }
76
+
77
+ /**
78
+ * Returns the array of embedding status items, or null if empty.
79
+ *
80
+ * Each item contains:
81
+ * - metaType {string} - Metadata type (echoed from input)
82
+ * - metaId {string} - Instance identifier (echoed from input)
83
+ * - state {string} - PENDING | STALE | COMPLETE
84
+ * - totalMetas {number} - Total meta rows for this instance
85
+ * - embeddedCount {number} - Rows with current-model embeddings
86
+ * - embeddedAt {number|null} - Unix epoch of most recent embedding
87
+ * - model {string|null} - Model name used for embeddings
88
+ *
89
+ * @return {Array<object>|null}
90
+ */
91
+ payload () {
92
+ const items = this.data()
93
+
94
+ if (!items || !Array.isArray(items) || items.length === 0) {
95
+ return null
96
+ }
97
+
98
+ return items
99
+ }
100
+ }