@jk2908/solas 0.3.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 - 2026-04-07
4
+
5
+ - Fixed `useSearchParams()` client builds.
6
+ - Reworked the code generators to keep the source templates readable while still emitting tidy generated files.
7
+ - Added a shared template dedent helper for generated source and tightened nested object and route map indentation.
8
+ - Made generated config output emit logger code only when a logger level is configured.
9
+
3
10
  ## 0.3.0 - 2026-04-07
4
11
 
5
12
  - Fixed `useSearchParams()` hydration so query-driven ui uses the initial request url on first render.
@@ -1,19 +1,28 @@
1
1
  import { Solas } from '../../solas.js';
2
- import { AUTOGEN_MSG, toSourceLiteral } from './utils.js';
2
+ import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
3
3
  /**
4
4
  * Generates the code to create an exported config object
5
5
  */
6
6
  export function writeConfig(config) {
7
- return `
7
+ const loggerLevel = config.logger?.level;
8
+ const importLines = [
9
+ `import type { PluginConfig } from '${Solas.Config.PKG_NAME}'`,
10
+ loggerLevel ? `import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'` : '',
11
+ ]
12
+ .filter(Boolean)
13
+ .join('\n');
14
+ const configStatement = `const config = ${toSourceLiteral(config)} as const satisfies PluginConfig`;
15
+ const loggerStatement = loggerLevel
16
+ ? `Logger.defaultLevel = ${toSourceLiteral(loggerLevel)}`
17
+ : '';
18
+ return source `
8
19
  ${AUTOGEN_MSG}
9
20
 
10
- import type { PluginConfig } from '${Solas.Config.PKG_NAME}'
11
- import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'
21
+ ${importLines}
12
22
 
13
- const config = ${toSourceLiteral(config)} as const satisfies PluginConfig
14
-
15
- if (config.logger?.level) Logger.defaultLevel = config.logger.level
23
+ ${configStatement}
24
+ ${loggerStatement}
16
25
 
17
26
  export { config }
18
- `.trim();
27
+ `;
19
28
  }
@@ -1,10 +1,10 @@
1
1
  import { Solas } from '../../solas.js';
2
- import { AUTOGEN_MSG } from './utils.js';
2
+ import { AUTOGEN_MSG, source } from './utils.js';
3
3
  /**
4
4
  * Generates the RSC entry code
5
5
  */
6
6
  export function writeRSCEntry() {
7
- return `
7
+ return source `
8
8
  ${AUTOGEN_MSG}
9
9
 
10
10
  import { createHandler } from '${Solas.Config.PKG_NAME}/env/rsc'
@@ -20,27 +20,27 @@ export function writeRSCEntry() {
20
20
  export default createHandler(config, manifest, importMap, artifactManifest)
21
21
 
22
22
  import.meta.hot?.accept()
23
- `.trim();
23
+ `;
24
24
  }
25
25
  /**
26
26
  * Generates the SSR entry code
27
27
  */
28
28
  export function writeSSREntry() {
29
- return `
29
+ return source `
30
30
  ${AUTOGEN_MSG}
31
31
 
32
32
  export { prerender, resume, ssr } from '${Solas.Config.PKG_NAME}/env/ssr'
33
- `.trim();
33
+ `;
34
34
  }
35
35
  /**
36
36
  * Generates the browser entry code
37
37
  */
38
38
  export function writeBrowserEntry() {
39
- return `
39
+ return source `
40
40
  ${AUTOGEN_MSG}
41
41
 
42
42
  import { browser } from '${Solas.Config.PKG_NAME}/env/browser'
43
43
 
44
44
  browser()
45
- `.trim();
45
+ `;
46
46
  }
@@ -1,14 +1,14 @@
1
1
  import { Solas } from '../../solas.js';
2
- import { AUTOGEN_MSG, toSourceLiteral } from './utils.js';
2
+ import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
3
3
  /**
4
4
  * Generates the code to create an exported manifest object
5
5
  */
6
6
  export function writeManifest(manifest) {
7
- return `
7
+ return source `
8
8
  ${AUTOGEN_MSG}
9
9
 
10
10
  import type { Manifest } from '${Solas.Config.PKG_NAME}'
11
11
 
12
12
  export const manifest = ${toSourceLiteral(manifest)} as const satisfies Manifest
13
- `.trim();
13
+ `;
14
14
  }
@@ -1,5 +1,5 @@
1
1
  import { Solas } from '../../solas.js';
2
- import { AUTOGEN_MSG, toIdentifier, toIdentifierList, toRelativeModuleSpecifier, toStringLiteral, } from './utils.js';
2
+ import { AUTOGEN_MSG, indent, source, toIdentifier, toIdentifierList, toRelativeModuleSpecifier, toStringLiteral, } from './utils.js';
3
3
  /**
4
4
  * Generates the import map for all route components, endpoints, layouts, shells, and middlewares
5
5
  */
@@ -61,26 +61,22 @@ export function writeMaps(imports, modules) {
61
61
  parts.push(`middlewares: [${middleware}]`);
62
62
  }
63
63
  if (parts.length === 0)
64
- return `\t${toStringLiteral(moduleId)}: {}`;
65
- return `\t${toStringLiteral(moduleId)}: {\n\t\t${parts.join(',\n\t\t')}\n\t}`;
64
+ return `${toStringLiteral(moduleId)}: {}`;
65
+ return `${toStringLiteral(moduleId)}: {\n${parts.map(part => indent(part, 1)).join(',\n')}\n}`;
66
66
  });
67
- return `
67
+ const importLines = [...statics, ...dynamics].join('\n');
68
+ const entries = map.map(entry => indent(entry, 1)).join(',\n');
69
+ return source `
68
70
  ${AUTOGEN_MSG}
69
71
 
70
72
  import type { ImportMap } from '${Solas.Config.PKG_NAME}'
71
-
72
- ${statics.length
73
- ? `${statics.join('\n')}
74
-
75
- `
76
- : ''}${dynamics.length
77
- ? `${dynamics.join('\n')}
78
-
79
- `
73
+ ${importLines
74
+ ? `
75
+ ${importLines}`
80
76
  : ''}
81
77
 
82
78
  export const importMap = {
83
- ${map.join(',\n')}
79
+ ${entries}
84
80
  } as const satisfies ImportMap
85
- `.trim();
81
+ `;
86
82
  }
@@ -15,7 +15,17 @@ export declare function toIdentifierList(values: readonly (string | null)[], lab
15
15
  * Escape text into a safe string literal for generated source
16
16
  */
17
17
  export declare function toStringLiteral(value: string, quoteStyle?: "'" | '"'): string;
18
+ /**
19
+ * Dedent an interpolated template literal while preserving indentation for
20
+ * multiline substitutions
21
+ */
22
+ export declare function source(strings: TemplateStringsArray, ...values: Array<string | number | boolean | false | null | undefined>): string;
18
23
  /**
19
24
  * Emit readable ts source for generated config and manifest data
20
25
  */
21
26
  export declare function toSourceLiteral(value: unknown, level?: number): string;
27
+ /**
28
+ * Indent each line of a block of source code by the specified level for embedding in
29
+ * generated output
30
+ */
31
+ export declare function indent(value: string, level?: number): string;
@@ -60,6 +60,31 @@ export function toStringLiteral(value, quoteStyle = "'") {
60
60
  .replace(/\r/g, '\\r')
61
61
  .replace(/\t/g, '\\t')}${quoteStyle}`;
62
62
  }
63
+ /**
64
+ * Dedent an interpolated template literal while preserving indentation for
65
+ * multiline substitutions
66
+ */
67
+ export function source(strings, ...values) {
68
+ let text = strings[0] ?? '';
69
+ for (let index = 0; index < values.length; index += 1) {
70
+ const value = values[index];
71
+ const indentation = text.match(/(?:^|\n)([ \t]*)$/)?.[1] ?? '';
72
+ const chunk = value === false || value == null ? '' : String(value);
73
+ text += chunk.replace(/\n/g, `\n${indentation}`);
74
+ text += strings[index + 1] ?? '';
75
+ }
76
+ const lines = text.replace(/^\n+|\n+$/g, '').split('\n');
77
+ const margin = lines
78
+ .filter(line => line.trim().length > 0)
79
+ .reduce((smallest, line) => {
80
+ const indentation = line.match(/^[ \t]*/)?.[0].length ?? 0;
81
+ return Math.min(smallest, indentation);
82
+ }, Number.POSITIVE_INFINITY);
83
+ if (!Number.isFinite(margin) || margin === 0) {
84
+ return lines.join('\n');
85
+ }
86
+ return lines.map(line => line.slice(margin)).join('\n');
87
+ }
63
88
  /**
64
89
  * Convert a string into a valid unquoted property key if possible, otherwise quote it
65
90
  * as a string literal for generated source
@@ -120,7 +145,7 @@ export function toSourceLiteral(value, level = 0) {
120
145
  }
121
146
  return `${prefix}${source.replace(/\n/g, `\n${INDENT.repeat(level + 1)}`)}`;
122
147
  }
123
- return `${prefix}${toSourceLiteral(entryValue, level + 1).replace(/\n/g, `\n${INDENT.repeat(level + 1)}`)}`;
148
+ return `${prefix}${toSourceLiteral(entryValue, level + 1)}`;
124
149
  })
125
150
  .join(',\n'),
126
151
  `${INDENT.repeat(level)}}`,
@@ -132,7 +157,7 @@ export function toSourceLiteral(value, level = 0) {
132
157
  * Indent each line of a block of source code by the specified level for embedding in
133
158
  * generated output
134
159
  */
135
- function indent(value, level = 1) {
160
+ export function indent(value, level = 1) {
136
161
  const prefix = INDENT.repeat(level);
137
162
  return value
138
163
  .split('\n')
@@ -1 +1,11 @@
1
- export declare function useSearchParams(): URLSearchParams;
1
+ export type ReadonlySearchParams = Iterable<[string, string]> & {
2
+ entries(): IterableIterator<[string, string]>;
3
+ forEach(callbackfn: (value: string, key: string, parent: ReadonlySearchParams) => void, thisArg?: unknown): void;
4
+ get(name: string): string | null;
5
+ getAll(name: string): string[];
6
+ has(name: string, value?: string): boolean;
7
+ keys(): IterableIterator<string>;
8
+ toString(): string;
9
+ values(): IterableIterator<string>;
10
+ };
11
+ export declare function useSearchParams(): ReadonlySearchParams;
@@ -1,6 +1,25 @@
1
1
  import { useMemo, useSyncExternalStore } from 'react';
2
- import { useRouter } from '../../router.js';
3
2
  import { Solas } from '../../solas.js';
3
+ import { useRouter } from '../router/use-router.js';
4
+ function createReadonlySearchParams(search) {
5
+ const params = new URLSearchParams(search);
6
+ const readonlyParams = {
7
+ [Symbol.iterator]: () => params[Symbol.iterator](),
8
+ entries: () => params.entries(),
9
+ forEach: (callbackfn, thisArg) => {
10
+ params.forEach((value, key) => {
11
+ callbackfn.call(thisArg, value, key, readonlyParams);
12
+ });
13
+ },
14
+ get: name => params.get(name),
15
+ getAll: name => params.getAll(name),
16
+ has: (name, value) => params.has(name, value),
17
+ keys: () => params.keys(),
18
+ toString: () => params.toString(),
19
+ values: () => params.values(),
20
+ };
21
+ return readonlyParams;
22
+ }
4
23
  export function useSearchParams() {
5
24
  const { url } = useRouter();
6
25
  const search = useSyncExternalStore(fn => {
@@ -11,5 +30,5 @@ export function useSearchParams() {
11
30
  window.removeEventListener(Solas.Events.names.NAVIGATION, fn);
12
31
  };
13
32
  }, () => window.location.search, () => url?.search);
14
- return useMemo(() => new URLSearchParams(search), [search]);
33
+ return useMemo(() => createReadonlySearchParams(search), [search]);
15
34
  }
@@ -1,6 +1,6 @@
1
- export { HttpException, abort, isHttpException, } from './internal/navigation/http-exception.js';
2
1
  export { HttpExceptionBoundary } from './internal/navigation/http-exception-boundary.js';
2
+ export { HttpException, abort, isHttpException, } from './internal/navigation/http-exception.js';
3
3
  export { Link } from './internal/navigation/link.js';
4
- export { Redirect, isRedirect, redirect } from './internal/navigation/redirect.js';
5
4
  export { RedirectBoundary } from './internal/navigation/redirect-boundary.js';
5
+ export { Redirect, isRedirect, redirect } from './internal/navigation/redirect.js';
6
6
  export { useSearchParams } from './internal/navigation/use-search-params.js';
@@ -1,6 +1,6 @@
1
- export { HttpException, abort, isHttpException, } from './internal/navigation/http-exception.js';
2
1
  export { HttpExceptionBoundary } from './internal/navigation/http-exception-boundary.js';
2
+ export { HttpException, abort, isHttpException, } from './internal/navigation/http-exception.js';
3
3
  export { Link } from './internal/navigation/link.js';
4
- export { Redirect, isRedirect, redirect } from './internal/navigation/redirect.js';
5
4
  export { RedirectBoundary } from './internal/navigation/redirect-boundary.js';
5
+ export { Redirect, isRedirect, redirect } from './internal/navigation/redirect.js';
6
6
  export { useSearchParams } from './internal/navigation/use-search-params.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jk2908/solas",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A React Server Components meta-framework powered by Vite",
5
5
  "keywords": [
6
6
  "framework",