@nordcraft/ssr 1.0.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.
Files changed (104) hide show
  1. package/README.md +5 -0
  2. package/dist/ToddleApiService.d.ts +23 -0
  3. package/dist/ToddleApiService.js +54 -0
  4. package/dist/ToddleApiService.js.map +1 -0
  5. package/dist/ToddleRoute.d.ts +20 -0
  6. package/dist/ToddleRoute.js +53 -0
  7. package/dist/ToddleRoute.js.map +1 -0
  8. package/dist/components/utils.d.ts +8 -0
  9. package/dist/components/utils.js +43 -0
  10. package/dist/components/utils.js.map +1 -0
  11. package/dist/const.d.ts +1 -0
  12. package/dist/const.js +18 -0
  13. package/dist/const.js.map +1 -0
  14. package/dist/custom-code/codeRefs.d.ts +30 -0
  15. package/dist/custom-code/codeRefs.js +176 -0
  16. package/dist/custom-code/codeRefs.js.map +1 -0
  17. package/dist/rendering/api.d.ts +13 -0
  18. package/dist/rendering/api.js +2 -0
  19. package/dist/rendering/api.js.map +1 -0
  20. package/dist/rendering/attributes.d.ts +15 -0
  21. package/dist/rendering/attributes.js +76 -0
  22. package/dist/rendering/attributes.js.map +1 -0
  23. package/dist/rendering/components.d.ts +21 -0
  24. package/dist/rendering/components.js +382 -0
  25. package/dist/rendering/components.js.map +1 -0
  26. package/dist/rendering/cookies.d.ts +3 -0
  27. package/dist/rendering/cookies.js +6 -0
  28. package/dist/rendering/cookies.js.map +1 -0
  29. package/dist/rendering/equals.d.ts +1 -0
  30. package/dist/rendering/equals.js +8 -0
  31. package/dist/rendering/equals.js.map +1 -0
  32. package/dist/rendering/fonts.d.ts +6 -0
  33. package/dist/rendering/fonts.js +67 -0
  34. package/dist/rendering/fonts.js.map +1 -0
  35. package/dist/rendering/formulaContext.d.ts +38 -0
  36. package/dist/rendering/formulaContext.js +120 -0
  37. package/dist/rendering/formulaContext.js.map +1 -0
  38. package/dist/rendering/head.d.ts +28 -0
  39. package/dist/rendering/head.js +252 -0
  40. package/dist/rendering/head.js.map +1 -0
  41. package/dist/rendering/html.d.ts +12 -0
  42. package/dist/rendering/html.js +14 -0
  43. package/dist/rendering/html.js.map +1 -0
  44. package/dist/rendering/request.d.ts +2 -0
  45. package/dist/rendering/request.js +11 -0
  46. package/dist/rendering/request.js.map +1 -0
  47. package/dist/rendering/speculation.d.ts +9 -0
  48. package/dist/rendering/speculation.js +22 -0
  49. package/dist/rendering/speculation.js.map +1 -0
  50. package/dist/rendering/template.d.ts +10 -0
  51. package/dist/rendering/template.js +36 -0
  52. package/dist/rendering/template.js.map +1 -0
  53. package/dist/rendering/testData.d.ts +2 -0
  54. package/dist/rendering/testData.js +58 -0
  55. package/dist/rendering/testData.js.map +1 -0
  56. package/dist/routing/routing.d.ts +26 -0
  57. package/dist/routing/routing.js +90 -0
  58. package/dist/routing/routing.js.map +1 -0
  59. package/dist/ssr.types.d.ts +101 -0
  60. package/dist/ssr.types.js +2 -0
  61. package/dist/ssr.types.js.map +1 -0
  62. package/dist/utils/headers.d.ts +12 -0
  63. package/dist/utils/headers.js +22 -0
  64. package/dist/utils/headers.js.map +1 -0
  65. package/dist/utils/media.d.ts +22 -0
  66. package/dist/utils/media.js +34 -0
  67. package/dist/utils/media.js.map +1 -0
  68. package/dist/utils/nanoid.d.ts +1 -0
  69. package/dist/utils/nanoid.js +19 -0
  70. package/dist/utils/nanoid.js.map +1 -0
  71. package/dist/utils/tags.d.ts +22 -0
  72. package/dist/utils/tags.js +23 -0
  73. package/dist/utils/tags.js.map +1 -0
  74. package/package.json +22 -0
  75. package/src/ToddleApiService.ts +67 -0
  76. package/src/ToddleRoute.ts +70 -0
  77. package/src/components/utils.test.ts +90 -0
  78. package/src/components/utils.ts +77 -0
  79. package/src/const.ts +17 -0
  80. package/src/custom-code/codeRefs.ts +271 -0
  81. package/src/rendering/api.ts +21 -0
  82. package/src/rendering/attributes.ts +117 -0
  83. package/src/rendering/components.ts +579 -0
  84. package/src/rendering/cookies.ts +10 -0
  85. package/src/rendering/equals.ts +9 -0
  86. package/src/rendering/fonts.ts +83 -0
  87. package/src/rendering/formulaContext.test.ts +57 -0
  88. package/src/rendering/formulaContext.ts +188 -0
  89. package/src/rendering/head.ts +391 -0
  90. package/src/rendering/html.ts +33 -0
  91. package/src/rendering/request.ts +19 -0
  92. package/src/rendering/speculation.ts +21 -0
  93. package/src/rendering/template.test.ts +18 -0
  94. package/src/rendering/template.ts +63 -0
  95. package/src/rendering/testData.test.ts +186 -0
  96. package/src/rendering/testData.ts +69 -0
  97. package/src/routing/routing.test.ts +97 -0
  98. package/src/routing/routing.ts +152 -0
  99. package/src/ssr.types.ts +117 -0
  100. package/src/utils/headers.ts +23 -0
  101. package/src/utils/media.test.ts +130 -0
  102. package/src/utils/media.ts +46 -0
  103. package/src/utils/nanoid.ts +21 -0
  104. package/src/utils/tags.ts +26 -0
@@ -0,0 +1,117 @@
1
+ import type {
2
+ ApiBase,
3
+ RedirectStatusCode,
4
+ } from '@nordcraft/core/dist/api/apiTypes'
5
+ import type {
6
+ Component,
7
+ RouteDeclaration,
8
+ } from '@nordcraft/core/dist/component/component.types'
9
+ import type { Formula } from '@nordcraft/core/dist/formula/formula'
10
+ import type { PluginFormula } from '@nordcraft/core/dist/formula/formulaTypes'
11
+ import type { OldTheme, Theme } from '@nordcraft/core/dist/styling/theme'
12
+
13
+ export type FileGetter = (args: {
14
+ package?: string
15
+ name: string
16
+ type: keyof ProjectFiles
17
+ }) => Promise<
18
+ Component | PluginAction | PluginFormula<string> | Route | Theme | ApiService
19
+ >
20
+
21
+ export interface ToddleProject {
22
+ name: string
23
+ description?: string | null
24
+ short_id: string
25
+ id: string
26
+ emoji?: string | null
27
+ type: 'app' | 'package'
28
+ thumbnail?: { path: string } | null
29
+ }
30
+
31
+ export interface ProjectFiles {
32
+ // Partial to make sure we check for the existence of a Component
33
+ components: Partial<Record<string, Component>>
34
+ packages?: Record<string, InstalledPackage>
35
+ actions?: Record<string, PluginAction>
36
+ formulas?: Record<string, PluginFormula<string>>
37
+ routes?: Record<string, Route>
38
+ config?: {
39
+ theme: OldTheme
40
+ meta?: {
41
+ icon?: { formula: Formula }
42
+ robots?: { formula: Formula }
43
+ sitemap?: { formula: Formula }
44
+ manifest?: { formula: Formula }
45
+ serviceWorker?: { formula: Formula }
46
+ }
47
+ }
48
+ themes?: Record<string, Theme>
49
+ services?: Record<string, ApiService>
50
+ }
51
+
52
+ interface BaseApiService {
53
+ name: string // Should we deprecate this?
54
+ baseUrl?: Formula
55
+ docsUrl?: Formula
56
+ apiKey?: Formula
57
+ meta?: Record<string, unknown>
58
+ }
59
+
60
+ interface SupabaseApiService extends BaseApiService {
61
+ type: 'supabase'
62
+ meta?: {
63
+ projectUrl?: Formula
64
+ }
65
+ }
66
+
67
+ interface XanoApiService extends BaseApiService {
68
+ type: 'xano'
69
+ }
70
+
71
+ interface CustomApiService extends BaseApiService {
72
+ type: 'custom'
73
+ }
74
+
75
+ export type ApiService = SupabaseApiService | XanoApiService | CustomApiService
76
+
77
+ export type InstalledPackage = Pick<
78
+ ProjectFiles,
79
+ // we might want to add themes + config later
80
+ 'components' | 'actions' | 'formulas'
81
+ > & {
82
+ manifest: {
83
+ name: string
84
+ // commit represents the commit hash (version) of the package
85
+ commit: string
86
+ }
87
+ }
88
+
89
+ export interface PluginAction {
90
+ name: string
91
+ description?: string
92
+ version?: 2 | never
93
+ arguments: Array<{
94
+ name: string
95
+ formula: Formula
96
+ }>
97
+ variableArguments: boolean | null
98
+ handler: string
99
+ // exported indicates that an action is exported in a package
100
+ exported?: boolean
101
+ }
102
+
103
+ interface BaseRoute {
104
+ source: RouteDeclaration
105
+ destination: ApiBase
106
+ }
107
+
108
+ export interface RewriteRoute extends BaseRoute {
109
+ type: 'rewrite'
110
+ }
111
+
112
+ export interface RedirectRoute extends BaseRoute {
113
+ type: 'redirect'
114
+ status?: RedirectStatusCode
115
+ }
116
+
117
+ export type Route = RewriteRoute | RedirectRoute
@@ -0,0 +1,23 @@
1
+ import { PROXY_URL_HEADER } from '@nordcraft/core/dist/utils/url'
2
+
3
+ /**
4
+ * Omit the `cookie` header from a set of headers.
5
+ * This is useful when proxying requests for routes/proxied API requests
6
+ * to ensure cookies are not forwarded.
7
+ */
8
+ export const skipCookieHeader = (headers: Headers) => {
9
+ const newHeaders = new Headers(headers)
10
+ newHeaders.delete('cookie')
11
+ return newHeaders
12
+ }
13
+
14
+ /**
15
+ * Omit the x-toddle-url header from a set of headers.
16
+ * Since this header is only relevant for toddle requests, it's not useful
17
+ * for other services to receive this.
18
+ */
19
+ export const skipToddleHeader = (headers: Headers) => {
20
+ const newHeaders = new Headers(headers)
21
+ newHeaders.delete(PROXY_URL_HEADER)
22
+ return newHeaders
23
+ }
@@ -0,0 +1,130 @@
1
+ import { describe, expect, test } from '@jest/globals'
2
+ import { transformRelativePaths } from './media'
3
+
4
+ describe('transformRelativePaths()', () => {
5
+ const transformer = transformRelativePaths('https://toddle.dev')
6
+
7
+ test('it transforms relative src attributes to be absolute', () => {
8
+ expect(
9
+ transformer({
10
+ nodes: {
11
+ '1': {
12
+ type: 'element',
13
+ attrs: {
14
+ src: {
15
+ type: 'value',
16
+ value: '/foo/img.png',
17
+ },
18
+ },
19
+ },
20
+ '2': {
21
+ type: 'element',
22
+ attrs: {
23
+ src: {
24
+ type: 'value',
25
+ value: 'picture.webp',
26
+ },
27
+ },
28
+ },
29
+ '3': {
30
+ type: 'element',
31
+ attrs: {
32
+ src: {
33
+ type: 'value',
34
+ value: './img.png',
35
+ },
36
+ },
37
+ },
38
+ },
39
+ } as any),
40
+ ).toEqual({
41
+ nodes: {
42
+ '1': {
43
+ type: 'element',
44
+ attrs: {
45
+ src: {
46
+ type: 'value',
47
+ value: 'https://toddle.dev/foo/img.png',
48
+ },
49
+ },
50
+ },
51
+ '2': {
52
+ type: 'element',
53
+ attrs: {
54
+ src: {
55
+ type: 'value',
56
+ value: 'https://toddle.dev/picture.webp',
57
+ },
58
+ },
59
+ },
60
+ '3': {
61
+ type: 'element',
62
+ attrs: {
63
+ src: {
64
+ type: 'value',
65
+ value: 'https://toddle.dev/img.png',
66
+ },
67
+ },
68
+ },
69
+ },
70
+ })
71
+ })
72
+ test('it keeps absolute urls', () => {
73
+ expect(
74
+ transformer({
75
+ nodes: {
76
+ '1': {
77
+ type: 'element',
78
+ attrs: {
79
+ src: {
80
+ type: 'value',
81
+ value: 'https://picsum.photos/200/300',
82
+ },
83
+ },
84
+ },
85
+ },
86
+ } as any),
87
+ ).toEqual({
88
+ nodes: {
89
+ '1': {
90
+ type: 'element',
91
+ attrs: {
92
+ src: {
93
+ type: 'value',
94
+ value: 'https://picsum.photos/200/300',
95
+ },
96
+ },
97
+ },
98
+ },
99
+ })
100
+ })
101
+ test('it does not transform non-src attributes', () => {
102
+ expect(
103
+ transformer({
104
+ nodes: {
105
+ '1': {
106
+ type: 'element',
107
+ attrs: {
108
+ href: {
109
+ type: 'value',
110
+ value: '/foo',
111
+ },
112
+ },
113
+ },
114
+ },
115
+ } as any),
116
+ ).toEqual({
117
+ nodes: {
118
+ '1': {
119
+ type: 'element',
120
+ attrs: {
121
+ href: {
122
+ type: 'value',
123
+ value: '/foo',
124
+ },
125
+ },
126
+ },
127
+ },
128
+ })
129
+ })
130
+ })
@@ -0,0 +1,46 @@
1
+ import type {
2
+ Component,
3
+ NodeModel,
4
+ } from '@nordcraft/core/dist/component/component.types'
5
+
6
+ export const isCloudflareImagePath = (path?: string | null): path is string =>
7
+ typeof path === 'string' && path.startsWith('/cdn-cgi/imagedelivery/')
8
+
9
+ /**
10
+ * Make all relative 'src' paths in a component absolute
11
+ */
12
+ export const transformRelativePaths =
13
+ (urlOrigin: string) => (component: Component) => ({
14
+ ...component,
15
+ nodes: Object.entries(component.nodes).reduce((acc, [key, node]) => {
16
+ return {
17
+ ...acc,
18
+ [key]: {
19
+ ...node,
20
+ ...(node.type === 'element'
21
+ ? {
22
+ attrs: Object.entries(node.attrs).reduce(
23
+ (acc, [key, formula]) => {
24
+ if (
25
+ ['src'].includes(key) &&
26
+ formula?.type === 'value' &&
27
+ typeof formula.value === 'string'
28
+ ) {
29
+ return {
30
+ ...acc,
31
+ [key]: {
32
+ ...formula,
33
+ value: new URL(formula.value, urlOrigin).href,
34
+ },
35
+ }
36
+ }
37
+ return { ...acc, [key]: formula }
38
+ },
39
+ {},
40
+ ),
41
+ }
42
+ : {}),
43
+ } as NodeModel,
44
+ }
45
+ }, {}),
46
+ })
@@ -0,0 +1,21 @@
1
+ // See https://github.com/ai/nanoid/blob/main/non-secure/index.js
2
+
3
+ // This alphabet uses `A-Za-z0-9_-` symbols.
4
+ // The order of characters is optimized for better gzip and brotli compression.
5
+ // References to the same file (works both for gzip and brotli):
6
+ // `'use`, `andom`, and `rict'`
7
+ // References to the brotli default dictionary:
8
+ // `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf`
9
+ const urlAlphabet =
10
+ 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
11
+
12
+ export const nanoid = (size = 21) => {
13
+ let id = ''
14
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
15
+ let i = size | 0
16
+ while (i--) {
17
+ // `| 0` is more compact and faster than `Math.floor()`.
18
+ id += urlAlphabet[(Math.random() * 64) | 0]
19
+ }
20
+ return id
21
+ }
@@ -0,0 +1,26 @@
1
+ import type { Component } from '@nordcraft/core/dist/component/component.types'
2
+
3
+ /**
4
+ * Used to replace any recursive references to the root component, as it would be rendered both by toddle runtime
5
+ * and by the custom element itself (which would cause an infinite loop)
6
+ */
7
+ export const replaceTagInNodes =
8
+ (oldTag: string, newTag: string) => (component: Component) => ({
9
+ ...component,
10
+ nodes: Object.entries(component.nodes).reduce((acc, [key, node]) => {
11
+ if (node.type !== 'element') {
12
+ return {
13
+ ...acc,
14
+ [key]: node,
15
+ }
16
+ }
17
+
18
+ return {
19
+ ...acc,
20
+ [key]: {
21
+ ...node,
22
+ tag: node.tag === oldTag ? newTag : node.tag,
23
+ },
24
+ }
25
+ }, {}),
26
+ })