acrylblends 0.7.1 → 0.7.3

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 (47) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +41 -1
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/README.md +2 -1
  5. package/docs/framework/UPSTREAM.md +1 -1
  6. package/docs/framework/api/summary.md +6 -3
  7. package/docs/framework/cordis/node-server.md +3 -0
  8. package/docs/framework/getting-started/deploy.md +133 -0
  9. package/docs/framework/getting-started/nodejs.md +37 -16
  10. package/docs/framework/guides/client-modules.md +209 -0
  11. package/docs/framework/guides/forms-and-sessions.md +1 -1
  12. package/docs/framework/guides/jsx-dom.md +6 -1
  13. package/docs/framework/guides/validation.md +87 -0
  14. package/docs/framework/helpers/streaming.md +119 -0
  15. package/docs/framework/helpers/testing.md +17 -0
  16. package/docs/framework/upstream.json +3 -3
  17. package/lib/browser/client.js +307 -0
  18. package/lib/browser/jsx-dom.js +1432 -0
  19. package/lib/index.js +1 -25
  20. package/lib/node-server/register.js +7 -49
  21. package/lib/node-server.d.ts +74 -7
  22. package/lib/node-server.js +264 -7
  23. package/lib/schema-validator.d.ts +40 -14
  24. package/lib/schema-validator.js +2 -2
  25. package/lib/src-0hI7M7cN.js +27 -0
  26. package/lib/transpile-CTnWof4z.js +90 -0
  27. package/package.json +1 -1
  28. package/plugins/acryl-inspect/upstream.json +1 -1
  29. package/plugins/acryl-node-server/src/adapter/server.ts +28 -5
  30. package/plugins/acryl-node-server/src/adapter/types.ts +14 -2
  31. package/plugins/acryl-node-server/src/client-modules.ts +293 -0
  32. package/plugins/acryl-node-server/src/register.ts +5 -62
  33. package/plugins/acryl-node-server/src/transpile.ts +120 -0
  34. package/plugins/acryl-node-server/upstream.json +4 -3
  35. package/plugins/acryl-router/src/browser/client.ts +3 -0
  36. package/plugins/acryl-router/src/browser/jsx-dom.ts +9 -0
  37. package/plugins/acryl-router/src/helpers/index.ts +1 -1
  38. package/plugins/acryl-router/src/helpers/schema-validator.ts +39 -12
  39. package/plugins/acryl-router/upstream.json +2 -2
  40. package/plugins/acryl-router-http/upstream.json +1 -1
  41. package/plugins/acryl-state/upstream.json +1 -1
  42. package/plugins/desktop/upstream.json +1 -1
  43. package/plugins/desktop-web/upstream.json +1 -1
  44. package/templates/agent/package.json +4 -3
  45. package/templates/blank/package.json +4 -3
  46. package/templates/todo/package.json +4 -3
  47. package/templates/todo/upstream.json +1 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * One TypeScript module compiled to JavaScript with the app's own `typescript`: the transform behind `acrylblends/node-server/register`
3
+ * (server JSX on Node) and `clientModules` (browser modules). `typescript` is loaded from the app on first use, so the framework itself has
4
+ * no dependencies.
5
+ */
6
+
7
+ import { createRequire } from 'node:module'
8
+ import { existsSync } from 'node:fs'
9
+ import { dirname, join } from 'node:path'
10
+
11
+ /** The part of the TypeScript compiler API the framework uses. */
12
+ interface Compiler {
13
+ transpileModule(input: string, options: { fileName: string, compilerOptions: Record<string, unknown> }): { outputText: string }
14
+ findConfigFile(searchPath: string, fileExists: (path: string) => boolean): string | undefined
15
+ readConfigFile(path: string, readFile: (path: string) => string | undefined): { config?: unknown }
16
+ parseJsonConfigFileContent(json: unknown, host: unknown, basePath: string): { options: { jsxImportSource?: string } }
17
+ preProcessFile(text: string, readImportFiles: boolean, detectJavaScriptImports: boolean): { importedFiles: { fileName: string, pos: number }[] }
18
+ sys: { fileExists(path: string): boolean, readFile(path: string): string | undefined }
19
+ }
20
+
21
+ /** Thrown when the app has no `typescript` to compile a file with. */
22
+ export class MissingTypeScriptError extends Error {
23
+ constructor(file: string, what: string) {
24
+ super(`acrylblends: ${file} ${what}, and compiling it needs the typescript package in this app: npm install --save-dev typescript`)
25
+ this.name = 'MissingTypeScriptError'
26
+ }
27
+ }
28
+
29
+ const compilers = new Map<string, Compiler>()
30
+ const importSources = new Map<string, string>()
31
+
32
+ /**
33
+ * The app's own `typescript`, resolved from the file, so each app compiles with the version it pinned.
34
+ * @param file - the module to compile.
35
+ * @param what - what the file needs compiling for, for the error message.
36
+ * @returns the compiler.
37
+ * @throws MissingTypeScriptError when the app has no `typescript`.
38
+ */
39
+ function compilerFor(file: string, what: string): Compiler {
40
+ // Only the app's own node_modules chain counts: Node's global folders (NODE_PATH, which a package manager's shims may set) are not the app.
41
+ let owner: string | undefined
42
+ for (let dir = dirname(file); owner === undefined; dir = dirname(dir)) {
43
+ if (existsSync(join(dir, 'node_modules', 'typescript', 'package.json'))) owner = dir
44
+ else if (dirname(dir) === dir) throw new MissingTypeScriptError(file, what)
45
+ }
46
+ const path = createRequire(join(owner, 'package.json')).resolve('typescript')
47
+ let compiler = compilers.get(path)
48
+ if (compiler === undefined) {
49
+ compiler = createRequire(path)(path) as Compiler
50
+ compilers.set(path, compiler)
51
+ }
52
+ return compiler
53
+ }
54
+
55
+ /** `jsxImportSource` of the tsconfig.json nearest to the file (following `extends`), read once per config. */
56
+ function importSourceFor(file: string, ts: Compiler, fallback: string): string {
57
+ const config = ts.findConfigFile(dirname(file), path => existsSync(path))
58
+ if (config === undefined) return fallback
59
+ let source = importSources.get(`${config}\0${fallback}`)
60
+ if (source === undefined) {
61
+ const { config: json } = ts.readConfigFile(config, path => ts.sys.readFile(path))
62
+ source = ts.parseJsonConfigFileContent(json ?? {}, ts.sys, dirname(config)).options.jsxImportSource ?? fallback
63
+ importSources.set(`${config}\0${fallback}`, source)
64
+ }
65
+ return source
66
+ }
67
+
68
+ /** How {@link compileModule} compiles one file. */
69
+ export interface CompileOptions {
70
+ /** The JSX runtime package; without it, the nearest tsconfig.json's `jsxImportSource`, then `defaultImportSource`. */
71
+ readonly importSource?: string | undefined
72
+ /** The runtime when neither `importSource` nor a tsconfig.json names one. */
73
+ readonly defaultImportSource: string
74
+ /** What the file needs compiling for, as the missing-typescript message says it. */
75
+ readonly what: string
76
+ /** Replaces an import specifier of the output (static, `export ... from` and dynamic); `undefined` keeps it. */
77
+ readonly rewrite?: ((specifier: string) => string | undefined) | undefined
78
+ }
79
+
80
+ /**
81
+ * A TypeScript or JSX module compiled to an ES module, with its source map inline (a file's own `@jsxImportSource` pragma wins over the
82
+ * options).
83
+ * @param file - absolute path of the module, which also locates the app's `typescript` and tsconfig.json.
84
+ * @param text - its source.
85
+ * @param options - the JSX runtime and the specifier rewrite.
86
+ * @returns the JavaScript.
87
+ * @throws MissingTypeScriptError when the app has no `typescript`.
88
+ */
89
+ export function compileModule(file: string, text: string, options: CompileOptions): string {
90
+ const ts = compilerFor(file, options.what)
91
+ const output = ts.transpileModule(text, {
92
+ fileName: file,
93
+ compilerOptions: {
94
+ module: 'esnext',
95
+ target: 'es2022',
96
+ jsx: 'react-jsx',
97
+ jsxImportSource: options.importSource ?? importSourceFor(file, ts, options.defaultImportSource),
98
+ allowImportingTsExtensions: true,
99
+ inlineSourceMap: true,
100
+ inlineSources: true,
101
+ },
102
+ }).outputText
103
+ return options.rewrite === undefined ? output : rewriteSpecifiers(ts, output, options.rewrite)
104
+ }
105
+
106
+ /** The output with each import specifier `rewrite` maps replaced; TypeScript's lexer finds them, so strings and comments are left alone. */
107
+ function rewriteSpecifiers(ts: Compiler, code: string, rewrite: (specifier: string) => string | undefined): string {
108
+ let result = ''
109
+ let last = 0
110
+ for (const { fileName, pos } of ts.preProcessFile(code, true, true).importedFiles.toSorted((a, b) => a.pos - b.pos)) {
111
+ // `pos` is the opening quote; a specifier written with escapes does not match its text and is left as it is.
112
+ const start = pos + 1
113
+ if (code.slice(start, start + fileName.length) !== fileName) continue
114
+ const replacement = rewrite(fileName)
115
+ if (replacement === undefined) continue
116
+ result += code.slice(last, start) + replacement
117
+ last = start + fileName.length
118
+ }
119
+ return result + code.slice(last)
120
+ }
@@ -2,16 +2,17 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-node-server",
5
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
5
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
9
9
  "tests",
10
10
  "LICENSE"
11
11
  ],
12
- "rewrittenSpecifiers": 28,
13
- "digest": "sha256:c830697a6c09e4f9137f5589cffb56e55d07a06044f22c4087206bcfc3e62ea6",
12
+ "rewrittenSpecifiers": 31,
13
+ "digest": "sha256:0a3278e006540d0dbecdfb6ada3ca865f3a9a78b649edd9454dab0c76281eda0",
14
14
  "testsNotRun": {
15
+ "tests/client-modules.spec.ts": "links typescript from the plugin's own node_modules (ACRYL's workspace layout); clientModules and build-client are exercised here by the templates' tests and verify:scaffolder",
15
16
  "tests/register.spec.ts": "links typescript from the plugin's own node_modules (ACRYL's workspace layout); the hook is exercised here by the templates' own .tsx tests and verify:scaffolder"
16
17
  },
17
18
  "notice": [
@@ -0,0 +1,3 @@
1
+ /** The typed RPC client (`acrylblends/client`) as one browser module with no imports, served by `clientModules`. */
2
+
3
+ export * from '../core/client/index.ts'
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The DOM renderer as one browser module: `acrylblends/jsx/dom`, its JSX runtimes and `client`, in a single file with no imports, so a page
3
+ * loads it without a bundler and the runtime and the hooks share one module. `clientModules` serves it and points those imports here.
4
+ */
5
+
6
+ export * from '../core/jsx/dom/index.ts'
7
+ export { jsx, jsxs } from '../core/jsx/dom/jsx-runtime.ts'
8
+ export { jsxDEV } from '../core/jsx/dom/jsx-dev-runtime.ts'
9
+ export { createRoot, hydrateRoot } from '../core/jsx/dom/client.ts'
@@ -13,5 +13,5 @@ export { testClient } from '../core/helper/testing/index.ts'
13
13
  export { defaultPlugin, disableSSG, isSSGContext, onlySSG, redirectPlugin, ssgParams, toSSG, X_ACRYL_DISABLE_SSG_HEADER_KEY, type SSGPlugin, type ToSSGOptions } from '../core/helper/ssg/index.ts'
14
14
  export { stream, streamSSE, streamText, SSEStreamingApi, type SSEMessage } from '../core/helper/streaming/index.ts'
15
15
  export { validator, type ValidationFunction } from '../core/validator/index.ts'
16
- export { schemaValidator, type StandardSchema } from './schema-validator.ts'
16
+ export { schemaValidator, type StandardIssue, type StandardResult, type StandardSchema, type StandardSchemaOptions, type StandardSchemaProps } from './schema-validator.ts'
17
17
  export { createWSMessageEvent, defineWebSocketHelper, WSContext, type SendOptions, type UpgradeWebSocket, type WSContextInit, type WSEvents, type WSMessageReceive, type WSReadyState } from '../core/helper/websocket/index.ts'
@@ -1,27 +1,54 @@
1
1
  import type { Env, MiddlewareHandler, ValidationTargets } from '../core/types.ts'
2
2
  import { validator } from '../core/validator/index.ts'
3
3
 
4
- /** The slice of the Standard Schema interface this needs; Schemastery (what Cordis uses), Zod and Valibot all implement it. */
5
- export interface StandardSchema<T = unknown> {
6
- readonly '~standard': {
7
- validate(value: unknown): StandardResult<T> | Promise<StandardResult<T>>
8
- }
4
+ /**
5
+ * The Standard Schema v1 interface (https://standardschema.dev), declared here so the framework needs no dependency for it. Schemastery
6
+ * (what Cordis uses), Zod and Valibot implement it, and a hand-written schema object of the same shape is accepted as well. Only
7
+ * `validate` is called, so `version` and `vendor` may be left out of a hand-written schema.
8
+ */
9
+ export interface StandardSchema<Input = unknown, Output = Input> {
10
+ readonly '~standard': StandardSchemaProps<Input, Output>
11
+ }
12
+
13
+ /** The `~standard` properties of a {@link StandardSchema}. */
14
+ export interface StandardSchemaProps<Input = unknown, Output = Input> {
15
+ /** The version of the standard; every library sets it. */
16
+ readonly version?: 1 | undefined
17
+ /** The name of the schema library; every library sets it. */
18
+ readonly vendor?: string | undefined
19
+ /** Validates an unknown value. */
20
+ readonly validate: (value: unknown, options?: StandardSchemaOptions | undefined) => StandardResult<Output> | Promise<StandardResult<Output>>
21
+ /** The input and output types, for inference only. */
22
+ readonly types?: { readonly input: Input, readonly output: Output } | undefined
23
+ }
24
+
25
+ /** Options a caller may pass to `validate`. */
26
+ export interface StandardSchemaOptions {
27
+ /** Library-specific parameters. */
28
+ readonly libraryOptions?: Record<string, unknown> | undefined
29
+ }
30
+
31
+ /** One validation issue: a message and where in the value it applies. */
32
+ export interface StandardIssue {
33
+ readonly message: string
34
+ readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[] | undefined
9
35
  }
10
36
 
11
- type StandardResult<T> =
12
- | { readonly value: T, readonly issues?: undefined }
13
- | { readonly issues: readonly { readonly message: string, readonly path?: readonly unknown[] | undefined }[] }
37
+ /** What `validate` answers: the parsed value, or the issues. */
38
+ export type StandardResult<Output> =
39
+ | { readonly value: Output, readonly issues?: undefined }
40
+ | { readonly issues: readonly StandardIssue[] }
14
41
 
15
- const pathOf = (path: readonly unknown[] | undefined): string =>
16
- (path ?? []).map(part => String(typeof part === 'object' && part !== null && 'key' in part ? (part as { key: unknown }).key : part)).join('.')
42
+ const pathOf = (path: StandardIssue['path']): string =>
43
+ (path ?? []).map(part => String(typeof part === 'object' ? part.key : part)).join('.')
17
44
 
18
45
  /**
19
46
  * The core `validator` driven by a Standard Schema. A failure answers 400 with `{ error, issues }`; success makes the parsed value the
20
- * handler's `c.req.valid(target)`, typed by the schema.
47
+ * handler's `c.req.valid(target)`, typed by the schema's output.
21
48
  */
22
49
  export function schemaValidator<Target extends keyof ValidationTargets, Out, E extends Env = Env, P extends string = string>(
23
50
  target: Target,
24
- schema: StandardSchema<Out>,
51
+ schema: StandardSchema<unknown, Out>,
25
52
  ): MiddlewareHandler<E, P, { in: { [K in Target]: Out }, out: { [K in Target]: Out } }> {
26
53
  const middleware = validator(target, async (value, c) => {
27
54
  const result = await schema['~standard'].validate(value)
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router",
5
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
5
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -10,7 +10,7 @@
10
10
  "LICENSE"
11
11
  ],
12
12
  "rewrittenSpecifiers": 6,
13
- "digest": "sha256:dde4e173c7004c7aadd3d96866e65b29230c31562a4fab9e186fd4499821282b",
13
+ "digest": "sha256:1dd20cb7b5bb87e4368ce507dfe85de3e992cc90069a3b43e24af053b1186842",
14
14
  "testsNotRun": {
15
15
  "tests/loader.spec.ts": "loads the built lib/ entry through the real Loader; the library ships source and has no build"
16
16
  },
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router-http",
5
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
5
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-state",
5
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
5
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
8
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
8
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -19,9 +19,10 @@
19
19
  "save": "node src/plugins/save/cli.ts",
20
20
  "upgrade": "node src/plugins/manifest/upgrade-cli.ts"
21
21
  },
22
- "dependencies": {},
23
- "devDependencies": {
24
- "@types/node": "^24.0.0",
22
+ "dependencies": {
25
23
  "typescript": "6.0.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.0.0"
26
27
  }
27
28
  }
@@ -19,9 +19,10 @@
19
19
  "save": "node src/plugins/save/cli.ts",
20
20
  "upgrade": "node src/plugins/manifest/upgrade-cli.ts"
21
21
  },
22
- "dependencies": {},
23
- "devDependencies": {
24
- "@types/node": "^24.0.0",
22
+ "dependencies": {
25
23
  "typescript": "6.0.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.0.0"
26
27
  }
27
28
  }
@@ -19,9 +19,10 @@
19
19
  "save": "node src/plugins/save/cli.ts",
20
20
  "upgrade": "node src/plugins/manifest/upgrade-cli.ts"
21
21
  },
22
- "dependencies": {},
23
- "devDependencies": {
24
- "@types/node": "^24.0.0",
22
+ "dependencies": {
25
23
  "typescript": "6.0.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.0.0"
26
27
  }
27
28
  }
@@ -5,7 +5,7 @@
5
5
  "examples/acryl-todo/src/index.ts": "src/todo.ts",
6
6
  "examples/acryl-todo/src/page.ts": "src/page.ts"
7
7
  },
8
- "commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
8
+ "commit": "e79bf3de59c700667ae617f5d367cc1fd2f8e037"
9
9
  },
10
10
  "rewrittenSpecifiers": 4,
11
11
  "digest": "sha256:9093704776563001f7f472f94cc5c2fdd79206342c1cb59c11fce62aa74df3ed"