@nestcorehq/node 0.0.0-stage → 0.15.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.
- package/README.md +242 -2
- package/dist/index.d.mts +2347 -0
- package/dist/index.d.ts +2347 -0
- package/dist/index.js +3198 -0
- package/dist/index.mjs +3135 -0
- package/package.json +47 -4
package/README.md
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nestcorehq/node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Node.js SDK for the NestCore platform — database, auth, edge
|
|
4
|
+
functions, storage, realtime, analytics, and migrations.
|
|
5
|
+
|
|
6
|
+
> Server-side SDK. The API key is a credential — keep it on the server, never
|
|
7
|
+
> ship it to a browser. For client apps, use the anon key (RLS-enforced).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @nestcorehq/node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createClient } from '@nestcorehq/node';
|
|
19
|
+
|
|
20
|
+
// Only the API key is required — the project slug is decoded from the key's
|
|
21
|
+
// JWT, and baseUrl defaults to https://api.nestcore.io.
|
|
22
|
+
const client = createClient({ apiKey: process.env.NESTCORE_API_KEY! });
|
|
23
|
+
|
|
24
|
+
// Local dev / self-host:
|
|
25
|
+
// createClient({ apiKey, baseUrl: 'http://localhost:3002' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Database
|
|
29
|
+
|
|
30
|
+
### Fluent query builder (recommended)
|
|
31
|
+
|
|
32
|
+
A Supabase-style, chainable builder. Every value is bound (`$1, $2, …`) — never
|
|
33
|
+
string-interpolated — and RLS still applies.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const { data, error } = await client.db
|
|
37
|
+
.from('posts')
|
|
38
|
+
.eq('status', 'published')
|
|
39
|
+
.gt('views', 100)
|
|
40
|
+
.order('created_at', { ascending: false })
|
|
41
|
+
.limit(10)
|
|
42
|
+
.select('id, title, views'); // → { rows, total }
|
|
43
|
+
|
|
44
|
+
// One row
|
|
45
|
+
const { data: post } = await client.db.from('posts').eq('id', id).single();
|
|
46
|
+
// First row or null (no error when empty)
|
|
47
|
+
const { data: maybe } = await client.db.from('posts').eq('slug', s).maybeSingle();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Filter operators: `eq, neq, gt, gte, lt, lte, like, ilike, in, is`.
|
|
51
|
+
Pagination: `limit(n)`, `range(from, to)` (inclusive), `page(n, size)`.
|
|
52
|
+
Ordering: `order(col, { ascending })`.
|
|
53
|
+
|
|
54
|
+
Writes:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
await client.db.from('posts').insert({ title: 'Hello' });
|
|
58
|
+
await client.db.from('posts').update(id, { title: 'Edited' });
|
|
59
|
+
await client.db.from('posts').delete(id);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Raw SQL (full power)
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const { data } = await client.db.query(
|
|
66
|
+
'SELECT * FROM users WHERE email = $1', [email], // always parameterize
|
|
67
|
+
);
|
|
68
|
+
// data: { rows, rowCount, fields }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Type generation
|
|
72
|
+
|
|
73
|
+
Generate TypeScript interfaces from your live schema (NestCore's
|
|
74
|
+
`supabase gen types` equivalent):
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { writeFileSync } from 'fs';
|
|
78
|
+
import { generateTypes } from '@nestcorehq/node';
|
|
79
|
+
|
|
80
|
+
writeFileSync('nestcore.types.ts', await generateTypes(client.db));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// then:
|
|
85
|
+
import type { Database, Posts } from './nestcore.types';
|
|
86
|
+
const { data } = await client.db.from('posts').eq('id', id).single<Posts>();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Excludes `auth_*` / internal tables by default; pass `{ tables }` or `{ exclude }`
|
|
90
|
+
to customize.
|
|
91
|
+
|
|
92
|
+
## Migrations
|
|
93
|
+
|
|
94
|
+
Versioned, checksummed SQL migrations with a registry table, sequential
|
|
95
|
+
execution, and rollback.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
migrations/
|
|
99
|
+
001_init.sql
|
|
100
|
+
002_add_posts.sql
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Each file may declare a rollback with a `-- migrate:down` marker:
|
|
104
|
+
|
|
105
|
+
```sql
|
|
106
|
+
-- 002: add posts
|
|
107
|
+
CREATE TABLE posts (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), title text);
|
|
108
|
+
|
|
109
|
+
-- migrate:down
|
|
110
|
+
DROP TABLE posts;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// Migrations run DDL — use a service-role key.
|
|
115
|
+
const admin = createClient({ apiKey: process.env.NESTCORE_SERVICE_KEY!, migrationsDir: 'migrations' });
|
|
116
|
+
|
|
117
|
+
await admin.migrations.runAll(); // apply all pending (sequential, stop-on-failure)
|
|
118
|
+
await admin.migrations.runOne('002'); // apply one
|
|
119
|
+
await admin.migrations.rollback(); // roll back the most recent (runs its -- migrate:down)
|
|
120
|
+
await admin.migrations.rollback('002'); // roll back a specific one
|
|
121
|
+
await admin.migrations.getStatus(); // [{ migrationId, status, applied, checksumMismatch }]
|
|
122
|
+
admin.migrations.createMigration('add tags'); // scaffold 003_add_tags.sql
|
|
123
|
+
await admin.migrations.seed('seed.sql'); // run a (re-runnable) seed script, not recorded
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Safety: migrations never run in parallel, completed ones are never re-run, and a
|
|
127
|
+
migration whose file changed after being applied (checksum mismatch) aborts the
|
|
128
|
+
run. Each file/section runs inside one transaction — keep scripts
|
|
129
|
+
transaction-safe (no `CREATE INDEX CONCURRENTLY`).
|
|
130
|
+
|
|
131
|
+
## Auth
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
await client.auth.signUp({ email, password });
|
|
135
|
+
await client.auth.signIn({ email, password });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Calling auth from your server
|
|
139
|
+
|
|
140
|
+
Auth endpoints are throttled per client IP. When your server calls them for its users (a Next.js server action, an API route), every user would share your server's IP and its limit. Pass the user's IP with the **service-role key** and each user gets their own bucket; the same IP goes into the auth audit log:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const client = createClient({
|
|
144
|
+
apiKey: process.env.NESTCORE_SERVICE_ROLE_KEY!, // never ship this key to a browser
|
|
145
|
+
clientIp: userIp, // e.g. the first address of x-forwarded-for set by YOUR proxy
|
|
146
|
+
});
|
|
147
|
+
await client.auth.signIn({ email, password });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`clientIp` is sent as `X-NestCore-Client-IP` and must be a single IPv4 or IPv6 address. The API ignores it on requests made with the anon key or a user token, so a browser can't choose its own bucket. Create one client per request.
|
|
151
|
+
|
|
152
|
+
Project auth answers `429` with a `code` when a limit from the project's auth settings is reached: `OTP_RATE_LIMITED` (too many wrong codes), `OTP_REQUEST_LIMIT` (too many codes requested for one email in an hour), `ACCOUNT_LOCKED` (too many wrong passwords).
|
|
153
|
+
|
|
154
|
+
## Edge functions
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
await client.functions.invoke('my-function', { body: { data: 'test' } });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Storage
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
await client.storage.from('avatars').upload(path, file);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Realtime
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const ch = client.channel('orders')
|
|
170
|
+
.on('postgres_changes', { event: 'INSERT', table: 'orders' }, (e) => console.log(e.new))
|
|
171
|
+
.subscribe();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
On Node < 22 pass a WebSocket constructor: `createClient({ ..., realtimeOptions: { WebSocket } })`.
|
|
175
|
+
|
|
176
|
+
## AI
|
|
177
|
+
|
|
178
|
+
Chat, streaming, tool calls and embeddings through the project's own provider keys (Console → Project Settings → AI). Calls need a signed-in user or the service-role key.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const { data } = await client.ai.chat({ messages: [{ role: 'user', content: 'Summarise my last order' }] });
|
|
182
|
+
|
|
183
|
+
for await (const ev of client.ai.chatStream({ messages })) {
|
|
184
|
+
if (ev.type === 'delta') process.stdout.write(ev.text);
|
|
185
|
+
if (ev.type === 'error') console.error(ev.code, ev.message);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { data: vectors } = await client.ai.embed(['first', 'second']); // 768 dimensions
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Tools: pass `tools: [{ name, description, parameters }]`; run each of `data.toolCalls`, then send the reply back as `{ role: 'assistant', content: null, toolCalls }` plus one `{ role: 'tool', toolCallId, content }` per call. See https://nestcore.io/docs/ai-gateway.
|
|
192
|
+
|
|
193
|
+
### Memory, collections, threads, semantic search
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// Memory: facts about the signed-in user (near-duplicates update the same memory)
|
|
197
|
+
await client.memory.add('Prefers vegetarian recipes', { kind: 'preference' });
|
|
198
|
+
const { data: mem } = await client.memory.search('what should I cook?'); // mem.degraded = keyword fallback
|
|
199
|
+
|
|
200
|
+
// Collections: documents chunked, embedded and searched (hybrid vector + keyword)
|
|
201
|
+
await admin.collections.create({ name: 'handbook' }); // service-role key
|
|
202
|
+
await admin.collections.from('handbook').ingest({ url: 'https://example.com/guide.pdf' }, { wait: true });
|
|
203
|
+
const { data: hits } = await client.collections.from('handbook').search('How do refunds work?');
|
|
204
|
+
|
|
205
|
+
// Threads: saved conversations; replies stream and are stored as they arrive
|
|
206
|
+
const { data: thread } = await client.ai.threads.create({ title: 'Trip' });
|
|
207
|
+
for await (const ev of client.ai.threads.from(thread.id).run({ input: 'Plan a weekend', memory: true, collections: ['handbook'] })) {
|
|
208
|
+
if (ev.type === 'delta') process.stdout.write(ev.text);
|
|
209
|
+
if (ev.type === 'citation') console.log(`[${ev.index}]`, ev.title);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Automatic embeddings (Console → AI → Embeddings), then search a table by meaning under RLS
|
|
213
|
+
const { data } = await client.db.from('articles').eq('published', true).semantic('body_embedding', 'reset my password', { k: 5 });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
See https://nestcore.io/docs/memory-api, /docs/collections, /docs/threads and /docs/embeddings.
|
|
217
|
+
|
|
218
|
+
## Errors
|
|
219
|
+
|
|
220
|
+
Every call returns `{ data, error }` (never throws) — check `error` before using
|
|
221
|
+
`data`:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const { data, error } = await client.db.from('posts').select();
|
|
225
|
+
if (error) { /* handle */ }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Migrating from `@orbitneststudio/node`
|
|
229
|
+
|
|
230
|
+
The SDK was renamed from OrbitNest to NestCore. Switch the dependency to
|
|
231
|
+
`@nestcorehq/node` and update your imports; nothing else has to change at once:
|
|
232
|
+
|
|
233
|
+
- The old export names still work as deprecated aliases: `OrbitNestError`
|
|
234
|
+
(same class as `NestCoreError`, so `instanceof` checks keep working),
|
|
235
|
+
`OrbitNestClient` and `OrbitNestConfig`.
|
|
236
|
+
- The default `baseUrl` is now `https://api.nestcore.io`.
|
|
237
|
+
- `clientIp` is now sent as `X-NestCore-Client-IP`.
|
|
238
|
+
- Analytics ids stored under the old `orbitnest_*` keys are moved to the new
|
|
239
|
+
`nestcore_*` keys on first read.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|