@sneat/core 0.15.2 → 0.17.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.
@@ -14,6 +14,7 @@ export * from './sneat-extensions';
14
14
  export * from './form-field';
15
15
  export * from './team-type';
16
16
  export * from './location-href';
17
+ export * from './reserved-public-path';
17
18
  export * from './interfaces';
18
19
  export * from './current-space-storage';
19
20
  export * from './directives';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/core/src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC;AACrB,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC","sourcesContent":["export * from './animations';\nexport * from './app.service';\nexport * from './core-models';\nexport * from './nav';\nexport * from './store';\nexport * from './sneat-enum-keys';\nexport * from './services';\nexport * from './exclude-undefined';\nexport * from './logging';\nexport * from './utils';\nexport * from './eq';\nexport * from './types';\nexport * from './sneat-extensions';\nexport * from './form-field';\nexport * from './team-type';\nexport * from './location-href';\nexport * from './interfaces';\nexport * from './current-space-storage';\nexport * from './directives';\nexport * from './environment-config';\nexport * from './logging/interfaces';\nexport * from './analytics.interface';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/core/src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC;AACrB,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC","sourcesContent":["export * from './animations';\nexport * from './app.service';\nexport * from './core-models';\nexport * from './nav';\nexport * from './store';\nexport * from './sneat-enum-keys';\nexport * from './services';\nexport * from './exclude-undefined';\nexport * from './logging';\nexport * from './utils';\nexport * from './eq';\nexport * from './types';\nexport * from './sneat-extensions';\nexport * from './form-field';\nexport * from './team-type';\nexport * from './location-href';\nexport * from './reserved-public-path';\nexport * from './interfaces';\nexport * from './current-space-storage';\nexport * from './directives';\nexport * from './environment-config';\nexport * from './logging/interfaces';\nexport * from './analytics.interface';\n"]}
@@ -0,0 +1,84 @@
1
+ // Reserved public paths for the standard Sneat routing model: the Angular
2
+ // application is mounted at the ROOT (`/`), and a small, fixed set of paths is
3
+ // reserved for the public landing/content surface. The edge (Cloudflare Worker)
4
+ // uses this to split traffic:
5
+ //
6
+ // if (isReservedPublicPath(pathname)) return serveLanding(request)
7
+ // return serveApplication(request) // root-mounted Angular SPA
8
+ //
9
+ // See docs/extension-standards/routing-and-deployment.md for the full contract.
10
+ /**
11
+ * Locale codes whose `/{locale}/*` subtree is reserved for landing/content
12
+ * pages. Two-letter ISO-639-1 codes.
13
+ *
14
+ * This is an explicit ALLOW-LIST, not an "any two-letter segment" rule, and
15
+ * that distinction is load-bearing: `/my` (the personal-dashboard section) is
16
+ * also two letters. Matching "any two letters" would swallow `/my/assets` as if
17
+ * `my` were a locale. With an allow-list, unknown two-letter segments fall
18
+ * through to the application (fail-closed): `/my/assets` → app, `/en/about` →
19
+ * landing.
20
+ */
21
+ export const DEFAULT_RESERVED_LOCALES = ['en'];
22
+ /**
23
+ * Returns `true` when `pathname` belongs to the public landing/content surface
24
+ * rather than the root-mounted Angular application.
25
+ *
26
+ * Reserved:
27
+ * - `/` — root landing page
28
+ * - `/{locale}` and `/{locale}/*` — localized content/marketing/legal/docs/SEO
29
+ * (locale ∈ the allow-list; see {@link ReservedPublicPathOptions.locales})
30
+ * - `/static` and `/static/*` — shared static files
31
+ * - `/.well-known` and `/.well-known/*` — well-known files
32
+ * - `/robots.txt`
33
+ * - `/sitemap.xml`, `/sitemap-*.xml`, `/sitemap-index.xml`
34
+ * - `/favicon.ico`, `/favicon.svg`
35
+ * - `/manifest.webmanifest`
36
+ *
37
+ * Everything else (e.g. `/`, `/space/...`, `/my/assets`, `/event/abc`,
38
+ * `/u/alex`) is an application route.
39
+ *
40
+ * Query strings and fragments are ignored — pass a bare pathname (a full URL is
41
+ * tolerated and reduced to its pathname).
42
+ */
43
+ export function isReservedPublicPath(pathname, options) {
44
+ const locales = options?.locales ?? DEFAULT_RESERVED_LOCALES;
45
+ let p = pathname || '/';
46
+ // Tolerate a full URL or a path carrying a query/hash.
47
+ if (/^[a-z]+:\/\//i.test(p)) {
48
+ try {
49
+ p = new URL(p).pathname;
50
+ }
51
+ catch {
52
+ // fall through with the raw value
53
+ }
54
+ }
55
+ else {
56
+ const cut = p.search(/[?#]/);
57
+ if (cut >= 0)
58
+ p = p.slice(0, cut);
59
+ }
60
+ if (!p.startsWith('/'))
61
+ p = '/' + p;
62
+ // Normalize a trailing slash away (except for the bare root).
63
+ if (p.length > 1 && p.endsWith('/'))
64
+ p = p.replace(/\/+$/, '') || '/';
65
+ if (p === '/')
66
+ return true;
67
+ const firstSegment = p.split('/')[1];
68
+ if (locales.includes(firstSegment))
69
+ return true;
70
+ if (p === '/static' || p.startsWith('/static/'))
71
+ return true;
72
+ if (p === '/.well-known' || p.startsWith('/.well-known/'))
73
+ return true;
74
+ if (p === '/robots.txt')
75
+ return true;
76
+ if (/^\/sitemap[-\w]*\.xml$/.test(p))
77
+ return true;
78
+ if (p === '/favicon.ico' || p === '/favicon.svg')
79
+ return true;
80
+ if (p === '/manifest.webmanifest')
81
+ return true;
82
+ return false;
83
+ }
84
+ //# sourceMappingURL=reserved-public-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-public-path.js","sourceRoot":"","sources":["../../../../../libs/core/src/lib/reserved-public-path.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+EAA+E;AAC/E,gFAAgF;AAChF,8BAA8B;AAC9B,EAAE;AACF,qEAAqE;AACrE,mEAAmE;AACnE,EAAE;AACF,gFAAgF;AAEhF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB,CAAC,IAAI,CAAC,CAAC;AAWlE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAgB,EAChB,OAAmC;IAEnC,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,wBAAwB,CAAC;IAE7D,IAAI,CAAC,GAAG,QAAQ,IAAI,GAAG,CAAC;IACxB,uDAAuD;IACvD,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,GAAG,IAAI,CAAC;YAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IACpC,8DAA8D;IAC9D,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAEtE,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,CAAC,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAC9D,IAAI,CAAC,KAAK,uBAAuB;QAAE,OAAO,IAAI,CAAC;IAE/C,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// Reserved public paths for the standard Sneat routing model: the Angular\n// application is mounted at the ROOT (`/`), and a small, fixed set of paths is\n// reserved for the public landing/content surface. The edge (Cloudflare Worker)\n// uses this to split traffic:\n//\n// if (isReservedPublicPath(pathname)) return serveLanding(request)\n// return serveApplication(request) // root-mounted Angular SPA\n//\n// See docs/extension-standards/routing-and-deployment.md for the full contract.\n\n/**\n * Locale codes whose `/{locale}/*` subtree is reserved for landing/content\n * pages. Two-letter ISO-639-1 codes.\n *\n * This is an explicit ALLOW-LIST, not an \"any two-letter segment\" rule, and\n * that distinction is load-bearing: `/my` (the personal-dashboard section) is\n * also two letters. Matching \"any two letters\" would swallow `/my/assets` as if\n * `my` were a locale. With an allow-list, unknown two-letter segments fall\n * through to the application (fail-closed): `/my/assets` → app, `/en/about` →\n * landing.\n */\nexport const DEFAULT_RESERVED_LOCALES: readonly string[] = ['en'];\n\nexport interface ReservedPublicPathOptions {\n /**\n * Locale codes reserved for `/{locale}/*` landing content. Defaults to\n * {@link DEFAULT_RESERVED_LOCALES}. Pass the set your landing actually\n * publishes so unknown locales fail closed to the application.\n */\n locales?: readonly string[];\n}\n\n/**\n * Returns `true` when `pathname` belongs to the public landing/content surface\n * rather than the root-mounted Angular application.\n *\n * Reserved:\n * - `/` — root landing page\n * - `/{locale}` and `/{locale}/*` — localized content/marketing/legal/docs/SEO\n * (locale ∈ the allow-list; see {@link ReservedPublicPathOptions.locales})\n * - `/static` and `/static/*` — shared static files\n * - `/.well-known` and `/.well-known/*` — well-known files\n * - `/robots.txt`\n * - `/sitemap.xml`, `/sitemap-*.xml`, `/sitemap-index.xml`\n * - `/favicon.ico`, `/favicon.svg`\n * - `/manifest.webmanifest`\n *\n * Everything else (e.g. `/`, `/space/...`, `/my/assets`, `/event/abc`,\n * `/u/alex`) is an application route.\n *\n * Query strings and fragments are ignored — pass a bare pathname (a full URL is\n * tolerated and reduced to its pathname).\n */\nexport function isReservedPublicPath(\n pathname: string,\n options?: ReservedPublicPathOptions,\n): boolean {\n const locales = options?.locales ?? DEFAULT_RESERVED_LOCALES;\n\n let p = pathname || '/';\n // Tolerate a full URL or a path carrying a query/hash.\n if (/^[a-z]+:\\/\\//i.test(p)) {\n try {\n p = new URL(p).pathname;\n } catch {\n // fall through with the raw value\n }\n } else {\n const cut = p.search(/[?#]/);\n if (cut >= 0) p = p.slice(0, cut);\n }\n if (!p.startsWith('/')) p = '/' + p;\n // Normalize a trailing slash away (except for the bare root).\n if (p.length > 1 && p.endsWith('/')) p = p.replace(/\\/+$/, '') || '/';\n\n if (p === '/') return true;\n\n const firstSegment = p.split('/')[1];\n if (locales.includes(firstSegment)) return true;\n\n if (p === '/static' || p.startsWith('/static/')) return true;\n if (p === '/.well-known' || p.startsWith('/.well-known/')) return true;\n if (p === '/robots.txt') return true;\n if (/^\\/sitemap[-\\w]*\\.xml$/.test(p)) return true;\n if (p === '/favicon.ico' || p === '/favicon.svg') return true;\n if (p === '/manifest.webmanifest') return true;\n\n return false;\n}\n"]}
package/lib/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './sneat-extensions';
14
14
  export * from './form-field';
15
15
  export * from './team-type';
16
16
  export * from './location-href';
17
+ export * from './reserved-public-path';
17
18
  export * from './interfaces';
18
19
  export * from './current-space-storage';
19
20
  export * from './directives';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Locale codes whose `/{locale}/*` subtree is reserved for landing/content
3
+ * pages. Two-letter ISO-639-1 codes.
4
+ *
5
+ * This is an explicit ALLOW-LIST, not an "any two-letter segment" rule, and
6
+ * that distinction is load-bearing: `/my` (the personal-dashboard section) is
7
+ * also two letters. Matching "any two letters" would swallow `/my/assets` as if
8
+ * `my` were a locale. With an allow-list, unknown two-letter segments fall
9
+ * through to the application (fail-closed): `/my/assets` → app, `/en/about` →
10
+ * landing.
11
+ */
12
+ export declare const DEFAULT_RESERVED_LOCALES: readonly string[];
13
+ export interface ReservedPublicPathOptions {
14
+ /**
15
+ * Locale codes reserved for `/{locale}/*` landing content. Defaults to
16
+ * {@link DEFAULT_RESERVED_LOCALES}. Pass the set your landing actually
17
+ * publishes so unknown locales fail closed to the application.
18
+ */
19
+ locales?: readonly string[];
20
+ }
21
+ /**
22
+ * Returns `true` when `pathname` belongs to the public landing/content surface
23
+ * rather than the root-mounted Angular application.
24
+ *
25
+ * Reserved:
26
+ * - `/` — root landing page
27
+ * - `/{locale}` and `/{locale}/*` — localized content/marketing/legal/docs/SEO
28
+ * (locale ∈ the allow-list; see {@link ReservedPublicPathOptions.locales})
29
+ * - `/static` and `/static/*` — shared static files
30
+ * - `/.well-known` and `/.well-known/*` — well-known files
31
+ * - `/robots.txt`
32
+ * - `/sitemap.xml`, `/sitemap-*.xml`, `/sitemap-index.xml`
33
+ * - `/favicon.ico`, `/favicon.svg`
34
+ * - `/manifest.webmanifest`
35
+ *
36
+ * Everything else (e.g. `/`, `/space/...`, `/my/assets`, `/event/abc`,
37
+ * `/u/alex`) is an application route.
38
+ *
39
+ * Query strings and fragments are ignored — pass a bare pathname (a full URL is
40
+ * tolerated and reduced to its pathname).
41
+ */
42
+ export declare function isReservedPublicPath(pathname: string, options?: ReservedPublicPathOptions): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/core",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },