atmx-web 0.44.0 → 0.47.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/atmx.es.js +679 -0
- package/{src/core/vendor/axiom_runtime.js → dist/atmx.umd.js} +20 -18
- package/dist/core/callback.d.ts +7 -0
- package/dist/core/context.d.ts +16 -0
- package/dist/core/query.d.ts +17 -0
- package/dist/core/router.d.ts +20 -0
- package/dist/core/types.d.ts +44 -0
- package/dist/core/wasm.d.ts +11 -0
- package/dist/dom/components/engine-status.d.ts +5 -0
- package/dist/dom/indicators.d.ts +2 -0
- package/dist/dom/lifecycle.d.ts +6 -0
- package/dist/dom/scanner.d.ts +2 -0
- package/dist/index.d.ts +18 -0
- package/dist/resolver/evaluator.d.ts +6 -0
- package/dist/resolver/render.d.ts +2 -0
- package/package.json +6 -2
- package/justfile +0 -8
- package/scripts/upload.sh +0 -61
- package/src/core/callback.ts +0 -92
- package/src/core/context.ts +0 -56
- package/src/core/query.ts +0 -162
- package/src/core/router.ts +0 -91
- package/src/core/types.ts +0 -49
- package/src/core/wasm.ts +0 -223
- package/src/dom/components/engine-status.ts +0 -62
- package/src/dom/indicators.ts +0 -36
- package/src/dom/lifecycle.ts +0 -379
- package/src/dom/scanner.ts +0 -125
- package/src/index.ts +0 -78
- package/src/resolver/evaluator.ts +0 -46
- package/src/resolver/render.ts +0 -196
- package/tsconfig.json +0 -25
- package/vite-env.d.ts +0 -11
- package/vite.config.ts +0 -27
- /package/{public → dist}/.axiom +0 -0
- /package/{public → dist}/axiom_runtime.wasm +0 -0
package/src/dom/lifecycle.ts
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
// FILE: src/dom/lifecycle.ts
|
|
2
|
-
import { wasmEngine, allocString, allocBytes } from "../core/wasm";
|
|
3
|
-
import { pendingRequests } from "../core/callback";
|
|
4
|
-
import { resolveRoute, buildRequestPath } from "../core/router";
|
|
5
|
-
import {
|
|
6
|
-
getContext,
|
|
7
|
-
updateContext,
|
|
8
|
-
getClosestData,
|
|
9
|
-
AtmxContextData,
|
|
10
|
-
} from "../core/context";
|
|
11
|
-
import { EventType } from "../core/types";
|
|
12
|
-
import { executeAction, evaluateExpression } from "../resolver/evaluator";
|
|
13
|
-
import { queryRegistry, ActiveQuery } from "../core/query";
|
|
14
|
-
import { atmx } from "../index";
|
|
15
|
-
|
|
16
|
-
let globalReqCounter = 0;
|
|
17
|
-
|
|
18
|
-
function triggerEscapeHatch(element: HTMLElement, eventName: string) {
|
|
19
|
-
const attrName = `ax-on:${eventName}`;
|
|
20
|
-
if (element.hasAttribute(attrName)) {
|
|
21
|
-
const expr = element.getAttribute(attrName)!;
|
|
22
|
-
const ctx = getContext(element);
|
|
23
|
-
executeAction(expr, ctx);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function logTransaction(direction: "OUT" | "IN", reqId: number, details: any) {
|
|
28
|
-
if (!atmx.debug) return;
|
|
29
|
-
const color = direction === "OUT" ? "#7c3aed" : "#059669";
|
|
30
|
-
const label = direction === "OUT" ? "➔ WASM CALL" : "← WASM RESP";
|
|
31
|
-
console.groupCollapsed(
|
|
32
|
-
`%c${label} [#${reqId}]`,
|
|
33
|
-
`color: ${color}; font-weight: bold;`,
|
|
34
|
-
);
|
|
35
|
-
console.log("Details:", details);
|
|
36
|
-
console.groupEnd();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function parseRpcCall(
|
|
40
|
-
element: HTMLElement,
|
|
41
|
-
attrValue: string,
|
|
42
|
-
): { target: string; args: Record<string, any> } | null {
|
|
43
|
-
const match = attrValue.trim().match(/^([\w\.]+)(?:\((.*)\))?$/);
|
|
44
|
-
if (!match) return null;
|
|
45
|
-
|
|
46
|
-
const target = match[1];
|
|
47
|
-
let args = {};
|
|
48
|
-
|
|
49
|
-
if (match[2] && match[2].trim() !== "") {
|
|
50
|
-
try {
|
|
51
|
-
const ctx = getContext(element);
|
|
52
|
-
const availableData = ctx.data || getClosestData(element);
|
|
53
|
-
const fn = new Function("$data", "$state", `return (${match[2]});`);
|
|
54
|
-
args = fn(availableData, ctx) || {};
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error(`ATMX Scope Error in "${attrValue}":`, e);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { target, args };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function connectQuery(element: HTMLElement) {
|
|
63
|
-
const attrVal = element.getAttribute("ax-query");
|
|
64
|
-
if (!attrVal) return;
|
|
65
|
-
|
|
66
|
-
const parsed = parseRpcCall(element, attrVal);
|
|
67
|
-
if (!parsed) return;
|
|
68
|
-
|
|
69
|
-
const route = resolveRoute(parsed.target);
|
|
70
|
-
if (!route) {
|
|
71
|
-
updateContext(element, {
|
|
72
|
-
state: "error",
|
|
73
|
-
error: { message: `Route not found: ${parsed.target}` },
|
|
74
|
-
});
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const queryKey = `${route.namespace}:${route.id}:${JSON.stringify(parsed.args)}`;
|
|
79
|
-
|
|
80
|
-
let activeQuery = queryRegistry.get(queryKey);
|
|
81
|
-
if (!activeQuery) {
|
|
82
|
-
activeQuery = new ActiveQuery(queryKey, route, parsed.args);
|
|
83
|
-
queryRegistry.set(queryKey, activeQuery);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!(element as any)._axListener) {
|
|
87
|
-
(element as any)._axListener = (newState: Partial<AtmxContextData>) => {
|
|
88
|
-
updateContext(element, newState);
|
|
89
|
-
if (newState.state === "data" || newState.state === "error") {
|
|
90
|
-
triggerEscapeHatch(element, newState.state);
|
|
91
|
-
element.dispatchEvent(
|
|
92
|
-
new CustomEvent(`atmx:${newState.state}`, {
|
|
93
|
-
bubbles: true,
|
|
94
|
-
detail: newState.data || newState.error,
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
activeQuery.subscribe((element as any)._axListener);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
activeQuery.fetch();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function executeMutation(element: HTMLElement) {
|
|
106
|
-
const attrVal = element.getAttribute("ax-mutate");
|
|
107
|
-
if (!attrVal) return;
|
|
108
|
-
|
|
109
|
-
const parsed = parseRpcCall(element, attrVal);
|
|
110
|
-
if (!parsed) return;
|
|
111
|
-
|
|
112
|
-
const route = resolveRoute(parsed.target);
|
|
113
|
-
if (!route) {
|
|
114
|
-
updateContext(element, {
|
|
115
|
-
state: "error",
|
|
116
|
-
error: { message: `Route not found: ${parsed.target}` },
|
|
117
|
-
});
|
|
118
|
-
triggerEscapeHatch(element, "error");
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
updateContext(element, { state: "mutating", error: null, data: null });
|
|
123
|
-
triggerEscapeHatch(element, "mutating");
|
|
124
|
-
|
|
125
|
-
const formDataObj: Record<string, any> = {};
|
|
126
|
-
if (element instanceof HTMLFormElement) {
|
|
127
|
-
const formData = new FormData(element);
|
|
128
|
-
formData.forEach((value, key) => {
|
|
129
|
-
const input = element.elements.namedItem(key);
|
|
130
|
-
let isNumber = false;
|
|
131
|
-
|
|
132
|
-
if (input instanceof HTMLInputElement) {
|
|
133
|
-
isNumber = input.type === "number";
|
|
134
|
-
} else if (input instanceof RadioNodeList && input.length > 0) {
|
|
135
|
-
isNumber = (input[0] as HTMLInputElement).type === "number";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const finalVal = isNumber && value !== "" ? Number(value) : value;
|
|
139
|
-
|
|
140
|
-
// ✨ NEW: Un-flatten dot notation! (e.g. "user.email" -> { user: { email: ... } })
|
|
141
|
-
if (key.includes(".")) {
|
|
142
|
-
const parts = key.split(".");
|
|
143
|
-
let current = formDataObj;
|
|
144
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
145
|
-
if (!current[parts[i]]) current[parts[i]] = {};
|
|
146
|
-
current = current[parts[i]];
|
|
147
|
-
}
|
|
148
|
-
current[parts[parts.length - 1]] = finalVal;
|
|
149
|
-
} else {
|
|
150
|
-
formDataObj[key] = finalVal;
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const mergedArgs = { ...formDataObj, ...parsed.args };
|
|
156
|
-
const { path, remainingArgs } = buildRequestPath(route, mergedArgs);
|
|
157
|
-
|
|
158
|
-
// ✨ NEW: Smart Body Extraction for FastAPI!
|
|
159
|
-
let payloadObj = remainingArgs;
|
|
160
|
-
if (route.bodyParam && remainingArgs[route.bodyParam] !== undefined) {
|
|
161
|
-
// If the developer passed the data inside the wrapper (e.g., args.user), extract it so it's flat!
|
|
162
|
-
payloadObj = remainingArgs[route.bodyParam];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let customHeaders: Record<string, string> = {};
|
|
166
|
-
const headersAttr = element.getAttribute("ax-headers");
|
|
167
|
-
if (headersAttr) {
|
|
168
|
-
try {
|
|
169
|
-
const fn = new Function("return (" + headersAttr + ");");
|
|
170
|
-
customHeaders = fn() || {};
|
|
171
|
-
} catch (e) {
|
|
172
|
-
console.error("ATMX: Invalid ax-headers syntax", e);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const isFormUrlEncoded = Object.entries(customHeaders).some(
|
|
177
|
-
([k, v]) =>
|
|
178
|
-
k.toLowerCase() === "content-type" &&
|
|
179
|
-
v.includes("application/x-www-form-urlencoded"),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
let payloadBytes = new Uint8Array(0);
|
|
183
|
-
if (Object.keys(payloadObj).length > 0) {
|
|
184
|
-
if (isFormUrlEncoded) {
|
|
185
|
-
const params = new URLSearchParams();
|
|
186
|
-
for (const [k, v] of Object.entries(payloadObj)) {
|
|
187
|
-
params.append(k, String(v));
|
|
188
|
-
}
|
|
189
|
-
payloadBytes = new TextEncoder().encode(params.toString());
|
|
190
|
-
} else {
|
|
191
|
-
payloadBytes = new TextEncoder().encode(JSON.stringify(payloadObj));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
executeWasmCall(
|
|
196
|
-
element,
|
|
197
|
-
route.namespace,
|
|
198
|
-
route.id,
|
|
199
|
-
route.method,
|
|
200
|
-
path,
|
|
201
|
-
payloadBytes,
|
|
202
|
-
route.isStream,
|
|
203
|
-
true,
|
|
204
|
-
customHeaders,
|
|
205
|
-
isFormUrlEncoded,
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function executeWasmCall(
|
|
210
|
-
element: HTMLElement,
|
|
211
|
-
namespace: string,
|
|
212
|
-
endpointId: number,
|
|
213
|
-
method: string,
|
|
214
|
-
path: string,
|
|
215
|
-
payloadBytes: Uint8Array,
|
|
216
|
-
isStream: boolean,
|
|
217
|
-
isMutation: boolean,
|
|
218
|
-
customHeaders: Record<string, string> = {},
|
|
219
|
-
isFormUrlEncoded: boolean = false,
|
|
220
|
-
) {
|
|
221
|
-
const reqId = ++globalReqCounter;
|
|
222
|
-
|
|
223
|
-
logTransaction("OUT", reqId, {
|
|
224
|
-
namespace,
|
|
225
|
-
endpointId,
|
|
226
|
-
method,
|
|
227
|
-
path,
|
|
228
|
-
headers: customHeaders,
|
|
229
|
-
payload:
|
|
230
|
-
payloadBytes.length > 0
|
|
231
|
-
? isFormUrlEncoded
|
|
232
|
-
? new TextDecoder().decode(payloadBytes)
|
|
233
|
-
: JSON.parse(new TextDecoder().decode(payloadBytes))
|
|
234
|
-
: null,
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
pendingRequests.set(reqId, {
|
|
238
|
-
isStream: isStream,
|
|
239
|
-
onResponse: (res) => handleResponse(element, res, isMutation, reqId),
|
|
240
|
-
onComplete: () => {
|
|
241
|
-
if (atmx.debug)
|
|
242
|
-
console.log(`%c✔ Stream Complete [#${reqId}]`, "color: #94a3b8;");
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const nsStr = allocString(namespace);
|
|
247
|
-
const mStr = allocString(method);
|
|
248
|
-
const pStr = allocString(path);
|
|
249
|
-
const tpStr = allocString("");
|
|
250
|
-
const hStr = allocString(
|
|
251
|
-
Object.keys(customHeaders).length > 0 ? JSON.stringify(customHeaders) : "",
|
|
252
|
-
);
|
|
253
|
-
const payloadPtr = allocBytes(payloadBytes);
|
|
254
|
-
|
|
255
|
-
wasmEngine.axiom_wasm_call(
|
|
256
|
-
reqId,
|
|
257
|
-
nsStr.ptr,
|
|
258
|
-
nsStr.len,
|
|
259
|
-
endpointId,
|
|
260
|
-
mStr.ptr,
|
|
261
|
-
mStr.len,
|
|
262
|
-
pStr.ptr,
|
|
263
|
-
pStr.len,
|
|
264
|
-
tpStr.ptr,
|
|
265
|
-
tpStr.len,
|
|
266
|
-
hStr.ptr,
|
|
267
|
-
hStr.len,
|
|
268
|
-
payloadPtr,
|
|
269
|
-
payloadBytes.length,
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
wasmEngine.axiom_free_memory(nsStr.ptr, nsStr.len);
|
|
273
|
-
wasmEngine.axiom_free_memory(mStr.ptr, mStr.len);
|
|
274
|
-
wasmEngine.axiom_free_memory(pStr.ptr, pStr.len);
|
|
275
|
-
wasmEngine.axiom_free_memory(tpStr.ptr, tpStr.len);
|
|
276
|
-
wasmEngine.axiom_free_memory(hStr.ptr, hStr.len);
|
|
277
|
-
if (payloadPtr) wasmEngine.axiom_free_memory(payloadPtr, payloadBytes.length);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function handleResponse(
|
|
281
|
-
element: HTMLElement,
|
|
282
|
-
res: any,
|
|
283
|
-
isMutation: boolean,
|
|
284
|
-
reqId: number,
|
|
285
|
-
) {
|
|
286
|
-
logTransaction("IN", reqId, {
|
|
287
|
-
eventType: EventType[res.eventType],
|
|
288
|
-
data: res.data,
|
|
289
|
-
error: res.error,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
if (res.eventType === EventType.Error) {
|
|
293
|
-
updateContext(element, {
|
|
294
|
-
state: "error",
|
|
295
|
-
error: res.error,
|
|
296
|
-
isFetching: false,
|
|
297
|
-
isMutating: false,
|
|
298
|
-
});
|
|
299
|
-
triggerEscapeHatch(element, "error");
|
|
300
|
-
element.dispatchEvent(
|
|
301
|
-
new CustomEvent("atmx:error", { bubbles: true, detail: res.error }),
|
|
302
|
-
);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (res.data) {
|
|
307
|
-
let source: "cache" | "network" = "network";
|
|
308
|
-
let isFetching = false;
|
|
309
|
-
|
|
310
|
-
if (res.eventType === EventType.CacheHit) {
|
|
311
|
-
source = "cache";
|
|
312
|
-
} else if (res.eventType === EventType.CacheHitAndFetching) {
|
|
313
|
-
source = "cache";
|
|
314
|
-
isFetching = true;
|
|
315
|
-
} else if (res.eventType === EventType.NetworkSuccess) {
|
|
316
|
-
source = "network";
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const nextState = isMutation ? "success" : "data";
|
|
320
|
-
|
|
321
|
-
updateContext(element, {
|
|
322
|
-
state: nextState,
|
|
323
|
-
data: res.data,
|
|
324
|
-
source,
|
|
325
|
-
isFetching,
|
|
326
|
-
isMutating: false,
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// ✨ PHASE 1: Declarative Auth
|
|
330
|
-
if (isMutation) {
|
|
331
|
-
const storeTokenAttr = element.getAttribute("ax-store-token");
|
|
332
|
-
if (storeTokenAttr) {
|
|
333
|
-
const tokenVal = evaluateExpression(
|
|
334
|
-
storeTokenAttr,
|
|
335
|
-
getContext(element),
|
|
336
|
-
);
|
|
337
|
-
if (tokenVal) {
|
|
338
|
-
const rpcAttr = element.getAttribute("ax-mutate");
|
|
339
|
-
const route = resolveRoute(parseRpcCall(element, rpcAttr!)!.target);
|
|
340
|
-
const headerName =
|
|
341
|
-
element.getAttribute("ax-token-header") || "Authorization";
|
|
342
|
-
atmx.setAuthToken(
|
|
343
|
-
route?.namespace || "default",
|
|
344
|
-
headerName,
|
|
345
|
-
tokenVal,
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (element.hasAttribute("ax-clear-token")) {
|
|
351
|
-
const rpcAttr = element.getAttribute("ax-mutate");
|
|
352
|
-
const route = resolveRoute(parseRpcCall(element, rpcAttr!)!.target);
|
|
353
|
-
const headerName =
|
|
354
|
-
element.getAttribute("ax-token-header") || "Authorization";
|
|
355
|
-
atmx.clearAuthToken(route?.namespace || "default", headerName);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// ✨ PHASE 1: Declarative Refresh
|
|
359
|
-
const refreshAttr = element.getAttribute("ax-refresh");
|
|
360
|
-
if (refreshAttr) {
|
|
361
|
-
const targets = refreshAttr.split(",").map((s) => s.trim());
|
|
362
|
-
for (const [_, activeQuery] of queryRegistry.entries()) {
|
|
363
|
-
const routeName = `${activeQuery.route.namespace}.${activeQuery.route.name}`;
|
|
364
|
-
if (targets.includes(routeName)) {
|
|
365
|
-
activeQuery.fetch(true); // Force refresh
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
triggerEscapeHatch(element, nextState);
|
|
372
|
-
element.dispatchEvent(
|
|
373
|
-
new CustomEvent(isMutation ? "atmx:success" : "atmx:data", {
|
|
374
|
-
bubbles: true,
|
|
375
|
-
detail: res.data,
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
}
|
package/src/dom/scanner.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// FILE: src/dom/scanner.ts
|
|
2
|
-
import { connectQuery, executeMutation, parseRpcCall } from "./lifecycle";
|
|
3
|
-
import { updateContext, getContext } from "../core/context";
|
|
4
|
-
import { queryRegistry } from "../core/query";
|
|
5
|
-
import { resolveRoute } from "../core/router";
|
|
6
|
-
import { evaluateExpression } from "../resolver/evaluator";
|
|
7
|
-
|
|
8
|
-
const intersectionObserver = new IntersectionObserver((entries) => {
|
|
9
|
-
entries.forEach((entry) => {
|
|
10
|
-
if (entry.isIntersecting) {
|
|
11
|
-
const el = entry.target as HTMLElement;
|
|
12
|
-
if (el.hasAttribute("ax-query")) connectQuery(el);
|
|
13
|
-
intersectionObserver.unobserve(el);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* ✨ NEW: Evaluates dynamic prefixed attributes like :ax-query="sdk.pyExample.login()"
|
|
20
|
-
*/
|
|
21
|
-
function evaluateDynamicAttributes(root: HTMLElement | Document) {
|
|
22
|
-
const dynamicElements = root.querySelectorAll<HTMLElement>(
|
|
23
|
-
"[\\:ax-query], [\\:ax-mutate]",
|
|
24
|
-
);
|
|
25
|
-
dynamicElements.forEach((el) => {
|
|
26
|
-
if (el.hasAttribute(":ax-query")) {
|
|
27
|
-
const expr = el.getAttribute(":ax-query")!;
|
|
28
|
-
const val = evaluateExpression(expr, getContext(el));
|
|
29
|
-
if (val) el.setAttribute("ax-query", String(val));
|
|
30
|
-
el.removeAttribute(":ax-query");
|
|
31
|
-
}
|
|
32
|
-
if (el.hasAttribute(":ax-mutate")) {
|
|
33
|
-
const expr = el.getAttribute(":ax-mutate")!;
|
|
34
|
-
const val = evaluateExpression(expr, getContext(el));
|
|
35
|
-
if (val) el.setAttribute("ax-mutate", String(val));
|
|
36
|
-
el.removeAttribute(":ax-mutate");
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function scanAndInitialize(root: HTMLElement | Document = document) {
|
|
42
|
-
// 0. Unwrap Dynamic Typescript Clients first
|
|
43
|
-
evaluateDynamicAttributes(root);
|
|
44
|
-
|
|
45
|
-
// 1. Initialize ax-query blocks
|
|
46
|
-
const queries = root.querySelectorAll("[ax-query]");
|
|
47
|
-
queries.forEach((el) => {
|
|
48
|
-
const htmlEl = el as HTMLElement;
|
|
49
|
-
updateContext(htmlEl, {});
|
|
50
|
-
|
|
51
|
-
const triggerMode = htmlEl.getAttribute("ax-trigger") || "load";
|
|
52
|
-
if (triggerMode === "load") {
|
|
53
|
-
connectQuery(htmlEl);
|
|
54
|
-
} else if (triggerMode === "intersect") {
|
|
55
|
-
intersectionObserver.observe(htmlEl);
|
|
56
|
-
} else {
|
|
57
|
-
htmlEl.addEventListener(triggerMode, (e) => {
|
|
58
|
-
e.preventDefault();
|
|
59
|
-
connectQuery(htmlEl);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// 2. Initialize ax-mutate blocks
|
|
65
|
-
const mutates = root.querySelectorAll("[ax-mutate]");
|
|
66
|
-
mutates.forEach((el) => {
|
|
67
|
-
const htmlEl = el as HTMLElement;
|
|
68
|
-
updateContext(htmlEl, {});
|
|
69
|
-
|
|
70
|
-
const trigger = htmlEl.tagName === "FORM" ? "submit" : "click";
|
|
71
|
-
|
|
72
|
-
htmlEl.addEventListener(trigger, (e) => {
|
|
73
|
-
if (
|
|
74
|
-
htmlEl.tagName === "A" ||
|
|
75
|
-
htmlEl.tagName === "BUTTON" ||
|
|
76
|
-
htmlEl.tagName === "FORM"
|
|
77
|
-
) {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
}
|
|
80
|
-
executeMutation(htmlEl);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// 3. Action Modifiers (e.g., ax-trigger="refresh")
|
|
85
|
-
const refreshers = root.querySelectorAll('[ax-trigger="refresh"]');
|
|
86
|
-
refreshers.forEach((el) => {
|
|
87
|
-
const htmlEl = el as HTMLElement;
|
|
88
|
-
htmlEl.addEventListener("click", (e) => {
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
|
|
91
|
-
const parentQuery = htmlEl.closest("[ax-query]") as HTMLElement;
|
|
92
|
-
if (parentQuery) {
|
|
93
|
-
const attrVal = parentQuery.getAttribute("ax-query")!;
|
|
94
|
-
const parsed = parseRpcCall(parentQuery, attrVal);
|
|
95
|
-
|
|
96
|
-
if (parsed) {
|
|
97
|
-
const route = resolveRoute(parsed.target);
|
|
98
|
-
if (route) {
|
|
99
|
-
const queryKey = `${route.namespace}:${route.id}:${JSON.stringify(parsed.args)}`;
|
|
100
|
-
const activeQuery = queryRegistry.get(queryKey);
|
|
101
|
-
if (activeQuery) {
|
|
102
|
-
activeQuery.fetch(true);
|
|
103
|
-
} else {
|
|
104
|
-
connectQuery(parentQuery);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function initMutationObserver() {
|
|
114
|
-
const observer = new MutationObserver((mutations) => {
|
|
115
|
-
mutations.forEach((mutation) => {
|
|
116
|
-
mutation.addedNodes.forEach((node) => {
|
|
117
|
-
if (node instanceof HTMLElement) {
|
|
118
|
-
scanAndInitialize(node);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
125
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// FILE: src/index.ts
|
|
2
|
-
import { initWasm, setAuthToken, clearAuthToken } from "./core/wasm";
|
|
3
|
-
import { scanAndInitialize, initMutationObserver } from "./dom/scanner";
|
|
4
|
-
import { connectQuery } from "./dom/lifecycle";
|
|
5
|
-
import { AtmxConfig, InitResult } from "./core/types";
|
|
6
|
-
import "./dom/components/engine-status";
|
|
7
|
-
|
|
8
|
-
export const ATMX_VERSION = "0.44.0";
|
|
9
|
-
|
|
10
|
-
class ATMX {
|
|
11
|
-
public version = ATMX_VERSION;
|
|
12
|
-
public initialized = false;
|
|
13
|
-
public debug = false;
|
|
14
|
-
|
|
15
|
-
public async init(config: AtmxConfig): Promise<InitResult> {
|
|
16
|
-
if (this.initialized) {
|
|
17
|
-
return { ok: true, contracts: {}, initDurationMs: 0 };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const result = await initWasm(config);
|
|
21
|
-
|
|
22
|
-
if (result.ok) {
|
|
23
|
-
this.debug = config.debug || false;
|
|
24
|
-
|
|
25
|
-
initMutationObserver();
|
|
26
|
-
scanAndInitialize(document.body);
|
|
27
|
-
|
|
28
|
-
this.initialized = true;
|
|
29
|
-
if (this.debug)
|
|
30
|
-
console.log(
|
|
31
|
-
"%c🚀 ATMX Debug Mode: Enabled",
|
|
32
|
-
"color: #2563eb; font-weight: bold;",
|
|
33
|
-
);
|
|
34
|
-
console.log(
|
|
35
|
-
`✅ ATMX v${this.version} Online (${result.initDurationMs}ms). Reactive Engine Started.`,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
document.dispatchEvent(new CustomEvent("atmx:ready", { detail: result }));
|
|
39
|
-
} else {
|
|
40
|
-
console.error("❌ ATMX Initialization Failed:", result.error);
|
|
41
|
-
// Broadcast fatal error so UI components can catch it
|
|
42
|
-
document.dispatchEvent(
|
|
43
|
-
new CustomEvent("atmx:error", { detail: result.error }),
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public triggerQuery(elementOrSelector: string | HTMLElement) {
|
|
51
|
-
const el =
|
|
52
|
-
typeof elementOrSelector === "string"
|
|
53
|
-
? (document.querySelector(elementOrSelector) as HTMLElement)
|
|
54
|
-
: elementOrSelector;
|
|
55
|
-
|
|
56
|
-
if (el && el instanceof HTMLElement && el.hasAttribute("ax-query")) {
|
|
57
|
-
connectQuery(el);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public on(
|
|
62
|
-
eventName: "ready" | "data" | "error" | "success" | "contract-loaded",
|
|
63
|
-
callback: (e: any) => void,
|
|
64
|
-
) {
|
|
65
|
-
document.addEventListener(`atmx:${eventName}`, callback);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public setAuthToken(namespace: string, methodName: string, token: string) {
|
|
69
|
-
setAuthToken(namespace, methodName, token);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public clearAuthToken(namespace: string, methodName: string) {
|
|
73
|
-
clearAuthToken(namespace, methodName);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const atmx = new ATMX();
|
|
78
|
-
if (typeof window !== "undefined") (window as any).atmx = atmx;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// FILE: src/resolver/evaluator.ts
|
|
2
|
-
import { AtmxContextData } from "../core/context";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Safely evaluates a JS expression from an HTML attribute against the local ATMX Context.
|
|
6
|
-
*/
|
|
7
|
-
export function evaluateExpression(
|
|
8
|
-
expr: string,
|
|
9
|
-
context: AtmxContextData,
|
|
10
|
-
locals: Record<string, any> = {},
|
|
11
|
-
): any {
|
|
12
|
-
if (!expr) return undefined;
|
|
13
|
-
try {
|
|
14
|
-
const keys = Object.keys(locals);
|
|
15
|
-
const values = Object.values(locals);
|
|
16
|
-
|
|
17
|
-
// Dynamically build a function that accepts $data, $error, $state, AND our locals ($item, etc.)
|
|
18
|
-
const fn = new Function(
|
|
19
|
-
"$data",
|
|
20
|
-
"$error",
|
|
21
|
-
"$state",
|
|
22
|
-
...keys,
|
|
23
|
-
`return (${expr});`,
|
|
24
|
-
);
|
|
25
|
-
return fn(context.data || {}, context.error || {}, context, ...values);
|
|
26
|
-
} catch (e) {
|
|
27
|
-
console.error(`ATMX Eval Error: ${expr}`, e);
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function executeAction(
|
|
33
|
-
expr: string,
|
|
34
|
-
context: AtmxContextData,
|
|
35
|
-
locals: Record<string, any> = {},
|
|
36
|
-
): void {
|
|
37
|
-
if (!expr) return;
|
|
38
|
-
try {
|
|
39
|
-
const keys = Object.keys(locals);
|
|
40
|
-
const values = Object.values(locals);
|
|
41
|
-
const fn = new Function("$data", "$error", "$state", ...keys, expr);
|
|
42
|
-
fn(context.data || {}, context.error || {}, context, ...values);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error(`ATMX Action Error: ${expr}`, e);
|
|
45
|
-
}
|
|
46
|
-
}
|