@apex-stack/core 0.1.13 → 0.1.15

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.
@@ -5,7 +5,8 @@ import {
5
5
  renderIslandsPage,
6
6
  renderPage,
7
7
  scanPages
8
- } from "./chunk-PAMD24NK.js";
8
+ } from "./chunk-D3VZDJ3R.js";
9
+ import "./chunk-MZVLRU3R.js";
9
10
 
10
11
  // src/commands/build.ts
11
12
  import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, rmSync, writeFileSync } from "fs";
@@ -1,3 +1,7 @@
1
+ import {
2
+ isApexStore
3
+ } from "./chunk-MZVLRU3R.js";
4
+
1
5
  // src/islands/render.ts
2
6
  import { renderIslands } from "@apex-stack/kit";
3
7
  var ISLAND_LOADER = (
@@ -118,34 +122,71 @@ function matchRoute(routes, url) {
118
122
  return null;
119
123
  }
120
124
 
125
+ // src/stores/loader.ts
126
+ import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
127
+ import { join as join2 } from "path";
128
+ async function loadStores(root, loadModule) {
129
+ const dir = join2(root, "stores");
130
+ if (!existsSync2(dir)) return [];
131
+ const out = [];
132
+ for (const file of readdirSync2(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"))) {
133
+ const id = `/stores/${file}`;
134
+ const mod = await loadModule(id);
135
+ const def = mod.default;
136
+ if (isApexStore(def)) {
137
+ const store = def;
138
+ out.push({ id, name: store.name, factory: store.factory });
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ function storesInitialState(stores) {
144
+ const state = {};
145
+ for (const s of stores) {
146
+ try {
147
+ state[s.name] = s.factory();
148
+ } catch {
149
+ state[s.name] = {};
150
+ }
151
+ }
152
+ return state;
153
+ }
154
+
121
155
  // src/dev/renderPage.ts
122
156
  import { renderComponent, stateIsland } from "@apex-stack/kit";
123
157
  async function renderPage(opts) {
124
158
  const mod = await opts.loadModule(opts.pageId);
125
159
  const loaderData = await mod.loader({ params: opts.params ?? {}, url: opts.url }) ?? {};
160
+ const stores = opts.stores ?? [];
126
161
  const { html } = renderComponent({
127
162
  template: mod.template,
128
163
  rootXData: mod.rootXData,
129
164
  componentId: mod.componentId,
130
165
  scopeId: mod.scopeId,
131
166
  loaderData,
132
- registry: opts.registry
167
+ registry: opts.registry,
168
+ stores: storesInitialState(stores)
133
169
  });
134
170
  const doc = shell({
135
171
  body: html,
136
172
  island: stateIsland(mod.componentId, loaderData),
137
173
  css: mod.css + (opts.componentCss ?? ""),
138
174
  pageId: opts.pageId,
139
- clientHref: opts.clientHref
175
+ clientHref: opts.clientHref,
176
+ storeIds: stores.map((s) => s.id)
140
177
  });
141
178
  return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
142
179
  }
143
- function shell({ body, island, css, pageId, clientHref }) {
180
+ function shell({ body, island, css, pageId, clientHref, storeIds = [] }) {
181
+ const storeImports = storeIds.map((id, i) => ` import __s${i} from ${JSON.stringify(id)}`).join("\n");
182
+ const storeRegs = storeIds.map((_, i) => ` Alpine.store(__s${i}.name, __s${i}.factory())`).join("\n");
144
183
  const clientScript = clientHref ? `<script type="module" src="${clientHref}"></script>` : `<script type="module">
145
184
  import Alpine from 'alpinejs'
146
- import ${JSON.stringify(pageId)}
185
+ ${storeImports ? `${storeImports}
186
+ ` : ""} import ${JSON.stringify(pageId)}
147
187
  window.Alpine = Alpine
148
- Alpine.start()
188
+ ${storeRegs ? `${storeRegs}
189
+ ` : ""} Alpine.start()
149
190
  </script>`;
150
191
  return `<!DOCTYPE html>
151
192
  <html lang="en">
@@ -167,5 +208,6 @@ export {
167
208
  renderIslandsPage,
168
209
  scanPages,
169
210
  matchRoute,
211
+ loadStores,
170
212
  renderPage
171
213
  };
@@ -0,0 +1,8 @@
1
+ // src/api/resource.ts
2
+ function isApexResource(x) {
3
+ return typeof x === "object" && x !== null && x.__apexResource === true;
4
+ }
5
+
6
+ export {
7
+ isApexResource
8
+ };
@@ -1,7 +1,6 @@
1
- // src/api/resource.ts
2
- function isApexResource(x) {
3
- return typeof x === "object" && x !== null && x.__apexResource === true;
4
- }
1
+ import {
2
+ isApexResource
3
+ } from "./chunk-HRJTOSYH.js";
5
4
 
6
5
  // src/api/routes.ts
7
6
  import { existsSync, readdirSync } from "fs";
@@ -135,7 +134,6 @@ function createMcpHandler(entries) {
135
134
  }
136
135
 
137
136
  export {
138
- isApexResource,
139
137
  expandApiModule,
140
138
  loadApiRoutes,
141
139
  createApiHandler,
@@ -0,0 +1,15 @@
1
+ // src/store.ts
2
+ function defineStore(name, factory) {
3
+ if (!name || /[^a-zA-Z0-9_$]/.test(name)) {
4
+ throw new Error(`defineStore: invalid store name "${name}" \u2014 use letters, digits, _ or $.`);
5
+ }
6
+ return { __apexStore: true, name, factory };
7
+ }
8
+ function isApexStore(x) {
9
+ return typeof x === "object" && x !== null && x.__apexStore === true;
10
+ }
11
+
12
+ export {
13
+ defineStore,
14
+ isApexStore
15
+ };
@@ -36,7 +36,7 @@ var LOGO = [
36
36
  var FROM = [99, 102, 241];
37
37
  var TO = [34, 211, 238];
38
38
  function banner(subtitle = "The full-stack, AI-native meta-framework for Alpine.js") {
39
- const width = LOGO[0].length;
39
+ const width = LOGO[0]?.length ?? 0;
40
40
  const rows = LOGO.map((line) => {
41
41
  if (!TTY) return ` ${line}`;
42
42
  let out = " ";
@@ -45,7 +45,7 @@ function banner(subtitle = "The full-stack, AI-native meta-framework for Alpine.
45
45
  const r = Math.round(FROM[0] + (TO[0] - FROM[0]) * t);
46
46
  const g = Math.round(FROM[1] + (TO[1] - FROM[1]) * t);
47
47
  const b = Math.round(FROM[2] + (TO[2] - FROM[2]) * t);
48
- out += `\x1B[38;2;${r};${g};${b}m${line[i]}`;
48
+ out += `\x1B[38;2;${r};${g};${b}m${line[i] ?? ""}`;
49
49
  }
50
50
  return out + RESET;
51
51
  });
@@ -70,7 +70,7 @@ function spinner(text) {
70
70
  let i = 0;
71
71
  process.stdout.write("\x1B[?25l");
72
72
  const id = setInterval(() => {
73
- process.stdout.write(`\r\x1B[2K ${color.cyan(FRAMES[i++ % FRAMES.length])} ${text}`);
73
+ process.stdout.write(`\r\x1B[2K ${color.cyan(FRAMES[i++ % FRAMES.length] ?? "")} ${text}`);
74
74
  }, 80);
75
75
  const end = (symbol, t) => {
76
76
  clearInterval(id);
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  banner,
5
5
  color,
6
6
  spinner
7
- } from "./chunk-7E5ZQ6UH.js";
7
+ } from "./chunk-QIXJSQLW.js";
8
8
 
9
9
  // src/cli.ts
10
10
  import { defineCommand as defineCommand2, runMain } from "citty";
@@ -109,10 +109,10 @@ var main = defineCommand2({
109
109
  },
110
110
  subCommands: {
111
111
  new: newCommand,
112
- dev: () => import("./dev-BIPK7O5N.js").then((m) => m.devCommand),
113
- build: () => import("./build-QRHQUUZC.js").then((m) => m.buildCommand),
114
- start: () => import("./start-VJJXI4W3.js").then((m) => m.startCommand),
115
- make: () => import("./make-4LINTKZH.js").then((m) => m.makeCommand),
112
+ dev: () => import("./dev-FS4VVUX2.js").then((m) => m.devCommand),
113
+ build: () => import("./build-MFLFMGGZ.js").then((m) => m.buildCommand),
114
+ start: () => import("./start-BZJZSQHW.js").then((m) => m.startCommand),
115
+ make: () => import("./make-62PPHZQY.js").then((m) => m.makeCommand),
116
116
  migrate: () => import("./migrate-NOGFOFV2.js").then((m) => m.migrateCommand),
117
117
  mcp: () => import("./mcp-DL4J6JFJ.js").then((m) => m.mcpCommand)
118
118
  },
@@ -2,7 +2,7 @@ import {
2
2
  banner,
3
3
  ready,
4
4
  spinner
5
- } from "./chunk-7E5ZQ6UH.js";
5
+ } from "./chunk-QIXJSQLW.js";
6
6
 
7
7
  // src/commands/dev.ts
8
8
  import { resolve } from "path";
@@ -20,7 +20,7 @@ var devCommand = defineCommand({
20
20
  process.stdout.write(banner());
21
21
  const sp = spinner(`Starting dev server${args.islands ? " (islands mode)" : ""}\u2026`);
22
22
  try {
23
- const { startDevServer } = await import("./server-ODH3M2IG.js");
23
+ const { startDevServer } = await import("./server-OXKBXSET.js");
24
24
  const { port: actual } = await startDevServer({ root, port, islands: Boolean(args.islands) });
25
25
  sp.succeed("Dev server ready");
26
26
  ready([
package/dist/index.d.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  import { ZodRawShape, z } from 'zod';
2
- import { Server } from 'node:http';
3
- import { ViteDevServer } from 'vite';
4
- import { ComponentRegistry } from '@apex-stack/kit';
5
2
 
6
3
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7
4
  /** Inferred, validated input object for a route's handler. */
@@ -73,64 +70,29 @@ interface ApexResource {
73
70
  }
74
71
  declare function isApexResource(x: unknown): x is ApexResource;
75
72
 
76
- interface DevServerOptions {
77
- root: string;
78
- port?: number;
79
- /** Page module rendered for every route in the spike (single-route). */
80
- pageId?: string;
81
- /** Render in islands mode (static-first, per-island hydration) instead of one page component. */
82
- islands?: boolean;
83
- }
84
- interface DevServer {
85
- vite: ViteDevServer;
86
- server: Server;
87
- port: number;
88
- close: () => Promise<void>;
73
+ type StoreState = Record<string, unknown>;
74
+ interface ApexStore {
75
+ readonly __apexStore: true;
76
+ readonly name: string;
77
+ readonly factory: () => StoreState;
89
78
  }
90
79
  /**
91
- * Start the Apex dev server: Vite in middleware mode (build/HMR/asset serving)
92
- * fronted by an h3 app whose catch-all handler SSRs the page. Written as h3
93
- * event handlers so the render path can migrate to Nitro unchanged.
94
- */
95
- declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
96
-
97
- /** The shape a compiled `.alpine` SSR module exports (see @apex-stack/vite). */
98
- interface PageModule {
99
- loader: (ctx: {
100
- params: Record<string, string>;
101
- url: string;
102
- }) => unknown | Promise<unknown>;
103
- template: string;
104
- rootXData: string | null;
105
- componentId: string;
106
- scopeId: string;
107
- css: string;
108
- }
109
- interface RenderPageOptions {
110
- /** Load a page's SSR module (dev: vite.ssrLoadModule; prod: static import). */
111
- loadModule: (id: string) => Promise<PageModule>;
112
- /** The page module id to render, e.g. `/pages/index.alpine`. */
113
- pageId: string;
114
- /** The incoming request path. */
115
- url: string;
116
- /** Route params captured from a dynamic segment (e.g. { slug: '...' }). */
117
- params?: Record<string, string>;
118
- /** Registry of embeddable components. */
119
- registry?: ComponentRegistry;
120
- /** Aggregated component CSS to include in the shell. */
121
- componentCss?: string;
122
- /** Post-process the shell HTML (dev: vite.transformIndexHtml). */
123
- transformHtml?: (url: string, html: string) => string | Promise<string>;
124
- /** In a production build, the href of the built client bundle for this page.
125
- * When set, the shell references it instead of the inline dev module. */
126
- clientHref?: string;
127
- }
128
- /**
129
- * The framework's render seam — deliberately dev-server-agnostic so it can move
130
- * verbatim into a Nitro route handler post-spike. Loads the page module, runs
131
- * its loader, renders the component to hydration-safe HTML, and assembles the
132
- * document shell (SSR body + state island + client entry).
80
+ * Define a global, SSR-safe store shared across every page, component, and island.
81
+ *
82
+ * ```ts
83
+ * // stores/cart.ts
84
+ * import { defineStore } from '@apex-stack/core'
85
+ * export default defineStore('cart', () => ({
86
+ * items: [] as string[],
87
+ * get count() { return this.items.length },
88
+ * add(x: string) { this.items.push(x) },
89
+ * }))
90
+ * ```
91
+ *
92
+ * Access it anywhere as `$store.cart` — `$store.cart.count` renders on the server
93
+ * and stays reactive after hydration.
133
94
  */
134
- declare function renderPage(opts: RenderPageOptions): Promise<string>;
95
+ declare function defineStore(name: string, factory: () => StoreState): ApexStore;
96
+ declare function isApexStore(x: unknown): x is ApexStore;
135
97
 
136
- export { type ApexResource, type ApexRoute, type ApexRouteConfig, type ApexRouteHandlerContext, type DevServer, type DevServerOptions, type HttpMethod, type PageModule, type RenderPageOptions, type ResourceRoute, defineApexRoute, isApexResource, renderPage, startDevServer };
98
+ export { type ApexResource, type ApexRoute, type ApexRouteConfig, type ApexRouteHandlerContext, type ApexStore, type HttpMethod, type ResourceRoute, type StoreState, defineApexRoute, defineStore, isApexResource, isApexStore };
package/dist/index.js CHANGED
@@ -1,13 +1,10 @@
1
- import {
2
- startDevServer
3
- } from "./chunk-SH3XEJGV.js";
4
- import "./chunk-4VG3CZ6H.js";
5
1
  import {
6
2
  isApexResource
7
- } from "./chunk-DSUIB3JH.js";
3
+ } from "./chunk-HRJTOSYH.js";
8
4
  import {
9
- renderPage
10
- } from "./chunk-PAMD24NK.js";
5
+ defineStore,
6
+ isApexStore
7
+ } from "./chunk-MZVLRU3R.js";
11
8
 
12
9
  // src/api/defineRoute.ts
13
10
  function defineApexRoute(config) {
@@ -22,7 +19,7 @@ function defineApexRoute(config) {
22
19
  }
23
20
  export {
24
21
  defineApexRoute,
22
+ defineStore,
25
23
  isApexResource,
26
- renderPage,
27
- startDevServer
24
+ isApexStore
28
25
  };
@@ -2,6 +2,9 @@
2
2
  import { existsSync, mkdirSync, writeFileSync } from "fs";
3
3
  import { dirname, join, resolve } from "path";
4
4
  import { defineCommand } from "citty";
5
+ function pascalCase(s) {
6
+ return s.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
7
+ }
5
8
  function pageTemplate(name) {
6
9
  return `<script server lang="ts">
7
10
  export function loader() {
@@ -30,6 +33,18 @@ function componentTemplate() {
30
33
  </style>
31
34
  `;
32
35
  }
36
+ function storeTemplate(name) {
37
+ return `import { defineStore } from '@apex-stack/core'
38
+
39
+ // Access anywhere as $store.${name} \u2014 SSR-rendered and reactive after hydration.
40
+ export default defineStore('${name}', () => ({
41
+ count: 0,
42
+ increment() {
43
+ this.count++
44
+ },
45
+ }))
46
+ `;
47
+ }
33
48
  function apiTemplate(name) {
34
49
  return `import { defineApexRoute } from '@apex-stack/core'
35
50
  import { z } from 'zod'
@@ -49,23 +64,25 @@ function plan(kind, name, root) {
49
64
  case "page":
50
65
  return { path: join(root, "pages", `${name}.alpine`), contents: pageTemplate(name) };
51
66
  case "component":
52
- return { path: join(root, "components", `${name}.alpine`), contents: componentTemplate() };
67
+ return { path: join(root, "components", `${pascalCase(name)}.alpine`), contents: componentTemplate() };
53
68
  case "api":
54
69
  return { path: join(root, "server", "api", `${name}.ts`), contents: apiTemplate(name) };
70
+ case "store":
71
+ return { path: join(root, "stores", `${name}.ts`), contents: storeTemplate(name) };
55
72
  }
56
73
  }
57
74
  var makeCommand = defineCommand({
58
- meta: { name: "make", description: "Generate a page, component, or API route" },
75
+ meta: { name: "make", description: "Generate a page, component, API route, or store" },
59
76
  args: {
60
- kind: { type: "positional", required: true, description: "page | component | api" },
77
+ kind: { type: "positional", required: true, description: "page | component | api | store" },
61
78
  name: { type: "positional", required: true, description: "Name (about, Counter, todos, \u2026)" },
62
79
  root: { type: "string", description: "Project root", default: "." }
63
80
  },
64
81
  run({ args }) {
65
82
  const kind = args.kind;
66
- if (kind !== "page" && kind !== "component" && kind !== "api") {
83
+ if (kind !== "page" && kind !== "component" && kind !== "api" && kind !== "store") {
67
84
  console.error(`
68
- Unknown type "${args.kind}". Use: page | component | api
85
+ Unknown type "${args.kind}". Use: page | component | api | store
69
86
  `);
70
87
  process.exit(1);
71
88
  }
@@ -5,13 +5,16 @@ import {
5
5
  createApiHandler,
6
6
  createMcpHandler,
7
7
  loadApiRoutes
8
- } from "./chunk-DSUIB3JH.js";
8
+ } from "./chunk-JWYNLP4L.js";
9
+ import "./chunk-HRJTOSYH.js";
9
10
  import {
11
+ loadStores,
10
12
  matchRoute,
11
13
  renderIslandsPage,
12
14
  renderPage,
13
15
  scanPages
14
- } from "./chunk-PAMD24NK.js";
16
+ } from "./chunk-D3VZDJ3R.js";
17
+ import "./chunk-MZVLRU3R.js";
15
18
 
16
19
  // src/dev/server.ts
17
20
  import { createServer as createHttpServer } from "http";
@@ -39,11 +42,12 @@ function firstFileFrame(stack, root) {
39
42
  let m;
40
43
  const frames = [];
41
44
  while (m = re.exec(stack)) {
42
- let file = m[1];
43
- if (/^[A-Za-z]:[\\/]/.test(file) === false && !file.startsWith("/")) continue;
44
- file = file.replace(/\//g, process.platform === "win32" ? "\\" : "/");
45
+ const raw = m[1];
46
+ if (!raw) continue;
47
+ if (/^[A-Za-z]:[\\/]/.test(raw) === false && !raw.startsWith("/")) continue;
48
+ const file = raw.replace(/\//g, process.platform === "win32" ? "\\" : "/");
45
49
  if (existsSync(file) && !file.includes("node_modules")) {
46
- frames.push({ file, line: Number(m[2]), col: Number(m[3]) });
50
+ frames.push({ file, line: Number(m[2] ?? 0), col: Number(m[3] ?? 0) });
47
51
  }
48
52
  }
49
53
  return frames.find((f) => f.file.startsWith(root)) ?? frames[0];
@@ -154,7 +158,11 @@ async function startDevServer(options) {
154
158
  const alpine = tryResolve("alpinejs");
155
159
  const kit = tryResolve("@apex-stack/kit");
156
160
  const coreClient = fileURLToPath(new URL("./client.js", import.meta.url));
157
- const alias = { "@apex-stack/core/client": coreClient };
161
+ const coreSelf = fileURLToPath(new URL("./index.js", import.meta.url));
162
+ const alias = {
163
+ "@apex-stack/core/client": coreClient,
164
+ "@apex-stack/core": coreSelf
165
+ };
158
166
  if (alpine) alias.alpinejs = alpine;
159
167
  if (kit) alias["@apex-stack/kit"] = kit;
160
168
  const vite = await createViteServer({
@@ -193,6 +201,7 @@ async function startDevServer(options) {
193
201
  options.root,
194
202
  (id) => ssrLoad(id)
195
203
  );
204
+ const stores = await loadStores(options.root, (id) => ssrLoad(id));
196
205
  const render = options.islands ? renderIslandsPage : renderPage;
197
206
  const html = await render({
198
207
  loadModule: (id) => ssrLoad(id),
@@ -201,6 +210,7 @@ async function startDevServer(options) {
201
210
  url,
202
211
  registry,
203
212
  componentCss,
213
+ stores,
204
214
  transformHtml: (u, doc) => vite.transformIndexHtml(u, doc)
205
215
  });
206
216
  setResponseHeader(event, "Content-Type", "text/html");
@@ -233,7 +243,6 @@ async function startDevServer(options) {
233
243
  }
234
244
  };
235
245
  }
236
-
237
246
  export {
238
247
  startDevServer
239
248
  };
@@ -3,12 +3,14 @@ import {
3
3
  createMcpHandler,
4
4
  expandApiModule,
5
5
  hasMcpRoutes
6
- } from "./chunk-DSUIB3JH.js";
6
+ } from "./chunk-JWYNLP4L.js";
7
+ import "./chunk-HRJTOSYH.js";
7
8
  import {
8
9
  matchRoute,
9
10
  renderIslandsPage,
10
11
  renderPage
11
- } from "./chunk-PAMD24NK.js";
12
+ } from "./chunk-D3VZDJ3R.js";
13
+ import "./chunk-MZVLRU3R.js";
12
14
 
13
15
  // src/commands/start.ts
14
16
  import { existsSync as existsSync2 } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apex-stack/core",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,6 +54,7 @@
54
54
  "engines": {
55
55
  "node": ">=20.19"
56
56
  },
57
+ "sideEffects": false,
57
58
  "scripts": {
58
59
  "build": "node scripts/copy-templates.mjs && tsup",
59
60
  "dev": "tsup --watch",
@@ -1,9 +0,0 @@
1
- import {
2
- startDevServer
3
- } from "./chunk-SH3XEJGV.js";
4
- import "./chunk-4VG3CZ6H.js";
5
- import "./chunk-DSUIB3JH.js";
6
- import "./chunk-PAMD24NK.js";
7
- export {
8
- startDevServer
9
- };