@eventmodelers/cli 1.0.53 → 1.0.55
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 +6 -0
- package/cli.js +29 -1
- package/package.json +1 -1
- package/shared/build-kit/lib/ralph.js +103 -5
- package/shared/build-kit/lib/util/find-slice.cjs +2 -0
- package/shared/skills/learn-eventmodelers-api/SKILL.md +3 -0
- package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +17 -4
- package/stacks/node/templates/root/.githooks/pre-commit +10 -3
- package/stacks/react/templates/.claude/skills/build-automation/SKILL.md +42 -0
- package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +43 -0
- package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +42 -0
- package/stacks/react/templates/build-kit/CLAUDE.md +62 -0
- package/stacks/react/templates/build-kit/README.md +79 -0
- package/stacks/react/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/react/templates/build-kit/lib/backend-prompt.md +135 -0
- package/stacks/react/templates/build-kit/lib/prompt.md +139 -0
- package/stacks/react/templates/build-kit/lib/ralph.js +508 -0
- package/stacks/react/templates/build-kit/package.json +9 -0
- package/stacks/react/templates/build-kit/ralph-claude.js +107 -0
- package/stacks/react/templates/build-kit/ralph-ollama.js +40 -0
- package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +17 -4
- package/stacks/supabase/templates/root/.githooks/pre-commit +10 -3
- package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +305 -0
- package/stacks/supabase-react/templates/.claude/skills/build-state-view/SKILL.md +238 -0
- package/stacks/supabase-react/templates/.claude/skills/init-style-guide/SKILL.md +60 -0
- package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/SKILL.md +28 -0
- package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/references/README.md +5 -0
- package/stacks/supabase-react/templates/build-kit/CLAUDE.md +149 -0
- package/stacks/supabase-react/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +139 -0
- package/stacks/supabase-react/templates/build-kit/lib/prompt.md +145 -0
- package/stacks/supabase-react/templates/root/.env.example +12 -0
- package/stacks/supabase-react/templates/root/.oxlintrc.json +9 -0
- package/stacks/supabase-react/templates/root/README.md +49 -0
- package/stacks/supabase-react/templates/root/index.html +13 -0
- package/stacks/supabase-react/templates/root/package.json +26 -0
- package/stacks/supabase-react/templates/root/public/favicon.svg +1 -0
- package/stacks/supabase-react/templates/root/public/icons.svg +24 -0
- package/stacks/supabase-react/templates/root/src/App.css +184 -0
- package/stacks/supabase-react/templates/root/src/App.tsx +122 -0
- package/stacks/supabase-react/templates/root/src/assets/hero.png +0 -0
- package/stacks/supabase-react/templates/root/src/assets/react.svg +1 -0
- package/stacks/supabase-react/templates/root/src/assets/vite.svg +1 -0
- package/stacks/supabase-react/templates/root/src/index.css +111 -0
- package/stacks/supabase-react/templates/root/src/lib/api.ts +127 -0
- package/stacks/supabase-react/templates/root/src/lib/supabase.ts +10 -0
- package/stacks/supabase-react/templates/root/src/main.tsx +10 -0
- package/stacks/supabase-react/templates/root/src/slices/.gitkeep +0 -0
- package/stacks/supabase-react/templates/root/src/vite-env.d.ts +13 -0
- package/stacks/supabase-react/templates/root/tsconfig.app.json +26 -0
- package/stacks/supabase-react/templates/root/tsconfig.json +7 -0
- package/stacks/supabase-react/templates/root/tsconfig.node.json +23 -0
- package/stacks/supabase-react/templates/root/vite.config.ts +7 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { supabase } from './supabase';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Common API client — the ONLY place that knows about base URLs, auth headers,
|
|
5
|
+
* correlation/causation tracing, and how to reach Supabase tables. Slice components
|
|
6
|
+
* must never call `fetch` directly, read the Supabase session, or import
|
|
7
|
+
* `./supabase` themselves — they call `postCommand`/`queryReadModel` and handle only
|
|
8
|
+
* the request/response shape their command/read model defines.
|
|
9
|
+
*
|
|
10
|
+
* Every call here has two implementations: a real one (Supabase) and a mock one that
|
|
11
|
+
* serves a numbered sample instead. This is what makes every slice component testable
|
|
12
|
+
* in isolation — flip `VITE_DATA_MODE` to `mock` and every read/write in the app is
|
|
13
|
+
* served from `samples/sample-<VITE_MOCK_SAMPLE>.json` files instead of a live backend.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const API_BASE = import.meta.env.VITE_API_BASE ?? '';
|
|
17
|
+
|
|
18
|
+
/** `true` when reads/writes should be served from samples instead of Supabase/the backend. */
|
|
19
|
+
export function isMockMode(): boolean {
|
|
20
|
+
return import.meta.env.VITE_DATA_MODE === 'mock';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Which numbered sample set is active in mock mode, e.g. `"1"`, `"2"`. */
|
|
24
|
+
export function activeMockSample(): string {
|
|
25
|
+
return import.meta.env.VITE_MOCK_SAMPLE ?? '1';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function getAuthHeaders(): Promise<Record<string, string>> {
|
|
29
|
+
const {
|
|
30
|
+
data: { session },
|
|
31
|
+
} = await supabase.auth.getSession();
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
Authorization: session ? `Bearer ${session.access_token}` : '',
|
|
36
|
+
'x-correlation-id': crypto.randomUUID(),
|
|
37
|
+
'x-causation-id': crypto.randomUUID(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class ApiError extends Error {
|
|
42
|
+
status: number;
|
|
43
|
+
body: unknown;
|
|
44
|
+
|
|
45
|
+
constructor(message: string, status: number, body?: unknown) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = 'ApiError';
|
|
48
|
+
this.status = status;
|
|
49
|
+
this.body = body;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* POST a command to `path` (taken verbatim from the command's API description in
|
|
55
|
+
* slice.json — never invented) with `body` as the JSON payload. Resolves with the
|
|
56
|
+
* parsed JSON response, or throws `ApiError` on a non-2xx response.
|
|
57
|
+
*
|
|
58
|
+
* In mock mode this never hits the network — it resolves with `{ ok: true }` so a
|
|
59
|
+
* command component can be exercised (submitting/success states) fully offline.
|
|
60
|
+
*/
|
|
61
|
+
export async function postCommand<TResponse = unknown>(
|
|
62
|
+
path: string,
|
|
63
|
+
body: unknown,
|
|
64
|
+
): Promise<TResponse> {
|
|
65
|
+
if (isMockMode()) {
|
|
66
|
+
return { ok: true } as TResponse;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const headers = await getAuthHeaders();
|
|
70
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers,
|
|
73
|
+
body: JSON.stringify(body),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const payload = await res.json().catch(() => undefined);
|
|
77
|
+
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const message =
|
|
80
|
+
(payload && (payload.error || payload.message)) || `Request failed with status ${res.status}`;
|
|
81
|
+
throw new ApiError(message, res.status, payload);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return payload as TResponse;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ReadModelParams {
|
|
88
|
+
/** `readmodel.apiEndpoint`, verbatim — the Postgres table/view name. */
|
|
89
|
+
table: string;
|
|
90
|
+
/** Column names to select, comma-separated (never `'*'`). */
|
|
91
|
+
select: string;
|
|
92
|
+
/** Simple equality filters, e.g. `{ customer_id: customerId }`. */
|
|
93
|
+
filters?: Record<string, unknown>;
|
|
94
|
+
/** `true` for a single-row read model (`.maybeSingle()`), `false` for a list. */
|
|
95
|
+
single?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read a read model's data — `params.table` via Supabase in the real implementation,
|
|
100
|
+
* or `samples[activeMockSample()]` in mock mode. `samples` is this component's own
|
|
101
|
+
* `samples/sample-*.json` map (see the component for how it's built); every read model
|
|
102
|
+
* component supplies its own, `queryReadModel` never reaches into a slice folder itself.
|
|
103
|
+
*/
|
|
104
|
+
export async function queryReadModel<T>(
|
|
105
|
+
params: ReadModelParams,
|
|
106
|
+
samples?: Record<string, T>,
|
|
107
|
+
): Promise<T> {
|
|
108
|
+
if (isMockMode()) {
|
|
109
|
+
const n = activeMockSample();
|
|
110
|
+
const sample = samples?.[n];
|
|
111
|
+
if (sample === undefined) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Mock mode is on (VITE_MOCK_SAMPLE=${n}) but no sample-${n}.json exists for table "${params.table}" — add one under this component's samples/ folder.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return sample;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let query = supabase.from(params.table).select(params.select);
|
|
120
|
+
for (const [column, value] of Object.entries(params.filters ?? {})) {
|
|
121
|
+
query = query.eq(column, value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { data, error } = params.single ? await query.maybeSingle() : await query;
|
|
125
|
+
if (error) throw new ApiError(error.message, 500, error);
|
|
126
|
+
return data as T;
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createClient, type SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
4
|
+
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
5
|
+
|
|
6
|
+
if (!supabaseUrl || !supabaseAnonKey) {
|
|
7
|
+
throw new Error('Missing VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY environment variables');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseAnonKey);
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
interface ImportMetaEnv {
|
|
4
|
+
readonly VITE_SUPABASE_URL: string;
|
|
5
|
+
readonly VITE_SUPABASE_ANON_KEY: string;
|
|
6
|
+
readonly VITE_API_BASE: string;
|
|
7
|
+
readonly VITE_DATA_MODE?: 'mock' | 'supabase';
|
|
8
|
+
readonly VITE_MOCK_SAMPLE?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ImportMeta {
|
|
12
|
+
readonly env: ImportMetaEnv;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["ES2023", "DOM"],
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"types": ["vite/client"],
|
|
8
|
+
"allowArbitraryExtensions": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
|
|
11
|
+
/* Bundler mode */
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"moduleDetection": "force",
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"erasableSyntaxOnly": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"erasableSyntaxOnly": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["vite.config.ts"]
|
|
23
|
+
}
|