@tinycloud/sdk-services 2.0.1 → 2.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{types.d.ts → BaseService-D9BFm_rV.d.cts} +179 -27
- package/dist/BaseService-D9BFm_rV.d.ts +440 -0
- package/dist/index.cjs +3221 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1843 -0
- package/dist/index.d.ts +1826 -41
- package/dist/index.js +3136 -58
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +909 -0
- package/dist/kv/index.cjs.map +1 -0
- package/dist/kv/index.d.cts +748 -0
- package/dist/kv/index.d.ts +745 -7
- package/dist/kv/index.js +877 -9
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +596 -0
- package/dist/sql/index.cjs.map +1 -0
- package/dist/sql/index.d.cts +228 -0
- package/dist/sql/index.d.ts +225 -7
- package/dist/sql/index.js +566 -8
- package/dist/sql/index.js.map +1 -1
- package/package.json +7 -6
- package/dist/base/BaseService.d.ts +0 -151
- package/dist/base/BaseService.d.ts.map +0 -1
- package/dist/base/BaseService.js +0 -221
- package/dist/base/BaseService.js.map +0 -1
- package/dist/base/index.d.ts +0 -6
- package/dist/base/index.d.ts.map +0 -1
- package/dist/base/index.js +0 -6
- package/dist/base/index.js.map +0 -1
- package/dist/base/types.d.ts +0 -36
- package/dist/base/types.d.ts.map +0 -1
- package/dist/base/types.js +0 -7
- package/dist/base/types.js.map +0 -1
- package/dist/context.d.ts +0 -142
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -218
- package/dist/context.js.map +0 -1
- package/dist/duckdb/DuckDbDatabaseHandle.d.ts +0 -23
- package/dist/duckdb/DuckDbDatabaseHandle.d.ts.map +0 -1
- package/dist/duckdb/DuckDbDatabaseHandle.js +0 -36
- package/dist/duckdb/DuckDbDatabaseHandle.js.map +0 -1
- package/dist/duckdb/DuckDbService.d.ts +0 -50
- package/dist/duckdb/DuckDbService.d.ts.map +0 -1
- package/dist/duckdb/DuckDbService.js +0 -285
- package/dist/duckdb/DuckDbService.js.map +0 -1
- package/dist/duckdb/IDuckDbService.d.ts +0 -84
- package/dist/duckdb/IDuckDbService.d.ts.map +0 -1
- package/dist/duckdb/IDuckDbService.js +0 -7
- package/dist/duckdb/IDuckDbService.js.map +0 -1
- package/dist/duckdb/index.d.ts +0 -10
- package/dist/duckdb/index.d.ts.map +0 -1
- package/dist/duckdb/index.js +0 -9
- package/dist/duckdb/index.js.map +0 -1
- package/dist/duckdb/types.d.ts +0 -148
- package/dist/duckdb/types.d.ts.map +0 -1
- package/dist/duckdb/types.js +0 -19
- package/dist/duckdb/types.js.map +0 -1
- package/dist/errors.d.ts +0 -62
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -149
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/kv/IKVService.d.ts +0 -148
- package/dist/kv/IKVService.d.ts.map +0 -1
- package/dist/kv/IKVService.js +0 -8
- package/dist/kv/IKVService.js.map +0 -1
- package/dist/kv/KVService.d.ts +0 -155
- package/dist/kv/KVService.d.ts.map +0 -1
- package/dist/kv/KVService.js +0 -419
- package/dist/kv/KVService.js.map +0 -1
- package/dist/kv/PrefixedKVService.d.ts +0 -246
- package/dist/kv/PrefixedKVService.d.ts.map +0 -1
- package/dist/kv/PrefixedKVService.js +0 -145
- package/dist/kv/PrefixedKVService.js.map +0 -1
- package/dist/kv/index.d.ts.map +0 -1
- package/dist/kv/types.d.ts +0 -204
- package/dist/kv/types.d.ts.map +0 -1
- package/dist/kv/types.js +0 -16
- package/dist/kv/types.js.map +0 -1
- package/dist/quota/TinyCloudQuota.d.ts +0 -27
- package/dist/quota/TinyCloudQuota.d.ts.map +0 -1
- package/dist/quota/TinyCloudQuota.js +0 -31
- package/dist/quota/TinyCloudQuota.js.map +0 -1
- package/dist/quota/index.d.ts +0 -3
- package/dist/quota/index.d.ts.map +0 -1
- package/dist/quota/index.js +0 -2
- package/dist/quota/index.js.map +0 -1
- package/dist/sql/DatabaseHandle.d.ts +0 -20
- package/dist/sql/DatabaseHandle.d.ts.map +0 -1
- package/dist/sql/DatabaseHandle.js +0 -27
- package/dist/sql/DatabaseHandle.js.map +0 -1
- package/dist/sql/ISQLService.d.ts +0 -67
- package/dist/sql/ISQLService.d.ts.map +0 -1
- package/dist/sql/ISQLService.js +0 -7
- package/dist/sql/ISQLService.js.map +0 -1
- package/dist/sql/SQLService.d.ts +0 -44
- package/dist/sql/SQLService.d.ts.map +0 -1
- package/dist/sql/SQLService.js +0 -216
- package/dist/sql/SQLService.js.map +0 -1
- package/dist/sql/index.d.ts.map +0 -1
- package/dist/sql/types.d.ts +0 -102
- package/dist/sql/types.d.ts.map +0 -1
- package/dist/sql/types.js +0 -21
- package/dist/sql/types.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -94
- package/dist/types.js.map +0 -1
- package/dist/types.schema.d.ts +0 -712
- package/dist/types.schema.d.ts.map +0 -1
- package/dist/types.schema.js +0 -342
- package/dist/types.schema.js.map +0 -1
- package/dist/types.schema.test.d.ts +0 -5
- package/dist/types.schema.test.d.ts.map +0 -1
- package/dist/types.schema.test.js +0 -677
- package/dist/types.schema.test.js.map +0 -1
- package/dist/vault/DataVaultService.d.ts +0 -267
- package/dist/vault/DataVaultService.d.ts.map +0 -1
- package/dist/vault/DataVaultService.js +0 -1040
- package/dist/vault/DataVaultService.js.map +0 -1
- package/dist/vault/IDataVaultService.d.ts +0 -158
- package/dist/vault/IDataVaultService.d.ts.map +0 -1
- package/dist/vault/IDataVaultService.js +0 -8
- package/dist/vault/IDataVaultService.js.map +0 -1
- package/dist/vault/SignatureCache.d.ts +0 -20
- package/dist/vault/SignatureCache.d.ts.map +0 -1
- package/dist/vault/SignatureCache.js +0 -167
- package/dist/vault/SignatureCache.js.map +0 -1
- package/dist/vault/createVaultCrypto.d.ts +0 -16
- package/dist/vault/createVaultCrypto.d.ts.map +0 -1
- package/dist/vault/createVaultCrypto.js +0 -12
- package/dist/vault/createVaultCrypto.js.map +0 -1
- package/dist/vault/index.d.ts +0 -11
- package/dist/vault/index.d.ts.map +0 -1
- package/dist/vault/index.js +0 -12
- package/dist/vault/index.js.map +0 -1
- package/dist/vault/types.d.ts +0 -141
- package/dist/vault/types.d.ts.map +0 -1
- package/dist/vault/types.js +0 -31
- package/dist/vault/types.js.map +0 -1
package/dist/kv/index.js
CHANGED
|
@@ -1,12 +1,880 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var ErrorCodes = {
|
|
3
|
+
// Common errors
|
|
4
|
+
NOT_FOUND: "NOT_FOUND",
|
|
5
|
+
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
6
|
+
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
7
|
+
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
|
8
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
9
|
+
TIMEOUT: "TIMEOUT",
|
|
10
|
+
ABORTED: "ABORTED",
|
|
11
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
12
|
+
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
13
|
+
// KV-specific errors
|
|
14
|
+
KV_NOT_FOUND: "KV_NOT_FOUND",
|
|
15
|
+
KV_WRITE_FAILED: "KV_WRITE_FAILED",
|
|
16
|
+
// SQL-specific errors
|
|
17
|
+
SQL_ERROR: "SQL_ERROR",
|
|
18
|
+
SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
|
|
19
|
+
SQL_DATABASE_NOT_FOUND: "SQL_DATABASE_NOT_FOUND",
|
|
20
|
+
SQL_RESPONSE_TOO_LARGE: "SQL_RESPONSE_TOO_LARGE",
|
|
21
|
+
SQL_QUOTA_EXCEEDED: "SQL_QUOTA_EXCEEDED",
|
|
22
|
+
SQL_INVALID_STATEMENT: "SQL_INVALID_STATEMENT",
|
|
23
|
+
SQL_SCHEMA_ERROR: "SQL_SCHEMA_ERROR",
|
|
24
|
+
SQL_READONLY_VIOLATION: "SQL_READONLY_VIOLATION",
|
|
25
|
+
// Storage quota errors
|
|
26
|
+
STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
|
|
27
|
+
STORAGE_LIMIT_REACHED: "STORAGE_LIMIT_REACHED",
|
|
28
|
+
// DuckDB-specific errors
|
|
29
|
+
DUCKDB_ERROR: "DUCKDB_ERROR",
|
|
30
|
+
DUCKDB_PERMISSION_DENIED: "DUCKDB_PERMISSION_DENIED",
|
|
31
|
+
DUCKDB_DATABASE_NOT_FOUND: "DUCKDB_DATABASE_NOT_FOUND",
|
|
32
|
+
DUCKDB_RESPONSE_TOO_LARGE: "DUCKDB_RESPONSE_TOO_LARGE",
|
|
33
|
+
DUCKDB_QUOTA_EXCEEDED: "DUCKDB_QUOTA_EXCEEDED",
|
|
34
|
+
DUCKDB_INVALID_STATEMENT: "DUCKDB_INVALID_STATEMENT",
|
|
35
|
+
DUCKDB_SCHEMA_ERROR: "DUCKDB_SCHEMA_ERROR",
|
|
36
|
+
DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
|
|
37
|
+
};
|
|
38
|
+
var defaultRetryPolicy = {
|
|
39
|
+
maxAttempts: 3,
|
|
40
|
+
backoff: "exponential",
|
|
41
|
+
baseDelayMs: 1e3,
|
|
42
|
+
maxDelayMs: 1e4,
|
|
43
|
+
retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
|
|
44
|
+
};
|
|
45
|
+
var TelemetryEvents = {
|
|
46
|
+
SERVICE_REQUEST: "service.request",
|
|
47
|
+
SERVICE_RESPONSE: "service.response",
|
|
48
|
+
SERVICE_ERROR: "service.error",
|
|
49
|
+
SERVICE_RETRY: "service.retry",
|
|
50
|
+
SESSION_CHANGED: "session.changed",
|
|
51
|
+
SESSION_EXPIRED: "session.expired"
|
|
52
|
+
};
|
|
53
|
+
function ok(data) {
|
|
54
|
+
return { ok: true, data };
|
|
55
|
+
}
|
|
56
|
+
function err(error) {
|
|
57
|
+
return { ok: false, error };
|
|
58
|
+
}
|
|
59
|
+
function serviceError(code, message, service, options) {
|
|
60
|
+
return {
|
|
61
|
+
code,
|
|
62
|
+
message,
|
|
63
|
+
service,
|
|
64
|
+
cause: options?.cause,
|
|
65
|
+
meta: options?.meta
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/errors.ts
|
|
70
|
+
function authRequiredError(service) {
|
|
71
|
+
return {
|
|
72
|
+
code: ErrorCodes.AUTH_REQUIRED,
|
|
73
|
+
message: "Authentication required. Please sign in first.",
|
|
74
|
+
service
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function timeoutError(service) {
|
|
78
|
+
return {
|
|
79
|
+
code: ErrorCodes.TIMEOUT,
|
|
80
|
+
message: "Request timed out.",
|
|
81
|
+
service
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function abortedError(service) {
|
|
85
|
+
return {
|
|
86
|
+
code: ErrorCodes.ABORTED,
|
|
87
|
+
message: "Request was aborted.",
|
|
88
|
+
service
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function parseAuthError(responseText) {
|
|
92
|
+
const match = responseText.match(/^Unauthorized Action:\s*(.+?)\s*\/\s*(tinycloud\.\S+)$/m);
|
|
93
|
+
if (match) {
|
|
94
|
+
return { resource: match[1].trim(), action: match[2].trim() };
|
|
95
|
+
}
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
function authUnauthorizedError(service, message, meta) {
|
|
99
|
+
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
100
|
+
}
|
|
101
|
+
function storageQuotaExceededError(service, message, meta) {
|
|
102
|
+
return {
|
|
103
|
+
code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,
|
|
104
|
+
message,
|
|
105
|
+
service,
|
|
106
|
+
meta
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function storageLimitReachedError(service, message, meta) {
|
|
110
|
+
return {
|
|
111
|
+
code: ErrorCodes.STORAGE_LIMIT_REACHED,
|
|
112
|
+
message,
|
|
113
|
+
service,
|
|
114
|
+
meta
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
if (error.name === "AbortError") {
|
|
120
|
+
return abortedError(service);
|
|
121
|
+
}
|
|
122
|
+
if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
|
|
123
|
+
return timeoutError(service);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
code: defaultCode,
|
|
127
|
+
message: error.message,
|
|
128
|
+
service,
|
|
129
|
+
cause: error
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
code: defaultCode,
|
|
134
|
+
message: String(error),
|
|
135
|
+
service
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/base/BaseService.ts
|
|
140
|
+
var BaseService = class {
|
|
141
|
+
constructor() {
|
|
142
|
+
/**
|
|
143
|
+
* Abort controller for this service's operations.
|
|
144
|
+
* Reset on sign-out.
|
|
145
|
+
*/
|
|
146
|
+
this.abortController = new AbortController();
|
|
147
|
+
/**
|
|
148
|
+
* Service-specific configuration.
|
|
149
|
+
*/
|
|
150
|
+
this._config = {};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get the service configuration.
|
|
154
|
+
*/
|
|
155
|
+
get config() {
|
|
156
|
+
return this._config;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Initialize the service with context.
|
|
160
|
+
* Called by the SDK after instantiation.
|
|
161
|
+
*
|
|
162
|
+
* @param context - The service context
|
|
163
|
+
*/
|
|
164
|
+
initialize(context) {
|
|
165
|
+
this.context = context;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Called when session changes (sign-in, sign-out, refresh).
|
|
169
|
+
* Override in subclasses to handle session changes.
|
|
170
|
+
*
|
|
171
|
+
* @param session - The new session, or null if signed out
|
|
172
|
+
*/
|
|
173
|
+
onSessionChange(session) {
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Called when SDK signs out.
|
|
177
|
+
* Aborts all pending operations.
|
|
178
|
+
*/
|
|
179
|
+
onSignOut() {
|
|
180
|
+
this.abortController.abort();
|
|
181
|
+
this.abortController = new AbortController();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get the abort signal for this service.
|
|
185
|
+
* Combines the service-level abort with context-level abort.
|
|
186
|
+
*/
|
|
187
|
+
get abortSignal() {
|
|
188
|
+
return this.abortController.signal;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Check if the service is authenticated.
|
|
192
|
+
*/
|
|
193
|
+
get isAuthenticated() {
|
|
194
|
+
return this.context?.isAuthenticated ?? false;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the current session.
|
|
198
|
+
* Throws if not authenticated.
|
|
199
|
+
*/
|
|
200
|
+
get session() {
|
|
201
|
+
if (!this.context?.session) {
|
|
202
|
+
throw new Error("Not authenticated");
|
|
203
|
+
}
|
|
204
|
+
return this.context.session;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check authentication and return error result if not authenticated.
|
|
208
|
+
* Use this at the start of methods that require authentication.
|
|
209
|
+
*
|
|
210
|
+
* @returns true if authenticated, false otherwise
|
|
211
|
+
*/
|
|
212
|
+
requireAuth() {
|
|
213
|
+
return this.isAuthenticated;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Emit a telemetry event.
|
|
217
|
+
*
|
|
218
|
+
* @param event - Event name
|
|
219
|
+
* @param data - Event data
|
|
220
|
+
*/
|
|
221
|
+
emit(event, data) {
|
|
222
|
+
this.context?.emit(event, data);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Emit a service request event.
|
|
226
|
+
*
|
|
227
|
+
* @param action - The action being performed
|
|
228
|
+
* @param key - Optional key/path being accessed
|
|
229
|
+
*/
|
|
230
|
+
emitRequest(action, key) {
|
|
231
|
+
this.emit(TelemetryEvents.SERVICE_REQUEST, {
|
|
232
|
+
service: this.getServiceName(),
|
|
233
|
+
action,
|
|
234
|
+
key,
|
|
235
|
+
timestamp: Date.now()
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Emit a service response event.
|
|
240
|
+
*
|
|
241
|
+
* @param action - The action that was performed
|
|
242
|
+
* @param ok - Whether the request was successful
|
|
243
|
+
* @param startTime - Start time for duration calculation
|
|
244
|
+
* @param status - Optional HTTP status code
|
|
245
|
+
*/
|
|
246
|
+
emitResponse(action, ok2, startTime, status) {
|
|
247
|
+
this.emit(TelemetryEvents.SERVICE_RESPONSE, {
|
|
248
|
+
service: this.getServiceName(),
|
|
249
|
+
action,
|
|
250
|
+
ok: ok2,
|
|
251
|
+
duration: Date.now() - startTime,
|
|
252
|
+
status
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Emit a service error event.
|
|
257
|
+
*
|
|
258
|
+
* @param error - The service error
|
|
259
|
+
*/
|
|
260
|
+
emitError(error) {
|
|
261
|
+
this.emit(TelemetryEvents.SERVICE_ERROR, {
|
|
262
|
+
service: this.getServiceName(),
|
|
263
|
+
error
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the service name from the static property.
|
|
268
|
+
* Subclasses must define static serviceName.
|
|
269
|
+
*/
|
|
270
|
+
getServiceName() {
|
|
271
|
+
return this.constructor.serviceName;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Create a combined abort signal from multiple sources.
|
|
275
|
+
*
|
|
276
|
+
* @param signals - Additional abort signals to combine
|
|
277
|
+
* @returns A combined abort signal
|
|
278
|
+
*/
|
|
279
|
+
combineSignals(...signals) {
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
const allSignals = [this.abortSignal, ...signals.filter(Boolean)];
|
|
282
|
+
for (const signal of allSignals) {
|
|
283
|
+
if (signal.aborted) {
|
|
284
|
+
controller.abort(signal.reason);
|
|
285
|
+
return controller.signal;
|
|
286
|
+
}
|
|
287
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), {
|
|
288
|
+
once: true
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return controller.signal;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Wrap an operation with error handling and telemetry.
|
|
295
|
+
*
|
|
296
|
+
* @param action - The action name for telemetry
|
|
297
|
+
* @param key - Optional key for telemetry
|
|
298
|
+
* @param operation - The operation to execute
|
|
299
|
+
* @returns Result of the operation
|
|
300
|
+
*/
|
|
301
|
+
async withTelemetry(action, key, operation) {
|
|
302
|
+
const startTime = Date.now();
|
|
303
|
+
this.emitRequest(action, key);
|
|
304
|
+
try {
|
|
305
|
+
const result = await operation();
|
|
306
|
+
if (result.ok) {
|
|
307
|
+
this.emitResponse(action, true, startTime);
|
|
308
|
+
} else {
|
|
309
|
+
this.emitResponse(action, false, startTime);
|
|
310
|
+
this.emitError(result.error);
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const serviceError2 = wrapError(this.getServiceName(), error);
|
|
315
|
+
this.emitResponse(action, false, startTime);
|
|
316
|
+
this.emitError(serviceError2);
|
|
317
|
+
return err(serviceError2);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/kv/PrefixedKVService.ts
|
|
323
|
+
var PrefixedKVService = class _PrefixedKVService {
|
|
324
|
+
/**
|
|
325
|
+
* Create a new PrefixedKVService.
|
|
326
|
+
*
|
|
327
|
+
* @param kv - The underlying KV service to delegate to
|
|
328
|
+
* @param prefix - The prefix to apply to all operations
|
|
329
|
+
*/
|
|
330
|
+
constructor(kv, prefix) {
|
|
331
|
+
this._kv = kv;
|
|
332
|
+
this._prefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The current prefix for this scoped view.
|
|
336
|
+
*/
|
|
337
|
+
get prefix() {
|
|
338
|
+
return this._prefix;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Compute the full key path by combining prefix and key.
|
|
342
|
+
*
|
|
343
|
+
* @param key - The key to prefix
|
|
344
|
+
* @returns The full path including prefix
|
|
345
|
+
*/
|
|
346
|
+
getFullKey(key) {
|
|
347
|
+
const normalizedKey = key.startsWith("/") ? key : `/${key}`;
|
|
348
|
+
return `${this._prefix}${normalizedKey}`;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get a value by key.
|
|
352
|
+
*/
|
|
353
|
+
async get(key, options) {
|
|
354
|
+
const fullKey = this.getFullKey(key);
|
|
355
|
+
return this._kv.get(fullKey, { ...options, prefix: "" });
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Store a value at a key.
|
|
359
|
+
*/
|
|
360
|
+
async put(key, value, options) {
|
|
361
|
+
const fullKey = this.getFullKey(key);
|
|
362
|
+
return this._kv.put(fullKey, value, { ...options, prefix: "" });
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* List keys within this prefix.
|
|
366
|
+
*/
|
|
367
|
+
async list(options) {
|
|
368
|
+
const removePrefix = options?.removePrefix ?? true;
|
|
369
|
+
return this._kv.list({
|
|
370
|
+
...options,
|
|
371
|
+
prefix: this._prefix,
|
|
372
|
+
removePrefix
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Delete a key.
|
|
377
|
+
*/
|
|
378
|
+
async delete(key, options) {
|
|
379
|
+
const fullKey = this.getFullKey(key);
|
|
380
|
+
return this._kv.delete(fullKey, { ...options, prefix: "" });
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Get metadata for a key without retrieving the value.
|
|
384
|
+
*/
|
|
385
|
+
async head(key, options) {
|
|
386
|
+
const fullKey = this.getFullKey(key);
|
|
387
|
+
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Create a nested prefix-scoped view.
|
|
391
|
+
*/
|
|
392
|
+
withPrefix(subPrefix) {
|
|
393
|
+
const normalizedSubPrefix = subPrefix.startsWith("/") ? subPrefix : `/${subPrefix}`;
|
|
394
|
+
const combinedPrefix = `${this._prefix}${normalizedSubPrefix}`;
|
|
395
|
+
return new _PrefixedKVService(this._kv, combinedPrefix);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// src/kv/types.ts
|
|
400
|
+
var KVAction = {
|
|
401
|
+
GET: "tinycloud.kv/get",
|
|
402
|
+
PUT: "tinycloud.kv/put",
|
|
403
|
+
LIST: "tinycloud.kv/list",
|
|
404
|
+
DELETE: "tinycloud.kv/del",
|
|
405
|
+
HEAD: "tinycloud.kv/metadata"
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/kv/KVService.ts
|
|
409
|
+
var KVService = class extends BaseService {
|
|
410
|
+
/**
|
|
411
|
+
* Create a new KVService instance.
|
|
412
|
+
*
|
|
413
|
+
* @param config - Service configuration
|
|
414
|
+
*/
|
|
415
|
+
constructor(config = {}) {
|
|
416
|
+
super();
|
|
417
|
+
this._config = config;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get the service configuration.
|
|
421
|
+
*/
|
|
422
|
+
get config() {
|
|
423
|
+
return this._config;
|
|
424
|
+
}
|
|
425
|
+
// Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
|
|
426
|
+
parseQuotaInfo(errorText) {
|
|
427
|
+
const match = errorText.match(
|
|
428
|
+
/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
|
|
429
|
+
);
|
|
430
|
+
if (match) {
|
|
431
|
+
return {
|
|
432
|
+
usedBytes: parseInt(match[1], 10),
|
|
433
|
+
limitBytes: parseInt(match[2], 10)
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
handleQuotaErrorResponse(response, errorText, key) {
|
|
439
|
+
if (response.status === 402) {
|
|
440
|
+
const quotaInfo = this.parseQuotaInfo(errorText);
|
|
441
|
+
return err(
|
|
442
|
+
storageQuotaExceededError(
|
|
443
|
+
"kv",
|
|
444
|
+
`Storage quota exceeded for key "${key}": ${errorText}`,
|
|
445
|
+
{
|
|
446
|
+
status: response.status,
|
|
447
|
+
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (response.status === 413) {
|
|
453
|
+
const quotaInfo = this.parseQuotaInfo(errorText);
|
|
454
|
+
return err(
|
|
455
|
+
storageLimitReachedError(
|
|
456
|
+
"kv",
|
|
457
|
+
`Storage limit reached for key "${key}": ${errorText}`,
|
|
458
|
+
{
|
|
459
|
+
status: response.status,
|
|
460
|
+
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get the full path with optional prefix.
|
|
469
|
+
*
|
|
470
|
+
* @param key - The key
|
|
471
|
+
* @param prefixOverride - Optional prefix override
|
|
472
|
+
* @returns The full path
|
|
473
|
+
*/
|
|
474
|
+
getFullPath(key, prefixOverride) {
|
|
475
|
+
const prefix = prefixOverride ?? this._config.prefix ?? "";
|
|
476
|
+
return prefix ? `${prefix}/${key}` : key;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get the host URL.
|
|
480
|
+
*/
|
|
481
|
+
get host() {
|
|
482
|
+
return this.context.hosts[0];
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Execute an invoke operation.
|
|
486
|
+
*
|
|
487
|
+
* @param path - Resource path
|
|
488
|
+
* @param action - KV action
|
|
489
|
+
* @param body - Optional request body
|
|
490
|
+
* @param signal - Optional abort signal
|
|
491
|
+
* @returns Fetch response
|
|
492
|
+
*/
|
|
493
|
+
async invokeOperation(path, action, body, signal) {
|
|
494
|
+
const session = this.context.session;
|
|
495
|
+
const headers = this.context.invoke(
|
|
496
|
+
session,
|
|
497
|
+
"kv",
|
|
498
|
+
path,
|
|
499
|
+
action
|
|
500
|
+
);
|
|
501
|
+
return this.context.fetch(`${this.host}/invoke`, {
|
|
502
|
+
method: "POST",
|
|
503
|
+
headers,
|
|
504
|
+
body,
|
|
505
|
+
signal: this.combineSignals(signal)
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Create KVResponseHeaders from fetch response headers.
|
|
510
|
+
*
|
|
511
|
+
* @param headers - Fetch response headers
|
|
512
|
+
* @returns KVResponseHeaders object
|
|
513
|
+
*/
|
|
514
|
+
createResponseHeaders(headers) {
|
|
515
|
+
return {
|
|
516
|
+
etag: headers.get("etag") ?? void 0,
|
|
517
|
+
contentType: headers.get("content-type") ?? void 0,
|
|
518
|
+
lastModified: headers.get("last-modified") ?? void 0,
|
|
519
|
+
contentLength: headers.get("content-length") ? parseInt(headers.get("content-length"), 10) : void 0,
|
|
520
|
+
get: (name) => headers.get(name)
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Parse response body based on content type.
|
|
525
|
+
*
|
|
526
|
+
* @param response - Fetch response
|
|
527
|
+
* @param raw - Whether to return raw text
|
|
528
|
+
* @returns Parsed data
|
|
529
|
+
*/
|
|
530
|
+
async parseResponse(response, raw = false) {
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
if (raw) {
|
|
535
|
+
return await response.text();
|
|
536
|
+
}
|
|
537
|
+
const contentType = response.headers.get("content-type");
|
|
538
|
+
if (contentType?.includes("application/json")) {
|
|
539
|
+
return await response.json();
|
|
540
|
+
} else if (contentType?.startsWith("text/")) {
|
|
541
|
+
return await response.text();
|
|
542
|
+
}
|
|
543
|
+
const text = await response.text();
|
|
544
|
+
if (!text) {
|
|
545
|
+
return void 0;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
return JSON.parse(text);
|
|
549
|
+
} catch {
|
|
550
|
+
return text;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get a value by key.
|
|
555
|
+
*/
|
|
556
|
+
async get(key, options) {
|
|
557
|
+
return this.withTelemetry("get", key, async () => {
|
|
558
|
+
if (!this.requireAuth()) {
|
|
559
|
+
return err(authRequiredError("kv"));
|
|
560
|
+
}
|
|
561
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
562
|
+
try {
|
|
563
|
+
const response = await this.invokeOperation(
|
|
564
|
+
path,
|
|
565
|
+
KVAction.GET,
|
|
566
|
+
void 0,
|
|
567
|
+
options?.signal
|
|
568
|
+
);
|
|
569
|
+
if (!response.ok) {
|
|
570
|
+
if (response.status === 401) {
|
|
571
|
+
const errorText2 = await response.text();
|
|
572
|
+
const { resource, action } = parseAuthError(errorText2);
|
|
573
|
+
return err(authUnauthorizedError("kv", errorText2, {
|
|
574
|
+
status: response.status,
|
|
575
|
+
...action && { requiredAction: action },
|
|
576
|
+
...resource && { resource }
|
|
577
|
+
}));
|
|
578
|
+
}
|
|
579
|
+
if (response.status === 404) {
|
|
580
|
+
return err(
|
|
581
|
+
serviceError(
|
|
582
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
583
|
+
`Key not found: ${key}`,
|
|
584
|
+
"kv"
|
|
585
|
+
)
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const errorText = await response.text();
|
|
589
|
+
return err(
|
|
590
|
+
serviceError(
|
|
591
|
+
ErrorCodes.NETWORK_ERROR,
|
|
592
|
+
`Failed to get key "${key}": ${response.status} - ${errorText}`,
|
|
593
|
+
"kv",
|
|
594
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
const data = await this.parseResponse(response, options?.raw);
|
|
599
|
+
return ok({
|
|
600
|
+
data,
|
|
601
|
+
headers: this.createResponseHeaders(response.headers)
|
|
602
|
+
});
|
|
603
|
+
} catch (error) {
|
|
604
|
+
return err(wrapError("kv", error));
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Store a value at a key.
|
|
610
|
+
*/
|
|
611
|
+
async put(key, value, options) {
|
|
612
|
+
return this.withTelemetry("put", key, async () => {
|
|
613
|
+
if (!this.requireAuth()) {
|
|
614
|
+
return err(authRequiredError("kv"));
|
|
615
|
+
}
|
|
616
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
617
|
+
let body;
|
|
618
|
+
if (typeof value === "string") {
|
|
619
|
+
body = value;
|
|
620
|
+
} else {
|
|
621
|
+
body = JSON.stringify(value);
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const response = await this.invokeOperation(
|
|
625
|
+
path,
|
|
626
|
+
KVAction.PUT,
|
|
627
|
+
body,
|
|
628
|
+
options?.signal
|
|
629
|
+
);
|
|
630
|
+
if (!response.ok) {
|
|
631
|
+
if (response.status === 401) {
|
|
632
|
+
const errorText2 = await response.text();
|
|
633
|
+
const { resource, action } = parseAuthError(errorText2);
|
|
634
|
+
return err(authUnauthorizedError("kv", errorText2, {
|
|
635
|
+
status: response.status,
|
|
636
|
+
...action && { requiredAction: action },
|
|
637
|
+
...resource && { resource }
|
|
638
|
+
}));
|
|
639
|
+
}
|
|
640
|
+
const errorText = await response.text();
|
|
641
|
+
const quotaError = this.handleQuotaErrorResponse(
|
|
642
|
+
response,
|
|
643
|
+
errorText,
|
|
644
|
+
key
|
|
645
|
+
);
|
|
646
|
+
if (quotaError) {
|
|
647
|
+
return quotaError;
|
|
648
|
+
}
|
|
649
|
+
return err(
|
|
650
|
+
serviceError(
|
|
651
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
652
|
+
`Failed to put key "${key}": ${response.status} - ${errorText}`,
|
|
653
|
+
"kv",
|
|
654
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
655
|
+
)
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return ok({
|
|
659
|
+
data: void 0,
|
|
660
|
+
headers: this.createResponseHeaders(response.headers)
|
|
661
|
+
});
|
|
662
|
+
} catch (error) {
|
|
663
|
+
return err(wrapError("kv", error));
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* List keys with optional prefix filtering.
|
|
669
|
+
*/
|
|
670
|
+
async list(options) {
|
|
671
|
+
return this.withTelemetry("list", options?.prefix, async () => {
|
|
672
|
+
if (!this.requireAuth()) {
|
|
673
|
+
return err(authRequiredError("kv"));
|
|
674
|
+
}
|
|
675
|
+
let listPath = options?.prefix ?? this._config.prefix ?? "";
|
|
676
|
+
if (options?.path) {
|
|
677
|
+
listPath = listPath ? `${listPath}/${options.path}` : options.path;
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
const response = await this.invokeOperation(
|
|
681
|
+
listPath,
|
|
682
|
+
KVAction.LIST,
|
|
683
|
+
void 0,
|
|
684
|
+
options?.signal
|
|
685
|
+
);
|
|
686
|
+
if (!response.ok) {
|
|
687
|
+
if (response.status === 401) {
|
|
688
|
+
const errorText2 = await response.text();
|
|
689
|
+
const { resource, action } = parseAuthError(errorText2);
|
|
690
|
+
return err(authUnauthorizedError("kv", errorText2, {
|
|
691
|
+
status: response.status,
|
|
692
|
+
...action && { requiredAction: action },
|
|
693
|
+
...resource && { resource }
|
|
694
|
+
}));
|
|
695
|
+
}
|
|
696
|
+
const errorText = await response.text();
|
|
697
|
+
return err(
|
|
698
|
+
serviceError(
|
|
699
|
+
ErrorCodes.NETWORK_ERROR,
|
|
700
|
+
`Failed to list keys: ${response.status} - ${errorText}`,
|
|
701
|
+
"kv",
|
|
702
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
703
|
+
)
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
let keys = await this.parseResponse(response, options?.raw);
|
|
707
|
+
keys = keys ?? [];
|
|
708
|
+
if (options?.removePrefix && listPath) {
|
|
709
|
+
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
710
|
+
keys = keys.map(
|
|
711
|
+
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
return ok({ keys });
|
|
715
|
+
} catch (error) {
|
|
716
|
+
return err(wrapError("kv", error));
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Delete a key.
|
|
722
|
+
*/
|
|
723
|
+
async delete(key, options) {
|
|
724
|
+
return this.withTelemetry("delete", key, async () => {
|
|
725
|
+
if (!this.requireAuth()) {
|
|
726
|
+
return err(authRequiredError("kv"));
|
|
727
|
+
}
|
|
728
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
729
|
+
try {
|
|
730
|
+
const response = await this.invokeOperation(
|
|
731
|
+
path,
|
|
732
|
+
KVAction.DELETE,
|
|
733
|
+
void 0,
|
|
734
|
+
options?.signal
|
|
735
|
+
);
|
|
736
|
+
if (!response.ok) {
|
|
737
|
+
if (response.status === 401) {
|
|
738
|
+
const errorText2 = await response.text();
|
|
739
|
+
const { resource, action } = parseAuthError(errorText2);
|
|
740
|
+
return err(authUnauthorizedError("kv", errorText2, {
|
|
741
|
+
status: response.status,
|
|
742
|
+
...action && { requiredAction: action },
|
|
743
|
+
...resource && { resource }
|
|
744
|
+
}));
|
|
745
|
+
}
|
|
746
|
+
if (response.status === 404) {
|
|
747
|
+
return err(
|
|
748
|
+
serviceError(
|
|
749
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
750
|
+
`Key not found: ${key}`,
|
|
751
|
+
"kv"
|
|
752
|
+
)
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const errorText = await response.text();
|
|
756
|
+
return err(
|
|
757
|
+
serviceError(
|
|
758
|
+
ErrorCodes.NETWORK_ERROR,
|
|
759
|
+
`Failed to delete key "${key}": ${response.status} - ${errorText}`,
|
|
760
|
+
"kv",
|
|
761
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return ok(void 0);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
return err(wrapError("kv", error));
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Get metadata for a key without retrieving the value.
|
|
773
|
+
*/
|
|
774
|
+
async head(key, options) {
|
|
775
|
+
return this.withTelemetry("head", key, async () => {
|
|
776
|
+
if (!this.requireAuth()) {
|
|
777
|
+
return err(authRequiredError("kv"));
|
|
778
|
+
}
|
|
779
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
780
|
+
try {
|
|
781
|
+
const response = await this.invokeOperation(
|
|
782
|
+
path,
|
|
783
|
+
KVAction.HEAD,
|
|
784
|
+
void 0,
|
|
785
|
+
options?.signal
|
|
786
|
+
);
|
|
787
|
+
if (!response.ok) {
|
|
788
|
+
if (response.status === 401) {
|
|
789
|
+
const errorText2 = await response.text();
|
|
790
|
+
const { resource, action } = parseAuthError(errorText2);
|
|
791
|
+
return err(authUnauthorizedError("kv", errorText2, {
|
|
792
|
+
status: response.status,
|
|
793
|
+
...action && { requiredAction: action },
|
|
794
|
+
...resource && { resource }
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
if (response.status === 404) {
|
|
798
|
+
return err(
|
|
799
|
+
serviceError(
|
|
800
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
801
|
+
`Key not found: ${key}`,
|
|
802
|
+
"kv"
|
|
803
|
+
)
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
const errorText = await response.text();
|
|
807
|
+
return err(
|
|
808
|
+
serviceError(
|
|
809
|
+
ErrorCodes.NETWORK_ERROR,
|
|
810
|
+
`Failed to get metadata for key "${key}": ${response.status} - ${errorText}`,
|
|
811
|
+
"kv",
|
|
812
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
return ok({
|
|
817
|
+
data: void 0,
|
|
818
|
+
headers: this.createResponseHeaders(response.headers)
|
|
819
|
+
});
|
|
820
|
+
} catch (error) {
|
|
821
|
+
return err(wrapError("kv", error));
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Create a prefix-scoped view of this KV service.
|
|
827
|
+
*
|
|
828
|
+
* Returns a PrefixedKVService that automatically prefixes all
|
|
829
|
+
* key operations with the specified prefix. This enables apps
|
|
830
|
+
* to isolate their data within a shared space.
|
|
831
|
+
*
|
|
832
|
+
* @param prefix - The prefix to apply to all operations
|
|
833
|
+
* @returns A PrefixedKVService scoped to the prefix
|
|
834
|
+
*
|
|
835
|
+
* ## Prefix Conventions
|
|
836
|
+
*
|
|
837
|
+
* | Pattern | Use Case | Example |
|
|
838
|
+
* | -- | -- | -- |
|
|
839
|
+
* | `/app.{domain}/` | App-private data | `/app.photos.xyz/settings.json` |
|
|
840
|
+
* | `/{type}/` | Shared data type | `/photos/vacation.jpg` |
|
|
841
|
+
* | `/.{name}/` | Hidden/system data | `/.cache/thumbnails/` |
|
|
842
|
+
* | `/public/` | Explicitly shareable | `/public/profile.json` |
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```typescript
|
|
846
|
+
* const space = sdk.space('default');
|
|
847
|
+
*
|
|
848
|
+
* // Create prefix-scoped views
|
|
849
|
+
* const myApp = space.kv.withPrefix('/app.myapp.com');
|
|
850
|
+
* const sharedPhotos = space.kv.withPrefix('/photos');
|
|
851
|
+
*
|
|
852
|
+
* // Operations are automatically prefixed
|
|
853
|
+
* await myApp.put('settings.json', { theme: 'dark' });
|
|
854
|
+
* // -> Actually writes to: /app.myapp.com/settings.json
|
|
855
|
+
*
|
|
856
|
+
* await myApp.get('settings.json');
|
|
857
|
+
* // -> Actually reads from: /app.myapp.com/settings.json
|
|
858
|
+
*
|
|
859
|
+
* await sharedPhotos.list();
|
|
860
|
+
* // -> Lists: /photos/*
|
|
861
|
+
*
|
|
862
|
+
* // Nested prefixes
|
|
863
|
+
* const settings = myApp.withPrefix('/settings');
|
|
864
|
+
* await settings.get('theme.json'); // -> /app.myapp.com/settings/theme.json
|
|
865
|
+
* ```
|
|
866
|
+
*/
|
|
867
|
+
withPrefix(prefix) {
|
|
868
|
+
return new PrefixedKVService(this, prefix);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
1
871
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Key-Value storage service for TinyCloud SDK.
|
|
872
|
+
* Service identifier for registration.
|
|
5
873
|
*/
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
874
|
+
KVService.serviceName = "kv";
|
|
875
|
+
export {
|
|
876
|
+
KVAction,
|
|
877
|
+
KVService,
|
|
878
|
+
PrefixedKVService
|
|
879
|
+
};
|
|
12
880
|
//# sourceMappingURL=index.js.map
|