xytara 1.0.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.
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+
3
+ const { createCommandArtifacts } = require("./commerce_artifacts");
4
+ const { createQuote, hydrateQuoteStates, markQuoteExecuted } = require("./commerce_quotes");
5
+ const { listRecords } = require("./record_queries");
6
+ const {
7
+ actOnDispute,
8
+ actOnRefund,
9
+ actOnRemediationTicket,
10
+ createDispute,
11
+ createRefund,
12
+ createRemediationTicket
13
+ } = require("./commerce_cases");
14
+
15
+ function createRuntimeState() {
16
+ return {
17
+ quotes: new Map(),
18
+ transactions: new Map(),
19
+ receipts: new Map(),
20
+ paymentLedger: [],
21
+ deliveries: new Map(),
22
+ pendingExecutions: new Map(),
23
+ disputes: new Map(),
24
+ refunds: new Map(),
25
+ remediationTickets: new Map()
26
+ };
27
+ }
28
+
29
+ function buildDeliveryEvent(event, delivery, atIso, extra) {
30
+ return {
31
+ event,
32
+ at_iso: atIso,
33
+ status: delivery.status,
34
+ callback_url: delivery.callback_url || null,
35
+ output_ref: delivery.output_ref || null,
36
+ proof_ref: delivery.proof_ref || null,
37
+ ...(extra || {})
38
+ };
39
+ }
40
+
41
+ function completePendingExecution(state, transactionId) {
42
+ if (!transactionId || !state.pendingExecutions.has(transactionId) || !state.transactions.has(transactionId)) {
43
+ return state.transactions.get(transactionId) || null;
44
+ }
45
+ const pending = state.pendingExecutions.get(transactionId);
46
+ const current = state.transactions.get(transactionId);
47
+ if (!pending || !current || Date.now() < pending.readyAtMs) {
48
+ return current;
49
+ }
50
+
51
+ const completedAtIso = new Date(pending.readyAtMs).toISOString();
52
+ const next = {
53
+ transaction: {
54
+ ...current.transaction,
55
+ status: "completed",
56
+ completed_at_iso: completedAtIso,
57
+ timing: {
58
+ ...current.transaction.timing,
59
+ completed_at_iso: completedAtIso,
60
+ duration_ms: pending.durationMs,
61
+ observed_latency_class: pending.observedLatencyClass,
62
+ assessment: pending.assessment
63
+ },
64
+ execution: {
65
+ ...current.transaction.execution,
66
+ status: "completed"
67
+ },
68
+ remediation: {
69
+ ...current.transaction.remediation,
70
+ updated_at_iso: completedAtIso
71
+ }
72
+ },
73
+ receipt: {
74
+ ...current.receipt,
75
+ status: "completed",
76
+ created_at_iso: completedAtIso,
77
+ timing: {
78
+ ...current.receipt.timing,
79
+ completed_at_iso: completedAtIso,
80
+ duration_ms: pending.durationMs,
81
+ observed_latency_class: pending.observedLatencyClass,
82
+ assessment: pending.assessment
83
+ },
84
+ remediation: {
85
+ ...current.receipt.remediation,
86
+ updated_at_iso: completedAtIso
87
+ }
88
+ },
89
+ command: current.command
90
+ };
91
+
92
+ state.transactions.set(transactionId, next);
93
+ state.receipts.set(next.receipt.receipt_id, next);
94
+ if (pending.quoteId && state.quotes.has(pending.quoteId)) {
95
+ const quote = state.quotes.get(pending.quoteId);
96
+ state.quotes.set(pending.quoteId, {
97
+ ...quote,
98
+ status: "executed",
99
+ payment_recorded_at_iso: completedAtIso,
100
+ executed_at_iso: completedAtIso
101
+ });
102
+ }
103
+ if (pending.deliveryId && state.deliveries.has(pending.deliveryId)) {
104
+ const delivery = state.deliveries.get(pending.deliveryId);
105
+ const nextDelivery = {
106
+ ...delivery,
107
+ status: "delivered",
108
+ delivered_at_iso: completedAtIso,
109
+ receipt_id: next.receipt.receipt_id,
110
+ proof_ref: next.receipt.proof_ref,
111
+ attempt_count: (delivery.attempt_count || 0) + 1,
112
+ last_attempt_at_iso: completedAtIso,
113
+ history: [
114
+ ...(Array.isArray(delivery.history) ? delivery.history : []),
115
+ buildDeliveryEvent("delivered", { ...delivery, status: "delivered", proof_ref: next.receipt.proof_ref }, completedAtIso, {
116
+ attempt_number: (delivery.attempt_count || 0) + 1
117
+ })
118
+ ]
119
+ };
120
+ state.deliveries.set(pending.deliveryId, nextDelivery);
121
+ }
122
+ state.pendingExecutions.delete(transactionId);
123
+ return next;
124
+ }
125
+
126
+ function hydratePendingExecutions(state) {
127
+ for (const transactionId of Array.from(state.pendingExecutions.keys())) {
128
+ completePendingExecution(state, transactionId);
129
+ }
130
+ }
131
+
132
+ function createCommandResult(state, payload, quote) {
133
+ const deferredCompletion = payload && payload.defer_completion === true;
134
+ const { transaction, receipt, command } = createCommandArtifacts(payload, quote, { deferredCompletion });
135
+ const result = { transaction, receipt, command };
136
+ state.transactions.set(transaction.transaction_id, result);
137
+ state.receipts.set(receipt.receipt_id, result);
138
+ const paymentRecord = {
139
+ payment_id: `pay_${quote.quote_id}`,
140
+ quote_id: quote.quote_id,
141
+ transaction_id: transaction.transaction_id,
142
+ receipt_id: receipt.receipt_id,
143
+ account_id: quote.account_id,
144
+ amount_minor: quote.amount_minor,
145
+ currency: quote.currency,
146
+ settlement_adapter: quote.settlement_adapter,
147
+ recorded_at_iso: receipt.created_at_iso
148
+ };
149
+ state.paymentLedger = [
150
+ paymentRecord,
151
+ ...(Array.isArray(state.paymentLedger) ? state.paymentLedger : [])
152
+ ].slice(0, 100);
153
+ const deliveryRecord = {
154
+ delivery_id: `delivery_${transaction.transaction_id}`,
155
+ transaction_id: transaction.transaction_id,
156
+ receipt_id: receipt.receipt_id,
157
+ quote_id: quote.quote_id,
158
+ account_id: quote.account_id,
159
+ callback_url: transaction.callback_url,
160
+ output_ref: transaction.output_ref,
161
+ proof_ref: receipt.proof_ref,
162
+ payment_id: paymentRecord.payment_id,
163
+ delivery_target_kind: transaction.callback_url ? "callback" : "direct",
164
+ status: deferredCompletion ? "pending_delivery" : "delivered",
165
+ created_at_iso: transaction.created_at_iso,
166
+ delivered_at_iso: deferredCompletion ? null : receipt.created_at_iso,
167
+ attempt_count: deferredCompletion ? 0 : 1,
168
+ last_attempt_at_iso: deferredCompletion ? null : receipt.created_at_iso,
169
+ history: [
170
+ buildDeliveryEvent("created", {
171
+ callback_url: transaction.callback_url,
172
+ output_ref: transaction.output_ref,
173
+ proof_ref: receipt.proof_ref,
174
+ status: deferredCompletion ? "pending_delivery" : "delivered"
175
+ }, transaction.created_at_iso),
176
+ ...(!deferredCompletion ? [
177
+ buildDeliveryEvent("delivered", {
178
+ callback_url: transaction.callback_url,
179
+ output_ref: transaction.output_ref,
180
+ proof_ref: receipt.proof_ref,
181
+ status: "delivered"
182
+ }, receipt.created_at_iso, { attempt_number: 1 })
183
+ ] : [])
184
+ ]
185
+ };
186
+ state.deliveries.set(deliveryRecord.delivery_id, deliveryRecord);
187
+ markQuoteExecuted(state, quote, result, paymentRecord, { deferredCompletion });
188
+ if (deferredCompletion) {
189
+ state.pendingExecutions.set(transaction.transaction_id, {
190
+ readyAtMs: Date.now() + 300,
191
+ quoteId: quote.quote_id,
192
+ deliveryId: deliveryRecord.delivery_id,
193
+ durationMs: 250,
194
+ observedLatencyClass: "instant",
195
+ assessment: "within_posture"
196
+ });
197
+ }
198
+ return result;
199
+ }
200
+
201
+ module.exports = {
202
+ completePendingExecution,
203
+ createCommandResult,
204
+ createDispute,
205
+ createQuote,
206
+ createRefund,
207
+ createRemediationTicket,
208
+ createRuntimeState,
209
+ hydrateQuoteStates,
210
+ hydratePendingExecutions,
211
+ actOnDispute,
212
+ actOnRefund,
213
+ actOnRemediationTicket,
214
+ listRecords
215
+ };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ function sendJson(res, statusCode, payload) {
4
+ const body = JSON.stringify(payload);
5
+ res.writeHead(statusCode, {
6
+ "content-type": "application/json; charset=utf-8",
7
+ "content-length": Buffer.byteLength(body)
8
+ });
9
+ res.end(body);
10
+ }
11
+
12
+ function readJsonBody(req) {
13
+ return new Promise((resolve, reject) => {
14
+ let body = "";
15
+ req.on("data", (chunk) => {
16
+ body += chunk;
17
+ if (body.length > 1024 * 1024) {
18
+ reject(new Error("request body too large"));
19
+ req.destroy();
20
+ }
21
+ });
22
+ req.on("end", () => {
23
+ if (!body) {
24
+ resolve({});
25
+ return;
26
+ }
27
+ try {
28
+ resolve(JSON.parse(body));
29
+ } catch (error) {
30
+ reject(new Error("body must be valid JSON"));
31
+ }
32
+ });
33
+ req.on("error", reject);
34
+ });
35
+ }
36
+
37
+ module.exports = {
38
+ readJsonBody,
39
+ sendJson
40
+ };
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ function listRecords(map, filters) {
4
+ const entries = Array.from(map.values());
5
+ const opts = filters || {};
6
+ let items = entries;
7
+ if (opts.status) items = items.filter((item) => item.status === opts.status);
8
+ if (opts.account_id) items = items.filter((item) => item.account_id === opts.account_id);
9
+ if (opts.quote_id) items = items.filter((item) => item.quote_id === opts.quote_id);
10
+ if (opts.transaction_id) items = items.filter((item) => item.transaction_id === opts.transaction_id);
11
+ if (opts.receipt_id) items = items.filter((item) => item.receipt_id === opts.receipt_id);
12
+ if (opts.intent) items = items.filter((item) => item.intent === opts.intent);
13
+ if (opts.callback_url) items = items.filter((item) => item.callback_url === opts.callback_url);
14
+ if (opts.proof_ref) items = items.filter((item) => item.proof_ref === opts.proof_ref);
15
+ if (opts.payment_id) items = items.filter((item) => item.payment_id === opts.payment_id);
16
+ if (opts.currency) items = items.filter((item) => item.currency === opts.currency);
17
+ if (opts.settlement_adapter) {
18
+ items = items.filter((item) => item.settlement_adapter === opts.settlement_adapter || item.settlement_mode === opts.settlement_adapter);
19
+ }
20
+ if (opts.reason_code) items = items.filter((item) => item.reason_code === opts.reason_code);
21
+ if (opts.action_code) items = items.filter((item) => item.action_code === opts.action_code);
22
+ if (opts.assigned_owner) items = items.filter((item) => item.assigned_owner === opts.assigned_owner);
23
+ if (opts.actor_role) {
24
+ items = items.filter((item) => {
25
+ if (item && item.created_actor && item.created_actor.role === opts.actor_role) {
26
+ return true;
27
+ }
28
+ const history = Array.isArray(item && item.history) ? item.history : [];
29
+ return history.some((event) => event && event.actor && event.actor.role === opts.actor_role);
30
+ });
31
+ }
32
+ if (opts.context_source) {
33
+ items = items.filter((item) => {
34
+ if (item && item.created_context && item.created_context.source === opts.context_source) {
35
+ return true;
36
+ }
37
+ const history = Array.isArray(item && item.history) ? item.history : [];
38
+ return history.some((event) => event && event.context && event.context.source === opts.context_source);
39
+ });
40
+ }
41
+ const limit = Math.max(1, Math.min(Number(opts.limit || 20), 100));
42
+ return items.slice(0, limit);
43
+ }
44
+
45
+ module.exports = {
46
+ listRecords
47
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ function buildMissingResponse(reason) {
4
+ return {
5
+ status: 404,
6
+ payload: { ok: false, reason }
7
+ };
8
+ }
9
+
10
+ function buildNamedRecordResponse(key, record, missingReason) {
11
+ if (!record) {
12
+ return buildMissingResponse(missingReason);
13
+ }
14
+ return {
15
+ status: 200,
16
+ payload: { ok: true, [key]: record }
17
+ };
18
+ }
19
+
20
+ function buildNamedRecordListResponse(key, records) {
21
+ return {
22
+ status: 200,
23
+ payload: { ok: true, [key]: records }
24
+ };
25
+ }
26
+
27
+ module.exports = {
28
+ buildMissingResponse,
29
+ buildNamedRecordListResponse,
30
+ buildNamedRecordResponse
31
+ };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ function isDisputeDetailPath(pathname) {
4
+ return /^\/v1\/disputes\/[^/]+$/.test(pathname);
5
+ }
6
+
7
+ function isDisputeActionPath(pathname) {
8
+ return /^\/v1\/disputes\/[^/]+\/actions$/.test(pathname);
9
+ }
10
+
11
+ function isRefundDetailPath(pathname) {
12
+ return /^\/v1\/refunds\/[^/]+$/.test(pathname);
13
+ }
14
+
15
+ function isRefundActionPath(pathname) {
16
+ return /^\/v1\/refunds\/[^/]+\/actions$/.test(pathname);
17
+ }
18
+
19
+ function isRemediationTicketDetailPath(pathname) {
20
+ return /^\/v1\/remediation-tickets\/[^/]+$/.test(pathname);
21
+ }
22
+
23
+ function isRemediationTicketActionPath(pathname) {
24
+ return /^\/v1\/remediation-tickets\/[^/]+\/actions$/.test(pathname);
25
+ }
26
+
27
+ module.exports = {
28
+ isDisputeActionPath,
29
+ isDisputeDetailPath,
30
+ isRefundActionPath,
31
+ isRefundDetailPath,
32
+ isRemediationTicketActionPath,
33
+ isRemediationTicketDetailPath
34
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ function getQueryFilters(url) {
4
+ return Object.fromEntries(url.searchParams.entries());
5
+ }
6
+
7
+ function getDecodedSuffixId(pathname, prefix) {
8
+ return decodeURIComponent(pathname.replace(prefix, ""));
9
+ }
10
+
11
+ function getDecodedActionTargetId(pathname, prefix) {
12
+ return decodeURIComponent(pathname.replace("/actions", "").replace(prefix, ""));
13
+ }
14
+
15
+ module.exports = {
16
+ getDecodedActionTargetId,
17
+ getDecodedSuffixId,
18
+ getQueryFilters
19
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "xytara",
3
+ "version": "1.0.0",
4
+ "description": "Machine commerce SDK for discovery, quoting, execution, lifecycle inspection, and adapters.",
5
+ "main": "index.js",
6
+ "type": "commonjs",
7
+ "license": "Apache-2.0",
8
+ "homepage": "https://xytara.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/naxytra/xytara.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/naxytra/xytara/issues"
15
+ },
16
+ "funding": {
17
+ "type": "individual",
18
+ "url": "https://xytara.com"
19
+ },
20
+ "keywords": [
21
+ "sdk",
22
+ "machine-client",
23
+ "catalog",
24
+ "workflow",
25
+ "payments",
26
+ "commerce",
27
+ "adapters"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "files": [
33
+ "index.js",
34
+ "client.js",
35
+ "server.js",
36
+ "fixtures/",
37
+ "lib/",
38
+ "adapters/",
39
+ ".env.example",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "sideEffects": false,
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "start": "node server.js",
49
+ "verify:package": "node scripts/verify_all.js",
50
+ "verify:adapters": "node scripts/verify_adapters.js",
51
+ "verify:examples": "node scripts/verify_examples.js",
52
+ "verify:service": "node scripts/verify_service.js",
53
+ "verify:all": "node scripts/verify_all.js && node scripts/verify_adapters.js && node scripts/verify_examples.js && node scripts/verify_service.js"
54
+ }
55
+ }