vsn 1.0.0 → 1.0.2
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.cjs +272 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -4
- package/dist/index.d.ts +27 -4
- package/dist/index.js +272 -12
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +5 -5
- package/dist/index.min.js.map +1 -1
- package/package.json +15 -1
package/dist/index.cjs
CHANGED
|
@@ -513,10 +513,12 @@ var ProgramNode = class extends BaseNode {
|
|
|
513
513
|
}
|
|
514
514
|
};
|
|
515
515
|
var UseNode = class extends BaseNode {
|
|
516
|
-
constructor(name, alias) {
|
|
516
|
+
constructor(name, alias, flags = {}, flagArgs = {}) {
|
|
517
517
|
super("Use");
|
|
518
518
|
this.name = name;
|
|
519
519
|
this.alias = alias;
|
|
520
|
+
this.flags = flags;
|
|
521
|
+
this.flagArgs = flagArgs;
|
|
520
522
|
}
|
|
521
523
|
};
|
|
522
524
|
var BlockNode = class extends BaseNode {
|
|
@@ -572,24 +574,117 @@ var OnBlockNode = class extends BaseNode {
|
|
|
572
574
|
}
|
|
573
575
|
};
|
|
574
576
|
var AssignmentNode = class extends BaseNode {
|
|
575
|
-
constructor(target, value) {
|
|
577
|
+
constructor(target, value, operator = "=") {
|
|
576
578
|
super("Assignment");
|
|
577
579
|
this.target = target;
|
|
578
580
|
this.value = value;
|
|
581
|
+
this.operator = operator;
|
|
579
582
|
}
|
|
580
583
|
async evaluate(context) {
|
|
581
584
|
if (!context.scope || !context.scope.setPath) {
|
|
582
585
|
return void 0;
|
|
583
586
|
}
|
|
584
587
|
const value = await this.value.evaluate(context);
|
|
588
|
+
if (this.operator !== "=") {
|
|
589
|
+
return await this.applyCompoundAssignment(context, value);
|
|
590
|
+
}
|
|
585
591
|
if (this.target instanceof IdentifierExpression && this.target.name.startsWith("root.") && context.rootScope) {
|
|
586
592
|
const path = this.target.name.slice("root.".length);
|
|
587
|
-
context.rootScope.setPath?.(
|
|
593
|
+
context.rootScope.setPath?.(path, value);
|
|
588
594
|
return value;
|
|
589
595
|
}
|
|
596
|
+
if (this.target instanceof MemberExpression || this.target instanceof IndexExpression) {
|
|
597
|
+
const resolved = await this.resolveAssignmentTarget(context);
|
|
598
|
+
if (resolved?.scope?.setPath) {
|
|
599
|
+
resolved.scope.setPath(resolved.path, value);
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
590
603
|
this.assignTarget(context, this.target, value);
|
|
591
604
|
return value;
|
|
592
605
|
}
|
|
606
|
+
async applyCompoundAssignment(context, value) {
|
|
607
|
+
if (!context.scope || !context.scope.setPath) {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
const resolved = await this.resolveAssignmentTarget(context);
|
|
611
|
+
if (!resolved) {
|
|
612
|
+
throw new Error("Compound assignment requires a simple identifier or member path");
|
|
613
|
+
}
|
|
614
|
+
const { scope, path } = resolved;
|
|
615
|
+
const current = scope?.getPath ? scope.getPath(path) : void 0;
|
|
616
|
+
let result;
|
|
617
|
+
if (this.operator === "+=") {
|
|
618
|
+
result = current + value;
|
|
619
|
+
} else if (this.operator === "-=") {
|
|
620
|
+
result = current - value;
|
|
621
|
+
} else if (this.operator === "*=") {
|
|
622
|
+
result = current * value;
|
|
623
|
+
} else {
|
|
624
|
+
result = current / value;
|
|
625
|
+
}
|
|
626
|
+
scope?.setPath?.(path, result);
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
async resolveAssignmentTarget(context) {
|
|
630
|
+
if (this.target instanceof IdentifierExpression) {
|
|
631
|
+
const isRoot = this.target.name.startsWith("root.");
|
|
632
|
+
const rawPath = isRoot ? this.target.name.slice("root.".length) : this.target.name;
|
|
633
|
+
if (isRoot) {
|
|
634
|
+
return { scope: context.scope, path: `root.${rawPath}` };
|
|
635
|
+
}
|
|
636
|
+
return { scope: context.scope, path: rawPath };
|
|
637
|
+
}
|
|
638
|
+
if (this.target instanceof MemberExpression) {
|
|
639
|
+
const resolvedPath = this.target.getIdentifierPath();
|
|
640
|
+
if (!resolvedPath) {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
const path = resolvedPath.path;
|
|
644
|
+
const isRoot = path.startsWith("root.");
|
|
645
|
+
const rawPath = isRoot ? path.slice("root.".length) : path;
|
|
646
|
+
if (isRoot) {
|
|
647
|
+
return { scope: context.scope, path: `root.${rawPath}` };
|
|
648
|
+
}
|
|
649
|
+
return { scope: context.scope, path: rawPath };
|
|
650
|
+
}
|
|
651
|
+
if (this.target instanceof IndexExpression) {
|
|
652
|
+
const path = await this.resolveIndexPath(context, this.target);
|
|
653
|
+
if (!path) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
const isRoot = path.startsWith("root.");
|
|
657
|
+
const rawPath = isRoot ? path.slice("root.".length) : path;
|
|
658
|
+
if (isRoot) {
|
|
659
|
+
return { scope: context.scope, path: `root.${rawPath}` };
|
|
660
|
+
}
|
|
661
|
+
return { scope: context.scope, path: rawPath };
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
async resolveIndexPath(context, expr) {
|
|
666
|
+
const base = await this.resolveTargetPath(context, expr.target);
|
|
667
|
+
if (!base) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
const indexValue = await expr.index.evaluate(context);
|
|
671
|
+
if (indexValue == null) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
return `${base}.${indexValue}`;
|
|
675
|
+
}
|
|
676
|
+
async resolveTargetPath(context, target) {
|
|
677
|
+
if (target instanceof IdentifierExpression) {
|
|
678
|
+
return target.name;
|
|
679
|
+
}
|
|
680
|
+
if (target instanceof MemberExpression) {
|
|
681
|
+
return target.getIdentifierPath()?.path ?? null;
|
|
682
|
+
}
|
|
683
|
+
if (target instanceof IndexExpression) {
|
|
684
|
+
return this.resolveIndexPath(context, target);
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
593
688
|
assignTarget(context, target, value) {
|
|
594
689
|
if (!context.scope || !context.scope.setPath) {
|
|
595
690
|
return;
|
|
@@ -1460,11 +1555,46 @@ var Parser = class _Parser {
|
|
|
1460
1555
|
this.stream.skipWhitespace();
|
|
1461
1556
|
alias = this.stream.expect("Identifier" /* Identifier */).value;
|
|
1462
1557
|
}
|
|
1558
|
+
const { flags, flagArgs } = this.parseUseFlags();
|
|
1463
1559
|
this.stream.skipWhitespace();
|
|
1464
1560
|
this.stream.expect("Semicolon" /* Semicolon */);
|
|
1465
|
-
return new UseNode(name, alias);
|
|
1561
|
+
return new UseNode(name, alias, flags, flagArgs);
|
|
1466
1562
|
});
|
|
1467
1563
|
}
|
|
1564
|
+
parseUseFlags() {
|
|
1565
|
+
const flags = {};
|
|
1566
|
+
const flagArgs = {};
|
|
1567
|
+
while (true) {
|
|
1568
|
+
this.stream.skipWhitespace();
|
|
1569
|
+
if (this.stream.peek()?.type !== "Bang" /* Bang */) {
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
this.stream.next();
|
|
1573
|
+
const name = this.stream.expect("Identifier" /* Identifier */).value;
|
|
1574
|
+
if (name !== "wait") {
|
|
1575
|
+
throw new Error(`Unknown flag ${name}`);
|
|
1576
|
+
}
|
|
1577
|
+
flags.wait = true;
|
|
1578
|
+
if (this.stream.peek()?.type === "LParen" /* LParen */) {
|
|
1579
|
+
this.stream.next();
|
|
1580
|
+
this.stream.skipWhitespace();
|
|
1581
|
+
const timeoutToken = this.stream.expect("Number" /* Number */);
|
|
1582
|
+
const timeoutMs = Number(timeoutToken.value);
|
|
1583
|
+
let intervalMs;
|
|
1584
|
+
this.stream.skipWhitespace();
|
|
1585
|
+
if (this.stream.peek()?.type === "Comma" /* Comma */) {
|
|
1586
|
+
this.stream.next();
|
|
1587
|
+
this.stream.skipWhitespace();
|
|
1588
|
+
const intervalToken = this.stream.expect("Number" /* Number */);
|
|
1589
|
+
intervalMs = Number(intervalToken.value);
|
|
1590
|
+
this.stream.skipWhitespace();
|
|
1591
|
+
}
|
|
1592
|
+
this.stream.expect("RParen" /* RParen */);
|
|
1593
|
+
flagArgs.wait = { timeoutMs, ...intervalMs !== void 0 ? { intervalMs } : {} };
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return { flags, flagArgs };
|
|
1597
|
+
}
|
|
1468
1598
|
wrapErrors(fn) {
|
|
1469
1599
|
try {
|
|
1470
1600
|
return fn();
|
|
@@ -1695,11 +1825,11 @@ ${caret}`;
|
|
|
1695
1825
|
parseAssignment() {
|
|
1696
1826
|
const target = this.parseAssignmentTarget();
|
|
1697
1827
|
this.stream.skipWhitespace();
|
|
1698
|
-
this.
|
|
1828
|
+
const operator = this.parseAssignmentOperator();
|
|
1699
1829
|
this.stream.skipWhitespace();
|
|
1700
1830
|
const value = this.parseExpression();
|
|
1701
1831
|
this.consumeStatementTerminator();
|
|
1702
|
-
return new AssignmentNode(target, value);
|
|
1832
|
+
return new AssignmentNode(target, value, operator);
|
|
1703
1833
|
}
|
|
1704
1834
|
parseExpression() {
|
|
1705
1835
|
return this.parsePipeExpression();
|
|
@@ -2281,7 +2411,14 @@ ${caret}`;
|
|
|
2281
2411
|
return this.parseObjectPattern();
|
|
2282
2412
|
}
|
|
2283
2413
|
if (token.type === "Identifier" /* Identifier */) {
|
|
2284
|
-
|
|
2414
|
+
const expr = this.parseCallExpression();
|
|
2415
|
+
if (expr instanceof CallExpression) {
|
|
2416
|
+
throw new Error("Invalid assignment target CallExpression");
|
|
2417
|
+
}
|
|
2418
|
+
if (expr instanceof IdentifierExpression || expr instanceof MemberExpression || expr instanceof IndexExpression) {
|
|
2419
|
+
return expr;
|
|
2420
|
+
}
|
|
2421
|
+
throw new Error("Invalid assignment target");
|
|
2285
2422
|
}
|
|
2286
2423
|
throw new Error(`Invalid assignment target ${token.type}`);
|
|
2287
2424
|
}
|
|
@@ -2560,12 +2697,31 @@ ${caret}`;
|
|
|
2560
2697
|
while (this.stream.peekNonWhitespace(index)?.type === "Dot" /* Dot */ && this.stream.peekNonWhitespace(index + 1)?.type === "Identifier" /* Identifier */) {
|
|
2561
2698
|
index += 2;
|
|
2562
2699
|
}
|
|
2563
|
-
|
|
2700
|
+
while (this.stream.peekNonWhitespace(index)?.type === "LBracket" /* LBracket */) {
|
|
2701
|
+
let depth = 0;
|
|
2702
|
+
while (true) {
|
|
2703
|
+
const token = this.stream.peekNonWhitespace(index);
|
|
2704
|
+
if (!token) {
|
|
2705
|
+
return false;
|
|
2706
|
+
}
|
|
2707
|
+
if (token.type === "LBracket" /* LBracket */) {
|
|
2708
|
+
depth += 1;
|
|
2709
|
+
} else if (token.type === "RBracket" /* RBracket */) {
|
|
2710
|
+
depth -= 1;
|
|
2711
|
+
if (depth === 0) {
|
|
2712
|
+
index += 1;
|
|
2713
|
+
break;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
index += 1;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
return this.isAssignmentOperatorStart(index);
|
|
2564
2720
|
}
|
|
2565
2721
|
if (first.type === "At" /* At */ || first.type === "Dollar" /* Dollar */) {
|
|
2566
2722
|
const second = this.stream.peekNonWhitespace(1);
|
|
2567
2723
|
const third = this.stream.peekNonWhitespace(2);
|
|
2568
|
-
return second?.type === "Identifier" /* Identifier */ &&
|
|
2724
|
+
return second?.type === "Identifier" /* Identifier */ && this.isAssignmentOperatorStart(2);
|
|
2569
2725
|
}
|
|
2570
2726
|
if (first.type === "LBrace" /* LBrace */ || first.type === "LBracket" /* LBracket */) {
|
|
2571
2727
|
const stack = [];
|
|
@@ -2580,7 +2736,7 @@ ${caret}`;
|
|
|
2580
2736
|
} else if (token.type === "RBrace" /* RBrace */ || token.type === "RBracket" /* RBracket */) {
|
|
2581
2737
|
stack.pop();
|
|
2582
2738
|
if (stack.length === 0) {
|
|
2583
|
-
return this.
|
|
2739
|
+
return this.isAssignmentOperatorStart(index + 1);
|
|
2584
2740
|
}
|
|
2585
2741
|
}
|
|
2586
2742
|
index += 1;
|
|
@@ -2588,6 +2744,20 @@ ${caret}`;
|
|
|
2588
2744
|
}
|
|
2589
2745
|
return false;
|
|
2590
2746
|
}
|
|
2747
|
+
isAssignmentOperatorStart(index) {
|
|
2748
|
+
const token = this.stream.peekNonWhitespace(index);
|
|
2749
|
+
if (!token) {
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
if (token.type === "Equals" /* Equals */) {
|
|
2753
|
+
return true;
|
|
2754
|
+
}
|
|
2755
|
+
if (token.type === "Plus" /* Plus */ || token.type === "Minus" /* Minus */ || token.type === "Star" /* Star */ || token.type === "Slash" /* Slash */) {
|
|
2756
|
+
const next = this.stream.peekNonWhitespace(index + 1);
|
|
2757
|
+
return next?.type === "Equals" /* Equals */;
|
|
2758
|
+
}
|
|
2759
|
+
return false;
|
|
2760
|
+
}
|
|
2591
2761
|
isCallStart() {
|
|
2592
2762
|
const first = this.stream.peekNonWhitespace(0);
|
|
2593
2763
|
if (!first || first.type !== "Identifier" /* Identifier */) {
|
|
@@ -2812,10 +2982,35 @@ ${caret}`;
|
|
|
2812
2982
|
parseAssignmentExpression() {
|
|
2813
2983
|
const target = this.parseAssignmentTarget();
|
|
2814
2984
|
this.stream.skipWhitespace();
|
|
2815
|
-
this.
|
|
2985
|
+
const operator = this.parseAssignmentOperator();
|
|
2816
2986
|
this.stream.skipWhitespace();
|
|
2817
2987
|
const value = this.parseExpression();
|
|
2818
|
-
return new AssignmentNode(target, value);
|
|
2988
|
+
return new AssignmentNode(target, value, operator);
|
|
2989
|
+
}
|
|
2990
|
+
parseAssignmentOperator() {
|
|
2991
|
+
const next = this.stream.peek();
|
|
2992
|
+
if (!next) {
|
|
2993
|
+
throw new Error("Expected assignment operator");
|
|
2994
|
+
}
|
|
2995
|
+
if (next.type === "Equals" /* Equals */) {
|
|
2996
|
+
this.stream.next();
|
|
2997
|
+
return "=";
|
|
2998
|
+
}
|
|
2999
|
+
if (next.type === "Plus" /* Plus */ || next.type === "Minus" /* Minus */ || next.type === "Star" /* Star */ || next.type === "Slash" /* Slash */) {
|
|
3000
|
+
const op = this.stream.next();
|
|
3001
|
+
this.stream.expect("Equals" /* Equals */);
|
|
3002
|
+
if (op.type === "Plus" /* Plus */) {
|
|
3003
|
+
return "+=";
|
|
3004
|
+
}
|
|
3005
|
+
if (op.type === "Minus" /* Minus */) {
|
|
3006
|
+
return "-=";
|
|
3007
|
+
}
|
|
3008
|
+
if (op.type === "Star" /* Star */) {
|
|
3009
|
+
return "*=";
|
|
3010
|
+
}
|
|
3011
|
+
return "/=";
|
|
3012
|
+
}
|
|
3013
|
+
throw new Error("Expected assignment operator");
|
|
2819
3014
|
}
|
|
2820
3015
|
parseTryBlock() {
|
|
2821
3016
|
this.stream.expect("Try" /* Try */);
|
|
@@ -3318,6 +3513,7 @@ var Engine = class _Engine {
|
|
|
3318
3513
|
ignoredAdded = /* @__PURE__ */ new WeakMap();
|
|
3319
3514
|
diagnostics;
|
|
3320
3515
|
logger;
|
|
3516
|
+
pendingUses = [];
|
|
3321
3517
|
constructor(options = {}) {
|
|
3322
3518
|
this.diagnostics = options.diagnostics ?? false;
|
|
3323
3519
|
this.logger = options.logger ?? console;
|
|
@@ -3387,6 +3583,10 @@ var Engine = class _Engine {
|
|
|
3387
3583
|
registerBehaviors(source) {
|
|
3388
3584
|
const program = new Parser(source, { customFlags: new Set(this.flagHandlers.keys()) }).parseProgram();
|
|
3389
3585
|
for (const use of program.uses) {
|
|
3586
|
+
if (use.flags?.wait) {
|
|
3587
|
+
this.pendingUses.push(this.waitForUseGlobal(use));
|
|
3588
|
+
continue;
|
|
3589
|
+
}
|
|
3390
3590
|
const value = this.resolveGlobalPath(use.name);
|
|
3391
3591
|
if (value === void 0) {
|
|
3392
3592
|
console.warn(`vsn: global '${use.name}' not found`);
|
|
@@ -3436,6 +3636,52 @@ var Engine = class _Engine {
|
|
|
3436
3636
|
}
|
|
3437
3637
|
return value;
|
|
3438
3638
|
}
|
|
3639
|
+
async waitForUses() {
|
|
3640
|
+
while (this.pendingUses.length > 0) {
|
|
3641
|
+
const pending = this.pendingUses;
|
|
3642
|
+
this.pendingUses = [];
|
|
3643
|
+
await Promise.all(pending);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
waitForUseGlobal(use) {
|
|
3647
|
+
const config = use.flagArgs?.wait ?? {};
|
|
3648
|
+
const timeoutMs = config.timeoutMs ?? 1e4;
|
|
3649
|
+
const initialDelayMs = config.intervalMs ?? 100;
|
|
3650
|
+
const maxDelayMs = 1e3;
|
|
3651
|
+
const existing = this.resolveGlobalPath(use.name);
|
|
3652
|
+
if (existing !== void 0) {
|
|
3653
|
+
this.registerGlobal(use.alias, existing);
|
|
3654
|
+
return Promise.resolve();
|
|
3655
|
+
}
|
|
3656
|
+
if (timeoutMs <= 0) {
|
|
3657
|
+
this.emitUseError(use.name, new Error(`vsn: global '${use.name}' not found`));
|
|
3658
|
+
return Promise.resolve();
|
|
3659
|
+
}
|
|
3660
|
+
return new Promise((resolve) => {
|
|
3661
|
+
let elapsedMs = 0;
|
|
3662
|
+
let delayMs = initialDelayMs;
|
|
3663
|
+
const check = () => {
|
|
3664
|
+
const value = this.resolveGlobalPath(use.name);
|
|
3665
|
+
if (value !== void 0) {
|
|
3666
|
+
this.registerGlobal(use.alias, value);
|
|
3667
|
+
resolve();
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
if (elapsedMs >= timeoutMs) {
|
|
3671
|
+
this.emitUseError(use.name, new Error(`vsn: global '${use.name}' not found`));
|
|
3672
|
+
resolve();
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3675
|
+
const scheduledDelay = Math.min(delayMs, timeoutMs - elapsedMs);
|
|
3676
|
+
setTimeout(() => {
|
|
3677
|
+
elapsedMs += scheduledDelay;
|
|
3678
|
+
delayMs = Math.min(delayMs * 2, maxDelayMs);
|
|
3679
|
+
check();
|
|
3680
|
+
}, scheduledDelay);
|
|
3681
|
+
};
|
|
3682
|
+
check();
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3439
3685
|
getScope(element, parentScope) {
|
|
3440
3686
|
const existing = this.scopes.get(element);
|
|
3441
3687
|
if (existing) {
|
|
@@ -3559,6 +3805,7 @@ var Engine = class _Engine {
|
|
|
3559
3805
|
}
|
|
3560
3806
|
}
|
|
3561
3807
|
async applyBehaviors(root) {
|
|
3808
|
+
await this.waitForUses();
|
|
3562
3809
|
if (this.behaviorRegistry.length === 0) {
|
|
3563
3810
|
return;
|
|
3564
3811
|
}
|
|
@@ -3966,6 +4213,19 @@ var Engine = class _Engine {
|
|
|
3966
4213
|
})
|
|
3967
4214
|
);
|
|
3968
4215
|
}
|
|
4216
|
+
emitUseError(name, error) {
|
|
4217
|
+
const selector = `use:${name}`;
|
|
4218
|
+
this.logger.warn?.("vsn:error", { error, selector });
|
|
4219
|
+
const target = globalThis.document;
|
|
4220
|
+
if (target && typeof target.dispatchEvent === "function") {
|
|
4221
|
+
target.dispatchEvent(
|
|
4222
|
+
new CustomEvent("vsn:error", {
|
|
4223
|
+
detail: { error, selector },
|
|
4224
|
+
bubbles: true
|
|
4225
|
+
})
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
3969
4229
|
attachOnHandler(element, config) {
|
|
3970
4230
|
const options = this.getListenerOptions(config.modifiers);
|
|
3971
4231
|
const listenerTarget = config.modifiers?.includes("outside") ? element.ownerDocument : element;
|