@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.
- package/README.md +5 -0
- package/dist/ToddleApiService.d.ts +23 -0
- package/dist/ToddleApiService.js +54 -0
- package/dist/ToddleApiService.js.map +1 -0
- package/dist/ToddleRoute.d.ts +20 -0
- package/dist/ToddleRoute.js +53 -0
- package/dist/ToddleRoute.js.map +1 -0
- package/dist/components/utils.d.ts +8 -0
- package/dist/components/utils.js +43 -0
- package/dist/components/utils.js.map +1 -0
- package/dist/const.d.ts +1 -0
- package/dist/const.js +18 -0
- package/dist/const.js.map +1 -0
- package/dist/custom-code/codeRefs.d.ts +30 -0
- package/dist/custom-code/codeRefs.js +176 -0
- package/dist/custom-code/codeRefs.js.map +1 -0
- package/dist/rendering/api.d.ts +13 -0
- package/dist/rendering/api.js +2 -0
- package/dist/rendering/api.js.map +1 -0
- package/dist/rendering/attributes.d.ts +15 -0
- package/dist/rendering/attributes.js +76 -0
- package/dist/rendering/attributes.js.map +1 -0
- package/dist/rendering/components.d.ts +21 -0
- package/dist/rendering/components.js +382 -0
- package/dist/rendering/components.js.map +1 -0
- package/dist/rendering/cookies.d.ts +3 -0
- package/dist/rendering/cookies.js +6 -0
- package/dist/rendering/cookies.js.map +1 -0
- package/dist/rendering/equals.d.ts +1 -0
- package/dist/rendering/equals.js +8 -0
- package/dist/rendering/equals.js.map +1 -0
- package/dist/rendering/fonts.d.ts +6 -0
- package/dist/rendering/fonts.js +67 -0
- package/dist/rendering/fonts.js.map +1 -0
- package/dist/rendering/formulaContext.d.ts +38 -0
- package/dist/rendering/formulaContext.js +120 -0
- package/dist/rendering/formulaContext.js.map +1 -0
- package/dist/rendering/head.d.ts +28 -0
- package/dist/rendering/head.js +252 -0
- package/dist/rendering/head.js.map +1 -0
- package/dist/rendering/html.d.ts +12 -0
- package/dist/rendering/html.js +14 -0
- package/dist/rendering/html.js.map +1 -0
- package/dist/rendering/request.d.ts +2 -0
- package/dist/rendering/request.js +11 -0
- package/dist/rendering/request.js.map +1 -0
- package/dist/rendering/speculation.d.ts +9 -0
- package/dist/rendering/speculation.js +22 -0
- package/dist/rendering/speculation.js.map +1 -0
- package/dist/rendering/template.d.ts +10 -0
- package/dist/rendering/template.js +36 -0
- package/dist/rendering/template.js.map +1 -0
- package/dist/rendering/testData.d.ts +2 -0
- package/dist/rendering/testData.js +58 -0
- package/dist/rendering/testData.js.map +1 -0
- package/dist/routing/routing.d.ts +26 -0
- package/dist/routing/routing.js +90 -0
- package/dist/routing/routing.js.map +1 -0
- package/dist/ssr.types.d.ts +101 -0
- package/dist/ssr.types.js +2 -0
- package/dist/ssr.types.js.map +1 -0
- package/dist/utils/headers.d.ts +12 -0
- package/dist/utils/headers.js +22 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/media.d.ts +22 -0
- package/dist/utils/media.js +34 -0
- package/dist/utils/media.js.map +1 -0
- package/dist/utils/nanoid.d.ts +1 -0
- package/dist/utils/nanoid.js +19 -0
- package/dist/utils/nanoid.js.map +1 -0
- package/dist/utils/tags.d.ts +22 -0
- package/dist/utils/tags.js +23 -0
- package/dist/utils/tags.js.map +1 -0
- package/package.json +22 -0
- package/src/ToddleApiService.ts +67 -0
- package/src/ToddleRoute.ts +70 -0
- package/src/components/utils.test.ts +90 -0
- package/src/components/utils.ts +77 -0
- package/src/const.ts +17 -0
- package/src/custom-code/codeRefs.ts +271 -0
- package/src/rendering/api.ts +21 -0
- package/src/rendering/attributes.ts +117 -0
- package/src/rendering/components.ts +579 -0
- package/src/rendering/cookies.ts +10 -0
- package/src/rendering/equals.ts +9 -0
- package/src/rendering/fonts.ts +83 -0
- package/src/rendering/formulaContext.test.ts +57 -0
- package/src/rendering/formulaContext.ts +188 -0
- package/src/rendering/head.ts +391 -0
- package/src/rendering/html.ts +33 -0
- package/src/rendering/request.ts +19 -0
- package/src/rendering/speculation.ts +21 -0
- package/src/rendering/template.test.ts +18 -0
- package/src/rendering/template.ts +63 -0
- package/src/rendering/testData.test.ts +186 -0
- package/src/rendering/testData.ts +69 -0
- package/src/routing/routing.test.ts +97 -0
- package/src/routing/routing.ts +152 -0
- package/src/ssr.types.ts +117 -0
- package/src/utils/headers.ts +23 -0
- package/src/utils/media.test.ts +130 -0
- package/src/utils/media.ts +46 -0
- package/src/utils/nanoid.ts +21 -0
- package/src/utils/tags.ts +26 -0
package/src/ssr.types.ts
ADDED
|
@@ -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
|
+
})
|