waygraph 0.7.15 → 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.
- package/README.md +90 -52
- package/dist/auto-explore-run.js +13 -7
- package/dist/auto-explore.js +47 -36
- package/dist/cli.d.ts +8 -12
- package/dist/cli.js +229 -249
- package/dist/engine.d.ts +77 -7
- package/dist/engine.js +108 -7
- package/dist/graph.d.ts +2 -1
- package/dist/graph.js +51 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/README.md +19 -5
- package/examples/saucedemo/docs/CONVENTION.md +22 -18
- package/examples/saucedemo/docs/HELPERS.md +186 -0
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/package.json +11 -5
- package/examples/saucedemo/scripts/capture-wrong-login.mjs +1 -1
- package/examples/saucedemo/scripts/drive-auto-cli.mjs +93 -0
- package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +1 -1
- package/examples/saucedemo/scripts/show-auto-menu.mjs +72 -0
- package/examples/saucedemo/src/blocks/SITE-MAP.md +11 -8
- package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
- package/{templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
- package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
- package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +4 -4
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
- package/{templates/quickstart/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
- package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -0
- package/examples/saucedemo/src/flows/cart-mem.flow.ts +1 -1
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +4 -4
- package/examples/saucedemo/src/flows/checkout.flow.ts +4 -4
- package/examples/saucedemo/src/flows/login.flow.ts +1 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +3 -3
- package/examples/saucedemo/src/flows/smoke.flow.ts +2 -2
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
- package/examples/saucedemo/tests/flow-block-verify.spec.ts +1 -1
- package/examples/saucedemo/tests/submit-login-branch.spec.ts +1 -1
- package/package.json +1 -1
- package/templates/quickstart/README-saucedemo-commands.md +72 -0
- package/templates/quickstart/docs/CONVENTION.md +5 -5
- package/templates/quickstart/docs/HELPERS.md +186 -0
- package/templates/quickstart/package.json +9 -5
- package/templates/quickstart/scripts/drive-auto-cli.mjs +93 -0
- package/templates/quickstart/src/blocks/SITE-MAP.md +11 -8
- package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
- package/{examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
- package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
- package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
- package/templates/quickstart/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
- package/{examples/saucedemo/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
- package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -0
- package/templates/quickstart/src/flows/cart-mem.flow.ts +1 -1
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +4 -4
- package/templates/quickstart/src/flows/checkout.flow.ts +4 -4
- package/templates/quickstart/src/flows/login.flow.ts +1 -1
- package/templates/quickstart/src/flows/shop.flow.ts +3 -3
- package/templates/quickstart/src/flows/smoke.flow.ts +2 -2
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
- package/templates/scaffold/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -536,9 +536,79 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
536
536
|
* hand.
|
|
537
537
|
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
538
538
|
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
539
|
-
* @example
|
|
539
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
540
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
540
541
|
*/
|
|
541
542
|
export declare function defineNavBlock<Out extends Checkpoint<string>>(options: NavBlockOptions<Out>): NavBlock<Out>;
|
|
543
|
+
/**
|
|
544
|
+
* Click-only {@link NavBlock}. Same runtime as {@link defineNavBlock} with
|
|
545
|
+
* `click` - use this in app code so you never wonder whether a Nav is url or
|
|
546
|
+
* click (no `__waygraphNavClick` sniffing at author time). Prefer
|
|
547
|
+
* {@link defineNavBlock} only for `url` / `goto` deep links.
|
|
548
|
+
*/
|
|
549
|
+
export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
550
|
+
name: string;
|
|
551
|
+
description?: string;
|
|
552
|
+
checkpoint: Out["__state"];
|
|
553
|
+
click: string | ((mem: MemPage) => string);
|
|
554
|
+
requires?: readonly MemKey<any>[];
|
|
555
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
556
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
557
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
558
|
+
};
|
|
559
|
+
export type NavClickBlock<Out extends Checkpoint<string>> = NavBlock<Out>;
|
|
560
|
+
/** @see {@link NavClickBlockOptions} */
|
|
561
|
+
export declare function defineNavClickBlock<Out extends Checkpoint<string>>(options: NavClickBlockOptions<Out>): NavClickBlock<Out>;
|
|
562
|
+
/**
|
|
563
|
+
* TypeScript salt over {@link defineBlock} for one-shot non-nav app steps
|
|
564
|
+
* (submit, upload, finish, logout) - **methods** on a page. Same runtime Block;
|
|
565
|
+
* marks `__waygraphSalt = "method"` so auto/docs can tell Method from bare
|
|
566
|
+
* {@link defineBlock}. Does not force `instanceOptions` - use
|
|
567
|
+
* {@link defineEffectBlock} when the auto menu should spawn one row per live
|
|
568
|
+
* DOM instance. Prefer hanging methods off a {@link definePageBlock} hub.
|
|
569
|
+
*/
|
|
570
|
+
export type MethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = DefinedBlock<In, Out>;
|
|
571
|
+
/** @deprecated Prefer {@link MethodBlock} - same type; Action was the 0.7 name. */
|
|
572
|
+
export type ActionBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = MethodBlock<In, Out>;
|
|
573
|
+
export declare function defineMethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: Parameters<typeof defineBlock<In, Out>>[0]): MethodBlock<In, Out>;
|
|
574
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
575
|
+
export declare const defineActionBlock: typeof defineMethodBlock;
|
|
576
|
+
/** One Block (or lazy factory) registered on a {@link PageBlock}. */
|
|
577
|
+
export type PageMethodEntry = DefinedBlock<any, any> | (() => DefinedBlock<any, any>);
|
|
578
|
+
export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
579
|
+
name: string;
|
|
580
|
+
description?: string;
|
|
581
|
+
/** Checkpoint tag for this screen (hub). */
|
|
582
|
+
checkpoint: Out["__state"];
|
|
583
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
584
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
585
|
+
requires?: readonly MemKey<any>[];
|
|
586
|
+
/**
|
|
587
|
+
* Methods available on this page. Array, or a record of Blocks / `() => Block`
|
|
588
|
+
* factories (lazy so the page object can colocate method definitions).
|
|
589
|
+
*/
|
|
590
|
+
methods?: readonly PageMethodEntry[] | Record<string, PageMethodEntry>;
|
|
591
|
+
/**
|
|
592
|
+
* Optional deep-link onto this page (`goto`). XOR with `click`. Omit when the
|
|
593
|
+
* hub is arrival-only (previous Block already resolved here).
|
|
594
|
+
*/
|
|
595
|
+
url?: string | ((mem: MemPage) => string);
|
|
596
|
+
/** Optional click-nav onto this page. XOR with `url`. */
|
|
597
|
+
click?: string | ((mem: MemPage) => string);
|
|
598
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
601
|
+
* Page hub Block: one Checkpoint = this screen; methods hang off the hub for
|
|
602
|
+
* readability / auto grouping. Runtime is still a Block (`__waygraphKind = "page"`).
|
|
603
|
+
* `act` is goto/click when `url`/`click` is set, otherwise a no-op (already here).
|
|
604
|
+
* Child methods stay ordinary Blocks for graph edges - the page only registers them.
|
|
605
|
+
*/
|
|
606
|
+
export type PageBlock<Out extends Checkpoint<string>> = NavBlock<Out> & {
|
|
607
|
+
/** Resolved method Blocks registered at define time. */
|
|
608
|
+
readonly methods: readonly DefinedBlock<any, any>[];
|
|
609
|
+
};
|
|
610
|
+
/** @see {@link PageBlockOptions} */
|
|
611
|
+
export declare function definePageBlock<Out extends Checkpoint<string>>(options: PageBlockOptions<Out>): PageBlock<Out>;
|
|
542
612
|
/**
|
|
543
613
|
* TypeScript salt over {@link defineBlock}: same runtime Block, but the type
|
|
544
614
|
* forces `requires` + `instanceOptions` so `waygraph auto` can list one menu
|
|
@@ -582,14 +652,14 @@ export declare function defineMemNavBlock<Out extends Checkpoint<string>>(option
|
|
|
582
652
|
instanceOptions: NonNullable<Block<Checkpoint<string>, Out>["instanceOptions"]>;
|
|
583
653
|
}): MemNavBlock<Out>;
|
|
584
654
|
/**
|
|
585
|
-
* "Where am I" - reverse-matches a live `page` against every NavBlock
|
|
586
|
-
* `library`, in order, returning the first one whose own `verify`
|
|
587
|
-
* pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
588
|
-
* only:
|
|
589
|
-
* whose whole job is already "confirm arrival here," so it's a reliable
|
|
655
|
+
* "Where am I" - reverse-matches a live `page` against every NavBlock /
|
|
656
|
+
* PageBlock in `library`, in order, returning the first one whose own `verify`
|
|
657
|
+
* Traits all pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
658
|
+
* Nav/Page hubs only: each has exactly one fixed `checkpoint` and a `verify`
|
|
659
|
+
* list whose whole job is already "confirm arrival here," so it's a reliable
|
|
590
660
|
* fingerprint - a regular `defineBlock` can branch to different `Out` tags
|
|
591
661
|
* depending on runtime evidence, so its `verify` alone doesn't identify a
|
|
592
|
-
* single state the way a
|
|
662
|
+
* single state the way a hub's does.
|
|
593
663
|
*
|
|
594
664
|
* The primitive `waygraph auto`'s discovered graph is *for* - the graph says
|
|
595
665
|
* what pages exist and how to reach them; `locate()` is how an autonomous
|
package/dist/engine.js
CHANGED
|
@@ -634,7 +634,8 @@ export function defineBlock(base) {
|
|
|
634
634
|
* hand.
|
|
635
635
|
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
636
636
|
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
637
|
-
* @example
|
|
637
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
638
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
638
639
|
*/
|
|
639
640
|
export function defineNavBlock(options) {
|
|
640
641
|
const built = defineBlock({
|
|
@@ -682,6 +683,101 @@ export function defineNavBlock(options) {
|
|
|
682
683
|
}
|
|
683
684
|
return built;
|
|
684
685
|
}
|
|
686
|
+
/** @see {@link NavClickBlockOptions} */
|
|
687
|
+
export function defineNavClickBlock(options) {
|
|
688
|
+
return defineNavBlock({
|
|
689
|
+
name: options.name,
|
|
690
|
+
checkpoint: options.checkpoint,
|
|
691
|
+
click: options.click,
|
|
692
|
+
...(options.description ? { description: options.description } : {}),
|
|
693
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
694
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
695
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
696
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
export function defineMethodBlock(base) {
|
|
700
|
+
const built = defineBlock(base);
|
|
701
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
702
|
+
value: "method",
|
|
703
|
+
enumerable: false,
|
|
704
|
+
configurable: false,
|
|
705
|
+
});
|
|
706
|
+
return built;
|
|
707
|
+
}
|
|
708
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
709
|
+
export const defineActionBlock = defineMethodBlock;
|
|
710
|
+
function resolvePageMethods(methods) {
|
|
711
|
+
if (!methods)
|
|
712
|
+
return [];
|
|
713
|
+
const entries = Array.isArray(methods) ? methods : Object.values(methods);
|
|
714
|
+
return entries.map((entry) => (typeof entry === "function" ? entry() : entry));
|
|
715
|
+
}
|
|
716
|
+
/** @see {@link PageBlockOptions} */
|
|
717
|
+
export function definePageBlock(options) {
|
|
718
|
+
if (options.url !== undefined && options.click !== undefined) {
|
|
719
|
+
throw new Error(`definePageBlock("${options.name}"): pass url XOR click (or neither for arrival-only hub)`);
|
|
720
|
+
}
|
|
721
|
+
const methodList = resolvePageMethods(options.methods);
|
|
722
|
+
const built = defineBlock({
|
|
723
|
+
name: options.name,
|
|
724
|
+
...(options.description ? { description: options.description } : {}),
|
|
725
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
726
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
727
|
+
instruction: {
|
|
728
|
+
async act(page, _input, mem) {
|
|
729
|
+
if (options.url !== undefined) {
|
|
730
|
+
const url = typeof options.url === "function" ? options.url(mem) : options.url;
|
|
731
|
+
await page.goto(url);
|
|
732
|
+
}
|
|
733
|
+
else if (options.click !== undefined) {
|
|
734
|
+
const selector = typeof options.click === "function" ? options.click(mem) : options.click;
|
|
735
|
+
await page.locator(selector).click();
|
|
736
|
+
}
|
|
737
|
+
// else: arrival-only hub — previous Block already landed here
|
|
738
|
+
},
|
|
739
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
740
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
741
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
745
|
+
value: "page",
|
|
746
|
+
enumerable: false,
|
|
747
|
+
configurable: false,
|
|
748
|
+
});
|
|
749
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
750
|
+
value: "page",
|
|
751
|
+
enumerable: false,
|
|
752
|
+
configurable: false,
|
|
753
|
+
});
|
|
754
|
+
Object.defineProperty(built, "methods", {
|
|
755
|
+
value: Object.freeze([...methodList]),
|
|
756
|
+
enumerable: true,
|
|
757
|
+
configurable: false,
|
|
758
|
+
writable: false,
|
|
759
|
+
});
|
|
760
|
+
Object.defineProperty(built, "__waygraphMethods", {
|
|
761
|
+
value: methodList.map((m) => m.name),
|
|
762
|
+
enumerable: false,
|
|
763
|
+
configurable: false,
|
|
764
|
+
});
|
|
765
|
+
if (options.url !== undefined || options.click !== undefined) {
|
|
766
|
+
Object.defineProperty(built, "__waygraphPageDeepLink", {
|
|
767
|
+
value: true,
|
|
768
|
+
enumerable: false,
|
|
769
|
+
configurable: false,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (options.click !== undefined) {
|
|
773
|
+
Object.defineProperty(built, "__waygraphNavClick", {
|
|
774
|
+
value: options.click,
|
|
775
|
+
enumerable: false,
|
|
776
|
+
configurable: false,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return built;
|
|
780
|
+
}
|
|
685
781
|
/**
|
|
686
782
|
* {@link defineBlock} constrained for effect-style instance menus: `requires` +
|
|
687
783
|
* `instanceOptions` are mandatory. Prefer this when the Block adds / removes /
|
|
@@ -695,6 +791,11 @@ export function defineEffectBlock(base) {
|
|
|
695
791
|
enumerable: false,
|
|
696
792
|
configurable: false,
|
|
697
793
|
});
|
|
794
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
795
|
+
value: "effect",
|
|
796
|
+
enumerable: false,
|
|
797
|
+
configurable: false,
|
|
798
|
+
});
|
|
698
799
|
return built;
|
|
699
800
|
}
|
|
700
801
|
/** @deprecated Prefer {@link defineEffectBlock} - identical helper. */
|
|
@@ -715,14 +816,14 @@ export function defineMemNavBlock(options) {
|
|
|
715
816
|
return built;
|
|
716
817
|
}
|
|
717
818
|
/**
|
|
718
|
-
* "Where am I" - reverse-matches a live `page` against every NavBlock
|
|
719
|
-
* `library`, in order, returning the first one whose own `verify`
|
|
720
|
-
* pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
721
|
-
* only:
|
|
722
|
-
* whose whole job is already "confirm arrival here," so it's a reliable
|
|
819
|
+
* "Where am I" - reverse-matches a live `page` against every NavBlock /
|
|
820
|
+
* PageBlock in `library`, in order, returning the first one whose own `verify`
|
|
821
|
+
* Traits all pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
822
|
+
* Nav/Page hubs only: each has exactly one fixed `checkpoint` and a `verify`
|
|
823
|
+
* list whose whole job is already "confirm arrival here," so it's a reliable
|
|
723
824
|
* fingerprint - a regular `defineBlock` can branch to different `Out` tags
|
|
724
825
|
* depending on runtime evidence, so its `verify` alone doesn't identify a
|
|
725
|
-
* single state the way a
|
|
826
|
+
* single state the way a hub's does.
|
|
726
827
|
*
|
|
727
828
|
* The primitive `waygraph auto`'s discovered graph is *for* - the graph says
|
|
728
829
|
* what pages exist and how to reach them; `locate()` is how an autonomous
|
package/dist/graph.d.ts
CHANGED
|
@@ -48,7 +48,8 @@ export declare function toMermaid(graph: WaygraphGraph): string;
|
|
|
48
48
|
/**
|
|
49
49
|
* Blocks present on disk but never wired into a `.flow.ts` defineFlow array.
|
|
50
50
|
* Required before `chain auto` can build a path from the discovered graph -
|
|
51
|
-
* orphan Blocks are intentionally excluded from auto shorthand.
|
|
51
|
+
* orphan Blocks are intentionally excluded from auto shorthand. Arrival-only
|
|
52
|
+
* Page hubs and Blocks listed on a Page's `methods` are not orphans.
|
|
52
53
|
*/
|
|
53
54
|
export declare function findOrphanBlocks(projectDir: string): Promise<OrphanBlock[]>;
|
|
54
55
|
/**
|
package/dist/graph.js
CHANGED
|
@@ -77,7 +77,8 @@ function exportNameToBlockName(exportName) {
|
|
|
77
77
|
/**
|
|
78
78
|
* Blocks present on disk but never wired into a `.flow.ts` defineFlow array.
|
|
79
79
|
* Required before `chain auto` can build a path from the discovered graph -
|
|
80
|
-
* orphan Blocks are intentionally excluded from auto shorthand.
|
|
80
|
+
* orphan Blocks are intentionally excluded from auto shorthand. Arrival-only
|
|
81
|
+
* Page hubs and Blocks listed on a Page's `methods` are not orphans.
|
|
81
82
|
*/
|
|
82
83
|
export async function findOrphanBlocks(projectDir) {
|
|
83
84
|
const blockFiles = discoverBlocks(projectDir);
|
|
@@ -88,6 +89,27 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
88
89
|
referenced.add(ref);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
const pageMethodNames = new Set();
|
|
93
|
+
for (const file of blockFiles) {
|
|
94
|
+
let mod;
|
|
95
|
+
try {
|
|
96
|
+
mod = await importModule(file);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (const exported of Object.values(mod)) {
|
|
102
|
+
if (!exported || typeof exported !== "object")
|
|
103
|
+
continue;
|
|
104
|
+
if (exported.__waygraphKind !== "page")
|
|
105
|
+
continue;
|
|
106
|
+
const names = exported.__waygraphMethods;
|
|
107
|
+
if (Array.isArray(names)) {
|
|
108
|
+
for (const n of names)
|
|
109
|
+
pageMethodNames.add(n);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
91
113
|
const orphans = [];
|
|
92
114
|
for (const file of blockFiles) {
|
|
93
115
|
const relFile = relative(projectDir, file);
|
|
@@ -98,7 +120,11 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
98
120
|
const exportName = m[1];
|
|
99
121
|
if (referenced.has(exportName))
|
|
100
122
|
continue;
|
|
101
|
-
if (!/defineBlock|defineEffectBlock|defineMemEffectBlock|defineNavBlock|defineMemNavBlock/.test(src))
|
|
123
|
+
if (!/defineBlock|defineMethodBlock|defineActionBlock|definePageBlock|defineEffectBlock|defineMemEffectBlock|defineNavBlock|defineNavClickBlock|defineMemNavBlock/.test(src))
|
|
124
|
+
continue;
|
|
125
|
+
// Arrival-only Page hubs are never orphans (even if import fails without tsx).
|
|
126
|
+
const assignSlice = src.slice(m.index, m.index + exportName.length + 80);
|
|
127
|
+
if (/=\s*definePageBlock\b/.test(assignSlice))
|
|
102
128
|
continue;
|
|
103
129
|
let block = exportNameToBlockName(exportName);
|
|
104
130
|
try {
|
|
@@ -106,6 +132,13 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
106
132
|
const exported = mod[exportName];
|
|
107
133
|
if (isBlockLike(exported))
|
|
108
134
|
block = exported.name;
|
|
135
|
+
if (exported &&
|
|
136
|
+
typeof exported === "object" &&
|
|
137
|
+
exported.__waygraphKind === "page") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (pageMethodNames.has(block))
|
|
141
|
+
continue;
|
|
109
142
|
}
|
|
110
143
|
catch {
|
|
111
144
|
// import may fail in partial projects - keep derived name
|
|
@@ -155,7 +188,8 @@ function isBlockLike(val) {
|
|
|
155
188
|
}
|
|
156
189
|
/** Set by `defineNavBlock` (non-enumerable) - see src/engine.ts. */
|
|
157
190
|
function isNavBlockMarked(val) {
|
|
158
|
-
|
|
191
|
+
const kind = val.__waygraphKind;
|
|
192
|
+
return kind === "nav" || kind === "page";
|
|
159
193
|
}
|
|
160
194
|
/**
|
|
161
195
|
* Resolves `typeName` to every literal Checkpoint tag it can mean, within
|
|
@@ -242,9 +276,9 @@ function extractDefineBlockGenerics(src) {
|
|
|
242
276
|
// alternation so both shapes come out of the same two capture groups.
|
|
243
277
|
// Effect helpers are TypeScript salt over defineBlock - same In/Out edges.
|
|
244
278
|
const arg = String.raw `(Checkpoint<[^>]*>|[A-Za-z_$][\w]*)`;
|
|
245
|
-
const re = new RegExp(String.raw `define(?:Mem)?
|
|
246
|
-
// Matches: defineBlock | defineEffectBlock |
|
|
247
|
-
// (
|
|
279
|
+
const re = new RegExp(String.raw `define(?:(?:Mem)?Effect|Method|Action)?Block<\s*${arg}\s*,\s*${arg}\s*>`, "g");
|
|
280
|
+
// Matches: defineBlock | defineMethodBlock | defineActionBlock | defineEffectBlock |
|
|
281
|
+
// defineMemEffectBlock (Nav / NavClick / Page use runtime markers, not this regex.)
|
|
248
282
|
const calls = [];
|
|
249
283
|
let m;
|
|
250
284
|
while ((m = re.exec(src)) !== null) {
|
|
@@ -289,16 +323,22 @@ export async function discoverGraph(projectDir) {
|
|
|
289
323
|
const blockName = exported.name;
|
|
290
324
|
if (isNavBlockMarked(exported)) {
|
|
291
325
|
try {
|
|
292
|
-
// NavBlock
|
|
293
|
-
// always exactly `checkpoint(options.checkpoint)`.
|
|
326
|
+
// NavBlock / PageBlock resolve() ignores input - always checkpoint(tag).
|
|
294
327
|
const resolveFn = exported.instruction.resolve;
|
|
295
328
|
const resolved = await resolveFn(undefined);
|
|
296
329
|
const to = resolved.__state;
|
|
297
330
|
nodeTags.add(to);
|
|
298
|
-
|
|
331
|
+
// Arrival-only Page hubs register the Checkpoint node but do not emit
|
|
332
|
+
// a Start-here edge (no deep link). Methods own the action edges.
|
|
333
|
+
const isPage = exported.__waygraphKind === "page";
|
|
334
|
+
const deepLink = !isPage ||
|
|
335
|
+
exported.__waygraphPageDeepLink === true;
|
|
336
|
+
if (deepLink) {
|
|
337
|
+
edges.push({ block: blockName, file: relFile, from: "*", to, kind: "nav" });
|
|
338
|
+
}
|
|
299
339
|
}
|
|
300
340
|
catch {
|
|
301
|
-
skipped.push({ block: blockName, file: relFile, reason: "NavBlock
|
|
341
|
+
skipped.push({ block: blockName, file: relFile, reason: "NavBlock/PageBlock resolve() threw" });
|
|
302
342
|
}
|
|
303
343
|
continue;
|
|
304
344
|
}
|
|
@@ -307,7 +347,7 @@ export async function discoverGraph(projectDir) {
|
|
|
307
347
|
skipped.push({
|
|
308
348
|
block: blockName,
|
|
309
349
|
file: relFile,
|
|
310
|
-
reason: "no defineBlock/defineEffectBlock<In, Out> generic call found",
|
|
350
|
+
reason: "no defineBlock/defineMethodBlock/defineEffectBlock<In, Out> generic call found",
|
|
311
351
|
});
|
|
312
352
|
continue;
|
|
313
353
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInsta
|
|
|
2
2
|
export { connect, checkpoint } from "./types.js";
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
-
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
5
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
+
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
7
7
|
export type { ActionPage } from "./types.js";
|
|
8
8
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
9
9
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { connect, checkpoint } from "./types.js";
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
4
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
5
5
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
@@ -32,11 +32,25 @@ npx playwright install chromium
|
|
|
32
32
|
| Want | Command |
|
|
33
33
|
|------|---------|
|
|
34
34
|
| Headless suite | `npm test` |
|
|
35
|
-
| Interactive explore | `npm run auto` |
|
|
36
|
-
|
|
|
37
|
-
|
|
|
35
|
+
| Interactive explore (headed panel) | `npm run auto` |
|
|
36
|
+
| Interactive explore (terminal menu) | `npm run auto:cli` |
|
|
37
|
+
| Shop demo stepper | `npm run demo:step` |
|
|
38
|
+
| Shop demo autoplay | `npm run demo:autoplay` |
|
|
39
|
+
| **Cart bulk** demo (add-all → remove-all) | `npm run demo:bulk` |
|
|
40
|
+
| Cart bulk stepper | `npm run demo:bulk:step` |
|
|
38
41
|
| List flows | `npm run flows` |
|
|
39
42
|
|
|
43
|
+
**Demo vs auto vs run for cart bulk**
|
|
44
|
+
|
|
45
|
+
| | CLI | Needs `.flow.ts`? |
|
|
46
|
+
|--|-----|-------------------|
|
|
47
|
+
| **`run --blocks`** | `npm run run:bulk` — Block names + `--data` | No |
|
|
48
|
+
| **`demo --blocks`** | `npm run demo:bulk` — named flow + `--data` | Optional (`cartBulkFlow`) |
|
|
49
|
+
| **`auto`** | `npm run auto` / `auto:cli` | No — live menu |
|
|
50
|
+
| **`auto --blocks From To`** | path-find on graph | No — checkpoints only |
|
|
51
|
+
|
|
52
|
+
QA watch+record: `npm run demo:bulk:video` (`--auto-play-video`).
|
|
53
|
+
|
|
40
54
|
## Layout (short)
|
|
41
55
|
|
|
42
56
|
```text
|
|
@@ -45,10 +59,10 @@ src/blocks/
|
|
|
45
59
|
saucedemo-web/ # URL /
|
|
46
60
|
NAV.md
|
|
47
61
|
nav-login.block.ts
|
|
48
|
-
|
|
62
|
+
methods/ # submit-login, logout, …
|
|
49
63
|
inventory/ # URL /inventory.html
|
|
50
64
|
nav-item-detail.block.ts # MemNav
|
|
51
|
-
|
|
65
|
+
methods/*.effect.block.ts # Add / Remove
|
|
52
66
|
cart/ # URL /cart.html
|
|
53
67
|
checkout-step-one/
|
|
54
68
|
checkout-step-two/
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# saucedemo waygraph conventions
|
|
2
2
|
|
|
3
|
-
Atomic Blocks - one nav / effect /
|
|
3
|
+
Atomic Blocks - one nav / effect / method per real URL or on-page mutation so
|
|
4
4
|
`waygraph auto` can discover a real state graph (not a fat multi-page Block).
|
|
5
5
|
|
|
6
|
-
**TypeScript salt only.**
|
|
7
|
-
`
|
|
8
|
-
still a `Block` - there is no separate Effect or Nav engine.
|
|
6
|
+
**TypeScript salt only.** Helpers force a useful shape. At runtime everything is
|
|
7
|
+
still a `Block` - there is no separate Page / Effect / Nav engine.
|
|
9
8
|
|
|
10
9
|
**Multi-route consumers:** folder layout must mirror Next.js `app/` page routes -
|
|
11
10
|
see package docs `docs/consumer.html` (no invented `root/`, `landing/`, or
|
|
@@ -15,14 +14,18 @@ see package docs `docs/consumer.html` (no invented `root/`, `landing/`, or
|
|
|
15
14
|
|
|
16
15
|
| Kind | File | Helper | `page.goto`? |
|
|
17
16
|
|------|------|--------|--------------|
|
|
17
|
+
| Page | `*.page.block.ts` | `definePageBlock` | Optional deep-link; usually arrival-only hub |
|
|
18
18
|
| Nav | `nav-*.block.ts` | `defineNavBlock` / `defineMemNavBlock` | Yes (`url` XOR `click`) |
|
|
19
|
-
| Effect | `*.effect.block.ts` | `defineEffectBlock` | No - mutates a live instance
|
|
20
|
-
|
|
|
19
|
+
| Effect | `*.effect.block.ts` in `methods/` | `defineEffectBlock` | No - mutates a live instance via mem + `instanceOptions` |
|
|
20
|
+
| Method | `*.method.block.ts` in `methods/` | `defineMethodBlock` | No - forms, submit, logout, predefined queues |
|
|
21
21
|
|
|
22
|
-
Only an explicit `page.goto()` (or NavBlock `click`) counts as navigation -
|
|
23
|
-
|
|
22
|
+
Only an explicit `page.goto()` (or NavBlock `click`) counts as navigation - a
|
|
23
|
+
method/effect that fills a form or clicks a button stays non-nav even when the
|
|
24
24
|
app changes URL as a side effect of `#continue`.
|
|
25
25
|
|
|
26
|
+
**Selectors:** put DOM strings in `*Sel` next to the page (static + `(id) => …`).
|
|
27
|
+
Mem keys store values, not selectors.
|
|
28
|
+
|
|
26
29
|
## Layout
|
|
27
30
|
|
|
28
31
|
```text
|
|
@@ -31,15 +34,16 @@ src/blocks/
|
|
|
31
34
|
saucedemo-web/ # THIS FOLDER IS URL "/"
|
|
32
35
|
NAV.md
|
|
33
36
|
nav-login.block.ts
|
|
34
|
-
|
|
35
|
-
submit-login.
|
|
37
|
+
methods/
|
|
38
|
+
submit-login.method.block.ts
|
|
36
39
|
submit-login-for-flow.ts
|
|
37
|
-
submit-logout.
|
|
40
|
+
submit-logout.method.block.ts
|
|
38
41
|
inventory/ # /inventory.html
|
|
39
42
|
NAV.md
|
|
43
|
+
inventory.page.block.ts # Page hub + methods registry
|
|
40
44
|
nav-item-detail.block.ts
|
|
41
|
-
|
|
42
|
-
inventory-items.ts
|
|
45
|
+
methods/
|
|
46
|
+
inventory-items.ts # InventorySel + collectors
|
|
43
47
|
add-to-cart.effect.block.ts
|
|
44
48
|
remove-from-cart.effect.block.ts
|
|
45
49
|
inventory-item/ # /inventory-item.html
|
|
@@ -53,18 +57,18 @@ src/blocks/
|
|
|
53
57
|
nav-checkout-info.block.ts
|
|
54
58
|
checkout-step-one/ # /checkout-step-one.html
|
|
55
59
|
NAV.md
|
|
56
|
-
|
|
57
|
-
submit-checkout-info.
|
|
60
|
+
methods/
|
|
61
|
+
submit-checkout-info.method.block.ts
|
|
58
62
|
checkout-step-two/ # /checkout-step-two.html
|
|
59
63
|
NAV.md
|
|
60
|
-
|
|
61
|
-
finish-order.
|
|
64
|
+
methods/
|
|
65
|
+
finish-order.method.block.ts
|
|
62
66
|
src/states/
|
|
63
67
|
src/flows/
|
|
64
68
|
```
|
|
65
69
|
|
|
66
70
|
`waygraph check` should report zero warnings - every navigation goes through
|
|
67
|
-
a `defineNavBlock
|
|
71
|
+
a `defineNavBlock` / Page deep-link, nothing else calls `page.goto`.
|
|
68
72
|
|
|
69
73
|
`waygraph auto` should discover Checkpoints as nodes and these Blocks as
|
|
70
74
|
edges (including cycles via logout / continue-shopping / back-to-products).
|