@seaverse/dataservice 1.0.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 +389 -0
- package/dist/index.d.mts +308 -0
- package/dist/index.d.ts +308 -0
- package/dist/index.js +371 -0
- package/dist/index.mjs +342 -0
- package/package.json +66 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var DataServiceError = class extends Error {
|
|
3
|
+
constructor(message, code, details, hint, statusCode) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.details = details;
|
|
7
|
+
this.hint = hint;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.name = "DataServiceError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/client.ts
|
|
14
|
+
function extractAppId() {
|
|
15
|
+
if (typeof globalThis !== "undefined" && "location" in globalThis) {
|
|
16
|
+
const location = globalThis.location;
|
|
17
|
+
const hostname = location.hostname;
|
|
18
|
+
const withoutSeaverse = hostname.replace(/\.app\.seaverse\.ai$/, "").replace(/\.seaverse\.ai$/, "");
|
|
19
|
+
return withoutSeaverse !== hostname ? withoutSeaverse : hostname;
|
|
20
|
+
}
|
|
21
|
+
if (typeof process !== "undefined" && process.env) {
|
|
22
|
+
return process.env.SEAVERSE_APP_ID || "default";
|
|
23
|
+
}
|
|
24
|
+
return "default";
|
|
25
|
+
}
|
|
26
|
+
var HTTPClient = class {
|
|
27
|
+
baseUrl;
|
|
28
|
+
headers;
|
|
29
|
+
fetchFn;
|
|
30
|
+
timeout;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.baseUrl = config.url.replace(/\/$/, "");
|
|
33
|
+
this.fetchFn = config.options?.fetch || globalThis.fetch;
|
|
34
|
+
this.timeout = config.options?.timeout || 3e4;
|
|
35
|
+
this.headers = {
|
|
36
|
+
"Authorization": config.token,
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"Prefer": "return=representation",
|
|
39
|
+
...config.options?.headers
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Make HTTP request with timeout and error handling
|
|
44
|
+
*/
|
|
45
|
+
async request(method, path, body, additionalHeaders) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
50
|
+
method,
|
|
51
|
+
headers: { ...this.headers, ...additionalHeaders },
|
|
52
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
53
|
+
signal: controller.signal
|
|
54
|
+
});
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
let error;
|
|
58
|
+
try {
|
|
59
|
+
error = await response.json();
|
|
60
|
+
} catch {
|
|
61
|
+
error = { message: response.statusText };
|
|
62
|
+
}
|
|
63
|
+
throw new DataServiceError(
|
|
64
|
+
error.message || "Request failed",
|
|
65
|
+
error.code,
|
|
66
|
+
error.details,
|
|
67
|
+
error.hint,
|
|
68
|
+
response.status
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
return text ? JSON.parse(text) : null;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
if (error instanceof DataServiceError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
if (error.name === "AbortError") {
|
|
79
|
+
throw new DataServiceError("Request timeout", "TIMEOUT");
|
|
80
|
+
}
|
|
81
|
+
throw new DataServiceError(
|
|
82
|
+
error.message || "Unknown error",
|
|
83
|
+
"NETWORK_ERROR"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async get(path, params) {
|
|
88
|
+
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
89
|
+
return this.request("GET", `${path}${query}`);
|
|
90
|
+
}
|
|
91
|
+
async post(path, body, headers) {
|
|
92
|
+
return this.request("POST", path, body, headers);
|
|
93
|
+
}
|
|
94
|
+
async patch(path, body) {
|
|
95
|
+
return this.request("PATCH", path, body);
|
|
96
|
+
}
|
|
97
|
+
async delete(path) {
|
|
98
|
+
await this.request("DELETE", path);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var QueryBuilderImpl = class {
|
|
102
|
+
constructor(client, tablePath, appId, collectionName) {
|
|
103
|
+
this.client = client;
|
|
104
|
+
this.tablePath = tablePath;
|
|
105
|
+
this.appId = appId;
|
|
106
|
+
this.collectionName = collectionName;
|
|
107
|
+
}
|
|
108
|
+
filters = [];
|
|
109
|
+
ordering = null;
|
|
110
|
+
limitValue = null;
|
|
111
|
+
offsetValue = null;
|
|
112
|
+
eq(field, value) {
|
|
113
|
+
this.filters.push(`${field}=eq.${encodeURIComponent(String(value))}`);
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
neq(field, value) {
|
|
117
|
+
this.filters.push(`${field}=neq.${encodeURIComponent(String(value))}`);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
gt(field, value) {
|
|
121
|
+
this.filters.push(`${field}=gt.${encodeURIComponent(String(value))}`);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
gte(field, value) {
|
|
125
|
+
this.filters.push(`${field}=gte.${encodeURIComponent(String(value))}`);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
lt(field, value) {
|
|
129
|
+
this.filters.push(`${field}=lt.${encodeURIComponent(String(value))}`);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
lte(field, value) {
|
|
133
|
+
this.filters.push(`${field}=lte.${encodeURIComponent(String(value))}`);
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
like(field, pattern) {
|
|
137
|
+
this.filters.push(`${field}=like.${encodeURIComponent(pattern)}`);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
ilike(field, pattern) {
|
|
141
|
+
this.filters.push(`${field}=ilike.${encodeURIComponent(pattern)}`);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
in(field, values) {
|
|
145
|
+
const encoded = values.map((v) => encodeURIComponent(String(v))).join(",");
|
|
146
|
+
this.filters.push(`${field}=in.(${encoded})`);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
contains(value) {
|
|
150
|
+
this.filters.push(`data=@>.${encodeURIComponent(JSON.stringify(value))}`);
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
overlaps(value) {
|
|
154
|
+
this.filters.push(`data=&&.${encodeURIComponent(JSON.stringify(value))}`);
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
order(field, options = {}) {
|
|
158
|
+
let orderStr = field;
|
|
159
|
+
if (options.descending)
|
|
160
|
+
orderStr += ".desc";
|
|
161
|
+
if (options.nullsFirst !== void 0) {
|
|
162
|
+
orderStr += options.nullsFirst ? ".nullsfirst" : ".nullslast";
|
|
163
|
+
}
|
|
164
|
+
this.ordering = orderStr;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
limit(count) {
|
|
168
|
+
this.limitValue = count;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
offset(count) {
|
|
172
|
+
this.offsetValue = count;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
async execute() {
|
|
176
|
+
const params = {
|
|
177
|
+
app_id: `eq.${this.appId}`,
|
|
178
|
+
collection_name: `eq.${this.collectionName}`,
|
|
179
|
+
...Object.fromEntries(this.filters.map((f, i) => [`filter${i}`, f]))
|
|
180
|
+
};
|
|
181
|
+
if (this.ordering)
|
|
182
|
+
params.order = this.ordering;
|
|
183
|
+
if (this.limitValue !== null)
|
|
184
|
+
params.limit = String(this.limitValue);
|
|
185
|
+
if (this.offsetValue !== null)
|
|
186
|
+
params.offset = String(this.offsetValue);
|
|
187
|
+
return this.client.get(this.tablePath, params);
|
|
188
|
+
}
|
|
189
|
+
async count() {
|
|
190
|
+
const params = {
|
|
191
|
+
app_id: `eq.${this.appId}`,
|
|
192
|
+
collection_name: `eq.${this.collectionName}`,
|
|
193
|
+
...Object.fromEntries(this.filters.map((f, i) => [`filter${i}`, f]))
|
|
194
|
+
};
|
|
195
|
+
const response = await this.client.get(this.tablePath, {
|
|
196
|
+
...params,
|
|
197
|
+
select: "count"
|
|
198
|
+
});
|
|
199
|
+
return response.length;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var CollectionImpl = class {
|
|
203
|
+
constructor(client, tablePath, appId, collectionName) {
|
|
204
|
+
this.client = client;
|
|
205
|
+
this.tablePath = tablePath;
|
|
206
|
+
this.appId = appId;
|
|
207
|
+
this.collectionName = collectionName;
|
|
208
|
+
}
|
|
209
|
+
async insert(data, id) {
|
|
210
|
+
const payload = {
|
|
211
|
+
app_id: this.appId,
|
|
212
|
+
collection_name: this.collectionName,
|
|
213
|
+
data
|
|
214
|
+
};
|
|
215
|
+
if (id) {
|
|
216
|
+
payload.id = id;
|
|
217
|
+
}
|
|
218
|
+
const response = await this.client.post(this.tablePath, payload);
|
|
219
|
+
return Array.isArray(response) ? response[0] : response;
|
|
220
|
+
}
|
|
221
|
+
async get(id) {
|
|
222
|
+
return this.selectById(id);
|
|
223
|
+
}
|
|
224
|
+
select() {
|
|
225
|
+
return new QueryBuilderImpl(this.client, this.tablePath, this.appId, this.collectionName);
|
|
226
|
+
}
|
|
227
|
+
async selectById(id) {
|
|
228
|
+
try {
|
|
229
|
+
const results = await this.client.get(
|
|
230
|
+
`/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
|
|
231
|
+
);
|
|
232
|
+
return results[0] || null;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error.statusCode === 404) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async update(id, data) {
|
|
241
|
+
const response = await this.client.patch(
|
|
242
|
+
`/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`,
|
|
243
|
+
{ data }
|
|
244
|
+
);
|
|
245
|
+
return Array.isArray(response) ? response[0] : response;
|
|
246
|
+
}
|
|
247
|
+
async patch(id, partial) {
|
|
248
|
+
const existing = await this.selectById(id);
|
|
249
|
+
if (!existing) {
|
|
250
|
+
throw new DataServiceError(`Record with id ${id} not found`, "NOT_FOUND");
|
|
251
|
+
}
|
|
252
|
+
const merged = { ...existing.data, ...partial };
|
|
253
|
+
return this.update(id, merged);
|
|
254
|
+
}
|
|
255
|
+
async delete(id) {
|
|
256
|
+
await this.client.delete(
|
|
257
|
+
`/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
async softDelete(id) {
|
|
261
|
+
const response = await this.client.post("/rpc/soft_delete_user_data", {
|
|
262
|
+
p_id: id
|
|
263
|
+
});
|
|
264
|
+
return response;
|
|
265
|
+
}
|
|
266
|
+
async restore(id) {
|
|
267
|
+
const response = await this.client.post("/rpc/restore_user_data", {
|
|
268
|
+
p_id: id
|
|
269
|
+
});
|
|
270
|
+
return response;
|
|
271
|
+
}
|
|
272
|
+
async search(criteria) {
|
|
273
|
+
return this.client.get(this.tablePath, {
|
|
274
|
+
app_id: `eq.${this.appId}`,
|
|
275
|
+
collection_name: `eq.${this.collectionName}`,
|
|
276
|
+
data: `@>.${JSON.stringify(criteria)}`
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async count() {
|
|
280
|
+
const results = await this.client.get(this.tablePath, {
|
|
281
|
+
app_id: `eq.${this.appId}`,
|
|
282
|
+
collection_name: `eq.${this.collectionName}`
|
|
283
|
+
});
|
|
284
|
+
return results.length;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var DataTableImpl = class {
|
|
288
|
+
constructor(client, tablePath, appId) {
|
|
289
|
+
this.client = client;
|
|
290
|
+
this.tablePath = tablePath;
|
|
291
|
+
this.appId = appId;
|
|
292
|
+
}
|
|
293
|
+
collection(name) {
|
|
294
|
+
return new CollectionImpl(this.client, this.tablePath, this.appId, name);
|
|
295
|
+
}
|
|
296
|
+
async batchInsert(baseCollectionName, records) {
|
|
297
|
+
const results = [];
|
|
298
|
+
for (let i = 0; i < records.length; i++) {
|
|
299
|
+
const uniqueCollectionName = `${baseCollectionName}_${Date.now()}_${i}`;
|
|
300
|
+
const collection = new CollectionImpl(
|
|
301
|
+
this.client,
|
|
302
|
+
this.tablePath,
|
|
303
|
+
this.appId,
|
|
304
|
+
uniqueCollectionName
|
|
305
|
+
);
|
|
306
|
+
const result = await collection.insert(records[i]);
|
|
307
|
+
results.push(result);
|
|
308
|
+
if (i < records.length - 1) {
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return results;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var DataServiceClientImpl = class {
|
|
316
|
+
client;
|
|
317
|
+
userData;
|
|
318
|
+
appId;
|
|
319
|
+
constructor(config) {
|
|
320
|
+
this.client = new HTTPClient(config);
|
|
321
|
+
this.appId = extractAppId();
|
|
322
|
+
this.userData = new DataTableImpl(this.client, "/user_data", this.appId);
|
|
323
|
+
}
|
|
324
|
+
async getStats() {
|
|
325
|
+
const response = await this.client.post("/rpc/get_user_data_stats");
|
|
326
|
+
return Array.isArray(response) ? response[0] : response;
|
|
327
|
+
}
|
|
328
|
+
async health() {
|
|
329
|
+
return this.client.post("/rpc/health");
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function createClient(config) {
|
|
333
|
+
return new DataServiceClientImpl(config);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/index.ts
|
|
337
|
+
var VERSION = "1.0.0";
|
|
338
|
+
export {
|
|
339
|
+
DataServiceError,
|
|
340
|
+
VERSION,
|
|
341
|
+
createClient
|
|
342
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@seaverse/dataservice",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-Friendly Universal Data Storage SDK for TypeScript/JavaScript",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
15
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:examples": "tsx examples/test-examples.ts",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
21
|
+
"security:check": "echo 'Checking for sensitive files...' && ! git ls-files | grep -E '\\.env(\\.development|\\.production)?$'",
|
|
22
|
+
"prepublishOnly": "npm run security:check && npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"data-storage",
|
|
26
|
+
"postgrest",
|
|
27
|
+
"database",
|
|
28
|
+
"sdk",
|
|
29
|
+
"typescript",
|
|
30
|
+
"ai-friendly",
|
|
31
|
+
"jsonb",
|
|
32
|
+
"rls"
|
|
33
|
+
],
|
|
34
|
+
"author": "SeaVerse Team <support@seaverse.com>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/seaverseai/sv-sdk.git",
|
|
39
|
+
"directory": "packages/dataservice"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/seaverseai/sv-sdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/seaverseai/sv-sdk/tree/main/packages/dataservice#readme",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.11.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
48
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
49
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
50
|
+
"eslint": "^8.56.0",
|
|
51
|
+
"prettier": "^3.2.4",
|
|
52
|
+
"tsup": "^8.0.1",
|
|
53
|
+
"typescript": "^5.3.3",
|
|
54
|
+
"vitest": "^1.2.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"dotenv": "^17.2.3",
|
|
58
|
+
"tsx": "^4.21.0"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|