@alloy-cx/sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/databases.d.ts +25 -0
- package/dist/databases.js +82 -0
- package/dist/http.d.ts +26 -0
- package/dist/http.js +84 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +31 -0
- package/dist/storage.d.ts +25 -0
- package/dist/storage.js +59 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +2 -0
- package/package.json +33 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alloy Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @alloy-cx/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js client for the Alloy private API: organization storage and storage databases. No runtime dependencies;
|
|
4
|
+
Node.js 18+. Works with both `require` and `import`, and ships TypeScript types.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @alloy-cx/sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Authentication
|
|
11
|
+
|
|
12
|
+
Code that Alloy runs for a user (the `run_code` workflow step, the `programming.run_code` tool, and commands in an
|
|
13
|
+
AI teammate's sandbox) gets `ALLOY_API_URL` and a temporary `ALLOY_API_TOKEN` with that user's rights in its
|
|
14
|
+
environment, so the client needs no arguments there:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const { AlloyClient } = require('@alloy-cx/sdk')
|
|
18
|
+
|
|
19
|
+
const client = new AlloyClient()
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Elsewhere, pass a personal access token from Organization Settings → API Tokens:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
const client = new AlloyClient({ token: 'alloy_pat_...', baseUrl: 'https://api.alloy.cx/api/private' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Never print the token or write it to a file.
|
|
29
|
+
|
|
30
|
+
## Storage
|
|
31
|
+
|
|
32
|
+
Paths are relative to the storage root.
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
await client.storage.list('reports') // direct children of a folder
|
|
36
|
+
await client.storage.upload('summary.csv', 'a,b\n1,2\n', 'reports')
|
|
37
|
+
await client.storage.meta('reports/summary.csv') // size, hash, timestamps, MIME type
|
|
38
|
+
await client.storage.download('reports/summary.csv') // Uint8Array
|
|
39
|
+
await client.storage.downloadText('reports/summary.csv') // string
|
|
40
|
+
await client.storage.createFolder('2026', 'reports') // returns the new folder's path
|
|
41
|
+
await client.storage.rename('reports/summary.csv', 'q1.csv') // returns the new path
|
|
42
|
+
await client.storage.move('reports/q1.csv', 'archive') // moves into the folder, returns the new path
|
|
43
|
+
await client.storage.delete('archive/q1.csv')
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Databases
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
await client.databases.list()
|
|
50
|
+
await client.databases.tables('crm')
|
|
51
|
+
await client.databases.table('crm', 'deals') // schema
|
|
52
|
+
|
|
53
|
+
const { rows, total } = await client.databases.rows('crm', 'deals', {
|
|
54
|
+
limit: 50,
|
|
55
|
+
orderBy: 'amount',
|
|
56
|
+
direction: 'DESC',
|
|
57
|
+
filters: [{ column: 'stage', operator: 'equals', value: 'won' }],
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const ids = await client.databases.insert('crm', 'deals', [{ title: 'Acme', amount: 1200 }])
|
|
61
|
+
const row = await client.databases.row('crm', 'deals', ids[0])
|
|
62
|
+
await client.databases.update('crm', 'deals', row.id, { amount: 1500 }, row.version)
|
|
63
|
+
await client.databases.updateMany('crm', 'deals', [{ id: ids[0], values: { stage: 'won' } }])
|
|
64
|
+
await client.databases.delete('crm', 'deals', ids)
|
|
65
|
+
|
|
66
|
+
await client.databases.query('crm', 'SELECT stage, SUM(amount) FROM deals WHERE amount > ? GROUP BY stage', [100])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`match: 'any'` returns rows matching any filter instead of all of them. `query` accepts only `SELECT` and
|
|
70
|
+
`WITH ... SELECT`.
|
|
71
|
+
|
|
72
|
+
## Errors
|
|
73
|
+
|
|
74
|
+
A failed request throws `AlloyApiError` with `status`, `message`, and the response `body`. With an expected version,
|
|
75
|
+
an update of a row changed since that version fails with status `409`.
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install
|
|
81
|
+
npm test
|
|
82
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HttpClient } from './http';
|
|
2
|
+
import type { CellValue, Database, ListRowsOptions, QueryResult, Row, RowPage, RowUpdate, Table } from './types';
|
|
3
|
+
/** The organization's storage databases: tables, rows, and read-only SQL. */
|
|
4
|
+
export declare class DatabasesApi {
|
|
5
|
+
private readonly http;
|
|
6
|
+
constructor(http: HttpClient);
|
|
7
|
+
list(): Promise<Database[]>;
|
|
8
|
+
tables(database: string): Promise<Table[]>;
|
|
9
|
+
table(database: string, table: string): Promise<Table>;
|
|
10
|
+
rows(database: string, table: string, options?: ListRowsOptions): Promise<RowPage>;
|
|
11
|
+
row(database: string, table: string, id: number): Promise<Row>;
|
|
12
|
+
/** Inserts rows and returns their ids, in order. */
|
|
13
|
+
insert(database: string, table: string, rows: Array<Record<string, CellValue>>): Promise<number[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Updates one row. With `expectedVersion` the update fails with a 409 when the row changed since
|
|
16
|
+
* that version; without it the last write wins.
|
|
17
|
+
*/
|
|
18
|
+
update(database: string, table: string, id: number, values: Record<string, CellValue>, expectedVersion?: number): Promise<boolean>;
|
|
19
|
+
/** Updates several rows, all or none, and returns how many changed. */
|
|
20
|
+
updateMany(database: string, table: string, updates: RowUpdate[]): Promise<number>;
|
|
21
|
+
/** Deletes rows by id and returns how many were deleted. */
|
|
22
|
+
delete(database: string, table: string, ids: number[]): Promise<number>;
|
|
23
|
+
/** Runs a read-only `SELECT` (or `WITH ... SELECT`), with `?` placeholders bound to `params`. */
|
|
24
|
+
query(database: string, sql: string, params?: CellValue[]): Promise<QueryResult>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabasesApi = void 0;
|
|
4
|
+
/** The organization's storage databases: tables, rows, and read-only SQL. */
|
|
5
|
+
class DatabasesApi {
|
|
6
|
+
http;
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
async list() {
|
|
11
|
+
const data = await this.http.request('GET', '/databases');
|
|
12
|
+
return data.databases;
|
|
13
|
+
}
|
|
14
|
+
async tables(database) {
|
|
15
|
+
const data = await this.http.request('GET', `/databases/${segment(database)}/tables`);
|
|
16
|
+
return data.tables;
|
|
17
|
+
}
|
|
18
|
+
async table(database, table) {
|
|
19
|
+
const data = await this.http.request('GET', tablePath(database, table));
|
|
20
|
+
return data.table;
|
|
21
|
+
}
|
|
22
|
+
async rows(database, table, options = {}) {
|
|
23
|
+
return await this.http.request('GET', `${tablePath(database, table)}/rows`, {
|
|
24
|
+
query: {
|
|
25
|
+
limit: options.limit,
|
|
26
|
+
offset: options.offset,
|
|
27
|
+
order_by: options.orderBy,
|
|
28
|
+
direction: options.direction,
|
|
29
|
+
match: options.match,
|
|
30
|
+
filters: options.filters ? JSON.stringify(options.filters) : undefined,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async row(database, table, id) {
|
|
35
|
+
const data = await this.http.request('GET', `${tablePath(database, table)}/rows/${id}`);
|
|
36
|
+
return data.row;
|
|
37
|
+
}
|
|
38
|
+
/** Inserts rows and returns their ids, in order. */
|
|
39
|
+
async insert(database, table, rows) {
|
|
40
|
+
const data = await this.http.request('POST', `${tablePath(database, table)}/rows`, { json: { rows } });
|
|
41
|
+
return data.ids;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Updates one row. With `expectedVersion` the update fails with a 409 when the row changed since
|
|
45
|
+
* that version; without it the last write wins.
|
|
46
|
+
*/
|
|
47
|
+
async update(database, table, id, values, expectedVersion) {
|
|
48
|
+
const data = await this.http.request('PATCH', `${tablePath(database, table)}/rows/${id}`, {
|
|
49
|
+
json: { values, ...(expectedVersion === undefined ? {} : { expected_version: expectedVersion }) },
|
|
50
|
+
});
|
|
51
|
+
return data.updated;
|
|
52
|
+
}
|
|
53
|
+
/** Updates several rows, all or none, and returns how many changed. */
|
|
54
|
+
async updateMany(database, table, updates) {
|
|
55
|
+
const data = await this.http.request('PATCH', `${tablePath(database, table)}/rows`, {
|
|
56
|
+
json: {
|
|
57
|
+
updates: updates.map(update => ({
|
|
58
|
+
id: update.id,
|
|
59
|
+
values: update.values,
|
|
60
|
+
...(update.expectedVersion === undefined ? {} : { expected_version: update.expectedVersion }),
|
|
61
|
+
})),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return data.updated;
|
|
65
|
+
}
|
|
66
|
+
/** Deletes rows by id and returns how many were deleted. */
|
|
67
|
+
async delete(database, table, ids) {
|
|
68
|
+
const data = await this.http.request('DELETE', `${tablePath(database, table)}/rows`, { json: { ids } });
|
|
69
|
+
return data.deleted;
|
|
70
|
+
}
|
|
71
|
+
/** Runs a read-only `SELECT` (or `WITH ... SELECT`), with `?` placeholders bound to `params`. */
|
|
72
|
+
async query(database, sql, params = []) {
|
|
73
|
+
return await this.http.request('POST', `/databases/${segment(database)}/query`, { json: { sql, params } });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.DatabasesApi = DatabasesApi;
|
|
77
|
+
function segment(value) {
|
|
78
|
+
return encodeURIComponent(value);
|
|
79
|
+
}
|
|
80
|
+
function tablePath(database, table) {
|
|
81
|
+
return `/databases/${segment(database)}/tables/${segment(table)}`;
|
|
82
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { JsonValue } from './types';
|
|
2
|
+
/** A non-2xx answer from the API. `body` is the parsed response, or its text when it is not JSON. */
|
|
3
|
+
export declare class AlloyApiError extends Error {
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly body: JsonValue;
|
|
6
|
+
constructor(status: number, message: string, body: JsonValue);
|
|
7
|
+
}
|
|
8
|
+
type QueryValue = string | number | boolean | undefined;
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
query?: Record<string, QueryValue>;
|
|
11
|
+
json?: JsonValue;
|
|
12
|
+
form?: FormData;
|
|
13
|
+
}
|
|
14
|
+
export declare class HttpClient {
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
private readonly token;
|
|
17
|
+
private readonly fetchImpl;
|
|
18
|
+
constructor(baseUrl: string, token: string, fetchImpl: typeof fetch);
|
|
19
|
+
/** Sends a request and returns the `data` of the JSON envelope. */
|
|
20
|
+
request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
|
|
21
|
+
/** Sends a request and returns the raw response body. */
|
|
22
|
+
requestBytes(method: string, path: string, options?: RequestOptions): Promise<Uint8Array>;
|
|
23
|
+
private send;
|
|
24
|
+
private readBody;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpClient = exports.AlloyApiError = void 0;
|
|
4
|
+
/** A non-2xx answer from the API. `body` is the parsed response, or its text when it is not JSON. */
|
|
5
|
+
class AlloyApiError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
body;
|
|
8
|
+
constructor(status, message, body) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
this.name = 'AlloyApiError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.AlloyApiError = AlloyApiError;
|
|
16
|
+
/** Reads the message out of either error shape the API answers with. */
|
|
17
|
+
function errorMessage(body, status) {
|
|
18
|
+
if (body && typeof body === 'object' && !Array.isArray(body)) {
|
|
19
|
+
const { message, error } = body;
|
|
20
|
+
if (typeof message === 'string')
|
|
21
|
+
return message;
|
|
22
|
+
if (typeof error === 'string')
|
|
23
|
+
return error;
|
|
24
|
+
if (error && typeof error === 'object' && !Array.isArray(error) && typeof error['message'] === 'string') {
|
|
25
|
+
return error['message'];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return typeof body === 'string' && body ? body : `Request failed with status ${status}`;
|
|
29
|
+
}
|
|
30
|
+
class HttpClient {
|
|
31
|
+
baseUrl;
|
|
32
|
+
token;
|
|
33
|
+
fetchImpl;
|
|
34
|
+
constructor(baseUrl, token, fetchImpl) {
|
|
35
|
+
this.baseUrl = baseUrl;
|
|
36
|
+
this.token = token;
|
|
37
|
+
this.fetchImpl = fetchImpl;
|
|
38
|
+
}
|
|
39
|
+
/** Sends a request and returns the `data` of the JSON envelope. */
|
|
40
|
+
async request(method, path, options = {}) {
|
|
41
|
+
const response = await this.send(method, path, options);
|
|
42
|
+
const body = await this.readBody(response);
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new AlloyApiError(response.status, errorMessage(body, response.status), body);
|
|
45
|
+
}
|
|
46
|
+
return body.data;
|
|
47
|
+
}
|
|
48
|
+
/** Sends a request and returns the raw response body. */
|
|
49
|
+
async requestBytes(method, path, options = {}) {
|
|
50
|
+
const response = await this.send(method, path, options);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const body = await this.readBody(response);
|
|
53
|
+
throw new AlloyApiError(response.status, errorMessage(body, response.status), body);
|
|
54
|
+
}
|
|
55
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
56
|
+
}
|
|
57
|
+
async send(method, path, options) {
|
|
58
|
+
const url = new URL(this.baseUrl.replace(/\/+$/, '') + path);
|
|
59
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
60
|
+
if (value !== undefined)
|
|
61
|
+
url.searchParams.set(name, String(value));
|
|
62
|
+
}
|
|
63
|
+
const headers = { Authorization: `Bearer ${this.token}` };
|
|
64
|
+
let body;
|
|
65
|
+
if (options.form) {
|
|
66
|
+
body = options.form;
|
|
67
|
+
}
|
|
68
|
+
else if (options.json !== undefined) {
|
|
69
|
+
headers['Content-Type'] = 'application/json';
|
|
70
|
+
body = JSON.stringify(options.json);
|
|
71
|
+
}
|
|
72
|
+
return await this.fetchImpl(url, { method, headers, ...(body === undefined ? {} : { body }) });
|
|
73
|
+
}
|
|
74
|
+
async readBody(response) {
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.HttpClient = HttpClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DatabasesApi } from './databases';
|
|
2
|
+
import { StorageApi } from './storage';
|
|
3
|
+
export { AlloyApiError } from './http';
|
|
4
|
+
export { DatabasesApi } from './databases';
|
|
5
|
+
export { StorageApi } from './storage';
|
|
6
|
+
export type * from './types';
|
|
7
|
+
export interface AlloyClientOptions {
|
|
8
|
+
/** Defaults to the `ALLOY_API_TOKEN` environment variable. */
|
|
9
|
+
token?: string;
|
|
10
|
+
/** The private API base URL, for example `https://api.alloy.cx/api/private`. Defaults to `ALLOY_API_URL`. */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Defaults to the global `fetch`. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Client for the Alloy private API. Inside an Alloy sandbox it needs no arguments: the token and
|
|
17
|
+
* URL come from the environment, and every call runs with the rights of the user the code runs for.
|
|
18
|
+
*/
|
|
19
|
+
export declare class AlloyClient {
|
|
20
|
+
readonly storage: StorageApi;
|
|
21
|
+
readonly databases: DatabasesApi;
|
|
22
|
+
constructor(options?: AlloyClientOptions);
|
|
23
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AlloyClient = exports.StorageApi = exports.DatabasesApi = exports.AlloyApiError = void 0;
|
|
4
|
+
const databases_1 = require("./databases");
|
|
5
|
+
const http_1 = require("./http");
|
|
6
|
+
const storage_1 = require("./storage");
|
|
7
|
+
var http_2 = require("./http");
|
|
8
|
+
Object.defineProperty(exports, "AlloyApiError", { enumerable: true, get: function () { return http_2.AlloyApiError; } });
|
|
9
|
+
var databases_2 = require("./databases");
|
|
10
|
+
Object.defineProperty(exports, "DatabasesApi", { enumerable: true, get: function () { return databases_2.DatabasesApi; } });
|
|
11
|
+
var storage_2 = require("./storage");
|
|
12
|
+
Object.defineProperty(exports, "StorageApi", { enumerable: true, get: function () { return storage_2.StorageApi; } });
|
|
13
|
+
/**
|
|
14
|
+
* Client for the Alloy private API. Inside an Alloy sandbox it needs no arguments: the token and
|
|
15
|
+
* URL come from the environment, and every call runs with the rights of the user the code runs for.
|
|
16
|
+
*/
|
|
17
|
+
class AlloyClient {
|
|
18
|
+
storage;
|
|
19
|
+
databases;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
const token = options.token ?? process.env['ALLOY_API_TOKEN'];
|
|
22
|
+
const baseUrl = options.baseUrl ?? process.env['ALLOY_API_URL'];
|
|
23
|
+
if (!token || !baseUrl) {
|
|
24
|
+
throw new Error('Alloy API token and URL are required: pass token and baseUrl, or set ALLOY_API_TOKEN and ALLOY_API_URL');
|
|
25
|
+
}
|
|
26
|
+
const http = new http_1.HttpClient(baseUrl, token, options.fetch ?? fetch);
|
|
27
|
+
this.storage = new storage_1.StorageApi(http);
|
|
28
|
+
this.databases = new databases_1.DatabasesApi(http);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.AlloyClient = AlloyClient;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HttpClient } from './http';
|
|
2
|
+
import type { FileMeta, StorageItem, UploadedFile } from './types';
|
|
3
|
+
/** Organization storage, with the rights of the token's user. Paths are relative to the storage root. */
|
|
4
|
+
export declare class StorageApi {
|
|
5
|
+
private readonly http;
|
|
6
|
+
constructor(http: HttpClient);
|
|
7
|
+
/** Lists the direct children of a folder; the root by default. */
|
|
8
|
+
list(path?: string): Promise<StorageItem[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Uploads a file named `name` into `folder` (the root by default), overwriting a file of the
|
|
11
|
+
* same name.
|
|
12
|
+
*/
|
|
13
|
+
upload(name: string, content: Uint8Array | string, folder?: string): Promise<UploadedFile>;
|
|
14
|
+
meta(path: string): Promise<FileMeta>;
|
|
15
|
+
download(path: string): Promise<Uint8Array>;
|
|
16
|
+
downloadText(path: string): Promise<string>;
|
|
17
|
+
/** Deletes a file or a folder with everything in it. */
|
|
18
|
+
delete(path: string): Promise<void>;
|
|
19
|
+
/** Creates folder `name` inside `parent` (the root by default) and returns its path. */
|
|
20
|
+
createFolder(name: string, parent?: string): Promise<string>;
|
|
21
|
+
/** Renames a file or folder in place and returns its new path. */
|
|
22
|
+
rename(path: string, newName: string): Promise<string>;
|
|
23
|
+
/** Moves a file or folder into the folder `destination` and returns its new path. */
|
|
24
|
+
move(path: string, destination: string): Promise<string>;
|
|
25
|
+
}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StorageApi = void 0;
|
|
4
|
+
/** Organization storage, with the rights of the token's user. Paths are relative to the storage root. */
|
|
5
|
+
class StorageApi {
|
|
6
|
+
http;
|
|
7
|
+
constructor(http) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
/** Lists the direct children of a folder; the root by default. */
|
|
11
|
+
async list(path = '/') {
|
|
12
|
+
return await this.http.request('GET', '/storage', { query: { path } });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Uploads a file named `name` into `folder` (the root by default), overwriting a file of the
|
|
16
|
+
* same name.
|
|
17
|
+
*/
|
|
18
|
+
async upload(name, content, folder) {
|
|
19
|
+
const form = new FormData();
|
|
20
|
+
if (folder !== undefined)
|
|
21
|
+
form.append('path', folder);
|
|
22
|
+
// Copied so a view over a SharedArrayBuffer, which Blob refuses, still uploads.
|
|
23
|
+
form.append('file', new Blob([typeof content === 'string' ? content : new Uint8Array(content)]), name);
|
|
24
|
+
return await this.http.request('POST', '/storage/upload', { form });
|
|
25
|
+
}
|
|
26
|
+
async meta(path) {
|
|
27
|
+
return await this.http.request('GET', '/storage/meta', { query: { path } });
|
|
28
|
+
}
|
|
29
|
+
async download(path) {
|
|
30
|
+
return await this.http.requestBytes('GET', '/storage/download', { query: { path } });
|
|
31
|
+
}
|
|
32
|
+
async downloadText(path) {
|
|
33
|
+
return new TextDecoder().decode(await this.download(path));
|
|
34
|
+
}
|
|
35
|
+
/** Deletes a file or a folder with everything in it. */
|
|
36
|
+
async delete(path) {
|
|
37
|
+
await this.http.request('DELETE', '/storage/file', { query: { path } });
|
|
38
|
+
}
|
|
39
|
+
/** Creates folder `name` inside `parent` (the root by default) and returns its path. */
|
|
40
|
+
async createFolder(name, parent) {
|
|
41
|
+
const data = await this.http.request('POST', '/storage/folder', {
|
|
42
|
+
json: { name, ...(parent === undefined ? {} : { path: parent }) },
|
|
43
|
+
});
|
|
44
|
+
return data.path;
|
|
45
|
+
}
|
|
46
|
+
/** Renames a file or folder in place and returns its new path. */
|
|
47
|
+
async rename(path, newName) {
|
|
48
|
+
const data = await this.http.request('POST', '/storage/rename', { json: { path, new_name: newName } });
|
|
49
|
+
return data.path;
|
|
50
|
+
}
|
|
51
|
+
/** Moves a file or folder into the folder `destination` and returns its new path. */
|
|
52
|
+
async move(path, destination) {
|
|
53
|
+
const data = await this.http.request('POST', '/storage/move', {
|
|
54
|
+
json: { source_path: path, dest_path: destination },
|
|
55
|
+
});
|
|
56
|
+
return data.path;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.StorageApi = StorageApi;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Any value a JSON response can hold. */
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
/** A storage entry, as `storage.list` returns it. */
|
|
6
|
+
export interface StorageItem {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
type: 'file' | 'folder';
|
|
10
|
+
/** Human-readable size of a file, for example `"1.2 KB"`. */
|
|
11
|
+
size?: string;
|
|
12
|
+
/** Number of entries in a folder. */
|
|
13
|
+
items?: number;
|
|
14
|
+
created: string;
|
|
15
|
+
modified: string;
|
|
16
|
+
}
|
|
17
|
+
export interface UploadedFile {
|
|
18
|
+
path: string;
|
|
19
|
+
name: string;
|
|
20
|
+
size: number;
|
|
21
|
+
mimetype: string;
|
|
22
|
+
}
|
|
23
|
+
export interface FileMeta {
|
|
24
|
+
path: string;
|
|
25
|
+
name: string;
|
|
26
|
+
size: number;
|
|
27
|
+
created_at: string;
|
|
28
|
+
modified_at: string;
|
|
29
|
+
hash: string;
|
|
30
|
+
mime_type: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** A cell of a database row. */
|
|
33
|
+
export type CellValue = string | number | boolean | null;
|
|
34
|
+
export interface Database {
|
|
35
|
+
alias: string;
|
|
36
|
+
displayName: string;
|
|
37
|
+
description: string;
|
|
38
|
+
sizeBytes: number;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
storagePath: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface Column {
|
|
43
|
+
name: string;
|
|
44
|
+
type: 'TEXT' | 'INTEGER' | 'REAL' | 'BOOLEAN' | 'TIMESTAMP';
|
|
45
|
+
nullable?: boolean;
|
|
46
|
+
unique?: boolean;
|
|
47
|
+
defaultValue?: CellValue;
|
|
48
|
+
}
|
|
49
|
+
export interface Table {
|
|
50
|
+
name: string;
|
|
51
|
+
columns: Column[];
|
|
52
|
+
rowCount: number;
|
|
53
|
+
}
|
|
54
|
+
export interface Row {
|
|
55
|
+
id: number;
|
|
56
|
+
/** Send it back as `expectedVersion` to update only if nobody changed the row since. */
|
|
57
|
+
version: number;
|
|
58
|
+
values: Record<string, CellValue>;
|
|
59
|
+
}
|
|
60
|
+
export interface RowPage {
|
|
61
|
+
rows: Row[];
|
|
62
|
+
/** Rows matching the filters, ignoring limit and offset. */
|
|
63
|
+
total: number;
|
|
64
|
+
}
|
|
65
|
+
export type FilterOperator = 'equals' | 'not_equals' | 'contains' | 'starts_with' | 'ends_with' | 'greater_than' | 'greater_or_equal' | 'less_than' | 'less_or_equal' | 'is_empty' | 'is_not_empty';
|
|
66
|
+
export interface RowFilter {
|
|
67
|
+
column: string;
|
|
68
|
+
operator: FilterOperator;
|
|
69
|
+
value?: CellValue;
|
|
70
|
+
}
|
|
71
|
+
export interface ListRowsOptions {
|
|
72
|
+
/** 1 to 1000. */
|
|
73
|
+
limit?: number;
|
|
74
|
+
offset?: number;
|
|
75
|
+
orderBy?: string;
|
|
76
|
+
direction?: 'ASC' | 'DESC';
|
|
77
|
+
/** Whether a row must match all filters (the default) or any of them. */
|
|
78
|
+
match?: 'all' | 'any';
|
|
79
|
+
filters?: RowFilter[];
|
|
80
|
+
}
|
|
81
|
+
export interface RowUpdate {
|
|
82
|
+
id: number;
|
|
83
|
+
values: Record<string, CellValue>;
|
|
84
|
+
expectedVersion?: number;
|
|
85
|
+
}
|
|
86
|
+
export interface QueryResult {
|
|
87
|
+
columns: string[];
|
|
88
|
+
rows: CellValue[][];
|
|
89
|
+
/** Set when the result was cut at the row limit. */
|
|
90
|
+
truncated: boolean;
|
|
91
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alloy-cx/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Client for the Alloy private API: organization storage and databases",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Alloy Systems",
|
|
7
|
+
"homepage": "https://alloy.cx",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Alloy-Systems/backend.git",
|
|
11
|
+
"directory": "sdk/node"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["alloy", "sdk", "storage", "database"],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.json",
|
|
28
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
29
|
+
"prepublishOnly": "npm test"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.10.0",
|
|
33
|
+
"typescript": "^5.7.0"
|
|
34
|
+
}
|
|
35
|
+
}
|