@alvera-ai/platform-sdk 0.9.0 → 0.10.0-rc.2

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.
@@ -1,128 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { createInterface } from 'node:readline';
3
- import { Command } from 'commander';
4
- import { ValiError } from 'valibot';
5
- import { createUnvalidatedPlatformApi } from '../client.js';
6
- import { getProfileName, resolveProfile } from '../config.js';
7
-
8
- export interface GlobalOpts {
9
- profile?: string;
10
- env?: string;
11
- }
12
-
13
- export function out(value: unknown): void {
14
- process.stdout.write(JSON.stringify(value, null, 2) + '\n');
15
- }
16
-
17
- export function die(message: string, code = 1): never {
18
- process.stderr.write(`alvera: ${message}\n`);
19
- process.exit(code);
20
- }
21
-
22
- export async function prompt(question: string, { hidden = false } = {}): Promise<string> {
23
- const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
24
- if (!hidden) {
25
- return new Promise((resolve) => rl.question(question, (ans) => {
26
- rl.close();
27
- resolve(ans);
28
- }));
29
- }
30
- const anyRl = rl as unknown as { _writeToOutput: (s: string) => void };
31
- const originalWrite = anyRl._writeToOutput.bind(rl);
32
- anyRl._writeToOutput = (s: string) => {
33
- if (s.includes(question)) originalWrite(s);
34
- else originalWrite('');
35
- };
36
- return new Promise((resolve) => rl.question(question, (ans) => {
37
- rl.close();
38
- process.stderr.write('\n');
39
- resolve(ans);
40
- }));
41
- }
42
-
43
- export function readBody(body: string | undefined, bodyFile: string | undefined): Record<string, unknown> {
44
- if (body && bodyFile) die('use only one of --body or --body-file');
45
- let raw: string;
46
- if (body) raw = body;
47
- else if (bodyFile === '-') raw = readFileSync(0, 'utf8');
48
- else if (bodyFile) raw = readFileSync(bodyFile, 'utf8');
49
- else die('missing request body (pass --body <json> or --body-file <path>)');
50
- try {
51
- const parsed = JSON.parse(raw);
52
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
53
- die('body must be a JSON object');
54
- }
55
- return parsed;
56
- } catch (err) {
57
- die(`invalid JSON in body: ${(err as Error).message}`);
58
- }
59
- }
60
-
61
- export function resolveTenant(explicit: string | undefined, profileTenant: string | null): string {
62
- const tenant = explicit ?? profileTenant;
63
- if (!tenant) die('tenant slug required (pass as argument or set `tenant_slug` in the profile)');
64
- return tenant;
65
- }
66
-
67
- export function authedApi(opts: GlobalOpts) {
68
- const profile = getProfileName(opts.profile);
69
- const resolved = resolveProfile(profile);
70
-
71
- if (resolved.apiKey) {
72
- return {
73
- api: createUnvalidatedPlatformApi({ baseUrl: resolved.baseUrl, apiKey: resolved.apiKey }),
74
- resolved,
75
- };
76
- }
77
-
78
- if (!resolved.sessionToken) {
79
- die(
80
- `no credentials for profile "${profile}". ` +
81
- `Run \`alvera login --profile ${profile}\`, set ALVERA_SESSION_TOKEN, or set ALVERA_API_KEY.`,
82
- );
83
- }
84
- if (resolved.expiresAt && new Date(resolved.expiresAt) < new Date()) {
85
- die(
86
- `session for profile "${profile}" expired at ${resolved.expiresAt}. ` +
87
- `Run \`alvera login --profile ${profile}\` to refresh.`,
88
- );
89
- }
90
- return {
91
- api: createUnvalidatedPlatformApi({ baseUrl: resolved.baseUrl, sessionToken: resolved.sessionToken }),
92
- resolved,
93
- };
94
- }
95
-
96
- export async function run(fn: () => Promise<unknown>): Promise<void> {
97
- try {
98
- const result = await fn();
99
- if (result !== undefined) out(result);
100
- } catch (err) {
101
- die(formatError(err));
102
- }
103
- }
104
-
105
- export function formatError(err: unknown): string {
106
- if (err instanceof ValiError) {
107
- const lines = err.issues.map((issue) => {
108
- const path = (issue.path ?? [])
109
- .map((p: { key?: unknown }) => String(p.key ?? '?'))
110
- .join('.') || '(root)';
111
- return ` ${path}: ${issue.message}`;
112
- });
113
- return `validation failed:\n${lines.join('\n')}`;
114
- }
115
- if (err instanceof Error) return err.message;
116
- if (err && typeof err === 'object') {
117
- const detail = (err as { errors?: { detail?: unknown } }).errors?.detail;
118
- if (typeof detail === 'string') return detail;
119
- return JSON.stringify(err, null, 2);
120
- }
121
- return String(err);
122
- }
123
-
124
- export function bodyOption(cmd: Command): Command {
125
- return cmd
126
- .option('--body <json>', 'request body as a JSON string')
127
- .option('--body-file <path>', 'path to a JSON file (or "-" for stdin)');
128
- }
package/src/cli/init.ts DELETED
@@ -1,123 +0,0 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
2
- import { createInterface } from 'node:readline';
3
- import { resolve } from 'node:path';
4
- import { Command } from 'commander';
5
-
6
- interface InitField {
7
- key: string;
8
- label: string;
9
- default?: string;
10
- }
11
- type InitSection = { header: string; fields: InitField[] }
12
-
13
- const DB_KEYS = ['HOST', 'PORT', 'NAME', 'SCHEMA', 'AUTH_METHOD', 'USER', 'PASS', 'ENABLE_SSL'] as const;
14
- const DB_LABELS = ['host', 'port', 'database name', 'schema', 'auth method', 'user', 'password', 'enable SSL'] as const;
15
- const DB_DEFAULTS: Record<string, string> = { PORT: '5432', SCHEMA: 'public', AUTH_METHOD: 'password', ENABLE_SSL: 'false' };
16
-
17
- function dbSection(label: string, prefix: string): InitSection {
18
- return {
19
- header: label,
20
- fields: DB_KEYS.map((k, i) => ({
21
- key: `${prefix}_${k}`,
22
- label: `${label} — ${DB_LABELS[i]}`,
23
- default: DB_DEFAULTS[k],
24
- })),
25
- };
26
- }
27
-
28
- const STORAGE_KEYS = ['TYPE', 'REGION', 'ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'ENDPOINT', 'BUCKET'] as const;
29
- const STORAGE_LABELS = ['type (aws/r2)', 'region', 'access key ID', 'secret access key', 'endpoint', 'bucket'] as const;
30
-
31
- function storageSection(label: string, prefix: string): InitSection {
32
- return {
33
- header: label,
34
- fields: STORAGE_KEYS.map((k, i) => ({
35
- key: `${prefix}_${k}`,
36
- label: `${label} — ${STORAGE_LABELS[i]}`,
37
- default: k === 'TYPE' ? 'aws' : undefined,
38
- })),
39
- };
40
- }
41
-
42
- async function collectAndWrite(sections: InitSection[], outPath: string): Promise<void> {
43
- const allFields = sections.flatMap((s) => s.fields);
44
-
45
- let answers: string[];
46
- if (process.stdin.isTTY) {
47
- const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
48
- const ask = (q: string): Promise<string> =>
49
- new Promise((res) => rl.question(q, (ans) => res(ans)));
50
- answers = [];
51
- for (const field of allFields) {
52
- const dflt = field.default ?? '';
53
- const suffix = dflt ? ` [${dflt}]` : '';
54
- answers.push(await ask(`${field.label}${suffix}: `));
55
- }
56
- rl.close();
57
- } else {
58
- const raw = readFileSync(0, 'utf8');
59
- answers = raw.split('\n');
60
- }
61
-
62
- const lines: string[] = [];
63
- let idx = 0;
64
- for (const section of sections) {
65
- if (lines.length > 0) lines.push('');
66
- lines.push(`# ${section.header}`);
67
- for (const field of section.fields) {
68
- const dflt = field.default ?? '';
69
- const answer = (answers[idx++] ?? '').trim();
70
- lines.push(`${field.key}=${answer || dflt}`);
71
- }
72
- }
73
-
74
- const abs = resolve(process.cwd(), outPath);
75
- writeFileSync(abs, lines.join('\n') + '\n');
76
- process.stderr.write(`Wrote ${abs}\n`);
77
- }
78
-
79
- export function register(program: Command): void {
80
- const initCmd = program.command('init').description('Generate a .env configuration file');
81
-
82
- initCmd
83
- .command('connected-app')
84
- .description('Generate .env for SDK / connected-app integration')
85
- .option('-o, --output <path>', 'output file path', '.env')
86
- .action(async (opts: { output: string }) => {
87
- await collectAndWrite([
88
- {
89
- header: 'Alvera connected-app configuration',
90
- fields: [
91
- { key: 'ALVERA_BASE_URL', label: 'Base URL', default: 'https://api.alvera.ai' },
92
- { key: 'ALVERA_TENANT', label: 'Tenant slug' },
93
- { key: 'ALVERA_DATALAKE', label: 'Datalake slug' },
94
- { key: 'ALVERA_CONNECTED_APP', label: 'Connected app slug' },
95
- ],
96
- },
97
- ], opts.output);
98
- });
99
-
100
- initCmd
101
- .command('infra-setup')
102
- .description('Generate .env for datalake infrastructure (databases + object storage)')
103
- .option('-o, --output <path>', 'output file path', '.env')
104
- .action(async (opts: { output: string }) => {
105
- await collectAndWrite([
106
- {
107
- header: 'Datalake',
108
- fields: [
109
- { key: 'ALVERA_DATALAKE_NAME', label: 'Datalake name' },
110
- { key: 'ALVERA_DATALAKE_DATA_DOMAIN', label: 'Data domain' },
111
- { key: 'ALVERA_DATALAKE_TIMEZONE', label: 'Timezone', default: 'UTC' },
112
- { key: 'ALVERA_DATALAKE_POOL_SIZE', label: 'Pool size', default: '10' },
113
- ],
114
- },
115
- dbSection('Database — Unregulated Reader', 'ALVERA_DB_UNREG_READER'),
116
- dbSection('Database — Unregulated Writer', 'ALVERA_DB_UNREG_WRITER'),
117
- dbSection('Database — Regulated Reader', 'ALVERA_DB_REG_READER'),
118
- dbSection('Database — Regulated Writer', 'ALVERA_DB_REG_WRITER'),
119
- storageSection('Storage — Unregulated', 'ALVERA_STORAGE_UNREG'),
120
- storageSection('Storage — Regulated', 'ALVERA_STORAGE_REG'),
121
- ], opts.output);
122
- });
123
- }
package/src/cli/raw.ts DELETED
@@ -1,65 +0,0 @@
1
- import { Command } from 'commander';
2
- import { getProfileName, resolveProfile } from '../config.js';
3
- import { type GlobalOpts, bodyOption, die, out, readBody } from './helpers.js';
4
-
5
- const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;
6
-
7
- export function register(program: Command): void {
8
- bodyOption(
9
- program
10
- .command('raw <method> <path>')
11
- .description('Send an authenticated HTTP request bypassing SDK validation')
12
- .option('--no-parse', 'print raw response text instead of pretty-printing JSON'),
13
- )
14
- .action(
15
- async (
16
- method: string,
17
- path: string,
18
- opts: { body?: string; bodyFile?: string; parse?: boolean },
19
- ) => {
20
- const upper = method.toUpperCase();
21
- if (!ALLOWED_METHODS.includes(upper as (typeof ALLOWED_METHODS)[number])) {
22
- die(`invalid method "${method}". Allowed: ${ALLOWED_METHODS.join(', ')}`);
23
- }
24
-
25
- const globalOpts = program.opts<GlobalOpts>();
26
- const profile = getProfileName(globalOpts.profile);
27
- const resolved = resolveProfile(profile);
28
- if (!resolved.sessionToken && !resolved.apiKey) {
29
- die(
30
- `no credentials for profile "${profile}". ` +
31
- `Run \`alvera login\`, set ALVERA_SESSION_TOKEN, or set ALVERA_API_KEY.`,
32
- );
33
- }
34
-
35
- const url = `${resolved.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
36
- const headers: Record<string, string> = resolved.apiKey
37
- ? { 'X-API-Key': resolved.apiKey }
38
- : { Authorization: `Bearer ${resolved.sessionToken!}` };
39
-
40
- let fetchBody: string | undefined;
41
- if (opts.body || opts.bodyFile) {
42
- const parsed = readBody(opts.body, opts.bodyFile);
43
- fetchBody = JSON.stringify(parsed);
44
- headers['Content-Type'] = 'application/json';
45
- }
46
-
47
- const resp = await fetch(url, { method: upper, headers, body: fetchBody });
48
- const text = await resp.text();
49
-
50
- if (!resp.ok) {
51
- die(`${upper} ${path} → ${resp.status} ${resp.statusText}: ${text}`);
52
- }
53
-
54
- if (opts.parse === false) {
55
- process.stdout.write(text + '\n');
56
- } else {
57
- try {
58
- out(JSON.parse(text));
59
- } catch {
60
- process.stdout.write(text + '\n');
61
- }
62
- }
63
- },
64
- );
65
- }