@braidlabs/gateway 0.7.1 → 0.8.1

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,56 @@
1
+ import type { FragmentManifest } from './registry.js';
2
+ /**
3
+ * Speculation Rules (Chromium) built from the registry: the pages a fragment pierces, for the
4
+ * fragments that asked the browser to fetch or render them before a click.
5
+ *
6
+ * Nothing here is stored or configured beyond the manifests. The gateway already knows which URL
7
+ * patterns compose which fragment; the browser is told the same patterns, in the format it reads.
8
+ */
9
+ export interface SpeculationRule {
10
+ source: 'document';
11
+ where: {
12
+ and: [
13
+ {
14
+ or: {
15
+ href_matches: {
16
+ pathname: string;
17
+ };
18
+ relative_to: 'document';
19
+ }[];
20
+ },
21
+ {
22
+ not: {
23
+ selector_matches: string;
24
+ };
25
+ }
26
+ ];
27
+ };
28
+ eagerness: 'moderate';
29
+ }
30
+ /**
31
+ * Links never speculated, whatever they point at: ones the page marks as not to be followed (as
32
+ * user-generated content often is), and ones it opts out by name.
33
+ */
34
+ export declare const SPECULATION_EXCLUDED_LINKS = "[rel~=\"nofollow\"], [data-braid-no-speculate]";
35
+ export interface SpeculationRules {
36
+ prefetch?: SpeculationRule[];
37
+ prerender?: SpeculationRule[];
38
+ }
39
+ /**
40
+ * The rule set for these manifests: every pierce pattern of a bound fragment that sets
41
+ * `speculate`.
42
+ *
43
+ * Two kinds are left out, because their patterns describe pages the fragment does not own:
44
+ *
45
+ * - **An unbound fragment** is chrome — a header, a notifications panel — pierced into pages
46
+ * that belong to someone else.
47
+ * - **A pattern that matches any path** (`/*`, `/:rest*`) would make every same-origin link on
48
+ * every page speculative, `/logout` included.
49
+ */
50
+ export declare function speculationRulesFor(manifests: readonly FragmentManifest[]): SpeculationRules;
51
+ /**
52
+ * Whether a pierce pattern covers every page of the site rather than some of them: anything that
53
+ * is not a literal first segment (`/*`, `/:page`, `/(.*)`), or that matches a path nobody wrote.
54
+ * The home page alone (`/`) is a page like any other. Registry analysis judges it the same way.
55
+ */
56
+ export declare function matchesAnyPath(pattern: string): boolean;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Links never speculated, whatever they point at: ones the page marks as not to be followed (as
3
+ * user-generated content often is), and ones it opts out by name.
4
+ */
5
+ export const SPECULATION_EXCLUDED_LINKS = '[rel~="nofollow"], [data-braid-no-speculate]';
6
+ /**
7
+ * The rule set for these manifests: every pierce pattern of a bound fragment that sets
8
+ * `speculate`.
9
+ *
10
+ * Two kinds are left out, because their patterns describe pages the fragment does not own:
11
+ *
12
+ * - **An unbound fragment** is chrome — a header, a notifications panel — pierced into pages
13
+ * that belong to someone else.
14
+ * - **A pattern that matches any path** (`/*`, `/:rest*`) would make every same-origin link on
15
+ * every page speculative, `/logout` included.
16
+ */
17
+ export function speculationRulesFor(manifests) {
18
+ const rules = {};
19
+ for (const action of ['prefetch', 'prerender']) {
20
+ const patterns = new Set();
21
+ for (const manifest of manifests) {
22
+ if (manifest.speculate !== action || manifest.bound === false)
23
+ continue;
24
+ for (const pattern of manifest.pierce ?? []) {
25
+ if (matchesAnyPath(pattern))
26
+ continue;
27
+ patterns.add(pattern);
28
+ const toggled = slashToggled(pattern);
29
+ if (toggled)
30
+ patterns.add(toggled);
31
+ }
32
+ }
33
+ if (patterns.size === 0)
34
+ continue;
35
+ rules[action] = [
36
+ {
37
+ source: 'document',
38
+ where: {
39
+ and: [
40
+ { or: [...patterns].map((pathname) => ({ href_matches: { pathname }, relative_to: 'document' })) },
41
+ { not: { selector_matches: SPECULATION_EXCLUDED_LINKS } },
42
+ ],
43
+ },
44
+ // on hover or pointerdown: early enough to feel instant, late enough not to fetch every link
45
+ eagerness: 'moderate',
46
+ },
47
+ ];
48
+ }
49
+ return rules;
50
+ }
51
+ /**
52
+ * Whether a pierce pattern covers every page of the site rather than some of them: anything that
53
+ * is not a literal first segment (`/*`, `/:page`, `/(.*)`), or that matches a path nobody wrote.
54
+ * The home page alone (`/`) is a page like any other. Registry analysis judges it the same way.
55
+ */
56
+ export function matchesAnyPath(pattern) {
57
+ if (pattern === '/')
58
+ return false;
59
+ if (!/^\/[^:*({?+\\]/.test(pattern))
60
+ return true;
61
+ try {
62
+ const compiled = new URLPattern({ pathname: pattern });
63
+ return ['/__braid_probe', '/__braid_probe/deeper'].some((pathname) => compiled.test({ pathname }));
64
+ }
65
+ catch {
66
+ return true;
67
+ }
68
+ }
69
+ /**
70
+ * The pattern that matches the trailing-slash twin of a URL this one matches.
71
+ *
72
+ * Pierce matching is slash-insensitive (`/checkout/*` also pierces `/checkout`), and a link the
73
+ * gateway would compose should be speculated too. URLPattern itself is strict, so the twin is
74
+ * listed alongside.
75
+ */
76
+ function slashToggled(pattern) {
77
+ if (pattern === '/' || pattern === '/*')
78
+ return null;
79
+ if (pattern.endsWith('/*'))
80
+ return pattern.slice(0, -2);
81
+ if (pattern.endsWith('*'))
82
+ return null;
83
+ return pattern.endsWith('/') ? pattern.slice(0, -1) : `${pattern}/`;
84
+ }
85
+ //# sourceMappingURL=speculation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speculation.js","sourceRoot":"","sources":["../../../../libs/braid-gateway/src/speculation.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,8CAA8C,CAAC;AAOzF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAsC;IACxE,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,KAAK,MAAM,MAAM,IAAI,CAAC,UAAU,EAAE,WAAW,CAAU,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,QAAQ,CAAC,SAAS,KAAK,MAAM,IAAI,QAAQ,CAAC,KAAK,KAAK,KAAK;gBAAE,SAAS;YACxE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBAC5C,IAAI,cAAc,CAAC,OAAO,CAAC;oBAAE,SAAS;gBACtC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtC,IAAI,OAAO;oBAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,SAAS;QAClC,KAAK,CAAC,MAAM,CAAC,GAAG;YACd;gBACE,MAAM,EAAE,UAAU;gBAClB,KAAK,EAAE;oBACL,GAAG,EAAE;wBACH,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE;wBAClG,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,EAAE;qBAC1D;iBACF;gBACD,6FAA6F;gBAC7F,SAAS,EAAE,UAAU;aACtB;SACF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,gBAAgB,EAAE,uBAAuB,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACrG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACrD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;AACtE,CAAC"}