@factoidal/core 0.7.0 → 0.10.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.
Files changed (193) hide show
  1. package/CHANGELOG.md +510 -1
  2. package/NOTICE +2 -21
  3. package/README.md +117 -993
  4. package/api.d.ts +53 -0
  5. package/api.js +5 -0
  6. package/api.mjs +56 -0
  7. package/bin/engine.mjs +38 -19
  8. package/bin/factoidal.mjs +513 -67
  9. package/bin/pack.mjs +37 -0
  10. package/bin/serve-worker.mjs +32 -0
  11. package/bin/serve.mjs +423 -0
  12. package/bin/store-worker-host.mjs +1 -1
  13. package/bin/store.mjs +1 -1
  14. package/browser.d.ts +110 -507
  15. package/browser.js +151 -2257
  16. package/docs/fuseki-replacement.md +108 -0
  17. package/fn.d.ts +24 -95
  18. package/fn.js +104 -150
  19. package/index.d.ts +598 -259
  20. package/index.js +4 -234
  21. package/index.mjs +176 -84
  22. package/l4-assets/l4factoidal.js +47 -7
  23. package/l4-assets/l4factoidal.mjs +1 -1
  24. package/l4-assets/l4factoidal.wasm +0 -0
  25. package/l4-assets/version.json +11 -7
  26. package/lib/api.mjs +2300 -0
  27. package/lib/browser-engine.mjs +69 -0
  28. package/lib/dataset.mjs +377 -0
  29. package/lib/delta-log-hosts.mjs +191 -0
  30. package/lib/lean-entry.mjs +204 -0
  31. package/lib/pure.mjs +98 -0
  32. package/lib/store.mjs +115 -0
  33. package/lib/version.mjs +5 -0
  34. package/lws/README.md +97 -0
  35. package/lws/server.mjs +331 -0
  36. package/package.json +59 -61
  37. package/rdfjs.js +5 -536
  38. package/rdfjs.mjs +594 -0
  39. package/sample-store.d.ts +8 -16
  40. package/sample-store.mjs +39 -29
  41. package/solid/client/README.md +100 -0
  42. package/solid/client/index.mjs +277 -0
  43. package/solid/server/README.md +96 -0
  44. package/solid/server/index.mjs +317 -0
  45. package/store-host/deno.mjs +8 -0
  46. package/store-host/index.mjs +12 -0
  47. package/store-host/node.mjs +8 -0
  48. package/xmpp/README.md +107 -0
  49. package/xmpp/index.mjs +229 -0
  50. package/browser-wasm.js +0 -872
  51. package/factoidal-npm-entry.js +0 -15477
  52. package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
  53. package/factoidal-npm-entry.wasm.js +0 -455
  54. package/factoidal.js +0 -13118
  55. package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
  56. package/factoidal.wasm.js +0 -457
  57. package/hacl-init.js +0 -92
  58. package/hacl-wasm/FStar.wasm +0 -0
  59. package/hacl-wasm/Hacl_Bignum.wasm +0 -0
  60. package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
  61. package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
  62. package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
  63. package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
  64. package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
  65. package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
  66. package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
  67. package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
  68. package/hacl-wasm/LowStar_Endianness.wasm +0 -0
  69. package/hacl-wasm/WasmSupport.wasm +0 -0
  70. package/hacl-wasm/api.js +0 -775
  71. package/hacl-wasm/api.json +0 -3787
  72. package/hacl-wasm/layouts.json +0 -1
  73. package/hacl-wasm/loader.js +0 -568
  74. package/hacl-wasm/shell.js +0 -12
  75. package/l4-core.d.ts +0 -159
  76. package/l4-core.js +0 -188
  77. package/l4.d.ts +0 -50
  78. package/l4.js +0 -104
  79. package/lib/api.js +0 -2300
  80. package/lib/engine-js.js +0 -165
  81. package/lib/engine-wasm.js +0 -300
  82. package/sample-store/CURRENT +0 -1
  83. package/sample-store/gen-1/manifest.sbm2 +0 -0
  84. package/sample-store/gen-1/manifest.tsv +0 -14
  85. package/sample-store/gen-1/predicate-0.ibk3 +0 -0
  86. package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
  87. package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
  88. package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
  89. package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
  90. package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
  91. package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
  92. package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
  93. package/sample-store/gen-1/predicate-1.ibk3 +0 -0
  94. package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
  95. package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
  96. package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
  97. package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
  98. package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
  99. package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
  100. package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
  101. package/sample-store/gen-1/predicate-10.ibk3 +0 -0
  102. package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
  103. package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
  104. package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
  105. package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
  106. package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
  107. package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
  108. package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
  109. package/sample-store/gen-1/predicate-11.ibk3 +0 -0
  110. package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
  111. package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
  112. package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
  113. package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
  114. package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
  115. package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
  116. package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
  117. package/sample-store/gen-1/predicate-12.ibk3 +0 -0
  118. package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
  119. package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
  120. package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
  121. package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
  122. package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
  123. package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
  124. package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
  125. package/sample-store/gen-1/predicate-2.ibk3 +0 -0
  126. package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
  127. package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
  128. package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
  129. package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
  130. package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
  131. package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
  132. package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
  133. package/sample-store/gen-1/predicate-3.ibk3 +0 -0
  134. package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
  135. package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
  136. package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
  137. package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
  138. package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
  139. package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
  140. package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
  141. package/sample-store/gen-1/predicate-4.ibk3 +0 -0
  142. package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
  143. package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
  144. package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
  145. package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
  146. package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
  147. package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
  148. package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
  149. package/sample-store/gen-1/predicate-5.ibk3 +0 -0
  150. package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
  151. package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
  152. package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
  153. package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
  154. package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
  155. package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
  156. package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
  157. package/sample-store/gen-1/predicate-6.ibk3 +0 -0
  158. package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
  159. package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
  160. package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
  161. package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
  162. package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
  163. package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
  164. package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
  165. package/sample-store/gen-1/predicate-7.ibk3 +0 -0
  166. package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
  167. package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
  168. package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
  169. package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
  170. package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
  171. package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
  172. package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
  173. package/sample-store/gen-1/predicate-8.ibk3 +0 -0
  174. package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
  175. package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
  176. package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
  177. package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
  178. package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
  179. package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
  180. package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
  181. package/sample-store/gen-1/predicate-9.ibk3 +0 -0
  182. package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
  183. package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
  184. package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
  185. package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
  186. package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
  187. package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
  188. package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
  189. package/select.d.ts +0 -116
  190. package/select.js +0 -492
  191. package/version.json +0 -102
  192. package/wasm.d.ts +0 -130
  193. package/wasm.js +0 -158
package/xmpp/index.mjs ADDED
@@ -0,0 +1,229 @@
1
+ // factoidal/xmpp — a functional-programming-style API over
2
+ // `L4Factoidal.XMPP` (formal/lean4/L4Factoidal/XMPP/README.md), the
3
+ // GC3-targeting XMPP implementation in Lean 4, served through the same
4
+ // wasm dispatch ABI as the rest of the Lean engine
5
+ // (formal/lean4/Wasm/Dispatch.lean, formal/lean4/Wasm/Ops/Xmpp.lean).
6
+ //
7
+ // Design record: formal/lean4/L4Factoidal/XMPP/README.md
8
+ //
9
+ // WHAT "FUNCTIONAL-PROGRAMMING-STYLE" MEANS HERE, CONCRETELY
10
+ // - Every exported function is pure data in, pure data out: plain
11
+ // strings/objects/arrays, no classes, no mutable instances to hold.
12
+ // - No exceptions for an EXPECTED outcome. A malformed JID, a stanza
13
+ // that isn't message/presence/iq, an unknown negotiation stage —
14
+ // these are ordinary answers, not exceptional control flow, so
15
+ // every function returns a `{ok: true, value}` or
16
+ // `{ok: false, error, unknownOp}` result (a Result/Either shape),
17
+ // never throws for them. The one thing that still throws is a
18
+ // genuine host/environment failure (the wasm module won't load at
19
+ // all) — see `loadXmppEngine()`.
20
+ // - Immutable results: nothing returned here is later mutated by this
21
+ // module. Build a new value; don't hand back something to poke at.
22
+ //
23
+ // WHAT THIS COVERS (matches exactly what `L4Factoidal.XMPP` implements
24
+ // today, 2026-09-07 — see the design record's "Status" section):
25
+ // - RFC 7622 JID parsing/rendering (`parseJid`, `renderJid`)
26
+ // - RFC 6120 stream header parsing/rendering (`parseStreamHeader`,
27
+ // `renderStreamHeader`) — tolerates the leading `<?xml ...?>`
28
+ // declaration real servers send (caught testing against a live
29
+ // ejabberd instance; see the design record)
30
+ // - RFC 6120 `<stream:features>` negotiation (`featuresFor`) — the
31
+ // TLS-then-SASL-then-bind ordering is a proved Lean theorem, not
32
+ // just this JS wrapper's behaviour
33
+ // - stanza parsing (`parseStanza`) for message/presence/iq
34
+ //
35
+ // WHAT THIS DOES NOT COVER YET, ON PURPOSE (ask for the op with
36
+ // `xmppOpsAvailable()`'s companion `ops` reflection before relying on
37
+ // it; an absent op means the resolved wasm predates it, not that this
38
+ // wrapper forgot it):
39
+ // - stanza construction FROM JSON (payload reconstruction needs a
40
+ // sibling-node XML parser the Lean side doesn't have yet)
41
+ // - GC3 itself (still an unfinished upstream XSF spec)
42
+ // - SASL/SCRAM computation (message FORMAT only exists so far; no
43
+ // HACL* HMAC-SHA-256 binding yet — see the design record's crypto
44
+ // section)
45
+ // - MUC/MIX interop modules
46
+
47
+ import { loadEngine } from '../bin/engine.mjs'
48
+
49
+ /** The XMPP op names this module calls. */
50
+ export const XMPP_OPS = [
51
+ 'xmppJidParse',
52
+ 'xmppJidRender',
53
+ 'xmppStreamHeaderParse',
54
+ 'xmppStreamHeaderRender',
55
+ 'xmppFeaturesFor',
56
+ 'xmppStanzaParse'
57
+ ]
58
+
59
+ function isUnknownOp (error) {
60
+ const text = error && error.message ? String(error.message) : String(error)
61
+ return text.indexOf('unknown op') >= 0
62
+ }
63
+
64
+ /**
65
+ * Load the engine handle this module's functions need. Exposed so a
66
+ * caller can load it once and pass it to every call via `options.engine`
67
+ * instead of paying the load cost per call — the one place in this
68
+ * module where "just call the function" isn't free, so it isn't hidden.
69
+ *
70
+ * @returns {Promise<object>} an engine handle with a `.call(op, args)` method
71
+ * @throws if the wasm module cannot be resolved or loaded at all — a
72
+ * host/environment failure, not a protocol-level outcome, so this is
73
+ * the one thing in this module that DOES throw
74
+ */
75
+ export async function loadXmppEngine () {
76
+ return loadEngine()
77
+ }
78
+
79
+ /**
80
+ * Whether the loaded engine carries the XMPP ops at all — protects
81
+ * against a resolved wasm bundle built before they existed.
82
+ *
83
+ * @param {object} [options]
84
+ * @param {object} [options.engine] an engine handle to reuse
85
+ * @returns {Promise<{available: boolean, missing: string[]}>}
86
+ */
87
+ export async function xmppOpsAvailable (options = {}) {
88
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
89
+ let names
90
+ try {
91
+ names = engine.call('ops', []).ops
92
+ } catch (_error) {
93
+ return { available: false, missing: XMPP_OPS.slice() }
94
+ }
95
+ const missing = XMPP_OPS.filter((op) => !names.includes(op))
96
+ return { available: missing.length === 0, missing }
97
+ }
98
+
99
+ /** Call one op, converting the engine's throw-on-`{ok:false}` convention
100
+ * into a Result value instead — the point of this module's shape. */
101
+ function callOp (engine, op, args) {
102
+ try {
103
+ return engine.call(op, args)
104
+ } catch (error) {
105
+ return { ok: false, error: String((error && error.message) || error), unknownOp: isUnknownOp(error) }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Parse an RFC 7622 JID string.
111
+ *
112
+ * Structural + length well-formedness only (per RFC 7622 §3.1); PRECIS
113
+ * enforcement (RFC 8264/8265 — Unicode normalisation, case folding,
114
+ * disallowed codepoints) is not implemented on the Lean side yet, so a
115
+ * string with disallowed characters but the right shape parses as
116
+ * `wellFormed: true`. This is a stated engine gap, not a wrapper bug.
117
+ *
118
+ * @param {string} jid
119
+ * @param {object} [options]
120
+ * @param {object} [options.engine] an engine handle to reuse
121
+ * @returns {Promise<{ok: true, value: {localpart: string|null,
122
+ * domainpart: string, resourcepart: string|null, wellFormed: boolean}}
123
+ * | {ok: false, error: string, unknownOp: boolean}>}
124
+ */
125
+ export async function parseJid (jid, options = {}) {
126
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
127
+ const result = callOp(engine, 'xmppJidParse', [String(jid)])
128
+ if (result.ok === false) return result
129
+ const { ok, ...value } = result
130
+ return { ok: true, value }
131
+ }
132
+
133
+ /**
134
+ * Render a JID from its parts back to a string.
135
+ *
136
+ * @param {{localpart?: string|null, domainpart: string, resourcepart?: string|null}} parts
137
+ * @param {object} [options]
138
+ * @param {object} [options.engine] an engine handle to reuse
139
+ * @returns {Promise<{ok: true, value: string} | {ok: false, error: string, unknownOp: boolean}>}
140
+ */
141
+ export async function renderJid (parts, options = {}) {
142
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
143
+ const result = callOp(engine, 'xmppJidRender', [JSON.stringify(parts)])
144
+ if (result.ok === false) return result
145
+ return { ok: true, value: result.jid }
146
+ }
147
+
148
+ /**
149
+ * Parse an RFC 6120 `<stream:stream ...>` opening tag. Unlike an
150
+ * ordinary stanza this element is never closed until the connection
151
+ * ends, so `rest` is the unconsumed remainder of the input (features,
152
+ * stanzas, and eventually `</stream:stream>`) for the caller to parse
153
+ * separately.
154
+ *
155
+ * @param {string} xml
156
+ * @param {object} [options]
157
+ * @param {object} [options.engine] an engine handle to reuse
158
+ * @returns {Promise<{ok: true, value: {from: string|null, to: string|null,
159
+ * id: string|null, version: string|null, lang: string|null, rest: string}}
160
+ * | {ok: false, error: string, unknownOp: boolean}>}
161
+ */
162
+ export async function parseStreamHeader (xml, options = {}) {
163
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
164
+ const result = callOp(engine, 'xmppStreamHeaderParse', [String(xml)])
165
+ if (result.ok === false) return result
166
+ const { ok, ...value } = result
167
+ return { ok: true, value }
168
+ }
169
+
170
+ /**
171
+ * Render an RFC 6120 stream header back to its opening-tag XML.
172
+ *
173
+ * @param {{from?: string|null, to?: string|null, id?: string|null,
174
+ * version?: string|null, lang?: string|null}} header
175
+ * @param {object} [options]
176
+ * @param {object} [options.engine] an engine handle to reuse
177
+ * @returns {Promise<{ok: true, value: string} | {ok: false, error: string, unknownOp: boolean}>}
178
+ */
179
+ export async function renderStreamHeader (header, options = {}) {
180
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
181
+ const result = callOp(engine, 'xmppStreamHeaderRender', [JSON.stringify(header)])
182
+ if (result.ok === false) return result
183
+ return { ok: true, value: result.xml }
184
+ }
185
+
186
+ /**
187
+ * What to advertise in `<stream:features>` at a given negotiation stage.
188
+ * The RFC 6120 ordering (STARTTLS only pre-TLS, SASL mechanisms only
189
+ * post-TLS, bind/session only once authenticated) is a proved theorem
190
+ * on the Lean side (`Core.featuresFor_startTls_iff_preTls` etc.) — this
191
+ * function cannot be called into producing a value that violates it,
192
+ * because no such value exists to return.
193
+ *
194
+ * @param {'preTls'|'postTls'|'authenticated'|'bound'} stage
195
+ * @param {string[]} [mechanisms] SASL mechanism names to offer at `postTls`
196
+ * @param {object} [options]
197
+ * @param {object} [options.engine] an engine handle to reuse
198
+ * @returns {Promise<{ok: true, value: {startTlsRequired: 'required'|'optional'|null,
199
+ * saslMechanisms: string[], canBind: boolean, canSession: boolean, xml: string}}
200
+ * | {ok: false, error: string, unknownOp: boolean}>}
201
+ */
202
+ export async function featuresFor (stage, mechanisms = [], options = {}) {
203
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
204
+ const result = callOp(engine, 'xmppFeaturesFor', [JSON.stringify({ stage, mechanisms })])
205
+ if (result.ok === false) return result
206
+ const { ok, ...value } = result
207
+ return { ok: true, value }
208
+ }
209
+
210
+ /**
211
+ * Parse a complete `message`/`presence`/`iq` stanza. Payload children
212
+ * are returned as their serialized XML fragment (`payloadXml`), not a
213
+ * structured tree — see the module header for why stanza construction
214
+ * from JSON isn't offered yet.
215
+ *
216
+ * @param {string} xml
217
+ * @param {object} [options]
218
+ * @param {object} [options.engine] an engine handle to reuse
219
+ * @returns {Promise<{ok: true, value: {kind: 'message'|'presence'|'iq',
220
+ * id: string|null, from: string|null, to: string|null, type: string|null,
221
+ * payloadXml: string}} | {ok: false, error: string, unknownOp: boolean}>}
222
+ */
223
+ export async function parseStanza (xml, options = {}) {
224
+ const engine = options.engine !== undefined ? options.engine : await loadXmppEngine()
225
+ const result = callOp(engine, 'xmppStanzaParse', [String(xml)])
226
+ if (result.ok === false) return result
227
+ const { ok, ...value } = result
228
+ return { ok: true, value }
229
+ }