@sovereignfs/sdk 1.25.0 → 2.0.1

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.
Files changed (55) hide show
  1. package/LICENSE +373 -661
  2. package/README.md +103 -0
  3. package/dist/browser.d.ts +2 -0
  4. package/dist/browser.js +4 -0
  5. package/dist/chunk-NYL4LLXF.js +65 -0
  6. package/dist/index.d.ts +57 -529
  7. package/dist/index.js +1283 -572
  8. package/dist/next.d.ts +37 -0
  9. package/dist/next.js +68 -0
  10. package/dist/session-DJl2XYMc.d.ts +44 -0
  11. package/dist/sovereign-CazGKEVc.d.ts +517 -0
  12. package/dist/testing.d.ts +181 -0
  13. package/dist/testing.js +560 -0
  14. package/package.json +53 -27
  15. package/dist/chunk-MLKGABMK.js +0 -9
  16. package/dist/chunk-NQLGPAL6.js +0 -62
  17. package/dist/e2ee-crypto.d.ts +0 -84
  18. package/dist/e2ee-crypto.js +0 -162
  19. package/dist/e2ee-device.d.ts +0 -21
  20. package/dist/e2ee-device.js +0 -13
  21. package/dist/e2ee-object.d.ts +0 -50
  22. package/dist/e2ee-object.js +0 -75
  23. package/dist/e2ee-state-U-GEJ7oL.d.ts +0 -411
  24. package/dist/e2ee-state.d.ts +0 -1
  25. package/dist/e2ee-state.js +0 -36
  26. package/src/__tests__/e2ee-crypto.test.ts +0 -156
  27. package/src/__tests__/e2ee-object.test.ts +0 -90
  28. package/src/__tests__/e2ee-state.test.ts +0 -83
  29. package/src/__tests__/sdk.test.ts +0 -427
  30. package/src/__tests__/sessions.test.ts +0 -63
  31. package/src/activity.ts +0 -19
  32. package/src/auth.ts +0 -134
  33. package/src/connections.ts +0 -106
  34. package/src/data.ts +0 -88
  35. package/src/db.ts +0 -34
  36. package/src/directory.ts +0 -31
  37. package/src/e2ee-crypto.ts +0 -260
  38. package/src/e2ee-device.ts +0 -75
  39. package/src/e2ee-object.ts +0 -118
  40. package/src/e2ee-state.ts +0 -72
  41. package/src/e2ee.ts +0 -65
  42. package/src/env.ts +0 -37
  43. package/src/errors.ts +0 -46
  44. package/src/host.ts +0 -251
  45. package/src/index.ts +0 -160
  46. package/src/mailer.ts +0 -7
  47. package/src/notifications.ts +0 -36
  48. package/src/platform.ts +0 -10
  49. package/src/plugins.ts +0 -146
  50. package/src/portability.ts +0 -176
  51. package/src/secrets.ts +0 -68
  52. package/src/sessions.ts +0 -36
  53. package/src/storage.ts +0 -49
  54. package/src/types.ts +0 -418
  55. package/src/unimplemented.ts +0 -48
package/src/storage.ts DELETED
@@ -1,49 +0,0 @@
1
- import { headers } from 'next/headers';
2
- import { requireHost } from './host';
3
- import type { StorageContext, StorageObject, StoragePutInput } from './types';
4
-
5
- const DEFAULT_TENANT_ID = 'default';
6
-
7
- async function storageContext(): Promise<StorageContext> {
8
- const h = await headers();
9
- const pluginId = h.get('x-sovereign-plugin-id');
10
- if (!pluginId) {
11
- throw new Error(
12
- 'sdk.storage requires a plugin route context (x-sovereign-plugin-id header missing).',
13
- );
14
- }
15
- return {
16
- tenantId: DEFAULT_TENANT_ID,
17
- pluginId,
18
- userId: h.get('x-sovereign-user-id'),
19
- };
20
- }
21
-
22
- /** Plugin-scoped file storage (RFC 0044). Requires the `storage:readWrite` manifest permission. */
23
- export const storage = {
24
- async put(input: StoragePutInput): Promise<StorageObject> {
25
- const context = await storageContext();
26
- return requireHost().storage.put(input, context);
27
- },
28
-
29
- async get(key: string): Promise<(StorageObject & { body: ReadableStream }) | null> {
30
- const context = await storageContext();
31
- return requireHost().storage.get(key, context);
32
- },
33
-
34
- async delete(key: string): Promise<void> {
35
- const context = await storageContext();
36
- return requireHost().storage.delete(key, context);
37
- },
38
-
39
- async list(prefix?: string): Promise<StorageObject[]> {
40
- const context = await storageContext();
41
- return requireHost().storage.list(prefix, context);
42
- },
43
-
44
- /** Create a short-lived, read-only download URL for an object (default 5 minutes, max 1 hour). */
45
- async getSignedUrl(key: string, options?: { expiresInSeconds?: number }): Promise<string> {
46
- const context = await storageContext();
47
- return requireHost().storage.getSignedUrl(key, options, context);
48
- },
49
- };
package/src/types.ts DELETED
@@ -1,418 +0,0 @@
1
- export interface SessionUser {
2
- id: string;
3
- tenantId: string;
4
- email: string;
5
- name: string | null;
6
- image: string | null;
7
- role: string;
8
- /** Effective capability strings derived from the role preset (RFC 0021). */
9
- capabilities: readonly string[];
10
- }
11
-
12
- export interface Session {
13
- user: SessionUser;
14
- /** Session expiry as a Unix timestamp (seconds). */
15
- expiresAt: number;
16
- }
17
-
18
- /** An authenticated session for the current user (SRS ACC-05). */
19
- export interface ActiveSession {
20
- /** Opaque session token — pass to `sdk.auth.revokeSession` to end it. */
21
- token: string;
22
- /** Whether this is the session making the current request. */
23
- current: boolean;
24
- /** Raw User-Agent string of the device that created the session, if known. */
25
- userAgent: string | null;
26
- ipAddress: string | null;
27
- /** Creation, last-active, and expiry as ISO 8601 strings. */
28
- createdAt: string;
29
- updatedAt: string;
30
- expiresAt: string;
31
- }
32
-
33
- export interface ChangePasswordInput {
34
- currentPassword: string;
35
- newPassword: string;
36
- }
37
-
38
- /** Display-safe user shape returned by `sdk.directory` (RFC 0041). */
39
- export interface DirectoryUser {
40
- id: string;
41
- email: string;
42
- name: string | null;
43
- image: string | null;
44
- }
45
-
46
- export interface SearchUsersInput {
47
- query: string;
48
- limit?: number;
49
- }
50
-
51
- export interface ResolveUsersInput {
52
- ids: string[];
53
- }
54
-
55
- export interface MailOptions {
56
- to: string | string[];
57
- subject: string;
58
- html?: string;
59
- text?: string;
60
- from?: string;
61
- }
62
-
63
- export interface PlatformConfig {
64
- /**
65
- * Name of the default tenant row — set in Console › Settings › Instance name.
66
- * In v1 (single-tenant) this is the platform-wide workspace name.
67
- * Multi-tenant deployments (post-v1) will surface per-tenant names separately.
68
- */
69
- tenantName: string;
70
- inviteOnly: boolean;
71
- version: string;
72
- /** Instance name; falls back to tenantName when no white-label name is configured. */
73
- instanceName: string;
74
- /** Validated 6-digit hex colour overriding --sv-color-accent, or undefined when unset. */
75
- instancePrimaryColor?: string;
76
- /**
77
- * Stable UUID that uniquely identifies this Sovereign installation.
78
- * Generated once at bootstrap and never changes. Use this to identify the
79
- * instance in federated contexts, license checks, or analytics.
80
- */
81
- instanceId: string;
82
- }
83
-
84
- /**
85
- * An activity event a plugin records via `sdk.activity.log()` (RFC 0005 —
86
- * reserved surface, not yet implemented). The plugin supplies only the event
87
- * shape; the runtime injects the actor, tenant, and emitting plugin and forces
88
- * the event to be user-scoped, so a plugin cannot forge actor identity.
89
- */
90
- export interface ActivityLogEntry {
91
- /** Dotted action verb, e.g. `"list.created"`. Namespaced by the runtime per plugin. */
92
- action: string;
93
- /** The user this event is about, if any — drives the user's personal-feed visibility. */
94
- subjectUserId?: string;
95
- /** Generic target kind, e.g. `"list"`. */
96
- targetType?: string;
97
- /** Generic target id, e.g. the list id. */
98
- targetId?: string;
99
- /** Human-readable one-line summary. */
100
- summary?: string;
101
- /** Structured detail (before/after, etc.). Avoid PII beyond what is necessary. */
102
- metadata?: Record<string, unknown>;
103
- }
104
-
105
- /**
106
- * The Drizzle client returned by `sdk.db.getClient()` — the live platform
107
- * Drizzle instance. Kept opaque (`unknown`) at the contract level so the
108
- * published SDK takes no dependency on a specific dialect's Drizzle types;
109
- * plugins type their own queries through their schema.
110
- */
111
- export type DrizzleClient = unknown;
112
-
113
- /**
114
- * Input to `sdk.notifications.send()` (RFC 0015).
115
- * The runtime auto-populates `source`, `sourceType`, and `tenantId` from
116
- * request context — plugins never set those.
117
- */
118
- export interface SendNotificationInput {
119
- /** User ID of the intended recipient. */
120
- recipientUserId: string;
121
- /** Short notification headline. */
122
- title: string;
123
- /** Optional longer body text. */
124
- body?: string;
125
- /** In-app route to navigate to when the notification is clicked. */
126
- url?: string;
127
- /**
128
- * Category tag — drives mute preferences.
129
- * Built-in categories: `'info'` (default), `'announcement'`, `'security'`.
130
- * Custom plugin categories are allowed; `'security'` cannot be muted.
131
- */
132
- category?: string;
133
- /**
134
- * Optional URL to an image shown in the OS push notification (the Push
135
- * API's `icon` option — not an `@sovereignfs/ui` `<Icon>` name; nothing
136
- * in-app reads this field today). Defaults to the sending plugin's own
137
- * `/plugin-icons/<pluginId>.svg` when unset — only set this to override
138
- * with a notification-specific image instead of the plugin's own icon.
139
- */
140
- icon?: string;
141
- }
142
-
143
- /** Plugin-scoped file storage object metadata (RFC 0044). */
144
- export interface StorageObject {
145
- id: string;
146
- pluginId: string;
147
- ownerUserId: string | null;
148
- key: string;
149
- contentType: string;
150
- size: number;
151
- checksum: string;
152
- /** The `metadata` passed to `put()`, round-tripped back on `get()`/`list()`. */
153
- metadata: Record<string, unknown> | null;
154
- createdAt: number;
155
- updatedAt: number;
156
- }
157
-
158
- export interface StoragePutInput {
159
- key: string;
160
- body: Blob | ArrayBuffer | Uint8Array;
161
- contentType: string;
162
- /** Owner of the object. Omit for a plugin-scoped (not per-user) object. */
163
- ownerUserId?: string;
164
- metadata?: Record<string, unknown> | null;
165
- }
166
-
167
- export interface StorageContext {
168
- tenantId: string;
169
- pluginId: string;
170
- userId: string | null;
171
- }
172
-
173
- /**
174
- * Client-side encryption profile shapes (RFC 0060, epic task 8.9). Persisted
175
- * via `sdk.e2ee`; the browser-only crypto that produces/consumes the
176
- * ciphertext these methods store lives in `e2ee-crypto`/`e2ee-device`/
177
- * `e2ee-object`/`e2ee-state`. Distinct from server-side field crypto
178
- * (`sdk.crypto.encryptField()`, RFC 0008): the runtime can decrypt a
179
- * server-side field; it can never decrypt a client-side encrypted object.
180
- *
181
- * The Client Master Key (CMK) itself never exists server-side, plaintext or
182
- * otherwise — only wrapped copies (`E2eeRecoveryWrapper`/
183
- * `E2eeDeviceEnrollment`) and non-sensitive KDF/algorithm metadata do.
184
- */
185
- export type E2eeProfileStatus = 'active' | 'disabled';
186
-
187
- /** Whether the current user has set up client-side encryption at all. */
188
- export interface E2eeProfile {
189
- id: string;
190
- userId: string;
191
- status: E2eeProfileStatus;
192
- /** e.g. `'AES-GCM-256'` — the algorithm used to encrypt objects under the CMK. */
193
- cmkAlgorithm: string;
194
- createdAt: number;
195
- updatedAt: number;
196
- }
197
-
198
- /** The CMK wrapped by a key derived from the user's recovery secret. One per user. */
199
- export interface E2eeRecoveryWrapper {
200
- id: string;
201
- userId: string;
202
- wrappedCmk: string;
203
- kdfAlgorithm: string;
204
- /** Opaque JSON-encoded KDF parameters (iterations, memory cost, etc.). */
205
- kdfParams: string;
206
- kdfSalt: string;
207
- algorithmVersion: string;
208
- createdAt: number;
209
- updatedAt: number;
210
- }
211
-
212
- /** The CMK wrapped by one enrolled device's own key. Many per user. */
213
- export interface E2eeDeviceEnrollment {
214
- id: string;
215
- userId: string;
216
- deviceId: string;
217
- deviceLabel: string | null;
218
- wrappedCmk: string;
219
- algorithmVersion: string;
220
- createdAt: number;
221
- lastUsedAt: number | null;
222
- /** `null` while the device is still enrolled. */
223
- revokedAt: number | null;
224
- }
225
-
226
- /**
227
- * Normalized state a plugin checks before touching client-side encrypted
228
- * data — the SDK never hides the recovery model (RFC 0060 "SDK
229
- * responsibilities"). `unsupported` means the browser lacks required
230
- * WebCrypto primitives.
231
- */
232
- export type E2eeState = 'not-set-up' | 'locked' | 'unlocked' | 'unsupported';
233
-
234
- /** No plugin scoping — one profile per user, regardless of caller. */
235
- export interface E2eeContext {
236
- tenantId: string;
237
- userId: string;
238
- }
239
-
240
- export interface CreateE2eeProfileInput {
241
- cmkAlgorithm: string;
242
- }
243
-
244
- export interface SetE2eeRecoveryWrapperInput {
245
- wrappedCmk: string;
246
- kdfAlgorithm: string;
247
- kdfParams: string;
248
- kdfSalt: string;
249
- algorithmVersion: string;
250
- }
251
-
252
- export interface EnrollE2eeDeviceInput {
253
- deviceId: string;
254
- deviceLabel: string | null;
255
- wrappedCmk: string;
256
- algorithmVersion: string;
257
- }
258
-
259
- /** Runtime-created secret scope for the experimental plugin vault (RFC 0043). */
260
- export type SecretScope = 'user' | 'plugin' | 'instance';
261
-
262
- /** Metadata returned for a stored secret. Plaintext values are never included. */
263
- export interface SecretRef {
264
- id: string;
265
- scope: SecretScope;
266
- label: string;
267
- metadata: Record<string, unknown> | null;
268
- createdAt: number;
269
- updatedAt: number;
270
- lastUsedAt: number | null;
271
- }
272
-
273
- export interface CreateSecretInput {
274
- scope: SecretScope;
275
- label: string;
276
- value: string;
277
- metadata?: Record<string, unknown> | null;
278
- }
279
-
280
- export interface UpdateSecretInput {
281
- id: string;
282
- value: string;
283
- }
284
-
285
- export interface SecretContext {
286
- tenantId: string;
287
- pluginId: string;
288
- userId: string | null;
289
- capabilities: readonly string[];
290
- }
291
-
292
- /** Runtime-created external provider connection scope (RFC 0049). */
293
- export type ConnectionScope = 'user' | 'plugin' | 'instance';
294
-
295
- /** Metadata status for a plugin-owned external provider connection. */
296
- export type ConnectionStatus = 'connected' | 'needs_reauth' | 'paused' | 'disconnected' | 'error';
297
-
298
- /** Metadata returned for an external connection. Secret values are never included. */
299
- export interface ConnectionRef {
300
- id: string;
301
- scope: ConnectionScope;
302
- provider: string;
303
- label: string;
304
- status: ConnectionStatus;
305
- secretRef: string | null;
306
- metadata: Record<string, unknown> | null;
307
- lastCheckedAt: number | null;
308
- lastUsedAt: number | null;
309
- lastError: SanitizedConnectionError | null;
310
- createdAt: number;
311
- updatedAt: number;
312
- disconnectedAt: number | null;
313
- }
314
-
315
- export interface CreateConnectionInput {
316
- scope: ConnectionScope;
317
- provider: string;
318
- label: string;
319
- secretRef?: string | null;
320
- metadata?: Record<string, unknown> | null;
321
- }
322
-
323
- export interface ConnectionListFilter {
324
- provider?: string;
325
- scope?: ConnectionScope;
326
- includeDisconnected?: boolean;
327
- }
328
-
329
- export interface UpdateConnectionInput {
330
- label?: string;
331
- status?: ConnectionStatus;
332
- metadata?: Record<string, unknown> | null;
333
- secretRef?: string | null;
334
- lastCheckedAt?: number | null;
335
- }
336
-
337
- export interface SanitizedConnectionError {
338
- code?: string;
339
- message: string;
340
- retryable?: boolean;
341
- status?: number;
342
- }
343
-
344
- export interface MarkConnectionErrorInput {
345
- error: SanitizedConnectionError;
346
- status?: Extract<ConnectionStatus, 'error' | 'needs_reauth'>;
347
- }
348
-
349
- export interface OAuthStateInput {
350
- provider: string;
351
- callbackPath: string;
352
- nonce?: string;
353
- metadata?: Record<string, unknown> | null;
354
- expiresInSeconds?: number;
355
- }
356
-
357
- export interface ConnectionOAuthState {
358
- pluginId: string;
359
- provider: string;
360
- userId: string;
361
- callbackPath: string;
362
- nonce: string;
363
- metadata: Record<string, unknown> | null;
364
- expiresAt: number;
365
- }
366
-
367
- /** Source summary for effective external provider configuration (Task 3.27). */
368
- export type ProviderConfigSource = 'env' | 'console' | 'mixed' | 'missing';
369
-
370
- /** Server-side effective external provider config for the calling plugin. */
371
- export interface ProviderConfig {
372
- provider: string;
373
- label: string;
374
- configured: boolean;
375
- source: ProviderConfigSource;
376
- publicValues: Record<string, string>;
377
- secretValues: Record<string, string>;
378
- callbackUrl: string | null;
379
- scopes: readonly string[];
380
- missingRequired: readonly string[];
381
- }
382
-
383
- export interface ConnectionContext {
384
- tenantId: string;
385
- pluginId: string;
386
- userId: string | null;
387
- capabilities: readonly string[];
388
- }
389
-
390
- /**
391
- * Context passed to a plugin's schedule handler (RFC 0046, Phase 1 subset).
392
- * There is no originating HTTP request — the platform's in-process scheduler
393
- * invokes handlers directly — so the runtime supplies synthetic `headers`
394
- * pre-stamped with the calling plugin's identity. Pass them to SDK methods
395
- * that attribute by request headers (e.g.
396
- * `sdk.notifications.send(input, ctx.headers)`).
397
- */
398
- export interface ScheduleContext {
399
- /** The invoking plugin's manifest id (e.g. `fs.sovereign.tasks`). */
400
- pluginId: string;
401
- /** The schedule's manifest-declared id (e.g. `due-reminders`). */
402
- scheduleId: string;
403
- /** Synthetic headers carrying `x-sovereign-plugin-id` for SDK attribution. */
404
- headers: Headers;
405
- }
406
-
407
- /**
408
- * A plugin's schedule handler — the **default export** of a manifest-declared
409
- * `schedules[].entry` module. Invoked server-side every `intervalMinutes`
410
- * while the plugin is installed and enabled.
411
- *
412
- * Handlers must be idempotent: the interval is a floor, not an exact cadence,
413
- * and restarts or multiple replicas can invoke a handler again sooner than
414
- * the interval — claim work with conditional updates before acting on it.
415
- * Thrown errors are caught and logged by the platform; there are no retries
416
- * in Phase 1.
417
- */
418
- export type ScheduleHandler = (ctx: ScheduleContext) => Promise<void>;
@@ -1,48 +0,0 @@
1
- import { EntitlementRequiredError, NotImplementedError } from './errors';
2
-
3
- /**
4
- * Surfaces declared in the SDK contract but reserved for post-v1. They throw
5
- * NotImplementedError so plugins fail loudly rather than silently misbehaving.
6
- */
7
-
8
- export const events = {
9
- publish(_event: string, _payload: unknown): Promise<void> {
10
- throw new NotImplementedError('sdk.events.publish() is not implemented in Sovereign v1.');
11
- },
12
- subscribe(_event: string, _handler: (payload: unknown) => void): void {
13
- throw new NotImplementedError('sdk.events.subscribe() is not implemented in Sovereign v1.');
14
- },
15
- };
16
-
17
- /**
18
- * Billing and entitlement surface (RFC 0003). Plugin authors use these helpers
19
- * to gate in-plugin features by entitlement tier. Route-level access is gated
20
- * automatically by the runtime middleware — calling `requireEntitlement()` is
21
- * only needed for feature flags within an already-accessible route.
22
- *
23
- * Both methods throw `NotImplementedError` until Task 0.8.01's runtime
24
- * implementation lands in a future release.
25
- */
26
- export const billing = {
27
- /**
28
- * Returns the current user's active entitlement for the calling plugin, or
29
- * `null` when no entitlement exists. Use this to branch UI by tier.
30
- */
31
- getEntitlement(
32
- _headers: Headers,
33
- ): Promise<{ tier: string | null; expiresAt: number | null } | null> {
34
- throw new NotImplementedError('sdk.billing.getEntitlement() is not yet implemented.');
35
- },
36
- /**
37
- * Asserts that the current user has an active entitlement for the calling
38
- * plugin. Throws `EntitlementRequiredError` when absent. Use for in-plugin
39
- * feature gating (route-level access is already gated by the middleware).
40
- */
41
- requireEntitlement(
42
- _headers: Headers,
43
- ): Promise<{ tier: string | null; expiresAt: number | null }> {
44
- throw new EntitlementRequiredError(
45
- 'sdk.billing.requireEntitlement() is not yet implemented — no entitlement check performed.',
46
- );
47
- },
48
- };