@yottagraph-app/aether-instructions 1.1.16 → 1.1.18
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 +1 -1
- package/rules/api.mdc +48 -14
- package/rules/cookbook.mdc +1 -1
- package/rules/pref.mdc +17 -8
- package/rules/server.mdc +31 -0
- package/skills/test-api-queries/SKILL.md +0 -63
- package/skills/test-api-queries/query-api.js +0 -172
package/package.json
CHANGED
package/rules/api.mdc
CHANGED
|
@@ -26,6 +26,52 @@ the directory is missing, run `/update_instructions` to install it.
|
|
|
26
26
|
|
|
27
27
|
For Lovelace **entity types, properties, relationships, and per-source schemas** (EDGAR, FRED, FDIC, etc.), read the **data-model skill** in `.cursor/skills/data-model/`. Start with `SKILL.md`, then `overview.md` and the source-specific folders. Both skills are distributed via `@yottagraph-app/aether-instructions` and installed during project init.
|
|
28
28
|
|
|
29
|
+
## Test Before You Build
|
|
30
|
+
|
|
31
|
+
**Before writing app code that calls the Query Server, test the actual API
|
|
32
|
+
call first and verify the response shape.** This avoids wasted iteration from incorrect
|
|
33
|
+
assumptions about response shapes.
|
|
34
|
+
|
|
35
|
+
### How to test
|
|
36
|
+
|
|
37
|
+
The gateway proxy authenticates on your behalf — no Auth0 tokens needed.
|
|
38
|
+
Read `broadchurch.yaml` for the three values you need:
|
|
39
|
+
|
|
40
|
+
| YAML path | Purpose |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `gateway.url` | Portal Gateway base URL |
|
|
43
|
+
| `tenant.org_id` | Your tenant ID (path segment) |
|
|
44
|
+
| `gateway.qs_api_key` | API key (sent as `X-Api-Key` header) |
|
|
45
|
+
|
|
46
|
+
Build the request URL as `{gateway.url}/api/qs/{tenant.org_id}/{endpoint}`
|
|
47
|
+
and include the header `X-Api-Key: {gateway.qs_api_key}`.
|
|
48
|
+
|
|
49
|
+
### Example calls
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Variables — read these from broadchurch.yaml
|
|
53
|
+
GW="https://broadchurch-portal-194773164895.us-central1.run.app"
|
|
54
|
+
ORG="org_abc123"
|
|
55
|
+
KEY="qs_..."
|
|
56
|
+
|
|
57
|
+
# Search for an entity by name
|
|
58
|
+
curl -s "$GW/api/qs/$ORG/entities/search" \
|
|
59
|
+
-X POST -H "Content-Type: application/json" \
|
|
60
|
+
-H "X-Api-Key: $KEY" \
|
|
61
|
+
-d '{"queries":[{"queryId":1,"query":"Microsoft"}],"maxResults":3}'
|
|
62
|
+
|
|
63
|
+
# Get entity properties (form-encoded)
|
|
64
|
+
curl -s -X POST "$GW/api/qs/$ORG/elemental/entities/properties" \
|
|
65
|
+
-H "X-Api-Key: $KEY" \
|
|
66
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
67
|
+
--data-urlencode 'eids=["00416400910670863867"]' \
|
|
68
|
+
--data-urlencode 'pids=[8,313]' | jq .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`/elemental/find` and `/elemental/entities/properties` require
|
|
72
|
+
`application/x-www-form-urlencoded` with JSON-stringified parameter values.
|
|
73
|
+
All other endpoints accept `application/json`.
|
|
74
|
+
|
|
29
75
|
## Client Usage
|
|
30
76
|
|
|
31
77
|
All API calls go through `useElementalClient()` from `@yottagraph-app/elemental-api/client`.
|
|
@@ -78,18 +124,6 @@ All methods return data directly and throw on non-2xx responses.
|
|
|
78
124
|
| `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
|
|
79
125
|
| `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
|
|
80
126
|
|
|
81
|
-
**News, events, and sentiment:**
|
|
82
|
-
|
|
83
|
-
| Method | Signature | Purpose |
|
|
84
|
-
|---|---|---|
|
|
85
|
-
| `getArticle` | `(artid: string)` | Article by ID |
|
|
86
|
-
| `getArticleText` | `(artid: string)` | Article full text |
|
|
87
|
-
| `getEvent` | `(eveid: string)` | Event by ID |
|
|
88
|
-
| `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
|
|
89
|
-
| `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
|
|
90
|
-
| `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
|
|
91
|
-
| `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
|
|
92
|
-
|
|
93
127
|
**Other:**
|
|
94
128
|
|
|
95
129
|
| Method | Signature | Purpose |
|
|
@@ -216,7 +250,7 @@ const res = await client.getPropertyValues({
|
|
|
216
250
|
eids: JSON.stringify([orgNeid]),
|
|
217
251
|
pids: JSON.stringify([filedPid]),
|
|
218
252
|
});
|
|
219
|
-
const docNeids = res.values.map((v) => String(v.value).padStart(20, '0'));
|
|
253
|
+
const docNeids = (res.values ?? []).map((v) => String(v.value).padStart(20, '0'));
|
|
220
254
|
```
|
|
221
255
|
|
|
222
256
|
See the **cookbook** rule for a full "Get filings for a company" recipe.
|
|
@@ -263,7 +297,7 @@ const res = await client.getPropertyValues({
|
|
|
263
297
|
});
|
|
264
298
|
|
|
265
299
|
// 3. Pad IDs to 20 chars to form valid NEIDs
|
|
266
|
-
const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
|
|
300
|
+
const docNeids = (res.values ?? []).map((v: any) => String(v.value).padStart(20, '0'));
|
|
267
301
|
|
|
268
302
|
// 4. Get details for each linked entity (response is nested under .report)
|
|
269
303
|
const reports = await Promise.all(
|
package/rules/cookbook.mdc
CHANGED
|
@@ -459,7 +459,7 @@ NOT supported — use `getPropertyValues` with the relationship PID instead.
|
|
|
459
459
|
pids: JSON.stringify([filedPid]),
|
|
460
460
|
});
|
|
461
461
|
|
|
462
|
-
const docNeids = res.values.map((v: any) =>
|
|
462
|
+
const docNeids = (res.values ?? []).map((v: any) =>
|
|
463
463
|
String(v.value).padStart(20, '0'),
|
|
464
464
|
);
|
|
465
465
|
|
package/rules/pref.mdc
CHANGED
|
@@ -46,19 +46,23 @@ The backing store auto-initializes on first use — no need to call
|
|
|
46
46
|
|
|
47
47
|
## Local Development
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`undefined` for reads and silently skip writes.
|
|
52
|
-
|
|
49
|
+
KV credentials are only available in deployed builds (Vercel auto-injects
|
|
50
|
+
them at runtime). In local dev, `getRedis()` returns `null` and KV routes
|
|
51
|
+
return `undefined` for reads and silently skip writes. `Pref<T>` still
|
|
52
|
+
works with its default value but won't persist across page refreshes.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
This is expected — push to `main` and test persistence on the deployed build.
|
|
55
|
+
|
|
56
|
+
**Auth dependency:** All `/api/kv/*` routes call `unsealCookie(event)` to
|
|
57
|
+
identify the user. In dev mode (no `NUXT_PUBLIC_AUTH0_CLIENT_SECRET` set),
|
|
58
|
+
this is bypassed automatically using `NUXT_PUBLIC_USER_NAME`. If you set an
|
|
59
|
+
Auth0 client secret without proper cookie setup, KV writes will silently
|
|
60
|
+
no-op.
|
|
57
61
|
|
|
58
62
|
## Direct API Alternative
|
|
59
63
|
|
|
60
64
|
If you prefer not to use the `Pref<T>` class, you can call the KV
|
|
61
|
-
routes directly:
|
|
65
|
+
routes directly from client-side code:
|
|
62
66
|
|
|
63
67
|
```typescript
|
|
64
68
|
// Read
|
|
@@ -73,6 +77,11 @@ await $fetch('/api/kv/write', {
|
|
|
73
77
|
});
|
|
74
78
|
```
|
|
75
79
|
|
|
80
|
+
These routes require the browser's auth cookie — they work from client-side
|
|
81
|
+
`$fetch` calls but not from server routes or external scripts. For
|
|
82
|
+
server-to-server KV access, use `getRedis()` from `server/utils/redis.ts`
|
|
83
|
+
directly.
|
|
84
|
+
|
|
76
85
|
## Feature-Scoped Preferences
|
|
77
86
|
|
|
78
87
|
Features should namespace preferences under the app's prefix:
|
package/rules/server.mdc
CHANGED
|
@@ -68,6 +68,9 @@ if (redis) {
|
|
|
68
68
|
|
|
69
69
|
Returns `null` if KV is not configured (env vars missing). Always check.
|
|
70
70
|
|
|
71
|
+
For client-side preferences, use `usePrefsStore()` and `Pref<T>` instead of
|
|
72
|
+
calling KV routes directly — see the `pref` rule.
|
|
73
|
+
|
|
71
74
|
## Neon Postgres
|
|
72
75
|
|
|
73
76
|
If `DATABASE_URL` is in `.env`, Postgres is ready to use. The project init
|
|
@@ -81,6 +84,12 @@ automatically when a Neon database is detected.
|
|
|
81
84
|
- If either is missing, the project wasn't provisioned with Neon (add it
|
|
82
85
|
from the Broadchurch Portal, then re-run `node init-project.js`)
|
|
83
86
|
|
|
87
|
+
**Local dev:** `DATABASE_URL` is not yet available for local development.
|
|
88
|
+
`getDb()` returns `null` when the credential is missing or invalid. Write
|
|
89
|
+
your server routes to handle this gracefully (return a "database not
|
|
90
|
+
configured" error or empty state). Push to `main` to test with a real
|
|
91
|
+
database on the deployed build, where credentials are auto-injected.
|
|
92
|
+
|
|
84
93
|
### Usage
|
|
85
94
|
|
|
86
95
|
`server/utils/neon.ts` exports `getDb()` (same lazy-init pattern as
|
|
@@ -103,6 +112,25 @@ The Neon driver uses tagged template literals for automatic SQL injection
|
|
|
103
112
|
protection — `await sql\`SELECT * FROM notes WHERE id = ${id}\`` is safe.
|
|
104
113
|
No ORM, no query builder, no connection pool setup needed.
|
|
105
114
|
|
|
115
|
+
### Creating tables
|
|
116
|
+
|
|
117
|
+
There is no migrations framework. Use `CREATE TABLE IF NOT EXISTS` directly
|
|
118
|
+
in a setup route or at the top of a route that needs the table:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const sql = getDb()!;
|
|
122
|
+
await sql`CREATE TABLE IF NOT EXISTS notes (
|
|
123
|
+
id SERIAL PRIMARY KEY,
|
|
124
|
+
content TEXT NOT NULL,
|
|
125
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
126
|
+
)`;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For simple apps, putting the `CREATE TABLE IF NOT EXISTS` in each route that
|
|
130
|
+
uses the table is fine — it's a no-op after the first call. For more complex
|
|
131
|
+
schemas, create a `server/api/db/setup.post.ts` route that initializes all
|
|
132
|
+
tables.
|
|
133
|
+
|
|
106
134
|
### If `server/utils/neon.ts` doesn't exist
|
|
107
135
|
|
|
108
136
|
Create it manually (or re-run init):
|
|
@@ -136,3 +164,6 @@ export function getDb(): NeonQueryFunction | null {
|
|
|
136
164
|
- They have access to Redis, Neon Postgres, secrets, and server-only APIs
|
|
137
165
|
- They do NOT have access to Vue composables, Vuetify, or any client-side code
|
|
138
166
|
- Use `defineEventHandler`, not Vue component patterns
|
|
167
|
+
|
|
168
|
+
See `architecture` rule for the full data architecture overview, `pref` rule
|
|
169
|
+
for client-side KV preferences.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: test-api-queries
|
|
3
|
-
description: Test Elemental API queries before integrating into app code. Use when writing code that calls the query server.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Test API Queries
|
|
7
|
-
|
|
8
|
-
**When writing code that calls the Query Server, test queries with this CLI tool before integrating them into app code.**
|
|
9
|
-
|
|
10
|
-
This prevents wasted iteration cycles from incorrect assumptions about API responses.
|
|
11
|
-
|
|
12
|
-
## Workflow
|
|
13
|
-
|
|
14
|
-
1. **Test the query** using the CLI tool below
|
|
15
|
-
2. **Verify the response** matches what you expect
|
|
16
|
-
3. **Then write the app code** with confidence
|
|
17
|
-
|
|
18
|
-
## CLI Tool
|
|
19
|
-
|
|
20
|
-
Location: `~/.cursor/skills/test-api-queries/query-api.js`
|
|
21
|
-
|
|
22
|
-
### Requirements
|
|
23
|
-
|
|
24
|
-
The CLI reads these from the project's `.env` file or shell environment:
|
|
25
|
-
|
|
26
|
-
- `AUTH0_M2M_DEV_TOKEN` - Auth0 M2M dev token for API access
|
|
27
|
-
- `NUXT_PUBLIC_QUERY_SERVER_ADDRESS` - Query server URL
|
|
28
|
-
|
|
29
|
-
**Before using this tool**, check that both variables are set in the project's `.env` file. If either is missing or empty, add them to `.env` (see `.env.example` for the format). Tell the user to ask the engineering team for the token value if they don't have it.
|
|
30
|
-
|
|
31
|
-
### Usage
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
node ~/.cursor/skills/test-api-queries/query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Examples
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
# Search for entities by name
|
|
41
|
-
node ~/.cursor/skills/test-api-queries/query-api.js POST /entities/search \
|
|
42
|
-
'{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
|
|
43
|
-
|
|
44
|
-
# Get entity details by ID
|
|
45
|
-
node ~/.cursor/skills/test-api-queries/query-api.js GET /entities/00416400910670863867
|
|
46
|
-
|
|
47
|
-
# Find entities by type
|
|
48
|
-
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
|
|
49
|
-
'{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
|
|
50
|
-
|
|
51
|
-
# Find entities with a specific property
|
|
52
|
-
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
|
|
53
|
-
'{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
|
|
54
|
-
|
|
55
|
-
# Get entity properties
|
|
56
|
-
node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/entities/properties \
|
|
57
|
-
'{"eids":["00416400910670863867"],"pids":[8,313]}'
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Notes
|
|
61
|
-
|
|
62
|
-
- Run from the project directory so the `.env` file is found
|
|
63
|
-
- `/elemental/find` and `/elemental/entities/properties` require form-encoded bodies—the tool handles this automatically
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Simple CLI tool to query the Elemental API.
|
|
4
|
-
*
|
|
5
|
-
* Auth: set AUTH0_M2M_DEV_TOKEN as an env var or in your .env file.
|
|
6
|
-
* Server: set NUXT_PUBLIC_QUERY_SERVER_ADDRESS in your .env file.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
const FORM_ENCODED_ENDPOINTS = ['/elemental/find', '/elemental/entities/properties'];
|
|
13
|
-
|
|
14
|
-
function loadEnvFile() {
|
|
15
|
-
// Try current working directory first (project .env), then script directory
|
|
16
|
-
const envPaths = [path.join(process.cwd(), '.env'), path.join(__dirname, '.env')];
|
|
17
|
-
|
|
18
|
-
for (const envPath of envPaths) {
|
|
19
|
-
try {
|
|
20
|
-
const contents = fs.readFileSync(envPath, 'utf8');
|
|
21
|
-
for (const line of contents.split('\n')) {
|
|
22
|
-
const trimmed = line.trim();
|
|
23
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
24
|
-
const eqIndex = trimmed.indexOf('=');
|
|
25
|
-
if (eqIndex === -1) continue;
|
|
26
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
27
|
-
const value = trimmed.slice(eqIndex + 1).trim();
|
|
28
|
-
if (!process.env[key]) {
|
|
29
|
-
process.env[key] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return; // Stop after first successful load
|
|
33
|
-
} catch {
|
|
34
|
-
// Try next path
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function requiresFormEncoding(endpoint) {
|
|
40
|
-
return FORM_ENCODED_ENDPOINTS.some((e) => endpoint.startsWith(e));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function buildFormBody(endpoint, params) {
|
|
44
|
-
const formParams = new URLSearchParams();
|
|
45
|
-
for (const [key, value] of Object.entries(params)) {
|
|
46
|
-
if (typeof value === 'object') {
|
|
47
|
-
formParams.append(key, JSON.stringify(value));
|
|
48
|
-
} else {
|
|
49
|
-
formParams.append(key, String(value));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return formParams.toString();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
loadEnvFile();
|
|
57
|
-
|
|
58
|
-
const args = process.argv.slice(2);
|
|
59
|
-
|
|
60
|
-
if (args.length < 2 || args[0] === '--help' || args[0] === '-h') {
|
|
61
|
-
console.log(`
|
|
62
|
-
Elemental API Query Tool
|
|
63
|
-
|
|
64
|
-
Usage:
|
|
65
|
-
node query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
|
|
66
|
-
|
|
67
|
-
Examples:
|
|
68
|
-
# Search for entities (JSON body)
|
|
69
|
-
node query-api.js POST /entities/search '{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
|
|
70
|
-
|
|
71
|
-
# Get entity details
|
|
72
|
-
node query-api.js GET /entities/00416400910670863867
|
|
73
|
-
|
|
74
|
-
# Find entities by expression (auto form-encoded)
|
|
75
|
-
node query-api.js POST /elemental/find '{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
|
|
76
|
-
|
|
77
|
-
# Find entities with a property value
|
|
78
|
-
node query-api.js POST /elemental/find '{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
|
|
79
|
-
|
|
80
|
-
# Get entity properties (auto form-encoded)
|
|
81
|
-
node query-api.js POST /elemental/entities/properties '{"eids":["00416400910670863867"],"pids":[8,313]}'
|
|
82
|
-
|
|
83
|
-
Environment variables (required):
|
|
84
|
-
AUTH0_M2M_DEV_TOKEN Bearer token for API auth
|
|
85
|
-
NUXT_PUBLIC_QUERY_SERVER_ADDRESS Query server URL (e.g. https://query.news.prod.g.lovelace.ai)
|
|
86
|
-
|
|
87
|
-
Setup:
|
|
88
|
-
1. Get a dev token from your team
|
|
89
|
-
2. Add AUTH0_M2M_DEV_TOKEN to your .env file or export in your shell
|
|
90
|
-
(NUXT_PUBLIC_QUERY_SERVER_ADDRESS should already be in your .env from project init)
|
|
91
|
-
|
|
92
|
-
Note: /elemental/find and /elemental/entities/properties require form-encoded bodies.
|
|
93
|
-
This tool automatically handles the encoding for these endpoints.
|
|
94
|
-
`);
|
|
95
|
-
process.exit(0);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const [method, endpoint, jsonParams] = args;
|
|
99
|
-
|
|
100
|
-
const token = process.env.AUTH0_M2M_DEV_TOKEN;
|
|
101
|
-
if (!token) {
|
|
102
|
-
console.error('Error: AUTH0_M2M_DEV_TOKEN is not set.');
|
|
103
|
-
console.error('Add it to your .env file or export it in your shell.');
|
|
104
|
-
console.error('Run: node query-api.js --help');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let params = {};
|
|
109
|
-
if (jsonParams) {
|
|
110
|
-
try {
|
|
111
|
-
params = JSON.parse(jsonParams);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.error('Error: Invalid JSON parameters');
|
|
114
|
-
console.error(err.message);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const server = process.env.NUXT_PUBLIC_QUERY_SERVER_ADDRESS;
|
|
120
|
-
if (!server) {
|
|
121
|
-
console.error('Error: NUXT_PUBLIC_QUERY_SERVER_ADDRESS is not set.');
|
|
122
|
-
console.error('Add it to your .env file or export it in your shell.');
|
|
123
|
-
console.error('Run: node query-api.js --help');
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : '/' + endpoint;
|
|
128
|
-
let url = `${server}${normalizedEndpoint}`;
|
|
129
|
-
|
|
130
|
-
const useFormEncoding = requiresFormEncoding(normalizedEndpoint);
|
|
131
|
-
const fetchOptions = {
|
|
132
|
-
method: method.toUpperCase(),
|
|
133
|
-
headers: {
|
|
134
|
-
Authorization: `Bearer ${token}`,
|
|
135
|
-
'Content-Type': useFormEncoding
|
|
136
|
-
? 'application/x-www-form-urlencoded'
|
|
137
|
-
: 'application/json',
|
|
138
|
-
Accept: 'application/json',
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
if (method.toUpperCase() === 'GET' && Object.keys(params).length > 0) {
|
|
143
|
-
const queryString = new URLSearchParams(params).toString();
|
|
144
|
-
url = `${url}?${queryString}`;
|
|
145
|
-
} else if (method.toUpperCase() !== 'GET' && Object.keys(params).length > 0) {
|
|
146
|
-
if (useFormEncoding) {
|
|
147
|
-
fetchOptions.body = buildFormBody(normalizedEndpoint, params);
|
|
148
|
-
} else {
|
|
149
|
-
fetchOptions.body = JSON.stringify(params);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
console.error(`\n📡 ${method.toUpperCase()} ${url}\n`);
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const response = await fetch(url, fetchOptions);
|
|
157
|
-
const data = await response.json();
|
|
158
|
-
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
console.error(`❌ Error ${response.status}: ${response.statusText}`);
|
|
161
|
-
} else {
|
|
162
|
-
console.error(`✅ Success (${response.status})\n`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
console.log(JSON.stringify(data, null, 2));
|
|
166
|
-
} catch (err) {
|
|
167
|
-
console.error('❌ Request failed:', err.message);
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
main();
|