@valtrix/sdk 0.2.0 → 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.
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +1 -23
- package/dist/client.d.ts +48 -0
- package/dist/client.js +90 -1
- package/dist/codegen.js +10 -1
- package/dist/env.d.ts +2 -0
- package/dist/env.js +23 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/protocol.d.ts +22 -1
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -3,6 +3,5 @@ export type CliIo = {
|
|
|
3
3
|
error: (message: string) => void;
|
|
4
4
|
fetchImpl?: typeof fetch;
|
|
5
5
|
};
|
|
6
|
-
export declare function parseEnvFile(content: string): Record<string, string>;
|
|
7
6
|
export declare function loadEnvFiles(cwd: string): Promise<Record<string, string>>;
|
|
8
7
|
export declare function runCli(argv: string[], env: Record<string, string | undefined>, io: CliIo): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,9 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { emitClientSource } from './codegen.js';
|
|
4
|
+
import { ENV_FILES, parseEnvFile } from './env.js';
|
|
4
5
|
const DEFAULT_OUT = path.join('valtrix', 'client.ts');
|
|
5
6
|
const DEFAULT_BASE_URL = 'https://api.valtrix.vojtadrmota.com';
|
|
6
|
-
const ENV_FILES = ['.env', '.env.local'];
|
|
7
|
-
export function parseEnvFile(content) {
|
|
8
|
-
const values = {};
|
|
9
|
-
for (const line of content.split(/\r?\n/)) {
|
|
10
|
-
const trimmed = line.trim();
|
|
11
|
-
if (!trimmed || trimmed.startsWith('#'))
|
|
12
|
-
continue;
|
|
13
|
-
const withoutExport = trimmed.startsWith('export ') ? trimmed.slice(7).trim() : trimmed;
|
|
14
|
-
const separator = withoutExport.indexOf('=');
|
|
15
|
-
if (separator === -1)
|
|
16
|
-
continue;
|
|
17
|
-
const key = withoutExport.slice(0, separator).trim();
|
|
18
|
-
if (!key)
|
|
19
|
-
continue;
|
|
20
|
-
let value = withoutExport.slice(separator + 1).trim();
|
|
21
|
-
const quote = value[0];
|
|
22
|
-
if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length >= 2) {
|
|
23
|
-
value = value.slice(1, -1);
|
|
24
|
-
}
|
|
25
|
-
values[key] = value;
|
|
26
|
-
}
|
|
27
|
-
return values;
|
|
28
|
-
}
|
|
29
7
|
export async function loadEnvFiles(cwd) {
|
|
30
8
|
const merged = {};
|
|
31
9
|
for (const file of ENV_FILES) {
|
package/dist/client.d.ts
CHANGED
|
@@ -133,17 +133,65 @@ export declare class ConnectApi {
|
|
|
133
133
|
expiresAt: string;
|
|
134
134
|
}>;
|
|
135
135
|
}
|
|
136
|
+
export type OrgStatus = 'active' | 'revoked' | 'expired';
|
|
137
|
+
export type Org = {
|
|
138
|
+
id: string;
|
|
139
|
+
displayName: string;
|
|
140
|
+
status: OrgStatus;
|
|
141
|
+
entityTypes: string[];
|
|
142
|
+
connections: Array<{
|
|
143
|
+
connector: string;
|
|
144
|
+
status: string;
|
|
145
|
+
}>;
|
|
146
|
+
connectedAt: string;
|
|
147
|
+
expiresAt: string | null;
|
|
148
|
+
revokedAt: string | null;
|
|
149
|
+
};
|
|
150
|
+
export type OrgsQuery = {
|
|
151
|
+
status?: OrgStatus;
|
|
152
|
+
limit?: number;
|
|
153
|
+
cursor?: string;
|
|
154
|
+
};
|
|
155
|
+
export type OrgsPage = {
|
|
156
|
+
orgs: Org[];
|
|
157
|
+
nextCursor: string | null;
|
|
158
|
+
};
|
|
159
|
+
export declare class OrgsApi {
|
|
160
|
+
private readonly http;
|
|
161
|
+
constructor(http: Http);
|
|
162
|
+
page(query?: OrgsQuery): Promise<OrgsPage>;
|
|
163
|
+
list(query?: Omit<OrgsQuery, 'cursor'>): Promise<Org[]>;
|
|
164
|
+
iterate(query?: Omit<OrgsQuery, 'cursor'>): AsyncGenerator<Org>;
|
|
165
|
+
get(orgId: string): Promise<Org>;
|
|
166
|
+
}
|
|
136
167
|
export type ClientShape = {
|
|
137
168
|
tables: Record<string, object>;
|
|
138
169
|
records: Record<string, object>;
|
|
139
170
|
};
|
|
171
|
+
export type RecordLookupResult = {
|
|
172
|
+
record: {
|
|
173
|
+
id: string;
|
|
174
|
+
entityType: string;
|
|
175
|
+
externalId: string;
|
|
176
|
+
orgId: string;
|
|
177
|
+
data: Record<string, unknown>;
|
|
178
|
+
syncedAt: string;
|
|
179
|
+
};
|
|
180
|
+
tables: Array<{
|
|
181
|
+
table: string;
|
|
182
|
+
row: Record<string, unknown> | null;
|
|
183
|
+
}>;
|
|
184
|
+
};
|
|
140
185
|
export type ValtrixClient<S extends ClientShape> = {
|
|
141
186
|
[K in keyof S['tables']]: TableHandle<S['tables'][K]>;
|
|
142
187
|
} & {
|
|
143
188
|
records: {
|
|
144
189
|
[K in keyof S['records']]: RecordHandle<S['records'][K]>;
|
|
190
|
+
} & {
|
|
191
|
+
get: (recordId: string) => Promise<RecordLookupResult>;
|
|
145
192
|
};
|
|
146
193
|
connect: ConnectApi;
|
|
194
|
+
orgs: OrgsApi;
|
|
147
195
|
schema: () => Promise<ApiSchema>;
|
|
148
196
|
};
|
|
149
197
|
export type CreateClientConfig<S extends ClientShape> = {
|
package/dist/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ENV_FILES, parseEnvFile } from './env.js';
|
|
1
2
|
import { Http } from './http.js';
|
|
2
3
|
function serializeScalar(value) {
|
|
3
4
|
if (typeof value === 'boolean')
|
|
@@ -196,11 +197,84 @@ export class ConnectApi {
|
|
|
196
197
|
return { connectUrl: response.connect_url, expiresAt: response.expires_at };
|
|
197
198
|
}
|
|
198
199
|
}
|
|
200
|
+
function mapOrg(org) {
|
|
201
|
+
return {
|
|
202
|
+
id: org.id,
|
|
203
|
+
displayName: org.display_name,
|
|
204
|
+
status: org.status,
|
|
205
|
+
entityTypes: org.scope.entity_types,
|
|
206
|
+
connections: org.connections.map(connection => ({ connector: connection.connector, status: connection.status })),
|
|
207
|
+
connectedAt: org.connected_at,
|
|
208
|
+
expiresAt: org.expires_at,
|
|
209
|
+
revokedAt: org.revoked_at,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export class OrgsApi {
|
|
213
|
+
http;
|
|
214
|
+
constructor(http) {
|
|
215
|
+
this.http = http;
|
|
216
|
+
}
|
|
217
|
+
async page(query = {}) {
|
|
218
|
+
const params = new URLSearchParams();
|
|
219
|
+
if (query.status)
|
|
220
|
+
params.set('status', query.status);
|
|
221
|
+
if (query.limit !== undefined)
|
|
222
|
+
params.set('limit', String(query.limit));
|
|
223
|
+
if (query.cursor)
|
|
224
|
+
params.set('cursor', query.cursor);
|
|
225
|
+
const response = await this.http.request('GET', '/v1/orgs', { query: params });
|
|
226
|
+
return { orgs: response.orgs.map(mapOrg), nextCursor: response.next_cursor };
|
|
227
|
+
}
|
|
228
|
+
async list(query = {}) {
|
|
229
|
+
const { orgs } = await this.page(query);
|
|
230
|
+
return orgs;
|
|
231
|
+
}
|
|
232
|
+
async *iterate(query = {}) {
|
|
233
|
+
let cursor;
|
|
234
|
+
for (;;) {
|
|
235
|
+
const { orgs, nextCursor } = await this.page({ ...query, cursor });
|
|
236
|
+
for (const org of orgs)
|
|
237
|
+
yield org;
|
|
238
|
+
if (!nextCursor)
|
|
239
|
+
return;
|
|
240
|
+
cursor = nextCursor;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async get(orgId) {
|
|
244
|
+
const response = await this.http.request('GET', `/v1/orgs/${encodeURIComponent(orgId)}`);
|
|
245
|
+
return mapOrg(response);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
199
248
|
const DEFAULT_BASE_URL = 'https://api.valtrix.vojtadrmota.com';
|
|
249
|
+
let cachedFileEnv = null;
|
|
250
|
+
function fileEnv(name) {
|
|
251
|
+
if (typeof process === 'undefined' || !process.getBuiltinModule)
|
|
252
|
+
return undefined;
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
if (!cachedFileEnv || cachedFileEnv.cwd !== cwd) {
|
|
255
|
+
const values = {};
|
|
256
|
+
try {
|
|
257
|
+
const fs = process.getBuiltinModule('node:fs');
|
|
258
|
+
for (const file of ENV_FILES) {
|
|
259
|
+
try {
|
|
260
|
+
Object.assign(values, parseEnvFile(fs.readFileSync(`${cwd}/${file}`, 'utf8')));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
cachedFileEnv = { cwd, values };
|
|
271
|
+
}
|
|
272
|
+
return cachedFileEnv.values[name];
|
|
273
|
+
}
|
|
200
274
|
function resolveEnv(name) {
|
|
201
275
|
if (typeof process === 'undefined')
|
|
202
276
|
return undefined;
|
|
203
|
-
return process.env?.[name];
|
|
277
|
+
return process.env?.[name] ?? fileEnv(name);
|
|
204
278
|
}
|
|
205
279
|
export function createClient(config) {
|
|
206
280
|
const apiKey = config.apiKey ?? resolveEnv('VALTRIX_API_KEY');
|
|
@@ -222,8 +296,23 @@ export function createClient(config) {
|
|
|
222
296
|
for (const entityType of config.records) {
|
|
223
297
|
records[entityType] = new RecordHandle(http, entityType);
|
|
224
298
|
}
|
|
299
|
+
records.get = async (recordId) => {
|
|
300
|
+
const response = await http.request('GET', `/v1/records/${encodeURIComponent(recordId)}`);
|
|
301
|
+
return {
|
|
302
|
+
record: {
|
|
303
|
+
id: response.record.id,
|
|
304
|
+
entityType: response.record.entity_type,
|
|
305
|
+
externalId: response.record.external_id,
|
|
306
|
+
orgId: response.record.org_id,
|
|
307
|
+
data: response.record.data,
|
|
308
|
+
syncedAt: response.record.synced_at,
|
|
309
|
+
},
|
|
310
|
+
tables: response.tables,
|
|
311
|
+
};
|
|
312
|
+
};
|
|
225
313
|
client.records = records;
|
|
226
314
|
client.connect = new ConnectApi(http);
|
|
315
|
+
client.orgs = new OrgsApi(http);
|
|
227
316
|
client.schema = () => http.request('GET', '/v1/schema');
|
|
228
317
|
return client;
|
|
229
318
|
}
|
package/dist/codegen.js
CHANGED
|
@@ -30,6 +30,8 @@ function columnDoc(column, indent) {
|
|
|
30
30
|
const extra = [];
|
|
31
31
|
if (column.type === 'date')
|
|
32
32
|
extra.push('ISO 8601 date string.');
|
|
33
|
+
if (column.type === 'reference')
|
|
34
|
+
extra.push('Valtrix record ID of the referenced record.');
|
|
33
35
|
if (column.writable)
|
|
34
36
|
extra.push('Writable through valtrix.records.');
|
|
35
37
|
return docComment(column.description, extra, indent);
|
|
@@ -53,7 +55,14 @@ export function emitClientSource(schema, options = {}) {
|
|
|
53
55
|
parts.push('');
|
|
54
56
|
tables.forEach((table, index) => {
|
|
55
57
|
const { interfaceName } = tableProperties[index];
|
|
56
|
-
const
|
|
58
|
+
const identityNote = table.row_identity === 'record'
|
|
59
|
+
? ' Rows keep their source record ids; _record_id (rec_ prefix) resolves via records.get().'
|
|
60
|
+
: table.row_identity === 'synthetic'
|
|
61
|
+
? ' Rows are derived aggregates; _record_id is synthetic (syn_ prefix) and does not resolve to a record.'
|
|
62
|
+
: table.row_identity === 'mixed'
|
|
63
|
+
? ' Rows mix record (rec_ prefix) and synthetic (syn_ prefix) ids; only record ids resolve via records.get().'
|
|
64
|
+
: '';
|
|
65
|
+
const header = docComment(`Table "${table.name}" (${table.entity_type}), schema ${table.schema_version ?? 'unpublished'}.${identityNote}`, [], '');
|
|
57
66
|
parts.push(`${header}export interface ${interfaceName} {`);
|
|
58
67
|
for (const column of table.columns) {
|
|
59
68
|
const doc = columnDoc(column, ' ');
|
package/dist/env.d.ts
ADDED
package/dist/env.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const ENV_FILES = ['.env', '.env.local'];
|
|
2
|
+
export function parseEnvFile(content) {
|
|
3
|
+
const values = {};
|
|
4
|
+
for (const line of content.split(/\r?\n/)) {
|
|
5
|
+
const trimmed = line.trim();
|
|
6
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
7
|
+
continue;
|
|
8
|
+
const withoutExport = trimmed.startsWith('export ') ? trimmed.slice(7).trim() : trimmed;
|
|
9
|
+
const separator = withoutExport.indexOf('=');
|
|
10
|
+
if (separator === -1)
|
|
11
|
+
continue;
|
|
12
|
+
const key = withoutExport.slice(0, separator).trim();
|
|
13
|
+
if (!key)
|
|
14
|
+
continue;
|
|
15
|
+
let value = withoutExport.slice(separator + 1).trim();
|
|
16
|
+
const quote = value[0];
|
|
17
|
+
if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length >= 2) {
|
|
18
|
+
value = value.slice(1, -1);
|
|
19
|
+
}
|
|
20
|
+
values[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return values;
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { ConnectApi, createClient, RecordHandle, TableHandle, } from './client.js';
|
|
2
|
-
export type { ClientShape, CreateClientConfig, FindManyQuery, MetaFields, OrderBy, RecordIdFilter, RowsPage, TableChange, TableWriteOutcome, UpsertResult, ValtrixClient, Where, WhereOperators, WhereValue, } from './client.js';
|
|
1
|
+
export { ConnectApi, createClient, OrgsApi, RecordHandle, TableHandle, } from './client.js';
|
|
2
|
+
export type { ClientShape, CreateClientConfig, FindManyQuery, MetaFields, OrderBy, Org, OrgsPage, OrgsQuery, OrgStatus, RecordIdFilter, RowsPage, TableChange, TableWriteOutcome, UpsertResult, ValtrixClient, Where, WhereOperators, WhereValue, } from './client.js';
|
|
3
3
|
export { errorFromResponse, ValtrixAuthError, ValtrixCursorExpiredError, ValtrixError, ValtrixRateLimitError, } from './errors.js';
|
|
4
4
|
export type { FieldIssue } from './errors.js';
|
|
5
5
|
export { camelCase, emitClientSource, pascalCase } from './codegen.js';
|
|
6
6
|
export type { GeneratedClient } from './codegen.js';
|
|
7
7
|
export { runCli } from './cli.js';
|
|
8
|
-
export type { ApiChange, ApiChangesResponse, ApiColumn, ApiConnectSessionResponse, ApiDeleteResponse, ApiRecord, ApiRecordResponse, ApiRecordType, ApiRowsResponse, ApiSchema, ApiTable, ApiTableOutcome, ApiUpsertResponse, } from './protocol.js';
|
|
8
|
+
export type { ApiChange, ApiChangesResponse, ApiColumn, ApiConnectSessionResponse, ApiDeleteResponse, ApiOrg, ApiOrgConnection, ApiOrgsResponse, ApiRecord, ApiRecordResponse, ApiRecordType, ApiRowsResponse, ApiSchema, ApiTable, ApiTableOutcome, ApiUpsertResponse, } from './protocol.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ConnectApi, createClient, RecordHandle, TableHandle, } from './client.js';
|
|
1
|
+
export { ConnectApi, createClient, OrgsApi, RecordHandle, TableHandle, } from './client.js';
|
|
2
2
|
export { errorFromResponse, ValtrixAuthError, ValtrixCursorExpiredError, ValtrixError, ValtrixRateLimitError, } from './errors.js';
|
|
3
3
|
export { camelCase, emitClientSource, pascalCase } from './codegen.js';
|
|
4
4
|
export { runCli } from './cli.js';
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type ApiColumn = {
|
|
2
2
|
key: string;
|
|
3
|
-
type: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
|
3
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'reference';
|
|
4
4
|
nullable: boolean;
|
|
5
5
|
description: string;
|
|
6
6
|
writable: boolean;
|
|
@@ -10,6 +10,7 @@ export type ApiTable = {
|
|
|
10
10
|
entity_type: string;
|
|
11
11
|
status: string;
|
|
12
12
|
row_count: number;
|
|
13
|
+
row_identity: 'record' | 'synthetic' | 'mixed' | null;
|
|
13
14
|
last_built_at: string | null;
|
|
14
15
|
schema_version: string | null;
|
|
15
16
|
columns: ApiColumn[];
|
|
@@ -80,3 +81,23 @@ export type ApiConnectSessionResponse = {
|
|
|
80
81
|
connect_url: string;
|
|
81
82
|
expires_at: string;
|
|
82
83
|
};
|
|
84
|
+
export type ApiOrgConnection = {
|
|
85
|
+
connector: string;
|
|
86
|
+
status: string;
|
|
87
|
+
};
|
|
88
|
+
export type ApiOrg = {
|
|
89
|
+
id: string;
|
|
90
|
+
display_name: string;
|
|
91
|
+
status: 'active' | 'revoked' | 'expired';
|
|
92
|
+
scope: {
|
|
93
|
+
entity_types: string[];
|
|
94
|
+
};
|
|
95
|
+
connections: ApiOrgConnection[];
|
|
96
|
+
connected_at: string;
|
|
97
|
+
expires_at: string | null;
|
|
98
|
+
revoked_at: string | null;
|
|
99
|
+
};
|
|
100
|
+
export type ApiOrgsResponse = {
|
|
101
|
+
orgs: ApiOrg[];
|
|
102
|
+
next_cursor: string | null;
|
|
103
|
+
};
|
package/package.json
CHANGED