rosefc 0.2.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rosefc",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "The next-century web framework: zero-hydration, compile-time reactivity, single binary deploy",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,10 +22,10 @@
22
22
  "test:go": "tsx src/cli/index.ts build example && node tests/test-go.mjs",
23
23
  "test:pack": "node tests/test-pack.mjs",
24
24
  "test:resilience": "tsx tests/test-resilience.ts",
25
- "build:go": "node -e \"const fs=require('fs');if(!fs.existsSync('dist')){console.error('build:go: dist/ missing - run npm run build first');process.exit(1)}\" && go build -o rosefd-server ."
25
+ "build:go": "node -e \"const fs=require('fs');if(!fs.existsSync('dist')){console.error('build:go: dist/ missing - run npm run build first');process.exit(1)}\" && go build -o rosefc-server ."
26
26
  },
27
27
  "bin": {
28
- "rosefd": "dist-cli/rosefd.mjs"
28
+ "rosefc": "dist-cli/rosefc.mjs"
29
29
  },
30
30
  "exports": {
31
31
  "./edge": "./dist-cli/edge.mjs",
@@ -37,7 +37,7 @@
37
37
  "files": [
38
38
  "dist-cli",
39
39
  "src/FRAMEWORK_VERSION",
40
- "src/types/rosefd.d.ts",
40
+ "src/types/rosefc.d.ts",
41
41
  "src/cli/content.ts",
42
42
  "src/cli/edge.ts",
43
43
  "src/cli/hot-vary.ts",
@@ -71,11 +71,11 @@
71
71
  },
72
72
  "repository": {
73
73
  "type": "git",
74
- "url": "git+https://github.com/okdunli/rosefd.git"
74
+ "url": "git+https://github.com/okdunli/rosefc.git"
75
75
  },
76
- "homepage": "https://github.com/okdunli/rosefd#readme",
76
+ "homepage": "https://github.com/okdunli/rosefc#readme",
77
77
  "bugs": {
78
- "url": "https://github.com/okdunli/rosefd/issues"
78
+ "url": "https://github.com/okdunli/rosefc/issues"
79
79
  },
80
80
  "keywords": [
81
81
  "framework",
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -162,7 +162,7 @@ export function byTag(
162
162
  return out;
163
163
  }
164
164
 
165
- globalThis.__rosefdContentMk = renderMarkdown;
165
+ globalThis.__rosefcContentMk = renderMarkdown;
166
166
 
167
167
  /** RSS 2.0 feed from a collection - wire to /feed.xml or /api/feed. */
168
168
  export function buildRss(
package/src/cli/edge.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- // Rosefd Edge adapter - Web-standard fetch handler.
2
+ // Rosefc Edge adapter - Web-standard fetch handler.
3
3
  import { pathToFileURL } from 'url';
4
4
  import { join } from 'path';
5
5
  import { buildShell, shellOpen, clientScriptTag, securityHeaders, mintNonce, getClientSource, setBundleMode } from './shell.js';
@@ -92,7 +92,7 @@ const withEdgeQueue = <T>(fn: () => Promise<T>): Promise<T> => {
92
92
  };
93
93
 
94
94
  /**
95
- * Create a fetch handler from a built Rosefd project.
95
+ * Create a fetch handler from a built Rosefc project.
96
96
  * @param outDir directory containing server.js and client.js
97
97
  * @param preloaded for runtimes with NO filesystem (Cloudflare Workers /
98
98
  * Pages): the caller statically imports the server bundle (so the deploy
@@ -187,7 +187,7 @@ export async function createEdgeHandler(
187
187
  // execute the render, so it asks for the JSON half of one - the state the
188
188
  // page's $data produced. Middleware already ran above (a wall short-
189
189
  // circuited before this point), so a gated route never leaks through it.
190
- if (pathname === '/__rosefd/data' && request.method === 'GET') {
190
+ if (pathname === '/__rosefc/data' && request.method === 'GET') {
191
191
  const target = searchParams.get('path') || '/';
192
192
  try {
193
193
  // The gate is per-page: judge the target path, not the channel URL.
@@ -1,5 +1,5 @@
1
1
  // Must run before any request can reach bootstrap(): see shell.ts.
2
- (globalThis as any).__ROSEFD_SKIP_MINIFY = true;
2
+ (globalThis as any).__ROSEFC_SKIP_MINIFY = true;
3
3
 
4
4
  import http from 'node:http';
5
5
  import { Readable } from 'node:stream';
@@ -123,13 +123,13 @@ if (shouldWarm) ready.then(async (h) => {
123
123
  const res = await runIsolated(() => h(new Request('http://localhost' + r)));
124
124
  const buf = await res.arrayBuffer();
125
125
  const suspect = res.status === 200 && res.headers.get('content-type')?.includes('text/html') && buf.byteLength < 2048;
126
- console.log('[rosefd] warm', r, 'status=' + res.status, 'bytes=' + buf.byteLength, suspect ? 'SUSPECT-EMPTY' : '');
126
+ console.log('[rosefc] warm', r, 'status=' + res.status, 'bytes=' + buf.byteLength, suspect ? 'SUSPECT-EMPTY' : '');
127
127
  } catch (e) {
128
- console.error('[rosefd] warm', r, 'FAILED:', (e as Error)?.stack ?? e);
128
+ console.error('[rosefc] warm', r, 'FAILED:', (e as Error)?.stack ?? e);
129
129
  }
130
130
  }
131
131
  }));
132
- }).catch((e) => console.error('[rosefd] warm loop aborted:', (e as Error)?.stack ?? e));
132
+ }).catch((e) => console.error('[rosefc] warm loop aborted:', (e as Error)?.stack ?? e));
133
133
 
134
134
  const hotCache = new Map<string, { raw: Buffer; br?: Buffer; gzip?: Buffer; headers: Record<string, string | string[]>; etag: string; fetchedAt: number; status?: number; refreshing?: boolean }>();
135
135
  const HOT_CACHE_MAX = 300;
@@ -163,7 +163,7 @@ function invalidateHot(): void {
163
163
  // invalidation on, so it calls this at its commit point instead. The request
164
164
  // path needs no hook: the handler schedules its own invalidation on the
165
165
  // response's finish (below).
166
- (globalThis as unknown as { __rosefdInvalidateHot?: () => void }).__rosefdInvalidateHot = invalidateHot;
166
+ (globalThis as unknown as { __rosefcInvalidateHot?: () => void }).__rosefcInvalidateHot = invalidateHot;
167
167
 
168
168
  const gzipFallback: zlib.ZlibOptions = { level: 6 };
169
169
 
@@ -332,7 +332,7 @@ function refusesStore(response: Response, carried: string[] = []): boolean {
332
332
 
333
333
  async function refetchHot(pathname: string, cookieHeader: string, dims: string[], names: string[]): Promise<void> {
334
334
  const gen = hotGen;
335
- const headers: Record<string, string> = { 'user-agent': 'rosefd-hot' };
335
+ const headers: Record<string, string> = { 'user-agent': 'rosefc-hot' };
336
336
  if (cookieHeader) headers.cookie = cookieHeader;
337
337
  // The warm render must negotiate the SAME dimensions the entry it fills is
338
338
  // keyed by: a header-less warm render stored under a language (or a declared
@@ -515,13 +515,13 @@ try {
515
515
  if (!(k in process.env)) process.env[k] = v;
516
516
  }
517
517
  } catch { /* no .env: fine */ }
518
- const REVALIDATE_TOKEN = process.env.ROSEFD_REVALIDATE_TOKEN || '';
518
+ const REVALIDATE_TOKEN = process.env.ROSEFC_REVALIDATE_TOKEN || '';
519
519
  let revalidateMod: any = null;
520
520
 
521
521
  function handleRevalidate(req: http.IncomingMessage, res: http.ServerResponse): boolean {
522
- if (req.method !== 'POST' || !req.url!.startsWith('/__rosefd/revalidate')) return false;
522
+ if (req.method !== 'POST' || !req.url!.startsWith('/__rosefc/revalidate')) return false;
523
523
  if (!REVALIDATE_TOKEN) { res.statusCode = 404; res.end(); return true; }
524
- if (req.headers['x-rosefd-token'] !== REVALIDATE_TOKEN) {
524
+ if (req.headers['x-rosefc-token'] !== REVALIDATE_TOKEN) {
525
525
  res.statusCode = 403; res.setHeader('content-type', 'application/json');
526
526
  res.end(JSON.stringify({ error: 'invalid token' })); return true;
527
527
  }
@@ -714,7 +714,7 @@ const server = http.createServer(async (req, res) => {
714
714
  response = await runIsolated(() => handle!(new Request(url, init)));
715
715
  } catch (err) {
716
716
  if (!replayable) throw err;
717
- console.warn('[rosefd] render failed, retrying once:', (err as Error)?.message ?? err);
717
+ console.warn('[rosefc] render failed, retrying once:', (err as Error)?.message ?? err);
718
718
  await new Promise((r) => setTimeout(r, 200));
719
719
  response = await runIsolated(() => handle!(new Request(url, init)));
720
720
  }
@@ -777,7 +777,7 @@ const server = http.createServer(async (req, res) => {
777
777
  writeAccessLine(req, res, 0);
778
778
  }
779
779
  } catch (err) {
780
- console.error('[rosefd] request failed:', (err as Error)?.stack ?? err);
780
+ console.error('[rosefc] request failed:', (err as Error)?.stack ?? err);
781
781
  if (!res.headersSent) {
782
782
  res.statusCode = 500;
783
783
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
@@ -789,7 +789,7 @@ const server = http.createServer(async (req, res) => {
789
789
 
790
790
  if (!process.env.VERCEL) {
791
791
  server.listen(port, () => {
792
- console.log(`🌹 Rosefd prod server on http://localhost:${port}`);
792
+ console.log(`🌹 Rosefc prod server on http://localhost:${port}`);
793
793
  });
794
794
 
795
795
  let stopping = false;
package/src/cli/shell.ts CHANGED
@@ -23,7 +23,7 @@ export function setBundleMode(mode: string): void {
23
23
  }
24
24
 
25
25
  /**
26
- * Extra connect-src sources from the app's rosefd.config.js
26
+ * Extra connect-src sources from the app's rosefc.config.js
27
27
  * (`export const csp = { connectSrc: 'https:' }`). The framework default is
28
28
  * 'self' - a closed network. Apps whose client talks to an API host (a cloud
29
29
  * gateway, a user-configured LLM endpoint) widen it here; the value is
@@ -156,7 +156,7 @@ if (!__app) {
156
156
  function __saveScroll() {
157
157
  try {
158
158
  const s = history.state || {};
159
- history.replaceState(Object.assign({}, s, { __rosefdScroll: window.scrollY }), '');
159
+ history.replaceState(Object.assign({}, s, { __rosefcScroll: window.scrollY }), '');
160
160
  } catch (e) { /* history state unavailable (file:// edge): best effort */ }
161
161
  }
162
162
  function __navigate(pathname, restore) {
@@ -165,7 +165,7 @@ function __navigate(pathname, restore) {
165
165
  if (restore) {
166
166
  // back/forward: restore the entry's scroll (top when it has none)
167
167
  const s = history.state || {};
168
- const y = typeof s.__rosefdScroll === 'number' ? s.__rosefdScroll : 0;
168
+ const y = typeof s.__rosefcScroll === 'number' ? s.__rosefcScroll : 0;
169
169
  return Promise.resolve(p).then(() => { window.scrollTo(0, y); }, () => { window.scrollTo(0, y); });
170
170
  }
171
171
  // a fresh navigation starts at the top, like a full page load
@@ -189,7 +189,7 @@ document.addEventListener('click', (e) => {
189
189
  });
190
190
  // Prefetch the target route's full render (including $data) on hover, keyboard
191
191
  // focus, or touch - the click that follows paints from cache: no await, no
192
- // request. Other frameworks prefetch data only; Rosefd prefetches the render
192
+ // request. Other frameworks prefetch data only; Rosefc prefetches the render
193
193
  // because the inlined bundle already contains every route.
194
194
  function __prefetchHandler(e) {
195
195
  const a = e.target.closest && e.target.closest('a[href^="/"]');
@@ -239,20 +239,20 @@ document.addEventListener('submit', (e) => {
239
239
  // running page. Both are DIRECT references (no wrapper arrow): the names take
240
240
  // no arguments, so the call is identical and every document that carries the
241
241
  // bundle pays eleven bytes for the pair instead of forty.
242
- window.__rosefd = { state: serializeState, prefetch: prefetchStats };
242
+ window.__rosefc = { state: serializeState, prefetch: prefetchStats };
243
243
  // initial=true: DOM is SSR output, resume state + wire markers (zero hydration).
244
244
  // If the document was rendered for a DIFFERENT route (a static-file server's
245
245
  // SPA fallback serves index.html for unknown paths), adopt nothing - let the
246
246
  // client router render the requested route from scratch.
247
- const __st = document.getElementById('__rosefd_state');
247
+ const __st = document.getElementById('__rosefc_state');
248
248
  let __state = __st ? JSON.parse(__st.textContent || '{}') : {};
249
249
  // The dev server's reload bridge hands the pre-reload signal graph back inside
250
- // the next document (window.__rosefd_resume). The resumed values win over the
250
+ // the next document (window.__rosefc_resume). The resumed values win over the
251
251
  // fresh render's, but the ROUTE stays the one the server just rendered - and
252
252
  // the route renders client-side from the bundle rather than adopting the SSR
253
253
  // DOM, because a resumed list can be longer than the document's rows and
254
254
  // adoption could not add them. Absent in every non-dev document.
255
- const __resumed = window.__rosefd_resume ? JSON.parse(window.__rosefd_resume) : null;
255
+ const __resumed = window.__rosefc_resume ? JSON.parse(window.__rosefc_resume) : null;
256
256
  if (__resumed) {
257
257
  const __route = __state.__route;
258
258
  __state = Object.assign(__state, __resumed);
@@ -272,7 +272,7 @@ if (__state.__notFound) {
272
272
  // layout): defer the initial wire() until the browser is idle so the SSR
273
273
  // paint is interactive-to-paint without blocking on the event wiring. Falls
274
274
  // back to eager wiring where requestIdleCallback is unavailable.
275
- if (window.__rosefdLazy && typeof requestIdleCallback === 'function') {
275
+ if (window.__rosefcLazy && typeof requestIdleCallback === 'function') {
276
276
  requestIdleCallback(() => start(__app, window.location.pathname, true));
277
277
  } else {
278
278
  start(__app, window.location.pathname, true);
@@ -286,7 +286,7 @@ let BOOTSTRAP_CACHE: string | null = null;
286
286
  function bootstrap(): string {
287
287
  if (BOOTSTRAP_CACHE === null) {
288
288
  // The bundled prod server (dist/prod.mjs) sets the skip flag: esbuild's transformSync spawns a worker thread whose entry is the bundle itself.
289
- if ((globalThis as any).__ROSEFD_SKIP_MINIFY) {
289
+ if ((globalThis as any).__ROSEFC_SKIP_MINIFY) {
290
290
  BOOTSTRAP_CACHE = BOOTSTRAP_SOURCE;
291
291
  } else {
292
292
  try {
@@ -366,8 +366,8 @@ export function mintNonce(): string {
366
366
  * The document's CSP. Two shapes, chosen per response:
367
367
  *
368
368
  * - no nonce (the default): `script-src 'sha256-…'` over the exact bytes the
369
- * tag will carry. Nothing else may run - the strongest policy Rosefd has,
370
- * and the reason a Rosefd document needs no 'unsafe-inline'.
369
+ * tag will carry. Nothing else may run - the strongest policy Rosefc has,
370
+ * and the reason a Rosefc document needs no 'unsafe-inline'.
371
371
  * - a nonce (a route exporting `csp = { nonce: true }`):
372
372
  * `script-src 'nonce-…' 'strict-dynamic'`. A hash dies the moment anything
373
373
  * between the server and the browser touches the document - a CDN, a WAF,
@@ -492,7 +492,7 @@ function mergeHeadBlocks(blocks: string[]): string {
492
492
  // The key is stamped into a double-quoted attribute value, so a quote
493
493
  // inside a (pathological) href must not break the markup.
494
494
  key = key.replace(/"/g, '');
495
- const marked = m[0].replace(/^<\w+/, (t) => `${t} data-rosefd-head="${key}"`);
495
+ const marked = m[0].replace(/^<\w+/, (t) => `${t} data-rosefc-head="${key}"`);
496
496
  put(key, marked);
497
497
  }
498
498
  }
@@ -554,7 +554,7 @@ export function shellOpen(styles: string = getStyles(), lang = 'en', dir = '', s
554
554
  <head>
555
555
  <meta charset="UTF-8">
556
556
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
557
- <title>Rosefd</title>${demoStyles}${styleTag}
557
+ <title>Rosefc</title>${demoStyles}${styleTag}
558
558
  ${FAVICON}
559
559
  </head>
560
560
  <body>${appOpen}`;
@@ -570,20 +570,20 @@ export function buildShell(ssrHtml: string, stateJson: string, client: string |
570
570
  let bodyAttrs = '';
571
571
  const headClean = headStamped.replace(/<(html|body)\b([^>]*)>/gi, (_m, tag: string, attrs: string) => {
572
572
  // mergeHeadBlocks stamps every passthrough element with its key; the
573
- const clean = attrs.replace(/\s*data-rosefd-head="[^"]*"/, '');
573
+ const clean = attrs.replace(/\s*data-rosefc-head="[^"]*"/, '');
574
574
  if (tag === 'html') htmlAttrs = clean;
575
575
  else bodyAttrs = clean;
576
576
  return '';
577
577
  });
578
578
 
579
- const titleTag = /<title>/i.test(mergedHead) ? '' : '<title>Rosefd</title>';
579
+ const titleTag = /<title>/i.test(mergedHead) ? '' : '<title>Rosefc</title>';
580
580
 
581
581
  const iconTag = /rel=["']?icon/i.test(mergedHead) ? '' : `\n ${FAVICON}`;
582
582
  const headTags = headClean ? `\n ${headClean}` : '';
583
583
  const styleTag = styles ? `\n <style>${styles}</style>` : '';
584
584
  const stateSafe = stateJson.replace(/</g, '\\u003c');
585
585
  const jsTail = js
586
- ? `\n <script type="application/json" id="__rosefd_state">${stateSafe}</script>\n ${clientScriptTag(client, nonce)}`
586
+ ? `\n <script type="application/json" id="__rosefc_state">${stateSafe}</script>\n ${clientScriptTag(client, nonce)}`
587
587
  : '';
588
588
  const open = js && shell ? '<div id="app">' : '';
589
589
  const close = js && shell ? '</div>' : '';
@@ -13,9 +13,9 @@ const shutdownHooks: ShutdownHook[] = [];
13
13
  let draining: Promise<HookResult[]> | null = null;
14
14
 
15
15
  // Exposed on globalThis because esbuild bundles app code with no external allowlist: a static import from the app would be inlined as a SECOND copy.
16
- (globalThis as unknown as { __rosefdOnShutdown?: typeof onShutdown }).__rosefdOnShutdown =
16
+ (globalThis as unknown as { __rosefcOnShutdown?: typeof onShutdown }).__rosefcOnShutdown =
17
17
  onShutdown;
18
- (globalThis as unknown as { __rosefdRunShutdownHooks?: typeof runShutdownHooks }).__rosefdRunShutdownHooks =
18
+ (globalThis as unknown as { __rosefcRunShutdownHooks?: typeof runShutdownHooks }).__rosefcRunShutdownHooks =
19
19
  runShutdownHooks;
20
20
 
21
21
  /** Returns an unregister function - a hot-swapped client must drop its old hook. */
@@ -41,7 +41,7 @@ export function runShutdownHooks(reason: string): Promise<HookResult[]> {
41
41
  } catch (err) {
42
42
  const message = err instanceof Error ? err.message : String(err);
43
43
  results.push({ name: hook.name, ok: false, error: message });
44
- console.error(`[rosefd] shutdown hook ${hook.name} failed (${reason}):`, message);
44
+ console.error(`[rosefc] shutdown hook ${hook.name} failed (${reason}):`, message);
45
45
  }
46
46
  }
47
47
  return results;
package/src/runtime/db.ts CHANGED
@@ -50,7 +50,7 @@
50
50
  // carry their own copy of this module) - a module-level variable would be
51
51
  // per-copy, so a route would see "no driver registered" from a different
52
52
  // bundle's registration. Same reason the rest of the server runtime publishes
53
- // itself through __rosefdAuth / __rosefdKvApi / __rosefdExtras.
53
+ // itself through __rosefcAuth / __rosefcKvApi / __rosefcExtras.
54
54
  //
55
55
  // Zero dependencies: no import statement in this file at all.
56
56
 
@@ -84,7 +84,7 @@ export interface Db extends Queries {
84
84
  tx<T>(fn: (t: Queries) => Promise<T> | T): Promise<T>;
85
85
  }
86
86
 
87
- const KEY = '__rosefdDb';
87
+ const KEY = '__rosefcDb';
88
88
 
89
89
  const slot = () => globalThis as unknown as Record<string, Driver | undefined>;
90
90
 
@@ -100,7 +100,7 @@ function requireDriver(): Driver {
100
100
  const driver = slot()[KEY];
101
101
  if (!driver) {
102
102
  throw new Error(
103
- 'rosefd/db: no database driver registered - call registerDb({ query }) once at boot ' +
103
+ 'rosefc/db: no database driver registered - call registerDb({ query }) once at boot ' +
104
104
  '(src/pages/_middleware.rose, or a module it imports) before any handler queries',
105
105
  );
106
106
  }