@facetlayer/prism-framework 0.4.0

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 (42) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/CHANGELOG +28 -0
  3. package/CLAUDE.md +44 -0
  4. package/README.md +47 -0
  5. package/build.mts +8 -0
  6. package/dist/call-command.d.ts +13 -0
  7. package/dist/call-command.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +475 -0
  11. package/dist/config/ConfigFile.d.ts +7 -0
  12. package/dist/config/ConfigFile.d.ts.map +1 -0
  13. package/dist/config/index.d.ts +4 -0
  14. package/dist/config/index.d.ts.map +1 -0
  15. package/dist/config/loadConfig.d.ts +11 -0
  16. package/dist/config/loadConfig.d.ts.map +1 -0
  17. package/dist/generate-api-clients.d.ts +6 -0
  18. package/dist/generate-api-clients.d.ts.map +1 -0
  19. package/dist/getPorts.d.ts +10 -0
  20. package/dist/getPorts.d.ts.map +1 -0
  21. package/dist/list-endpoints-command.d.ts +5 -0
  22. package/dist/list-endpoints-command.d.ts.map +1 -0
  23. package/dist/loadEnv.d.ts +12 -0
  24. package/dist/loadEnv.d.ts.map +1 -0
  25. package/docs/endpoint-tools.md +116 -0
  26. package/docs/env-files.md +64 -0
  27. package/docs/generate-api-clients-config.md +84 -0
  28. package/docs/getting-started.md +86 -0
  29. package/package.json +43 -0
  30. package/src/call-command.ts +147 -0
  31. package/src/cli.ts +163 -0
  32. package/src/config/ConfigFile.ts +7 -0
  33. package/src/config/index.ts +3 -0
  34. package/src/config/loadConfig.ts +58 -0
  35. package/src/generate-api-clients.ts +203 -0
  36. package/src/getPorts.ts +39 -0
  37. package/src/list-endpoints-command.ts +34 -0
  38. package/src/loadEnv.ts +59 -0
  39. package/test/call-command.test.ts +96 -0
  40. package/test/generate-api-clients.test.ts +33 -0
  41. package/test/generate-api-clients.test.ts.disabled +75 -0
  42. package/tsconfig.json +21 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Convert OpenAPI path parameter format {param} to Express format :param
3
+ */
4
+ export declare function convertToExpressPath(openApiPath: string): string;
5
+ export declare function generateApiClients(baseUrl: string, outputFiles: string[]): Promise<void>;
6
+ //# sourceMappingURL=generate-api-clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-api-clients.d.ts","sourceRoot":"","sources":["../src/generate-api-clients.ts"],"names":[],"mappings":"AAqCA;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEhE;AA2DD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqG9F"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Gets the port from a directory's .env file
3
+ * @param options - Configuration options
4
+ * @param options.dir - Directory to search for .env file (defaults to current working directory)
5
+ * @returns The port number
6
+ */
7
+ export declare function getPort(options?: {
8
+ dir?: string;
9
+ }): number;
10
+ //# sourceMappingURL=getPorts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPorts.d.ts","sourceRoot":"","sources":["../src/getPorts.ts"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAS1D"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * List all available endpoints by calling /endpoints.json
3
+ */
4
+ export declare function listEndpoints(baseUrl: string): Promise<void>;
5
+ //# sourceMappingURL=list-endpoints-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-endpoints-command.d.ts","sourceRoot":"","sources":["../src/list-endpoints-command.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BlE"}
@@ -0,0 +1,12 @@
1
+ export interface EnvConfig {
2
+ port: number;
3
+ baseUrl: string;
4
+ }
5
+ /**
6
+ * Load and parse the .env file from the project directory
7
+ * @param cwd - Current working directory to search from
8
+ * @returns Configuration object with port and baseUrl
9
+ * @throws Error if .env file is missing or PRISM_API_PORT is not defined
10
+ */
11
+ export declare function loadEnv(cwd: string): EnvConfig;
12
+ //# sourceMappingURL=loadEnv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadEnv.d.ts","sourceRoot":"","sources":["../src/loadEnv.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA2C9C"}
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: endpoint-tools
3
+ description: CLI tools for listing and calling API endpoints on a running Prism server
4
+ ---
5
+
6
+ # Endpoint Tools
7
+
8
+ The `prism` CLI provides commands for interacting with your API endpoints. These tools connect to a running Prism API server.
9
+
10
+ ## Prerequisites
11
+
12
+ Both commands require:
13
+ - A running Prism API server
14
+ - A `.env` file with `PRISM_API_PORT` set to your server's port
15
+
16
+ ## prism list-endpoints
17
+
18
+ Lists all available endpoints from a running server.
19
+
20
+ ```bash
21
+ prism list-endpoints
22
+ ```
23
+
24
+ Output:
25
+ ```
26
+ Using API server at: http://localhost:4003
27
+
28
+ Available endpoints:
29
+
30
+ GET /users
31
+ List all users
32
+ POST /users
33
+ Create a new user
34
+ GET /users/:id
35
+ Get user by ID
36
+ DELETE /users/:id
37
+ Delete a user
38
+ ```
39
+
40
+ ## prism call
41
+
42
+ Calls an endpoint on a running server.
43
+
44
+ ### Basic Usage
45
+
46
+ **Important:** Do not use the `/api` prefix in endpoint paths. Use the path directly as defined in the endpoint.
47
+
48
+ ```bash
49
+ # GET request
50
+ prism call /users
51
+
52
+ # Explicit method
53
+ prism call GET /users
54
+
55
+ # POST with data
56
+ prism call POST /users --name "John Doe" --email "john@example.com"
57
+
58
+ # Other methods
59
+ prism call PUT /users/123 --name "Jane"
60
+ prism call PATCH /users/123 --status active
61
+ prism call DELETE /users/123
62
+ ```
63
+
64
+ ### Passing Data
65
+
66
+ Named arguments become the request body:
67
+
68
+ ```bash
69
+ prism call POST /users --name "John" --age 30 --active true
70
+ ```
71
+
72
+ Sends:
73
+ ```json
74
+ {
75
+ "name": "John",
76
+ "age": 30,
77
+ "active": true
78
+ }
79
+ ```
80
+
81
+ ### JSON Objects
82
+
83
+ Arguments that look like JSON are parsed automatically:
84
+
85
+ ```bash
86
+ prism call POST /config --settings '{"timeout": 30, "retries": 3}'
87
+ ```
88
+
89
+ ### Output
90
+
91
+ The command prints the response status and body:
92
+
93
+ ```
94
+ Response status: 200
95
+ Response: {"id":"123","name":"John","email":"john@example.com"}
96
+ ```
97
+
98
+ ## Troubleshooting
99
+
100
+ ### "Failed to connect"
101
+
102
+ 1. **Check the server is running** - Start your API server
103
+ 2. **Verify PRISM_API_PORT** - Ensure `.env` contains `PRISM_API_PORT` matching your server port
104
+ 3. **Check dotenv is loaded** - Your server must load the `.env` file:
105
+
106
+ ```typescript
107
+ import { config } from 'dotenv';
108
+ config({ path: '.env' });
109
+
110
+ const PORT = parseInt(process.env.PRISM_API_PORT, 10);
111
+ await startServer({ app, port: PORT });
112
+ ```
113
+
114
+ ### "Endpoint not found"
115
+
116
+ Use `prism list-endpoints` to see available endpoints and verify the path.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: env-files
3
+ description: Recommended strategy for environment variable configuration in Prism Framework projects
4
+ ---
5
+
6
+ # Environment Files Strategy
7
+
8
+ The typical env variables needed for a Prism app are:
9
+
10
+ ### Backend
11
+
12
+ Next to the API / backend code there should be a .env file with:
13
+
14
+
15
+ | name | example value | description |
16
+ | ---- | ------------- | ----------- |
17
+ | PRISM_API_PORT | `<port number>` | The port for the web server |
18
+ | DATABASE_DIR | data | The relative path to a folder that has SQlite databases |
19
+ | WEB_BASE_URL | `http://localhost:<number>` | The URL for the web server |
20
+ | ALLOW_LOCALHOST | `true` | Whether to allow localhost CORS origins for local development |
21
+
22
+ ### Frontend
23
+
24
+ #### Next.js
25
+
26
+ | name | example value | description |
27
+ | ---- | ------------- | ----------- |
28
+ | PORT | `<port number>` | The port for the web server. Should match WEB_BASE_URL from the backend. |
29
+ | NEXT_PUBLIC_API_URL | `http://localhost:<number>` | The URL for the API server. Should match PRISM_API_PORT from the backend. |
30
+
31
+ Remember that if a variable is used in the frontend code, it needs a prefix of `NEXT_PUBLIC_`.
32
+
33
+ Next.js doesn't load the .env file by default so it's recommended to have this script in package.json:
34
+
35
+ "scripts": {
36
+ "dev": "dotenv -e .env next dev",
37
+ ...
38
+ },
39
+
40
+ #### Vite
41
+
42
+ | name | example value | description |
43
+ | ---- | ------------- | ----------- |
44
+ | VITE_API_URL | `http://localhost:<number>` | The URL for the API server. Only needed if not using the Vite proxy approach. |
45
+
46
+ Vite uses the `VITE_` prefix instead of `NEXT_PUBLIC_` for client-exposed variables. Vite loads `.env` files automatically — no extra setup needed.
47
+
48
+ Access in code:
49
+
50
+ ```typescript
51
+ const apiUrl = import.meta.env.VITE_API_URL;
52
+ ```
53
+
54
+ See the `vite-setup` doc in `@facetlayer/prism-framework-ui` for more details.
55
+
56
+ # Port assignment
57
+
58
+ It's recommended to use the `@facetlayer/port-assignment` tool if you need to assign new unique port numbers.
59
+
60
+ Example:
61
+
62
+ npx @facetlayer/port-assignment claim --name <project name>
63
+
64
+ Run `npx @facetlayer/port-assigment list-docs` for more documentation.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: generate-api-clients-config
3
+ description: Configuration file setup for automatic API client type generation
4
+ ---
5
+
6
+ # Generate API Clients Configuration
7
+
8
+ The `prism generate-api-clients` command generates TypeScript types from your API's OpenAPI schema. You can configure output targets using a `.prism.qc` config file.
9
+
10
+ ## Config File Location
11
+
12
+ Create a `.prism.qc` file in your project root (where you run `prism` commands from).
13
+
14
+ ## Basic Syntax
15
+
16
+ The config uses the QC format. Each target is defined with a `generate-api-client` command:
17
+
18
+ ```
19
+ generate-api-client
20
+ output-file=<path-to-output-file>
21
+ ```
22
+
23
+ The `output-file` path is relative to the project root.
24
+
25
+ ## Examples
26
+
27
+ ### Single Output File
28
+
29
+ ```
30
+ generate-api-client
31
+ output-file=./src/api-types.ts
32
+ ```
33
+
34
+ ### Multiple Output Files
35
+
36
+ You can define multiple targets to generate the same types to multiple locations:
37
+
38
+ ```
39
+ generate-api-client
40
+ output-file=./ui/src/lib/api-types.ts
41
+
42
+ generate-api-client
43
+ output-file=./mobile/src/api-types.ts
44
+ ```
45
+
46
+ ### Typical Project Layouts
47
+
48
+ **Monorepo with separate UI package:**
49
+
50
+ ```
51
+ generate-api-client
52
+ output-file=./ui/src/lib/api-types.ts
53
+ ```
54
+
55
+ **Monorepo with web directory:**
56
+
57
+ ```
58
+ generate-api-client
59
+ output-file=./web/src/client/api-types.ts
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ Once configured, run:
65
+
66
+ ```bash
67
+ prism generate-api-clients
68
+ ```
69
+
70
+ The command will:
71
+ 1. Read the API server URL from your `.env` file (`PRISM_API_PORT`)
72
+ 2. Fetch the OpenAPI schema from `http://localhost:<port>/openapi.json`
73
+ 3. Generate TypeScript types and write them to all configured output files
74
+
75
+ You can also override the config by specifying `--out` directly:
76
+
77
+ ```bash
78
+ prism generate-api-clients --out ./custom/path/types.ts
79
+ ```
80
+
81
+ ## Requirements
82
+
83
+ - The API server must be running and serving `/openapi.json`
84
+ - A `.env` file with `PRISM_API_PORT` must exist in the project root
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: getting-started
3
+ description: Guide for setting up a new Prism Framework project with type-safe API and frontend client generation
4
+ ---
5
+
6
+ # Prism Framework Getting Started Guide
7
+
8
+ ## Intro
9
+
10
+ "Prism framework" is a set of Node.js libraries for a full-stack app that can target multiple platforms.
11
+
12
+ This includes the following NPM libraries:
13
+
14
+ ### `@facetlayer/prism-framework`
15
+ - Base library with tooling for various development and testing tasks
16
+ - Should be installed at the top level in the devDependencies section
17
+ - First place to look for documentation (`prism list-docs`)
18
+
19
+ ### `@facetlayer/prism-framework-api`
20
+ - Backend API framework
21
+ - Can be hosted on HTTP with Express.js
22
+ - Also supports other launch methods such as IPC for Electron
23
+
24
+ ### `@facetlayer/prism-framework-ui`
25
+ - Helpers for React-based frontend web apps
26
+
27
+ ### `@facetlayer/prism-framework-desktop`
28
+ - Helper framework for Electron based desktop apps
29
+
30
+ ## Getting started
31
+
32
+ - Install `@facetlayer/prism-framework`
33
+ - Start using the `prism` CLI tool, especially:
34
+ - `prism list-docs` - List available documentation files.
35
+ - `prism get-doc <name>` - Read a documentation file.
36
+
37
+ ## Example Project Setup
38
+
39
+ Some ways to set up the repo for a Prism project:
40
+
41
+ ### Option 1: API in separate directory
42
+
43
+ - `./api` - Backend API
44
+ - `./api/package.json` - Contains prism-framework-api
45
+ - `./api/src/` - Backend service implementation
46
+ - `./web` or `./ui` - Frontend web app
47
+ - `./web/package.json` - Contains Next.js or Vite, and prism-framework-ui
48
+ - `./web/src` - Frontend implementation
49
+
50
+ ### Option 2: API in top level directory
51
+
52
+ - `./package.json` - Contains prism-framework-api
53
+ - `./src` - Backend API source code
54
+ - `./web` or `./ui` - Frontend web app
55
+ - `./web/package.json` - Contains Next.js or Vite, and prism-framework-ui
56
+ - `./web/src`
57
+
58
+ ### Frontend framework choice
59
+
60
+ - **Vite + React** - Recommended for local tools, GUIs, and apps that don't need SSR. Lighter weight and faster dev server. See the `vite-setup` doc in `@facetlayer/prism-framework-ui`.
61
+ - **Next.js** - Recommended for production web apps that need SSR, routing, or deployment to platforms like Vercel. See the `nextjs-setup` doc in `@facetlayer/prism-framework-ui`.
62
+
63
+ ## Packages
64
+
65
+ - For package management use `pnpm`
66
+ - Make sure to set up a `pnpm-workspace.yaml` file in the top level
67
+
68
+ ### Top level tools
69
+
70
+ The top level of the project should have these dependencies:
71
+
72
+ `pnpm add typescript dotenv @facetlayer/prism-framework`
73
+
74
+ ## Local service management
75
+
76
+ Prism projects usually use the `candle` tool (from @facetlayer/candle) to run local services.
77
+
78
+ Examples:
79
+
80
+ Browse documentation:
81
+ `candle list-docs`
82
+
83
+ Set up services in the .candle.json file:
84
+ `candle add-service api "node --watch src/_main/api.ts" --root ./api
85
+ `candle add-service web "pnpm dev" --root ./web
86
+
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@facetlayer/prism-framework",
3
+ "version": "0.4.0",
4
+ "description": "Base library and CLI tools for the Prism app framework",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "types": "dist/cli.d.ts",
8
+ "bin": {
9
+ "prism": "dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "node build.mts build",
13
+ "prepublishOnly": "node build.mts validate && node build.mts build",
14
+ "typecheck": "tsc -p .",
15
+ "test": "vitest run --testTimeout=30000",
16
+ "local:install": "pnpm build && npm i -g ."
17
+ },
18
+ "keywords": [
19
+ "framework",
20
+ "testing",
21
+ "tools",
22
+ "typescript"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "packageManager": "pnpm@10.15.1",
27
+ "dependencies": {
28
+ "@facetlayer/doc-files-helper": "0.1.2",
29
+ "@facetlayer/prism-framework-api": "0.2.0",
30
+ "@facetlayer/qc": "^0.1.0",
31
+ "dotenv": "^16.4.7",
32
+ "uuid": "^11.1.0",
33
+ "yargs": "18.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@facetlayer/build-config-nodejs": "^0.3.0",
37
+ "@types/node": "^22.10.2",
38
+ "@types/uuid": "^10.0.0",
39
+ "@types/yargs": "17.0.34",
40
+ "typescript": "^5.7.2",
41
+ "vitest": "^3.0.0"
42
+ }
43
+ }
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ const EVERY_METHOD = new Set(['GET','POST','PUT','PATCH',"DELETE"]);
4
+
5
+ export interface CallEndpointLooseOptions {
6
+ baseUrl: string
7
+ positionalArgs: string[]
8
+ namedArgs: Record<string,any>
9
+ quiet?: boolean
10
+ }
11
+
12
+ interface CallEndpointOptions {
13
+ baseUrl: string
14
+ method: string
15
+ path: string
16
+ requestBody: null | Record<string, any>
17
+ }
18
+
19
+ function parseValue(value: any): any {
20
+ if (typeof value === 'string') {
21
+ // Special case: If the string value looks like {..} or [..], then
22
+ // parse it as JSON. This way we can use object-based endpoints
23
+ // using CLI parameters.
24
+ const trimmed = value.trim();
25
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
26
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
27
+ try {
28
+ return JSON.parse(trimmed);
29
+ } catch {
30
+ // If JSON parsing fails, keep the original string
31
+ return value;
32
+ }
33
+ }
34
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
35
+ // Recursively parse nested objects (yargs creates these from dot notation)
36
+ const result: Record<string, any> = {};
37
+ for (const [k, v] of Object.entries(value)) {
38
+ result[k] = parseValue(v);
39
+ }
40
+ return result;
41
+ }
42
+ return value;
43
+ }
44
+
45
+ export function parseNamedArgs(namedArgs: Record<string, any>): Record<string, any> {
46
+ const result: Record<string, any> = {};
47
+ for (const [key, value] of Object.entries(namedArgs)) {
48
+ result[key] = parseValue(value);
49
+ }
50
+ return result;
51
+ }
52
+
53
+ function parseOptions(looseOptions: CallEndpointLooseOptions): CallEndpointOptions {
54
+ const result: CallEndpointOptions = {
55
+ baseUrl: looseOptions.baseUrl,
56
+ method: 'GET',
57
+ path: '/',
58
+ requestBody: parseNamedArgs(looseOptions.namedArgs),
59
+ };
60
+
61
+ // Look at the positional args and figure out what they mean.
62
+ for (const positional of looseOptions.positionalArgs) {
63
+ if (positional.startsWith('/')) {
64
+ result.path = positional;
65
+ continue;
66
+ }
67
+
68
+ if (EVERY_METHOD.has(positional.toUpperCase())) {
69
+ result.method = positional.toUpperCase();
70
+ continue;
71
+ }
72
+
73
+ if (positional.startsWith("http:") || positional.startsWith("https:")) {
74
+ result.baseUrl = positional;
75
+ continue;
76
+ }
77
+
78
+ throw new Error("unrecognized positional arg:" + positional);
79
+ }
80
+
81
+ return result;
82
+
83
+ }
84
+
85
+ /**
86
+ * Make an HTTP request to the local Prism API server
87
+ */
88
+ export async function callEndpoint(looseOptions: CallEndpointLooseOptions) {
89
+ const options = parseOptions(looseOptions);
90
+ const url = `${options.baseUrl}${options.path}`;
91
+
92
+ const requestOptions: RequestInit = {
93
+ method: options.method,
94
+ headers: {
95
+ 'Content-Type': 'application/json',
96
+ },
97
+ };
98
+
99
+ // Add body for methods that support it
100
+ if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(options.method)
101
+ && options.requestBody
102
+ && Object.keys(options.requestBody).length > 0) {
103
+ requestOptions.body = JSON.stringify(options.requestBody);
104
+ }
105
+
106
+ try {
107
+ const response = await fetch(url, requestOptions);
108
+
109
+ if (!looseOptions.quiet)
110
+ console.log('Response status: ' + response.status);
111
+
112
+ // Get the response text first
113
+ const responseText = await response.text();
114
+
115
+ if (!looseOptions.quiet)
116
+ console.log('Response: ', responseText);
117
+
118
+ // Try to parse as JSON
119
+ let responseData;
120
+ try {
121
+ responseData = responseText ? JSON.parse(responseText) : null;
122
+ } catch {
123
+ // If not JSON, return as text
124
+ responseData = responseText;
125
+ }
126
+
127
+ if (!response.ok) {
128
+ throw new Error(
129
+ `HTTP ${response.status} ${response.statusText}\n` +
130
+ `Response: ${JSON.stringify(responseData, null, 2)}`
131
+ );
132
+ }
133
+
134
+ return responseData;
135
+ } catch (error) {
136
+ if (error instanceof Error) {
137
+ if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
138
+ throw new Error(
139
+ `Failed to connect to ${url}\n\n` +
140
+ 'Make sure your Prism API server is running.\n' +
141
+ `The server should be listening on the port specified in .env (PRISM_API_PORT)`
142
+ );
143
+ }
144
+ }
145
+ throw error;
146
+ }
147
+ }