@kmlckj/licos-platform-sdk 0.6.0 → 0.6.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.
- package/README.md +32 -26
- package/package.json +45 -45
- package/src/browser.d.ts +0 -27
- package/src/browser.js +1 -64
- package/src/database.js +255 -0
- package/src/index.d.ts +197 -32
- package/src/index.js +3 -68
- package/src/runtime.js +70 -0
- package/src/shared.js +0 -87
- package/src/storage.js +155 -0
package/README.md
CHANGED
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
# @kmlckj/licos-platform-sdk
|
|
2
2
|
|
|
3
|
-
LICOS
|
|
3
|
+
LICOS platform SDK for server-side project runtime code.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Runtime Configuration
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `download(url, outputPath)` 会把文件写到本地路径
|
|
11
|
-
- 预览 / 下载都要求鉴权;`download(...)` 会自动带上 `token` 或 `LICOS_RUNTIME_TOKEN`
|
|
7
|
+
Server-side helpers call platform Studio runtime APIs. Project code does not
|
|
8
|
+
pass API base URLs, project IDs, tokens, or environment scopes. The SDK loads
|
|
9
|
+
them from runtime environment variables injected by LICOS:
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
11
|
+
- `LICOS_PLATFORM_API_BASE_URL`
|
|
12
|
+
- `LICOS_RUNTIME_TOKEN` or `LICOS_USER_TOKEN`
|
|
13
|
+
- `LICOS_PROJECT_ID` or `AGENT_PROJECT_ID`
|
|
14
|
+
- `LICOS_WORKSPACE_ID` or `AGENT_WORKSPACE_ID`
|
|
15
|
+
- `LICOS_PROJECT_ENV`, mapped internally to `dev` or `prod`
|
|
19
16
|
|
|
20
|
-
##
|
|
17
|
+
## Database
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
- TypeScript 前端可显式导入浏览器入口 `@kmlckj/licos-platform-sdk/browser`,拿到浏览器专用类型
|
|
25
|
-
- 预览 / 下载接口同样要求传 `token`
|
|
19
|
+
Database helpers call the runtime database data plane only. They do not expose
|
|
20
|
+
database control-plane or service deletion APIs.
|
|
26
21
|
|
|
27
22
|
```js
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
23
|
+
import { database } from '@kmlckj/licos-platform-sdk';
|
|
24
|
+
|
|
25
|
+
const rows = await database
|
|
26
|
+
.table('todos')
|
|
27
|
+
.select('id', 'title')
|
|
28
|
+
.eq('done', false)
|
|
29
|
+
.order('created_at', { desc: true })
|
|
30
|
+
.limit(10)
|
|
31
|
+
.execute();
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
import { download, previewUrl } from '@kmlckj/licos-platform-sdk/browser';
|
|
34
|
+
## Object Storage
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
```js
|
|
37
|
+
import { storage } from '@kmlckj/licos-platform-sdk';
|
|
38
|
+
|
|
39
|
+
await storage.createFolder('reports');
|
|
40
|
+
const file = await storage.uploadFile('/tmp/report.pdf');
|
|
41
|
+
const link = await storage.shareUrl(file.id);
|
|
39
42
|
```
|
|
43
|
+
|
|
44
|
+
The browser entry does not export object storage helpers because runtime tokens
|
|
45
|
+
must not be bundled into public frontend code.
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@kmlckj/licos-platform-sdk",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "LICOS platform SDK
|
|
5
|
-
"author": "kmlckj",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./src/index.js",
|
|
8
|
-
"browser": "./src/browser.js",
|
|
9
|
-
"types": "./src/index.d.ts",
|
|
10
|
-
"typesVersions": {
|
|
11
|
-
"*": {
|
|
12
|
-
"browser": [
|
|
13
|
-
"src/browser.d.ts"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"exports": {
|
|
18
|
-
".": {
|
|
19
|
-
"browser": {
|
|
20
|
-
"types": "./src/browser.d.ts",
|
|
21
|
-
"default": "./src/browser.js"
|
|
22
|
-
},
|
|
23
|
-
"types": "./src/index.d.ts",
|
|
24
|
-
"import": "./src/index.js",
|
|
25
|
-
"default": "./src/index.js"
|
|
26
|
-
},
|
|
27
|
-
"./browser": {
|
|
28
|
-
"types": "./src/browser.d.ts",
|
|
29
|
-
"import": "./src/browser.js",
|
|
30
|
-
"default": "./src/browser.js"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"files": [
|
|
34
|
-
"src",
|
|
35
|
-
"README.md"
|
|
36
|
-
],
|
|
37
|
-
"scripts": {
|
|
38
|
-
"test": "node --test"
|
|
39
|
-
},
|
|
40
|
-
"license": "MIT",
|
|
41
|
-
"publishConfig": {
|
|
42
|
-
"access": "public",
|
|
43
|
-
"registry": "https://registry.npmjs.org"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@kmlckj/licos-platform-sdk",
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "LICOS platform SDK package shell for browser and Node runtimes",
|
|
5
|
+
"author": "kmlckj",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"browser": "./src/browser.js",
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"typesVersions": {
|
|
11
|
+
"*": {
|
|
12
|
+
"browser": [
|
|
13
|
+
"src/browser.d.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"browser": {
|
|
20
|
+
"types": "./src/browser.d.ts",
|
|
21
|
+
"default": "./src/browser.js"
|
|
22
|
+
},
|
|
23
|
+
"types": "./src/index.d.ts",
|
|
24
|
+
"import": "./src/index.js",
|
|
25
|
+
"default": "./src/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./browser": {
|
|
28
|
+
"types": "./src/browser.d.ts",
|
|
29
|
+
"import": "./src/browser.js",
|
|
30
|
+
"default": "./src/browser.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "node --test"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/browser.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
export interface StorageClientOptions {
|
|
2
|
-
baseUrl?: string;
|
|
3
|
-
token?: string;
|
|
4
|
-
fetch?: typeof fetch;
|
|
5
|
-
headers?: HeadersInit;
|
|
6
|
-
credentials?: RequestCredentials;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
export class PlatformSdkError extends Error {
|
|
10
2
|
status?: number;
|
|
11
3
|
code?: number;
|
|
@@ -14,22 +6,3 @@ export class PlatformSdkError extends Error {
|
|
|
14
6
|
|
|
15
7
|
export class ConfigurationError extends PlatformSdkError {}
|
|
16
8
|
export class ApiError extends PlatformSdkError {}
|
|
17
|
-
|
|
18
|
-
export class StorageClient {
|
|
19
|
-
constructor(options?: StorageClientOptions);
|
|
20
|
-
previewUrl(bucket: string, objectKey: string): string;
|
|
21
|
-
downloadUrl(bucket: string, objectKey: string): string;
|
|
22
|
-
download(url: string): Promise<Blob>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function previewUrl(
|
|
26
|
-
bucket: string,
|
|
27
|
-
objectKey: string,
|
|
28
|
-
options?: StorageClientOptions,
|
|
29
|
-
): string;
|
|
30
|
-
export function downloadUrl(
|
|
31
|
-
bucket: string,
|
|
32
|
-
objectKey: string,
|
|
33
|
-
options?: StorageClientOptions,
|
|
34
|
-
): string;
|
|
35
|
-
export function download(url: string, options?: StorageClientOptions): Promise<Blob>;
|
package/src/browser.js
CHANGED
|
@@ -1,64 +1 @@
|
|
|
1
|
-
|
|
2
|
-
ApiError,
|
|
3
|
-
ConfigurationError,
|
|
4
|
-
PlatformSdkError,
|
|
5
|
-
downloadBlob,
|
|
6
|
-
encodeObjectKey,
|
|
7
|
-
encodeStorageSegment,
|
|
8
|
-
normalizeBaseUrl,
|
|
9
|
-
} from './shared.js';
|
|
10
|
-
|
|
11
|
-
export class StorageClient {
|
|
12
|
-
constructor(options = {}) {
|
|
13
|
-
this.baseUrl = options.baseUrl;
|
|
14
|
-
this.token = options.token;
|
|
15
|
-
this.fetchImpl = options.fetch ?? fetch;
|
|
16
|
-
this.headers = options.headers;
|
|
17
|
-
this.credentials = options.credentials;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
previewUrl(bucket, objectKey) {
|
|
21
|
-
const baseUrl = normalizeBaseUrl(this.baseUrl);
|
|
22
|
-
return `${baseUrl}/api/v1/public/storage/${encodeStorageSegment(bucket)}/${encodeObjectKey(objectKey)}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
downloadUrl(bucket, objectKey) {
|
|
26
|
-
return `${this.previewUrl(bucket, objectKey)}?download=1`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async download(url, outputPath) {
|
|
30
|
-
if (outputPath !== undefined) {
|
|
31
|
-
throw new ConfigurationError(
|
|
32
|
-
'Front-end mode does not support downloading directly to a filesystem path. Omit outputPath to receive a Blob.',
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return downloadBlob(this.fetchImpl, url, {
|
|
36
|
-
token: this.token,
|
|
37
|
-
headers: this.headers,
|
|
38
|
-
credentials: this.credentials,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function defaultClient(options = {}) {
|
|
44
|
-
return new StorageClient(options);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function previewUrl(bucket, objectKey, options = {}) {
|
|
48
|
-
return defaultClient(options).previewUrl(bucket, objectKey);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function downloadUrl(bucket, objectKey, options = {}) {
|
|
52
|
-
return defaultClient(options).downloadUrl(bucket, objectKey);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function download(url, outputPathOrOptions, maybeOptions = {}) {
|
|
56
|
-
if (typeof outputPathOrOptions === 'string') {
|
|
57
|
-
throw new ConfigurationError(
|
|
58
|
-
'Front-end mode does not support downloading directly to a filesystem path. Omit outputPath to receive a Blob.',
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
return defaultClient(outputPathOrOptions ?? maybeOptions).download(url);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export { PlatformSdkError, ConfigurationError, ApiError };
|
|
1
|
+
export { PlatformSdkError, ConfigurationError, ApiError } from './shared.js';
|
package/src/database.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { ApiError } from './shared.js';
|
|
2
|
+
import { authHeaders, runtimeConfig } from './runtime.js';
|
|
3
|
+
|
|
4
|
+
function databaseConfig() {
|
|
5
|
+
return runtimeConfig();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function databaseUrl(config, route) {
|
|
9
|
+
return `${config.baseUrl}/api/v1/studio/runtime/database/projects/${encodeURIComponent(config.projectId)}${route}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function compactObject(value) {
|
|
13
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeSelect(select) {
|
|
17
|
+
if (select === undefined || select === null) return undefined;
|
|
18
|
+
if (typeof select === 'string') {
|
|
19
|
+
return select.split(',').map((item) => item.trim()).filter(Boolean);
|
|
20
|
+
}
|
|
21
|
+
return Array.from(select).map((item) => String(item));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function decodeResponse(response) {
|
|
25
|
+
const text = await response.text();
|
|
26
|
+
const payload = text ? JSON.parse(text) : null;
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new ApiError(text || `platform database API returned ${response.status}`, {
|
|
29
|
+
status: response.status,
|
|
30
|
+
details: payload,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!payload || typeof payload !== 'object') return payload;
|
|
34
|
+
if ((payload.code !== undefined && payload.code !== 0) || payload.success === false) {
|
|
35
|
+
throw new ApiError(payload.message || 'platform database API failed', {
|
|
36
|
+
status: response.status,
|
|
37
|
+
code: typeof payload.code === 'number' ? payload.code : undefined,
|
|
38
|
+
details: payload,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return payload.data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function requestJson(route, body) {
|
|
45
|
+
const config = databaseConfig();
|
|
46
|
+
const payload = { environment: config.environment, ...compactObject(body) };
|
|
47
|
+
const response = await fetch(databaseUrl(config, route), {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: authHeaders(config),
|
|
50
|
+
body: JSON.stringify(payload),
|
|
51
|
+
});
|
|
52
|
+
return decodeResponse(response);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function query(table, options = {}) {
|
|
56
|
+
return requestJson('/query', {
|
|
57
|
+
table,
|
|
58
|
+
select: normalizeSelect(options.select),
|
|
59
|
+
filters: options.filters?.length ? options.filters : undefined,
|
|
60
|
+
orderBy: options.orderBy?.length ? options.orderBy : undefined,
|
|
61
|
+
range: options.range,
|
|
62
|
+
limit: options.limit,
|
|
63
|
+
offset: options.offset,
|
|
64
|
+
count: options.count,
|
|
65
|
+
head: options.head,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function single(table, options = {}) {
|
|
70
|
+
return requestJson('/single', {
|
|
71
|
+
table,
|
|
72
|
+
select: normalizeSelect(options.select),
|
|
73
|
+
filters: options.filters?.length ? options.filters : undefined,
|
|
74
|
+
orderBy: options.orderBy?.length ? options.orderBy : undefined,
|
|
75
|
+
mode: options.maybe ? 'maybe_single' : 'single',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function maybeSingle(table, options = {}) {
|
|
80
|
+
return single(table, { ...options, maybe: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function aggregate(table, aggregateName, options = {}) {
|
|
84
|
+
return requestJson('/aggregate', {
|
|
85
|
+
table,
|
|
86
|
+
filters: options.filters?.length ? options.filters : undefined,
|
|
87
|
+
aggregate: aggregateName,
|
|
88
|
+
field: options.field,
|
|
89
|
+
groupBy: options.groupBy?.length ? options.groupBy : undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function insert(table, options = {}) {
|
|
94
|
+
return requestJson('/insert', {
|
|
95
|
+
table,
|
|
96
|
+
row: options.row,
|
|
97
|
+
rows: options.rows,
|
|
98
|
+
returning: options.returning ?? true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function updateRows(table, options = {}) {
|
|
103
|
+
return requestJson('/update', {
|
|
104
|
+
table,
|
|
105
|
+
values: options.values,
|
|
106
|
+
filters: options.filters?.length ? options.filters : undefined,
|
|
107
|
+
updates: options.updates?.length ? options.updates : undefined,
|
|
108
|
+
returning: options.returning ?? true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function deleteRows(table, options = {}) {
|
|
113
|
+
return requestJson('/delete', {
|
|
114
|
+
table,
|
|
115
|
+
filters: options.filters?.length ? options.filters : undefined,
|
|
116
|
+
deletes: options.deletes?.length ? options.deletes : undefined,
|
|
117
|
+
returning: options.returning ?? true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function upsert(table, options = {}) {
|
|
122
|
+
return requestJson('/upsert', {
|
|
123
|
+
table,
|
|
124
|
+
row: options.row,
|
|
125
|
+
rows: options.rows,
|
|
126
|
+
conflictKeys: options.conflictKeys?.length ? options.conflictKeys : undefined,
|
|
127
|
+
returning: options.returning ?? true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function transaction(operations) {
|
|
132
|
+
const config = databaseConfig();
|
|
133
|
+
const normalized = operations.map((operation) => ({
|
|
134
|
+
type: operation.type,
|
|
135
|
+
request: {
|
|
136
|
+
...operation.request,
|
|
137
|
+
environment: config.environment,
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
return requestJson('/transaction', { operations: normalized });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function rpc(functionName, args = {}) {
|
|
144
|
+
return requestJson(`/rpc/${encodeURIComponent(functionName)}`, { args });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function table(name) {
|
|
148
|
+
return new TableQuery(name);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class TableQuery {
|
|
152
|
+
constructor(name) {
|
|
153
|
+
this.name = name;
|
|
154
|
+
this._select = undefined;
|
|
155
|
+
this._filters = [];
|
|
156
|
+
this._orderBy = [];
|
|
157
|
+
this._range = undefined;
|
|
158
|
+
this._limit = undefined;
|
|
159
|
+
this._offset = undefined;
|
|
160
|
+
this._count = undefined;
|
|
161
|
+
this._head = undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
select(...fields) {
|
|
165
|
+
this._select = fields.length === 1 && fields[0].includes(',') ? normalizeSelect(fields[0]) : fields;
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
filter(field, op, value) {
|
|
170
|
+
this._filters.push({ field, op, value });
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
eq(field, value) { return this.filter(field, 'eq', value); }
|
|
175
|
+
neq(field, value) { return this.filter(field, 'neq', value); }
|
|
176
|
+
gt(field, value) { return this.filter(field, 'gt', value); }
|
|
177
|
+
gte(field, value) { return this.filter(field, 'gte', value); }
|
|
178
|
+
lt(field, value) { return this.filter(field, 'lt', value); }
|
|
179
|
+
lte(field, value) { return this.filter(field, 'lte', value); }
|
|
180
|
+
in(field, value) { return this.filter(field, 'in', value); }
|
|
181
|
+
notIn(field, value) { return this.filter(field, 'not_in', value); }
|
|
182
|
+
isNull(field) { return this.filter(field, 'is_null'); }
|
|
183
|
+
isNotNull(field) { return this.filter(field, 'is_not_null'); }
|
|
184
|
+
like(field, value) { return this.filter(field, 'like', value); }
|
|
185
|
+
ilike(field, value) { return this.filter(field, 'ilike', value); }
|
|
186
|
+
|
|
187
|
+
order(field, options = {}) {
|
|
188
|
+
this._orderBy.push({ field, direction: options.desc ? 'desc' : 'asc' });
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
range(from, to) {
|
|
193
|
+
this._range = { from, to };
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
limit(value) {
|
|
198
|
+
this._limit = value;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
offset(value) {
|
|
203
|
+
this._offset = value;
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
withCount(options = {}) {
|
|
208
|
+
this._count = true;
|
|
209
|
+
this._head = Boolean(options.head);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
execute() {
|
|
214
|
+
return query(this.name, {
|
|
215
|
+
select: this._select,
|
|
216
|
+
filters: this._filters,
|
|
217
|
+
orderBy: this._orderBy,
|
|
218
|
+
range: this._range,
|
|
219
|
+
limit: this._limit,
|
|
220
|
+
offset: this._offset,
|
|
221
|
+
count: this._count,
|
|
222
|
+
head: this._head,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
single() {
|
|
227
|
+
return single(this.name, {
|
|
228
|
+
select: this._select,
|
|
229
|
+
filters: this._filters,
|
|
230
|
+
orderBy: this._orderBy,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
maybeSingle() {
|
|
235
|
+
return maybeSingle(this.name, {
|
|
236
|
+
select: this._select,
|
|
237
|
+
filters: this._filters,
|
|
238
|
+
orderBy: this._orderBy,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const database = {
|
|
244
|
+
query,
|
|
245
|
+
single,
|
|
246
|
+
maybeSingle,
|
|
247
|
+
aggregate,
|
|
248
|
+
insert,
|
|
249
|
+
updateRows,
|
|
250
|
+
deleteRows,
|
|
251
|
+
upsert,
|
|
252
|
+
transaction,
|
|
253
|
+
rpc,
|
|
254
|
+
table,
|
|
255
|
+
};
|
package/src/index.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
export interface StorageClientOptions {
|
|
2
|
-
baseUrl?: string;
|
|
3
|
-
token?: string;
|
|
4
|
-
fetch?: typeof fetch;
|
|
5
|
-
headers?: HeadersInit;
|
|
6
|
-
credentials?: RequestCredentials;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
export class PlatformSdkError extends Error {
|
|
10
2
|
status?: number;
|
|
11
3
|
code?: number;
|
|
@@ -15,27 +7,200 @@ export class PlatformSdkError extends Error {
|
|
|
15
7
|
export class ConfigurationError extends PlatformSdkError {}
|
|
16
8
|
export class ApiError extends PlatformSdkError {}
|
|
17
9
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
10
|
+
export interface DatabaseFilter {
|
|
11
|
+
field: string;
|
|
12
|
+
op: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in' | 'is_null' | 'is_not_null' | 'like' | 'ilike' | string;
|
|
13
|
+
value?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DatabaseOrderBy {
|
|
17
|
+
field: string;
|
|
18
|
+
direction?: 'asc' | 'desc';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DatabaseRange {
|
|
22
|
+
from: number;
|
|
23
|
+
to: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DatabaseResponse {
|
|
27
|
+
data?: Record<string, unknown>[];
|
|
28
|
+
count?: number;
|
|
29
|
+
affectedRows?: number;
|
|
30
|
+
message?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DatabaseQueryOptions {
|
|
35
|
+
select?: string | string[];
|
|
36
|
+
filters?: DatabaseFilter[];
|
|
37
|
+
orderBy?: DatabaseOrderBy[];
|
|
38
|
+
range?: DatabaseRange;
|
|
39
|
+
limit?: number;
|
|
40
|
+
offset?: number;
|
|
41
|
+
count?: boolean;
|
|
42
|
+
head?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DatabaseMutationOptions {
|
|
46
|
+
row?: Record<string, unknown>;
|
|
47
|
+
rows?: Record<string, unknown>[];
|
|
48
|
+
values?: Record<string, unknown>;
|
|
49
|
+
filters?: DatabaseFilter[];
|
|
50
|
+
updates?: Array<{ filters?: DatabaseFilter[]; values?: Record<string, unknown> }>;
|
|
51
|
+
deletes?: Array<{ filters?: DatabaseFilter[] }>;
|
|
52
|
+
conflictKeys?: string[];
|
|
53
|
+
returning?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DatabaseOperation {
|
|
57
|
+
type: 'query' | 'insert' | 'update' | 'delete' | 'upsert' | string;
|
|
58
|
+
request: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TableQuery {
|
|
62
|
+
select(...fields: string[]): TableQuery;
|
|
63
|
+
filter(field: string, op: string, value?: unknown): TableQuery;
|
|
64
|
+
eq(field: string, value: unknown): TableQuery;
|
|
65
|
+
neq(field: string, value: unknown): TableQuery;
|
|
66
|
+
gt(field: string, value: unknown): TableQuery;
|
|
67
|
+
gte(field: string, value: unknown): TableQuery;
|
|
68
|
+
lt(field: string, value: unknown): TableQuery;
|
|
69
|
+
lte(field: string, value: unknown): TableQuery;
|
|
70
|
+
in(field: string, value: unknown[]): TableQuery;
|
|
71
|
+
notIn(field: string, value: unknown[]): TableQuery;
|
|
72
|
+
isNull(field: string): TableQuery;
|
|
73
|
+
isNotNull(field: string): TableQuery;
|
|
74
|
+
like(field: string, value: string): TableQuery;
|
|
75
|
+
ilike(field: string, value: string): TableQuery;
|
|
76
|
+
order(field: string, options?: { desc?: boolean }): TableQuery;
|
|
77
|
+
range(from: number, to: number): TableQuery;
|
|
78
|
+
limit(value: number): TableQuery;
|
|
79
|
+
offset(value: number): TableQuery;
|
|
80
|
+
withCount(options?: { head?: boolean }): TableQuery;
|
|
81
|
+
execute(): Promise<DatabaseResponse>;
|
|
82
|
+
single(): Promise<DatabaseResponse>;
|
|
83
|
+
maybeSingle(): Promise<DatabaseResponse>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function query(table: string, options?: DatabaseQueryOptions): Promise<DatabaseResponse>;
|
|
87
|
+
export function single(table: string, options?: DatabaseQueryOptions & { maybe?: boolean }): Promise<DatabaseResponse>;
|
|
88
|
+
export function maybeSingle(table: string, options?: DatabaseQueryOptions): Promise<DatabaseResponse>;
|
|
89
|
+
export function aggregate(table: string, aggregateName: string, options?: { field?: string; groupBy?: string[]; filters?: DatabaseFilter[] }): Promise<DatabaseResponse>;
|
|
90
|
+
export function insert(table: string, options?: DatabaseMutationOptions): Promise<DatabaseResponse>;
|
|
91
|
+
export function updateRows(table: string, options?: DatabaseMutationOptions): Promise<DatabaseResponse>;
|
|
92
|
+
export function deleteRows(table: string, options?: DatabaseMutationOptions): Promise<DatabaseResponse>;
|
|
93
|
+
export function upsert(table: string, options?: DatabaseMutationOptions): Promise<DatabaseResponse>;
|
|
94
|
+
export function transaction(operations: DatabaseOperation[]): Promise<DatabaseResponse[]>;
|
|
95
|
+
export function rpc(functionName: string, args?: Record<string, unknown>): Promise<DatabaseResponse>;
|
|
96
|
+
export function table(name: string): TableQuery;
|
|
97
|
+
|
|
98
|
+
export const database: {
|
|
99
|
+
query: typeof query;
|
|
100
|
+
single: typeof single;
|
|
101
|
+
maybeSingle: typeof maybeSingle;
|
|
102
|
+
aggregate: typeof aggregate;
|
|
103
|
+
insert: typeof insert;
|
|
104
|
+
updateRows: typeof updateRows;
|
|
105
|
+
deleteRows: typeof deleteRows;
|
|
106
|
+
upsert: typeof upsert;
|
|
107
|
+
transaction: typeof transaction;
|
|
108
|
+
rpc: typeof rpc;
|
|
109
|
+
table: typeof table;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export interface StorageSummary {
|
|
113
|
+
workspaceId?: string;
|
|
114
|
+
projectId?: string;
|
|
115
|
+
scope?: string;
|
|
116
|
+
bucketName?: string;
|
|
117
|
+
usedSizeBytes?: number;
|
|
118
|
+
fileCount?: number;
|
|
119
|
+
folderCount?: number;
|
|
120
|
+
serviceEnabled?: boolean;
|
|
121
|
+
[key: string]: unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface StorageFolder {
|
|
125
|
+
id?: string;
|
|
126
|
+
workspaceId?: string;
|
|
127
|
+
projectId?: string;
|
|
128
|
+
scope?: string;
|
|
129
|
+
parentId?: string | null;
|
|
130
|
+
folderName?: string;
|
|
131
|
+
folderPath?: string;
|
|
132
|
+
createdAt?: string;
|
|
133
|
+
updatedAt?: string;
|
|
134
|
+
[key: string]: unknown;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface StorageFile {
|
|
138
|
+
id?: string;
|
|
139
|
+
workspaceId?: string;
|
|
140
|
+
projectId?: string;
|
|
141
|
+
scope?: string;
|
|
142
|
+
folderId?: string | null;
|
|
143
|
+
folderPath?: string;
|
|
144
|
+
fileName?: string;
|
|
145
|
+
contentType?: string;
|
|
146
|
+
sizeBytes?: number;
|
|
147
|
+
storageBucket?: string;
|
|
148
|
+
storageKey?: string;
|
|
149
|
+
downloadUrl?: string;
|
|
150
|
+
createdAt?: string;
|
|
151
|
+
updatedAt?: string;
|
|
152
|
+
[key: string]: unknown;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface StorageEntries {
|
|
156
|
+
currentFolderId?: string | null;
|
|
157
|
+
currentFolderName?: string;
|
|
158
|
+
currentFolderPath?: string;
|
|
159
|
+
breadcrumbs?: unknown[];
|
|
160
|
+
folders?: StorageFolder[];
|
|
161
|
+
files?: StorageFile[];
|
|
162
|
+
[key: string]: unknown;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface UploadFileOptions {
|
|
166
|
+
folderId?: string;
|
|
167
|
+
fileName?: string;
|
|
168
|
+
contentType?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface ShareUrlOptions {
|
|
172
|
+
expirySeconds?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function summary(): Promise<StorageSummary>;
|
|
176
|
+
export function listFolders(): Promise<StorageFolder[]>;
|
|
177
|
+
export function listEntries(options?: { folderId?: string }): Promise<StorageEntries>;
|
|
178
|
+
export function listFiles(): Promise<StorageFile[]>;
|
|
179
|
+
export function createFolder(folderName: string, options?: { parentId?: string }): Promise<StorageFolder>;
|
|
180
|
+
export function renameFolder(folderId: string, folderName: string): Promise<StorageFolder>;
|
|
181
|
+
export function deleteFolder(folderId: string): Promise<void>;
|
|
182
|
+
export function uploadFile(
|
|
183
|
+
input: string | Blob | Buffer | Uint8Array,
|
|
184
|
+
options?: UploadFileOptions,
|
|
185
|
+
): Promise<StorageFile>;
|
|
186
|
+
export function renameFile(fileId: string, fileName: string): Promise<StorageFile>;
|
|
187
|
+
export function moveFile(fileId: string, options?: { folderId?: string }): Promise<StorageFile>;
|
|
188
|
+
export function shareUrl(fileId: string, options?: ShareUrlOptions): Promise<{ url?: string; expiresAt?: string; [key: string]: unknown }>;
|
|
189
|
+
export function objectExists(options?: { fileId?: string; objectKey?: string }): Promise<{ fileId?: string; bucketName?: string; objectKey?: string; exists?: boolean; [key: string]: unknown }>;
|
|
190
|
+
export function deleteFile(fileId: string): Promise<void>;
|
|
191
|
+
|
|
192
|
+
export const storage: {
|
|
193
|
+
summary: typeof summary;
|
|
194
|
+
listFolders: typeof listFolders;
|
|
195
|
+
listEntries: typeof listEntries;
|
|
196
|
+
listFiles: typeof listFiles;
|
|
197
|
+
createFolder: typeof createFolder;
|
|
198
|
+
renameFolder: typeof renameFolder;
|
|
199
|
+
deleteFolder: typeof deleteFolder;
|
|
200
|
+
uploadFile: typeof uploadFile;
|
|
201
|
+
renameFile: typeof renameFile;
|
|
202
|
+
moveFile: typeof moveFile;
|
|
203
|
+
shareUrl: typeof shareUrl;
|
|
204
|
+
objectExists: typeof objectExists;
|
|
205
|
+
deleteFile: typeof deleteFile;
|
|
206
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,68 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
ApiError,
|
|
6
|
-
ConfigurationError,
|
|
7
|
-
PlatformSdkError,
|
|
8
|
-
downloadBlob,
|
|
9
|
-
encodeObjectKey,
|
|
10
|
-
encodeStorageSegment,
|
|
11
|
-
normalizeBaseUrl,
|
|
12
|
-
} from './shared.js';
|
|
13
|
-
|
|
14
|
-
export class StorageClient {
|
|
15
|
-
constructor(options = {}) {
|
|
16
|
-
this.baseUrl = options.baseUrl;
|
|
17
|
-
this.token = options.token;
|
|
18
|
-
this.fetchImpl = options.fetch ?? fetch;
|
|
19
|
-
this.headers = options.headers;
|
|
20
|
-
this.credentials = options.credentials;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
previewUrl(bucket, objectKey) {
|
|
24
|
-
const baseUrl = normalizeBaseUrl(this.baseUrl);
|
|
25
|
-
return `${baseUrl}/api/v1/public/storage/${encodeStorageSegment(bucket)}/${encodeObjectKey(objectKey)}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
downloadUrl(bucket, objectKey) {
|
|
29
|
-
return `${this.previewUrl(bucket, objectKey)}?download=1`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async download(url, outputPath) {
|
|
33
|
-
const blob = await downloadBlob(this.fetchImpl, url, {
|
|
34
|
-
token: this.token,
|
|
35
|
-
headers: this.headers,
|
|
36
|
-
credentials: this.credentials,
|
|
37
|
-
});
|
|
38
|
-
if (outputPath === undefined) {
|
|
39
|
-
return blob;
|
|
40
|
-
}
|
|
41
|
-
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
42
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
43
|
-
await fs.writeFile(outputPath, buffer);
|
|
44
|
-
return outputPath;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function defaultClient(options = {}) {
|
|
50
|
-
return new StorageClient(options);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function previewUrl(bucket, objectKey, options = {}) {
|
|
54
|
-
return defaultClient(options).previewUrl(bucket, objectKey);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function downloadUrl(bucket, objectKey, options = {}) {
|
|
58
|
-
return defaultClient(options).downloadUrl(bucket, objectKey);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function download(url, outputPathOrOptions, maybeOptions = {}) {
|
|
62
|
-
if (typeof outputPathOrOptions === 'string') {
|
|
63
|
-
return defaultClient(maybeOptions).download(url, outputPathOrOptions);
|
|
64
|
-
}
|
|
65
|
-
return defaultClient(outputPathOrOptions ?? {}).download(url);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export { PlatformSdkError, ConfigurationError, ApiError };
|
|
1
|
+
export { PlatformSdkError, ConfigurationError, ApiError } from './shared.js';
|
|
2
|
+
export * from './database.js';
|
|
3
|
+
export * from './storage.js';
|
package/src/runtime.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ConfigurationError } from './shared.js';
|
|
2
|
+
|
|
3
|
+
export function env(name) {
|
|
4
|
+
const value = process.env[name];
|
|
5
|
+
if (typeof value !== 'string') return undefined;
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
return trimmed ? trimmed : undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeBaseUrl(value) {
|
|
11
|
+
let trimmed = value.trim().replace(/\/+$/, '');
|
|
12
|
+
if (trimmed.endsWith('/api/v1')) {
|
|
13
|
+
trimmed = trimmed.slice(0, -'/api/v1'.length);
|
|
14
|
+
}
|
|
15
|
+
return trimmed.replace(/\/+$/, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function serviceBaseUrl(hostEnv, portEnv, defaultPort) {
|
|
19
|
+
const host = env(hostEnv);
|
|
20
|
+
if (!host) return undefined;
|
|
21
|
+
return `http://${host}:${env(portEnv) || defaultPort}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function platformBaseUrl() {
|
|
25
|
+
const value =
|
|
26
|
+
env('LICOS_PLATFORM_API_BASE_URL') ||
|
|
27
|
+
serviceBaseUrl('AIOS_PLATFORM_SERVICE_HOST', 'AIOS_PLATFORM_SERVICE_PORT', '9100') ||
|
|
28
|
+
serviceBaseUrl('LICOS_PLATFORM_SERVICE_HOST', 'LICOS_PLATFORM_SERVICE_PORT', '9100') ||
|
|
29
|
+
serviceBaseUrl('PLATFORM_SERVICE_HOST', 'PLATFORM_SERVICE_PORT', '9100');
|
|
30
|
+
if (!value) {
|
|
31
|
+
throw new ConfigurationError('LICOS_PLATFORM_API_BASE_URL is not configured');
|
|
32
|
+
}
|
|
33
|
+
return normalizeBaseUrl(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function projectEnvironment(overrideEnvName) {
|
|
37
|
+
const explicit = ((overrideEnvName ? env(overrideEnvName) : undefined) || '').toLowerCase();
|
|
38
|
+
if (explicit === 'dev' || explicit === 'prod') return explicit;
|
|
39
|
+
const projectEnv = (env('LICOS_PROJECT_ENV') || env('AGENT_ENV') || '').toLowerCase();
|
|
40
|
+
return ['prod', 'production', 'release'].includes(projectEnv) ? 'prod' : 'dev';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function runtimeConfig({ environmentOverrideEnv } = {}) {
|
|
44
|
+
const projectId = env('LICOS_PROJECT_ID') || env('AGENT_PROJECT_ID');
|
|
45
|
+
if (!projectId) {
|
|
46
|
+
throw new ConfigurationError('LICOS_PROJECT_ID or AGENT_PROJECT_ID is not configured');
|
|
47
|
+
}
|
|
48
|
+
const token = env('LICOS_RUNTIME_TOKEN') || env('LICOS_USER_TOKEN');
|
|
49
|
+
if (!token) {
|
|
50
|
+
throw new ConfigurationError('LICOS_RUNTIME_TOKEN or LICOS_USER_TOKEN is not configured');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
baseUrl: platformBaseUrl(),
|
|
54
|
+
projectId,
|
|
55
|
+
environment: projectEnvironment(environmentOverrideEnv),
|
|
56
|
+
token,
|
|
57
|
+
workspaceId: env('LICOS_WORKSPACE_ID') || env('AGENT_WORKSPACE_ID'),
|
|
58
|
+
userId: env('LICOS_USER_ID') || env('AGENT_USER_ID'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function authHeaders(config, contentType = 'application/json') {
|
|
63
|
+
const result = {
|
|
64
|
+
Authorization: `Bearer ${config.token}`,
|
|
65
|
+
};
|
|
66
|
+
if (contentType) result['Content-Type'] = contentType;
|
|
67
|
+
if (config.workspaceId) result['X-Workspace-Id'] = config.workspaceId;
|
|
68
|
+
if (config.userId) result['X-User-Id'] = config.userId;
|
|
69
|
+
return result;
|
|
70
|
+
}
|
package/src/shared.js
CHANGED
|
@@ -10,90 +10,3 @@ export class PlatformSdkError extends Error {
|
|
|
10
10
|
|
|
11
11
|
export class ConfigurationError extends PlatformSdkError {}
|
|
12
12
|
export class ApiError extends PlatformSdkError {}
|
|
13
|
-
|
|
14
|
-
export function isNodeRuntime() {
|
|
15
|
-
return typeof process !== 'undefined' && !!process?.versions?.node;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function readEnv(name) {
|
|
19
|
-
if (typeof process === 'undefined' || !process?.env) {
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
const value = process.env[name];
|
|
23
|
-
return value ? value : undefined;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function normalizeBaseUrl(baseUrl) {
|
|
27
|
-
if (typeof baseUrl === 'string') {
|
|
28
|
-
return baseUrl.replace(/\/+$/, '');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const envBaseUrl = readEnv('LICOS_ORCHESTRATOR_API_BASE_URL');
|
|
32
|
-
if (envBaseUrl) {
|
|
33
|
-
return envBaseUrl.replace(/\/+$/, '');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!isNodeRuntime()) {
|
|
37
|
-
return '';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
throw new ConfigurationError(
|
|
41
|
-
'Missing required environment variable: LICOS_ORCHESTRATOR_API_BASE_URL',
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function encodeStorageSegment(value) {
|
|
46
|
-
return encodeURIComponent(value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function encodeObjectKey(value) {
|
|
50
|
-
return value.split('/').map(encodeStorageSegment).join('/');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function headersFromInit(headersInit) {
|
|
54
|
-
return new Headers(headersInit ?? undefined);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function buildRequestHeaders(options = {}, { authenticated = false } = {}) {
|
|
58
|
-
const headers = headersFromInit(options.headers);
|
|
59
|
-
if (authenticated && !headers.has('Authorization')) {
|
|
60
|
-
const resolvedToken = options.token ?? readEnv('LICOS_RUNTIME_TOKEN');
|
|
61
|
-
if (resolvedToken) {
|
|
62
|
-
headers.set('Authorization', `Bearer ${resolvedToken}`);
|
|
63
|
-
} else if (isNodeRuntime()) {
|
|
64
|
-
throw new ConfigurationError(
|
|
65
|
-
'Missing authentication token. Pass options.token, provide an Authorization header, or set LICOS_RUNTIME_TOKEN.',
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return headers;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function buildRequestInit(options = {}, init = {}) {
|
|
73
|
-
const requestInit = { ...init };
|
|
74
|
-
if (options.credentials) {
|
|
75
|
-
requestInit.credentials = options.credentials;
|
|
76
|
-
}
|
|
77
|
-
return requestInit;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function downloadBlob(fetchImpl, url, options = {}) {
|
|
81
|
-
const response = await fetchImpl(
|
|
82
|
-
url,
|
|
83
|
-
buildRequestInit(options, {
|
|
84
|
-
method: 'GET',
|
|
85
|
-
headers: buildRequestHeaders(options, { authenticated: true }),
|
|
86
|
-
}),
|
|
87
|
-
);
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
throw new ApiError(`Platform download failed: HTTP ${response.status}`, {
|
|
90
|
-
status: response.status,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
if (typeof response.blob === 'function') {
|
|
94
|
-
return response.blob();
|
|
95
|
-
}
|
|
96
|
-
return new Blob([await response.arrayBuffer()], {
|
|
97
|
-
type: response.headers.get('content-type') ?? undefined,
|
|
98
|
-
});
|
|
99
|
-
}
|
package/src/storage.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { ApiError } from './shared.js';
|
|
5
|
+
import { authHeaders, runtimeConfig } from './runtime.js';
|
|
6
|
+
|
|
7
|
+
function storageConfig() {
|
|
8
|
+
return runtimeConfig({ environmentOverrideEnv: 'LICOS_STORAGE_SCOPE' });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function query(config, params = {}) {
|
|
12
|
+
const search = new URLSearchParams();
|
|
13
|
+
search.set('projectId', config.projectId);
|
|
14
|
+
search.set('scope', config.environment);
|
|
15
|
+
for (const [key, value] of Object.entries(params)) {
|
|
16
|
+
if (value !== undefined && value !== null) search.set(key, String(value));
|
|
17
|
+
}
|
|
18
|
+
return search.toString();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function storageUrl(config, route, params) {
|
|
22
|
+
return `${config.baseUrl}/api/v1/studio/storage${route}?${query(config, params)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function decodeResponse(response) {
|
|
26
|
+
const text = await response.text();
|
|
27
|
+
const payload = text ? JSON.parse(text) : null;
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new ApiError(text || `platform storage API returned ${response.status}`, {
|
|
30
|
+
status: response.status,
|
|
31
|
+
details: payload,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (!payload || typeof payload !== 'object') return payload;
|
|
35
|
+
if ((payload.code !== undefined && payload.code !== 0) || payload.success === false) {
|
|
36
|
+
throw new ApiError(payload.message || 'platform storage API failed', {
|
|
37
|
+
status: response.status,
|
|
38
|
+
code: typeof payload.code === 'number' ? payload.code : undefined,
|
|
39
|
+
details: payload,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return payload.data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function requestJson(method, route, { params, body } = {}) {
|
|
46
|
+
const config = storageConfig();
|
|
47
|
+
const response = await fetch(storageUrl(config, route, params), {
|
|
48
|
+
method,
|
|
49
|
+
headers: authHeaders(config),
|
|
50
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
51
|
+
});
|
|
52
|
+
return decodeResponse(response);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function filePart(input, options = {}) {
|
|
56
|
+
if (typeof input === 'string') {
|
|
57
|
+
const data = await readFile(input);
|
|
58
|
+
return {
|
|
59
|
+
blob: new Blob([data], { type: options.contentType || 'application/octet-stream' }),
|
|
60
|
+
fileName: options.fileName || path.basename(input),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (input instanceof Blob) {
|
|
64
|
+
return { blob: input, fileName: options.fileName || 'file' };
|
|
65
|
+
}
|
|
66
|
+
if (input instanceof Uint8Array || Buffer.isBuffer(input)) {
|
|
67
|
+
return {
|
|
68
|
+
blob: new Blob([input], { type: options.contentType || 'application/octet-stream' }),
|
|
69
|
+
fileName: options.fileName || 'file',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError('uploadFile input must be a file path, Blob, Buffer, or Uint8Array');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function summary() {
|
|
76
|
+
return requestJson('GET', '/summary');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function listFolders() {
|
|
80
|
+
return (await requestJson('GET', '/folders')) || [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function listEntries({ folderId } = {}) {
|
|
84
|
+
return requestJson('GET', '/entries', { params: { folderId } });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function listFiles() {
|
|
88
|
+
return (await requestJson('GET', '/files')) || [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function createFolder(folderName, { parentId } = {}) {
|
|
92
|
+
return requestJson('POST', '/folders', { body: { folderName, parentId } });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function renameFolder(folderId, folderName) {
|
|
96
|
+
return requestJson('PUT', `/folders/${encodeURIComponent(folderId)}`, { body: { folderName } });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function deleteFolder(folderId) {
|
|
100
|
+
await requestJson('DELETE', `/folders/${encodeURIComponent(folderId)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function uploadFile(input, options = {}) {
|
|
104
|
+
const config = storageConfig();
|
|
105
|
+
const { blob, fileName } = await filePart(input, options);
|
|
106
|
+
const form = new FormData();
|
|
107
|
+
form.set('projectId', config.projectId);
|
|
108
|
+
form.set('scope', config.environment);
|
|
109
|
+
if (options.folderId) form.set('folderId', options.folderId);
|
|
110
|
+
form.set('file', blob, fileName);
|
|
111
|
+
const response = await fetch(`${config.baseUrl}/api/v1/studio/storage/files/upload`, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: authHeaders(config, undefined),
|
|
114
|
+
body: form,
|
|
115
|
+
});
|
|
116
|
+
return decodeResponse(response);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function renameFile(fileId, fileName) {
|
|
120
|
+
return requestJson('PUT', `/files/${encodeURIComponent(fileId)}/rename`, { body: { fileName } });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function moveFile(fileId, { folderId } = {}) {
|
|
124
|
+
return requestJson('PUT', `/files/${encodeURIComponent(fileId)}/move`, { body: { folderId } });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function shareUrl(fileId, { expirySeconds } = {}) {
|
|
128
|
+
return requestJson('POST', `/files/${encodeURIComponent(fileId)}/share-url`, {
|
|
129
|
+
body: { expirySeconds },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function objectExists({ fileId, objectKey } = {}) {
|
|
134
|
+
return requestJson('POST', '/objects/exists', { body: { fileId, objectKey } });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function deleteFile(fileId) {
|
|
138
|
+
await requestJson('DELETE', `/files/${encodeURIComponent(fileId)}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const storage = {
|
|
142
|
+
summary,
|
|
143
|
+
listFolders,
|
|
144
|
+
listEntries,
|
|
145
|
+
listFiles,
|
|
146
|
+
createFolder,
|
|
147
|
+
renameFolder,
|
|
148
|
+
deleteFolder,
|
|
149
|
+
uploadFile,
|
|
150
|
+
renameFile,
|
|
151
|
+
moveFile,
|
|
152
|
+
shareUrl,
|
|
153
|
+
objectExists,
|
|
154
|
+
deleteFile,
|
|
155
|
+
};
|