@sovereignfs/sdk 1.25.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 +661 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NQLGPAL6.js +62 -0
- package/dist/e2ee-crypto.d.ts +84 -0
- package/dist/e2ee-crypto.js +162 -0
- package/dist/e2ee-device.d.ts +21 -0
- package/dist/e2ee-device.js +13 -0
- package/dist/e2ee-object.d.ts +50 -0
- package/dist/e2ee-object.js +75 -0
- package/dist/e2ee-state-U-GEJ7oL.d.ts +411 -0
- package/dist/e2ee-state.d.ts +1 -0
- package/dist/e2ee-state.js +36 -0
- package/dist/index.d.ts +543 -0
- package/dist/index.js +652 -0
- package/package.json +50 -0
- package/src/__tests__/e2ee-crypto.test.ts +156 -0
- package/src/__tests__/e2ee-object.test.ts +90 -0
- package/src/__tests__/e2ee-state.test.ts +83 -0
- package/src/__tests__/sdk.test.ts +427 -0
- package/src/__tests__/sessions.test.ts +63 -0
- package/src/activity.ts +19 -0
- package/src/auth.ts +134 -0
- package/src/connections.ts +106 -0
- package/src/data.ts +88 -0
- package/src/db.ts +34 -0
- package/src/directory.ts +31 -0
- package/src/e2ee-crypto.ts +260 -0
- package/src/e2ee-device.ts +75 -0
- package/src/e2ee-object.ts +118 -0
- package/src/e2ee-state.ts +72 -0
- package/src/e2ee.ts +65 -0
- package/src/env.ts +37 -0
- package/src/errors.ts +46 -0
- package/src/host.ts +251 -0
- package/src/index.ts +160 -0
- package/src/mailer.ts +7 -0
- package/src/notifications.ts +36 -0
- package/src/platform.ts +10 -0
- package/src/plugins.ts +146 -0
- package/src/portability.ts +176 -0
- package/src/secrets.ts +68 -0
- package/src/sessions.ts +36 -0
- package/src/storage.ts +49 -0
- package/src/types.ts +418 -0
- package/src/unimplemented.ts +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-MLKGABMK.js";
|
|
4
|
+
|
|
5
|
+
// src/auth.ts
|
|
6
|
+
var auth_exports = {};
|
|
7
|
+
__export(auth_exports, {
|
|
8
|
+
changePassword: () => changePassword,
|
|
9
|
+
getSession: () => getSession,
|
|
10
|
+
hasCapability: () => hasCapability,
|
|
11
|
+
listSessions: () => listSessions,
|
|
12
|
+
requireSession: () => requireSession,
|
|
13
|
+
revokeSession: () => revokeSession,
|
|
14
|
+
signOut: () => signOut
|
|
15
|
+
});
|
|
16
|
+
import { headers } from "next/headers";
|
|
17
|
+
|
|
18
|
+
// src/errors.ts
|
|
19
|
+
var NotImplementedError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "NotImplementedError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var NotAuthenticatedError = class extends Error {
|
|
26
|
+
constructor() {
|
|
27
|
+
super("No authenticated session. The caller must be within an authenticated request.");
|
|
28
|
+
this.name = "NotAuthenticatedError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var ConsentRequiredError = class extends Error {
|
|
32
|
+
constructor(message = "User consent is required to access this plugin data.") {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "ConsentRequiredError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var EntitlementRequiredError = class extends Error {
|
|
38
|
+
constructor(message = "An active entitlement is required to access this feature.") {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "EntitlementRequiredError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/sessions.ts
|
|
45
|
+
function markCurrentSessions(sessions, currentToken) {
|
|
46
|
+
return sessions.map((s) => ({
|
|
47
|
+
token: s.token,
|
|
48
|
+
current: s.token === currentToken,
|
|
49
|
+
userAgent: s.userAgent ?? null,
|
|
50
|
+
ipAddress: s.ipAddress ?? null,
|
|
51
|
+
createdAt: s.createdAt,
|
|
52
|
+
updatedAt: s.updatedAt,
|
|
53
|
+
expiresAt: s.expiresAt
|
|
54
|
+
})).sort((a, b) => {
|
|
55
|
+
if (a.current !== b.current) return a.current ? -1 : 1;
|
|
56
|
+
return a.createdAt < b.createdAt ? 1 : -1;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/auth.ts
|
|
61
|
+
var DEFAULT_TENANT_ID = "default";
|
|
62
|
+
var AUTH_URL = process.env.SOVEREIGN_AUTH_URL ?? `http://localhost:${process.env.AUTH_PORT ?? "3001"}`;
|
|
63
|
+
async function getSession() {
|
|
64
|
+
const h = await headers();
|
|
65
|
+
const id = h.get("x-sovereign-user-id");
|
|
66
|
+
if (!id) return null;
|
|
67
|
+
const role = h.get("x-sovereign-user-role") ?? "platform:user";
|
|
68
|
+
const capsRaw = h.get("x-sovereign-user-capabilities");
|
|
69
|
+
const capabilities = capsRaw ? JSON.parse(capsRaw) : [];
|
|
70
|
+
return {
|
|
71
|
+
user: {
|
|
72
|
+
id,
|
|
73
|
+
// v1 is single-tenant, so every session belongs to the default tenant.
|
|
74
|
+
// Multi-tenant deployments will source this from the verified session.
|
|
75
|
+
tenantId: DEFAULT_TENANT_ID,
|
|
76
|
+
email: h.get("x-sovereign-user-email") ?? "",
|
|
77
|
+
name: h.get("x-sovereign-user-name") ?? null,
|
|
78
|
+
image: h.get("x-sovereign-user-image") ?? null,
|
|
79
|
+
role,
|
|
80
|
+
capabilities
|
|
81
|
+
},
|
|
82
|
+
expiresAt: Number(h.get("x-sovereign-session-expires-at") ?? 0)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function hasCapability(session, capability) {
|
|
86
|
+
return session?.user.capabilities.includes(capability) ?? false;
|
|
87
|
+
}
|
|
88
|
+
async function requireSession() {
|
|
89
|
+
const session = await getSession();
|
|
90
|
+
if (!session) throw new NotAuthenticatedError();
|
|
91
|
+
return session;
|
|
92
|
+
}
|
|
93
|
+
async function authFetch(path, method, body) {
|
|
94
|
+
const cookie = (await headers()).get("cookie") ?? "";
|
|
95
|
+
return fetch(`${AUTH_URL}${path}`, {
|
|
96
|
+
method,
|
|
97
|
+
headers: {
|
|
98
|
+
cookie,
|
|
99
|
+
origin: AUTH_URL,
|
|
100
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
101
|
+
},
|
|
102
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async function authError(res, action) {
|
|
106
|
+
const body = await res.text().catch(() => "");
|
|
107
|
+
const detail = body ? ` \u2014 ${body}` : "";
|
|
108
|
+
return new Error(`${action} (${String(res.status)})${detail}`);
|
|
109
|
+
}
|
|
110
|
+
async function changePassword(input) {
|
|
111
|
+
const res = await authFetch("/api/auth/change-password", "POST", {
|
|
112
|
+
currentPassword: input.currentPassword,
|
|
113
|
+
newPassword: input.newPassword,
|
|
114
|
+
revokeOtherSessions: false
|
|
115
|
+
});
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const message = (await res.json().catch(() => null))?.message;
|
|
118
|
+
throw new Error(message ?? `Failed to change password (${String(res.status)}).`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function listSessions() {
|
|
122
|
+
const [listRes, sessionRes] = await Promise.all([
|
|
123
|
+
authFetch("/api/auth/list-sessions", "GET"),
|
|
124
|
+
authFetch("/api/auth/get-session", "GET")
|
|
125
|
+
]);
|
|
126
|
+
if (!listRes.ok) {
|
|
127
|
+
throw await authError(listRes, "Failed to list sessions");
|
|
128
|
+
}
|
|
129
|
+
const raw = await listRes.json();
|
|
130
|
+
const currentToken = sessionRes.ok ? (await sessionRes.json())?.session?.token ?? null : null;
|
|
131
|
+
return markCurrentSessions(raw, currentToken);
|
|
132
|
+
}
|
|
133
|
+
async function revokeSession(token) {
|
|
134
|
+
const res = await authFetch("/api/auth/revoke-session", "POST", { token });
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
throw await authError(res, "Failed to revoke session");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function signOut() {
|
|
140
|
+
const res = await authFetch("/api/auth/sign-out", "POST", {});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
throw await authError(res, "Failed to sign out");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/activity.ts
|
|
147
|
+
import { headers as headers2 } from "next/headers";
|
|
148
|
+
|
|
149
|
+
// src/host.ts
|
|
150
|
+
var HOST_KEY = /* @__PURE__ */ Symbol.for("@sovereignfs/sdk:host");
|
|
151
|
+
function holder() {
|
|
152
|
+
return globalThis;
|
|
153
|
+
}
|
|
154
|
+
function provideHost(host) {
|
|
155
|
+
holder()[HOST_KEY] = host;
|
|
156
|
+
}
|
|
157
|
+
function requireHost() {
|
|
158
|
+
const host = holder()[HOST_KEY];
|
|
159
|
+
if (!host) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
"@sovereignfs/sdk: no runtime host is registered. SDK methods run inside the Sovereign runtime \u2014 start the platform with `pnpm dev` or `pnpm sv dev` and ensure the plugin is installed."
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return host;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/activity.ts
|
|
168
|
+
var activity = {
|
|
169
|
+
async log(entry) {
|
|
170
|
+
const h = await headers2();
|
|
171
|
+
const actorId = h.get("x-sovereign-user-id");
|
|
172
|
+
const pluginId = h.get("x-sovereign-plugin-id");
|
|
173
|
+
await requireHost().activity.log(entry, actorId, pluginId);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/connections.ts
|
|
178
|
+
import { headers as headers3 } from "next/headers";
|
|
179
|
+
var DEFAULT_TENANT_ID2 = "default";
|
|
180
|
+
function parseCapabilities(raw) {
|
|
181
|
+
if (!raw) return [];
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(raw);
|
|
184
|
+
return Array.isArray(parsed) ? parsed.filter((cap) => typeof cap === "string") : [];
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function connectionContext() {
|
|
190
|
+
const h = await headers3();
|
|
191
|
+
const pluginId = h.get("x-sovereign-plugin-id");
|
|
192
|
+
if (!pluginId) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"sdk.connections requires a plugin route context (x-sovereign-plugin-id header missing)."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
tenantId: DEFAULT_TENANT_ID2,
|
|
199
|
+
pluginId,
|
|
200
|
+
userId: h.get("x-sovereign-user-id"),
|
|
201
|
+
capabilities: parseCapabilities(h.get("x-sovereign-user-capabilities"))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function requireUserForUserScope(scope, userId) {
|
|
205
|
+
if ((scope === void 0 || scope === "user") && !userId) throw new NotAuthenticatedError();
|
|
206
|
+
}
|
|
207
|
+
var connections = {
|
|
208
|
+
async create(input) {
|
|
209
|
+
const context = await connectionContext();
|
|
210
|
+
requireUserForUserScope(input.scope, context.userId);
|
|
211
|
+
return requireHost().connections.create(input, context);
|
|
212
|
+
},
|
|
213
|
+
async list(filter) {
|
|
214
|
+
const context = await connectionContext();
|
|
215
|
+
requireUserForUserScope(filter?.scope, context.userId);
|
|
216
|
+
return requireHost().connections.list(filter, context);
|
|
217
|
+
},
|
|
218
|
+
async get(id) {
|
|
219
|
+
const context = await connectionContext();
|
|
220
|
+
return requireHost().connections.get(id, context);
|
|
221
|
+
},
|
|
222
|
+
async update(id, input) {
|
|
223
|
+
const context = await connectionContext();
|
|
224
|
+
return requireHost().connections.update(id, input, context);
|
|
225
|
+
},
|
|
226
|
+
async disconnect(id) {
|
|
227
|
+
const context = await connectionContext();
|
|
228
|
+
return requireHost().connections.disconnect(id, context);
|
|
229
|
+
},
|
|
230
|
+
async markUsed(id) {
|
|
231
|
+
const context = await connectionContext();
|
|
232
|
+
return requireHost().connections.markUsed(id, context);
|
|
233
|
+
},
|
|
234
|
+
async markError(id, input) {
|
|
235
|
+
const context = await connectionContext();
|
|
236
|
+
return requireHost().connections.markError(id, input, context);
|
|
237
|
+
},
|
|
238
|
+
async createOAuthState(input) {
|
|
239
|
+
const context = await connectionContext();
|
|
240
|
+
requireUserForUserScope("user", context.userId);
|
|
241
|
+
return requireHost().connections.createOAuthState(input, context);
|
|
242
|
+
},
|
|
243
|
+
async verifyOAuthState(state) {
|
|
244
|
+
const context = await connectionContext();
|
|
245
|
+
requireUserForUserScope("user", context.userId);
|
|
246
|
+
return requireHost().connections.verifyOAuthState(state, context);
|
|
247
|
+
},
|
|
248
|
+
async getProviderConfig(provider) {
|
|
249
|
+
const context = await connectionContext();
|
|
250
|
+
return requireHost().connections.getProviderConfig(provider, context);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/data.ts
|
|
255
|
+
import { headers as headers4 } from "next/headers";
|
|
256
|
+
var DEFAULT_TENANT_ID3 = "default";
|
|
257
|
+
var data = {
|
|
258
|
+
/**
|
|
259
|
+
* Provider: register a resolver for one of the contracts this plugin exposes.
|
|
260
|
+
* Call this before any consumer can query the contract — typically at the top
|
|
261
|
+
* of a route handler or server component that runs when the plugin is loaded.
|
|
262
|
+
* Registrations are in-process and reset on server restart.
|
|
263
|
+
*/
|
|
264
|
+
provide(contract, resolver) {
|
|
265
|
+
requireHost().data.provide(contract, resolver);
|
|
266
|
+
},
|
|
267
|
+
/** Consumer: read a provider plugin's contract for the current user (consent-gated). */
|
|
268
|
+
async query(ref, params) {
|
|
269
|
+
const h = await headers4();
|
|
270
|
+
const consumerId = h.get("x-sovereign-plugin-id");
|
|
271
|
+
const userId = h.get("x-sovereign-user-id");
|
|
272
|
+
if (!userId) throw new ConsentRequiredError();
|
|
273
|
+
const result = await requireHost().data.query(
|
|
274
|
+
ref,
|
|
275
|
+
consumerId,
|
|
276
|
+
userId,
|
|
277
|
+
DEFAULT_TENANT_ID3,
|
|
278
|
+
params
|
|
279
|
+
);
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/directory.ts
|
|
285
|
+
import { headers as headers5 } from "next/headers";
|
|
286
|
+
var DEFAULT_TENANT_ID4 = "default";
|
|
287
|
+
var directory = {
|
|
288
|
+
/**
|
|
289
|
+
* Search active users in the current tenant by display name or email.
|
|
290
|
+
* Results include only display-safe profile fields.
|
|
291
|
+
*/
|
|
292
|
+
async searchUsers(input) {
|
|
293
|
+
const h = await headers5();
|
|
294
|
+
const userId = h.get("x-sovereign-user-id");
|
|
295
|
+
if (!userId) throw new NotAuthenticatedError();
|
|
296
|
+
return requireHost().directory.searchUsers(input, userId, DEFAULT_TENANT_ID4);
|
|
297
|
+
},
|
|
298
|
+
/**
|
|
299
|
+
* Resolve explicit user IDs already stored by a plugin into display-safe
|
|
300
|
+
* profile rows for active users.
|
|
301
|
+
*/
|
|
302
|
+
async resolveUsers(input) {
|
|
303
|
+
const h = await headers5();
|
|
304
|
+
const userId = h.get("x-sovereign-user-id");
|
|
305
|
+
if (!userId) throw new NotAuthenticatedError();
|
|
306
|
+
return requireHost().directory.resolveUsers(input, userId, DEFAULT_TENANT_ID4);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/db.ts
|
|
311
|
+
var db_exports = {};
|
|
312
|
+
__export(db_exports, {
|
|
313
|
+
getClient: () => getClient
|
|
314
|
+
});
|
|
315
|
+
import { headers as headers6 } from "next/headers";
|
|
316
|
+
async function getClient() {
|
|
317
|
+
let pluginId = null;
|
|
318
|
+
try {
|
|
319
|
+
const h = await headers6();
|
|
320
|
+
pluginId = h.get("x-sovereign-plugin-id");
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
return requireHost().db.getClient(pluginId);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/e2ee.ts
|
|
327
|
+
import { headers as headers7 } from "next/headers";
|
|
328
|
+
var DEFAULT_TENANT_ID5 = "default";
|
|
329
|
+
async function e2eeContext() {
|
|
330
|
+
const h = await headers7();
|
|
331
|
+
const userId = h.get("x-sovereign-user-id");
|
|
332
|
+
if (!userId) throw new NotAuthenticatedError();
|
|
333
|
+
return { tenantId: DEFAULT_TENANT_ID5, userId };
|
|
334
|
+
}
|
|
335
|
+
var e2ee = {
|
|
336
|
+
async getProfile() {
|
|
337
|
+
const context = await e2eeContext();
|
|
338
|
+
return requireHost().e2ee.getProfile(context);
|
|
339
|
+
},
|
|
340
|
+
async createProfile(input) {
|
|
341
|
+
const context = await e2eeContext();
|
|
342
|
+
return requireHost().e2ee.createProfile(input, context);
|
|
343
|
+
},
|
|
344
|
+
async getRecoveryWrapper() {
|
|
345
|
+
const context = await e2eeContext();
|
|
346
|
+
return requireHost().e2ee.getRecoveryWrapper(context);
|
|
347
|
+
},
|
|
348
|
+
async setRecoveryWrapper(input) {
|
|
349
|
+
const context = await e2eeContext();
|
|
350
|
+
return requireHost().e2ee.setRecoveryWrapper(input, context);
|
|
351
|
+
},
|
|
352
|
+
async enrollDevice(input) {
|
|
353
|
+
const context = await e2eeContext();
|
|
354
|
+
return requireHost().e2ee.enrollDevice(input, context);
|
|
355
|
+
},
|
|
356
|
+
async listDevices() {
|
|
357
|
+
const context = await e2eeContext();
|
|
358
|
+
return requireHost().e2ee.listDevices(context);
|
|
359
|
+
},
|
|
360
|
+
async revokeDevice(id) {
|
|
361
|
+
const context = await e2eeContext();
|
|
362
|
+
return requireHost().e2ee.revokeDevice(id, context);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/env.ts
|
|
367
|
+
import { headers as headers8 } from "next/headers";
|
|
368
|
+
var env = {
|
|
369
|
+
async get(key) {
|
|
370
|
+
const h = await headers8();
|
|
371
|
+
const pluginId = h.get("x-sovereign-plugin-id");
|
|
372
|
+
if (!pluginId) return null;
|
|
373
|
+
const slug = pluginId.replace(/[.-]/g, "_").toUpperCase();
|
|
374
|
+
return process.env[`SV_PLUGIN_${slug}_${key}`] ?? null;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/mailer.ts
|
|
379
|
+
var mailer_exports = {};
|
|
380
|
+
__export(mailer_exports, {
|
|
381
|
+
send: () => send
|
|
382
|
+
});
|
|
383
|
+
async function send(options) {
|
|
384
|
+
await requireHost().mailer.send(options);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/notifications.ts
|
|
388
|
+
function getPluginId(headers13) {
|
|
389
|
+
return headers13.get("x-sovereign-plugin-id");
|
|
390
|
+
}
|
|
391
|
+
var notifications = {
|
|
392
|
+
/**
|
|
393
|
+
* Send a notification to a user.
|
|
394
|
+
*
|
|
395
|
+
* The `source` and `sourceType` fields are set by the runtime from the
|
|
396
|
+
* calling plugin's ID (injected via `x-sovereign-plugin-id` header) — plugins
|
|
397
|
+
* cannot forge sender identity.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* await sdk.notifications.send({
|
|
402
|
+
* recipientUserId: userId,
|
|
403
|
+
* title: 'Your export is ready',
|
|
404
|
+
* url: '/myPlugin/exports',
|
|
405
|
+
* category: 'info',
|
|
406
|
+
* });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
async send(input, requestHeaders) {
|
|
410
|
+
const pluginId = requestHeaders ? getPluginId(requestHeaders) ?? "unknown" : "unknown";
|
|
411
|
+
return requireHost().notifications.send(input, pluginId);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/platform.ts
|
|
416
|
+
var platform_exports = {};
|
|
417
|
+
__export(platform_exports, {
|
|
418
|
+
getConfig: () => getConfig
|
|
419
|
+
});
|
|
420
|
+
async function getConfig() {
|
|
421
|
+
return requireHost().platform.getConfig();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/plugins.ts
|
|
425
|
+
import { headers as headers9 } from "next/headers";
|
|
426
|
+
function parseCapabilities2(raw) {
|
|
427
|
+
if (!raw) return [];
|
|
428
|
+
try {
|
|
429
|
+
const parsed = JSON.parse(raw);
|
|
430
|
+
return Array.isArray(parsed) ? parsed.filter((cap) => typeof cap === "string") : [];
|
|
431
|
+
} catch {
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function requestContext() {
|
|
436
|
+
const h = await headers9();
|
|
437
|
+
return {
|
|
438
|
+
userId: h.get("x-sovereign-user-id"),
|
|
439
|
+
capabilities: parseCapabilities2(h.get("x-sovereign-user-capabilities"))
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
var plugins = {
|
|
443
|
+
/** Discover one installed plugin's status. `null` when not installed. */
|
|
444
|
+
async get(id) {
|
|
445
|
+
const { userId, capabilities } = await requestContext();
|
|
446
|
+
return requireHost().plugins.get(id, userId, capabilities);
|
|
447
|
+
},
|
|
448
|
+
/** Discover installed plugins, optionally filtered to those providing a contract. */
|
|
449
|
+
async list(filter) {
|
|
450
|
+
const { userId, capabilities } = await requestContext();
|
|
451
|
+
return requireHost().plugins.list(filter, userId, capabilities);
|
|
452
|
+
},
|
|
453
|
+
/** Whether the current user has granted this plugin's requested data contract. */
|
|
454
|
+
async getConsentStatus(ref) {
|
|
455
|
+
const h = await headers9();
|
|
456
|
+
const consumerId = h.get("x-sovereign-plugin-id");
|
|
457
|
+
const userId = h.get("x-sovereign-user-id");
|
|
458
|
+
if (!consumerId || !userId) return "not_granted";
|
|
459
|
+
return requireHost().plugins.getConsentStatus(ref, consumerId, userId);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// src/portability.ts
|
|
464
|
+
import { headers as headers10 } from "next/headers";
|
|
465
|
+
var portability = {
|
|
466
|
+
/** Provider: register this plugin's export resolver. */
|
|
467
|
+
async provideExport(resolver) {
|
|
468
|
+
const pluginId = (await headers10()).get("x-sovereign-plugin-id");
|
|
469
|
+
if (!pluginId) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"sdk.portability.provideExport() must be called from a plugin route context (x-sovereign-plugin-id header missing)."
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
requireHost().portability.provideExport(pluginId, resolver);
|
|
475
|
+
},
|
|
476
|
+
/** Provider: register this plugin's import handler. */
|
|
477
|
+
async provideImport(handler) {
|
|
478
|
+
const pluginId = (await headers10()).get("x-sovereign-plugin-id");
|
|
479
|
+
if (!pluginId) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
"sdk.portability.provideImport() must be called from a plugin route context (x-sovereign-plugin-id header missing)."
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
requireHost().portability.provideImport(pluginId, handler);
|
|
485
|
+
},
|
|
486
|
+
/** Provider: register this plugin's deletion handler (RFC 0033). */
|
|
487
|
+
async provideDelete(handler) {
|
|
488
|
+
const pluginId = (await headers10()).get("x-sovereign-plugin-id");
|
|
489
|
+
if (!pluginId) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
"sdk.portability.provideDelete() must be called from a plugin route context (x-sovereign-plugin-id header missing)."
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
requireHost().portability.provideDelete(pluginId, handler);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/secrets.ts
|
|
499
|
+
import { headers as headers11 } from "next/headers";
|
|
500
|
+
var DEFAULT_TENANT_ID6 = "default";
|
|
501
|
+
function parseCapabilities3(raw) {
|
|
502
|
+
if (!raw) return [];
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(raw);
|
|
505
|
+
return Array.isArray(parsed) ? parsed.filter((cap) => typeof cap === "string") : [];
|
|
506
|
+
} catch {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function secretContext() {
|
|
511
|
+
const h = await headers11();
|
|
512
|
+
const pluginId = h.get("x-sovereign-plugin-id");
|
|
513
|
+
if (!pluginId) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
"sdk.secrets requires a plugin route context (x-sovereign-plugin-id header missing)."
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
tenantId: DEFAULT_TENANT_ID6,
|
|
520
|
+
pluginId,
|
|
521
|
+
userId: h.get("x-sovereign-user-id"),
|
|
522
|
+
capabilities: parseCapabilities3(h.get("x-sovereign-user-capabilities"))
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function requireUserForUserScope2(scope, userId) {
|
|
526
|
+
if (scope === "user" && !userId) throw new NotAuthenticatedError();
|
|
527
|
+
}
|
|
528
|
+
var secrets = {
|
|
529
|
+
async create(input) {
|
|
530
|
+
const context = await secretContext();
|
|
531
|
+
requireUserForUserScope2(input.scope, context.userId);
|
|
532
|
+
return requireHost().secrets.create(input, context);
|
|
533
|
+
},
|
|
534
|
+
async get(id) {
|
|
535
|
+
const context = await secretContext();
|
|
536
|
+
return requireHost().secrets.get(id, context);
|
|
537
|
+
},
|
|
538
|
+
async list(scope) {
|
|
539
|
+
const context = await secretContext();
|
|
540
|
+
if (scope) requireUserForUserScope2(scope, context.userId);
|
|
541
|
+
return requireHost().secrets.list(scope, context);
|
|
542
|
+
},
|
|
543
|
+
async update(id, value) {
|
|
544
|
+
const context = await secretContext();
|
|
545
|
+
return requireHost().secrets.update(id, value, context);
|
|
546
|
+
},
|
|
547
|
+
async delete(id) {
|
|
548
|
+
const context = await secretContext();
|
|
549
|
+
return requireHost().secrets.delete(id, context);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// src/storage.ts
|
|
554
|
+
import { headers as headers12 } from "next/headers";
|
|
555
|
+
var DEFAULT_TENANT_ID7 = "default";
|
|
556
|
+
async function storageContext() {
|
|
557
|
+
const h = await headers12();
|
|
558
|
+
const pluginId = h.get("x-sovereign-plugin-id");
|
|
559
|
+
if (!pluginId) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
"sdk.storage requires a plugin route context (x-sovereign-plugin-id header missing)."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
tenantId: DEFAULT_TENANT_ID7,
|
|
566
|
+
pluginId,
|
|
567
|
+
userId: h.get("x-sovereign-user-id")
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
var storage = {
|
|
571
|
+
async put(input) {
|
|
572
|
+
const context = await storageContext();
|
|
573
|
+
return requireHost().storage.put(input, context);
|
|
574
|
+
},
|
|
575
|
+
async get(key) {
|
|
576
|
+
const context = await storageContext();
|
|
577
|
+
return requireHost().storage.get(key, context);
|
|
578
|
+
},
|
|
579
|
+
async delete(key) {
|
|
580
|
+
const context = await storageContext();
|
|
581
|
+
return requireHost().storage.delete(key, context);
|
|
582
|
+
},
|
|
583
|
+
async list(prefix) {
|
|
584
|
+
const context = await storageContext();
|
|
585
|
+
return requireHost().storage.list(prefix, context);
|
|
586
|
+
},
|
|
587
|
+
/** Create a short-lived, read-only download URL for an object (default 5 minutes, max 1 hour). */
|
|
588
|
+
async getSignedUrl(key, options) {
|
|
589
|
+
const context = await storageContext();
|
|
590
|
+
return requireHost().storage.getSignedUrl(key, options, context);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/unimplemented.ts
|
|
595
|
+
var events = {
|
|
596
|
+
publish(_event, _payload) {
|
|
597
|
+
throw new NotImplementedError("sdk.events.publish() is not implemented in Sovereign v1.");
|
|
598
|
+
},
|
|
599
|
+
subscribe(_event, _handler) {
|
|
600
|
+
throw new NotImplementedError("sdk.events.subscribe() is not implemented in Sovereign v1.");
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var billing = {
|
|
604
|
+
/**
|
|
605
|
+
* Returns the current user's active entitlement for the calling plugin, or
|
|
606
|
+
* `null` when no entitlement exists. Use this to branch UI by tier.
|
|
607
|
+
*/
|
|
608
|
+
getEntitlement(_headers) {
|
|
609
|
+
throw new NotImplementedError("sdk.billing.getEntitlement() is not yet implemented.");
|
|
610
|
+
},
|
|
611
|
+
/**
|
|
612
|
+
* Asserts that the current user has an active entitlement for the calling
|
|
613
|
+
* plugin. Throws `EntitlementRequiredError` when absent. Use for in-plugin
|
|
614
|
+
* feature gating (route-level access is already gated by the middleware).
|
|
615
|
+
*/
|
|
616
|
+
requireEntitlement(_headers) {
|
|
617
|
+
throw new EntitlementRequiredError(
|
|
618
|
+
"sdk.billing.requireEntitlement() is not yet implemented \u2014 no entitlement check performed."
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// src/index.ts
|
|
624
|
+
var sdk = {
|
|
625
|
+
// Stable (v1.0.0).
|
|
626
|
+
auth: auth_exports,
|
|
627
|
+
db: db_exports,
|
|
628
|
+
mailer: mailer_exports,
|
|
629
|
+
platform: platform_exports,
|
|
630
|
+
// Experimental / reserved — shape may change; some throw until implemented.
|
|
631
|
+
data,
|
|
632
|
+
storage,
|
|
633
|
+
directory,
|
|
634
|
+
notifications,
|
|
635
|
+
secrets,
|
|
636
|
+
connections,
|
|
637
|
+
e2ee,
|
|
638
|
+
events,
|
|
639
|
+
activity,
|
|
640
|
+
portability,
|
|
641
|
+
plugins,
|
|
642
|
+
env,
|
|
643
|
+
billing
|
|
644
|
+
};
|
|
645
|
+
export {
|
|
646
|
+
ConsentRequiredError,
|
|
647
|
+
EntitlementRequiredError,
|
|
648
|
+
NotAuthenticatedError,
|
|
649
|
+
NotImplementedError,
|
|
650
|
+
provideHost,
|
|
651
|
+
sdk
|
|
652
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sovereignfs/sdk",
|
|
3
|
+
"version": "1.25.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Sovereign SDK — the plugin↔platform contract (types and interfaces).",
|
|
6
|
+
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./e2ee-crypto": {
|
|
16
|
+
"types": "./dist/e2ee-crypto.d.ts",
|
|
17
|
+
"default": "./dist/e2ee-crypto.js"
|
|
18
|
+
},
|
|
19
|
+
"./e2ee-device": {
|
|
20
|
+
"types": "./dist/e2ee-device.d.ts",
|
|
21
|
+
"default": "./dist/e2ee-device.js"
|
|
22
|
+
},
|
|
23
|
+
"./e2ee-object": {
|
|
24
|
+
"types": "./dist/e2ee-object.d.ts",
|
|
25
|
+
"default": "./dist/e2ee-object.js"
|
|
26
|
+
},
|
|
27
|
+
"./e2ee-state": {
|
|
28
|
+
"types": "./dist/e2ee-state.d.ts",
|
|
29
|
+
"default": "./dist/e2ee-state.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"src"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"next": ">=15.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"next": "^15.0.0",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"@sovereignfs/tsconfig": "0.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
48
|
+
},
|
|
49
|
+
"types": "./dist/index.d.ts"
|
|
50
|
+
}
|