@selvajs/platform 0.14.3-beta.0 → 0.15.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +1 -1
  2. package/dist/computeServer/index.d.ts +1 -1
  3. package/dist/computeServer/index.d.ts.map +1 -1
  4. package/dist/computeServer/interface.d.ts +29 -1
  5. package/dist/computeServer/interface.d.ts.map +1 -1
  6. package/dist/computeServer/types.d.ts +12 -1
  7. package/dist/computeServer/types.d.ts.map +1 -1
  8. package/dist/computeServer/types.js.map +1 -1
  9. package/dist/data/index.d.ts +2 -1
  10. package/dist/data/index.d.ts.map +1 -1
  11. package/dist/data/index.js +1 -1
  12. package/dist/data/index.js.map +1 -1
  13. package/dist/data/interface.d.ts +69 -8
  14. package/dist/data/interface.d.ts.map +1 -1
  15. package/dist/data/interface.js +7 -1
  16. package/dist/data/interface.js.map +1 -1
  17. package/dist/definitions/interface.d.ts +16 -1
  18. package/dist/definitions/interface.d.ts.map +1 -1
  19. package/dist/definitions/schemas.d.ts +1 -0
  20. package/dist/definitions/schemas.d.ts.map +1 -1
  21. package/dist/definitions/schemas.js +8 -0
  22. package/dist/definitions/schemas.js.map +1 -1
  23. package/dist/definitions/types.d.ts +24 -1
  24. package/dist/definitions/types.d.ts.map +1 -1
  25. package/dist/events/index.d.ts +1 -1
  26. package/dist/events/index.d.ts.map +1 -1
  27. package/dist/events/index.js +1 -1
  28. package/dist/events/index.js.map +1 -1
  29. package/dist/events/interface.d.ts +30 -0
  30. package/dist/events/interface.d.ts.map +1 -1
  31. package/dist/events/interface.js +7 -0
  32. package/dist/events/interface.js.map +1 -1
  33. package/dist/index.d.ts +9 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +5 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/logging/interface.d.ts +82 -0
  38. package/dist/logging/interface.d.ts.map +1 -0
  39. package/dist/logging/interface.js +33 -0
  40. package/dist/logging/interface.js.map +1 -0
  41. package/dist/metrics/interface.d.ts +6 -0
  42. package/dist/metrics/interface.d.ts.map +1 -1
  43. package/dist/metrics/interface.js.map +1 -1
  44. package/dist/organizations/__tests__/slug-schema.test.d.ts +2 -0
  45. package/dist/organizations/__tests__/slug-schema.test.d.ts.map +1 -0
  46. package/dist/organizations/__tests__/slug-schema.test.js +38 -0
  47. package/dist/organizations/__tests__/slug-schema.test.js.map +1 -0
  48. package/dist/organizations/index.d.ts +1 -1
  49. package/dist/organizations/index.d.ts.map +1 -1
  50. package/dist/organizations/index.js +1 -1
  51. package/dist/organizations/index.js.map +1 -1
  52. package/dist/organizations/schemas.d.ts +16 -2
  53. package/dist/organizations/schemas.d.ts.map +1 -1
  54. package/dist/organizations/schemas.js +32 -2
  55. package/dist/organizations/schemas.js.map +1 -1
  56. package/dist/solveCache/interface.d.ts +86 -0
  57. package/dist/solveCache/interface.d.ts.map +1 -0
  58. package/dist/solveCache/interface.js +12 -0
  59. package/dist/solveCache/interface.js.map +1 -0
  60. package/dist/testing/suites/definitionStoreSuite.d.ts.map +1 -1
  61. package/dist/testing/suites/definitionStoreSuite.js +57 -0
  62. package/dist/testing/suites/definitionStoreSuite.js.map +1 -1
  63. package/dist/testing/suites/eventSinkSuite.js +1 -0
  64. package/dist/testing/suites/eventSinkSuite.js.map +1 -1
  65. package/dist/testing/suites/platformPermissionStoreSuite.d.ts +9 -0
  66. package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +1 -1
  67. package/dist/testing/suites/platformPermissionStoreSuite.js +34 -1
  68. package/dist/testing/suites/platformPermissionStoreSuite.js.map +1 -1
  69. package/dist/testing/suites/solveMetricSinkSuite.d.ts +5 -0
  70. package/dist/testing/suites/solveMetricSinkSuite.d.ts.map +1 -1
  71. package/dist/testing/suites/solveMetricSinkSuite.js +17 -8
  72. package/dist/testing/suites/solveMetricSinkSuite.js.map +1 -1
  73. package/dist/testing/suites/storageProviderSuite.d.ts.map +1 -1
  74. package/dist/testing/suites/storageProviderSuite.js +23 -2
  75. package/dist/testing/suites/storageProviderSuite.js.map +1 -1
  76. package/package.json +9 -24
  77. package/src/access/index.ts +0 -23
  78. package/src/access/rules.ts +0 -276
  79. package/src/auth/index.ts +0 -8
  80. package/src/auth/interface.ts +0 -279
  81. package/src/auth/types.ts +0 -32
  82. package/src/bindings/index.ts +0 -2
  83. package/src/bindings/interface.ts +0 -61
  84. package/src/computeServer/index.ts +0 -28
  85. package/src/computeServer/interface.ts +0 -73
  86. package/src/computeServer/secrets.ts +0 -102
  87. package/src/computeServer/types.ts +0 -74
  88. package/src/computeServer/utils.ts +0 -112
  89. package/src/config.ts +0 -99
  90. package/src/context.ts +0 -80
  91. package/src/data/index.ts +0 -1
  92. package/src/data/interface.ts +0 -70
  93. package/src/definitions/index.ts +0 -26
  94. package/src/definitions/interface.ts +0 -87
  95. package/src/definitions/paths.ts +0 -31
  96. package/src/definitions/schemas.ts +0 -64
  97. package/src/definitions/types.ts +0 -111
  98. package/src/errors/interface.ts +0 -45
  99. package/src/errors.ts +0 -16
  100. package/src/events/audit.ts +0 -67
  101. package/src/events/index.ts +0 -9
  102. package/src/events/interface.ts +0 -102
  103. package/src/index.ts +0 -247
  104. package/src/invites/index.ts +0 -2
  105. package/src/invites/interface.ts +0 -38
  106. package/src/invites/types.ts +0 -39
  107. package/src/metrics/interface.ts +0 -86
  108. package/src/organizations/index.ts +0 -23
  109. package/src/organizations/interface.ts +0 -68
  110. package/src/organizations/paths.ts +0 -45
  111. package/src/organizations/schemas.ts +0 -61
  112. package/src/organizations/types.ts +0 -41
  113. package/src/pagination.ts +0 -45
  114. package/src/permissions/index.ts +0 -3
  115. package/src/permissions/interface.ts +0 -60
  116. package/src/permissions/types.ts +0 -21
  117. package/src/platformProjects/index.ts +0 -3
  118. package/src/platformProjects/interface.ts +0 -27
  119. package/src/platformProjects/schemas.ts +0 -13
  120. package/src/platformProjects/types.ts +0 -22
  121. package/src/projects/index.ts +0 -4
  122. package/src/projects/interface.ts +0 -58
  123. package/src/projects/schemas.ts +0 -28
  124. package/src/projects/types.ts +0 -36
  125. package/src/shareLinks/index.ts +0 -5
  126. package/src/shareLinks/interface.ts +0 -35
  127. package/src/shareLinks/schemas.ts +0 -21
  128. package/src/shareLinks/types.ts +0 -34
  129. package/src/storage/assetClasses.ts +0 -134
  130. package/src/storage/cacheBust.ts +0 -26
  131. package/src/storage/image.ts +0 -82
  132. package/src/storage/index.ts +0 -12
  133. package/src/storage/interface.ts +0 -27
  134. package/src/testing/index.ts +0 -47
  135. package/src/testing/suites/authProviderSuite.ts +0 -207
  136. package/src/testing/suites/computeServerStoreSuite.ts +0 -195
  137. package/src/testing/suites/definitionStoreSuite.ts +0 -630
  138. package/src/testing/suites/emailLinkAuthSuite.ts +0 -83
  139. package/src/testing/suites/eventSinkSuite.ts +0 -379
  140. package/src/testing/suites/helpers.ts +0 -87
  141. package/src/testing/suites/inviteStoreSuite.ts +0 -143
  142. package/src/testing/suites/orgStoreSuite.ts +0 -429
  143. package/src/testing/suites/platformPermissionStoreSuite.ts +0 -196
  144. package/src/testing/suites/platformProjectGrantStoreSuite.ts +0 -166
  145. package/src/testing/suites/projectStoreSuite.ts +0 -381
  146. package/src/testing/suites/shareLinkStoreSuite.ts +0 -203
  147. package/src/testing/suites/solveMetricSinkSuite.ts +0 -135
  148. package/src/testing/suites/storageProviderSuite.ts +0 -196
  149. package/src/testing/suites/userProfileStoreSuite.ts +0 -134
  150. package/src/userProfile/index.ts +0 -3
  151. package/src/userProfile/interface.ts +0 -48
  152. package/src/userProfile/types.ts +0 -24
  153. package/src/utils/audit.ts +0 -46
  154. package/src/utils/index.ts +0 -2
@@ -1,68 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { ListOptions, Page } from '../pagination.js';
3
- import type { Organization, OrgMember } from './types.js';
4
- import type { OrgRole, OrgPermission } from './schemas.js';
5
-
6
- /**
7
- * Organizations + org members. Tenancy boundary for everything else; reads and
8
- * writes scope by `ctx.actingOrgId`.
9
- */
10
- export interface IOrgStore {
11
- // Organizations
12
- listOrgs(ctx: RequestContext, opts?: ListOptions): Promise<Page<Organization>>;
13
- getOrg(ctx: RequestContext, id: string): Promise<Organization | null>;
14
- getOrgBySlug(ctx: RequestContext, slug: string): Promise<Organization | null>;
15
- createOrg(ctx: RequestContext, org: Organization): Promise<void>;
16
- updateOrg(
17
- ctx: RequestContext,
18
- id: string,
19
- patch: Partial<Pick<Organization, 'name' | 'slug' | 'assets'>>
20
- ): Promise<void>;
21
- /**
22
- * Soft-delete the org. Cascades soft-delete to org members, projects in the
23
- * org, and project members. Definitions and share links cascade through the
24
- * project deletion. Invites and the org compute-server override are NOT
25
- * cascaded — clean those up explicitly if needed.
26
- */
27
- deleteOrg(ctx: RequestContext, id: string): Promise<void>;
28
-
29
- // Org members
30
- listOrgMembers(ctx: RequestContext, orgId: string, opts?: ListOptions): Promise<Page<OrgMember>>;
31
- getOrgMember(ctx: RequestContext, orgId: string, userId: string): Promise<OrgMember | null>;
32
- /**
33
- * Find ONE org membership for the user. Used by the request-bootstrap
34
- * path to resolve `actingOrgId` without N+1-ing over `listOrgs`.
35
- *
36
- * Returns `null` when the user has no live membership. When the user has
37
- * multiple memberships (multi-tenant deployments), the choice between
38
- * them is adapter-defined — single-tenant deployments have exactly one,
39
- * and multi-tenant deployments will eventually use a URL prefix
40
- * (`/o/{slug}/...`) to pick explicitly. Soft-deleted memberships are
41
- * excluded.
42
- *
43
- * Both `org` and `member` are scoped by `ctx` (RLS-aware on Supabase).
44
- */
45
- findUserMembership(
46
- ctx: RequestContext,
47
- userId: string
48
- ): Promise<{ org: Organization; member: OrgMember } | null>;
49
- addOrgMember(ctx: RequestContext, member: OrgMember): Promise<void>;
50
- updateOrgMemberRole(
51
- ctx: RequestContext,
52
- orgId: string,
53
- userId: string,
54
- role: OrgRole
55
- ): Promise<void>;
56
- /**
57
- * Replace `OrgPermission` set without changing role. Use this for
58
- * permission edits — round-tripping through remove + add cascades the
59
- * org-removal soft-delete into project memberships.
60
- */
61
- updateOrgMemberPermissions(
62
- ctx: RequestContext,
63
- orgId: string,
64
- userId: string,
65
- permissions: readonly OrgPermission[]
66
- ): Promise<void>;
67
- removeOrgMember(ctx: RequestContext, orgId: string, userId: string): Promise<void>;
68
- }
@@ -1,45 +0,0 @@
1
- import type { OrgAssetKind } from './schemas.js';
2
-
3
- /**
4
- * Path-segment safety check. Blocks traversal, separators, NUL, and empty
5
- * segments. Allowed alphabet `[A-Za-z0-9._-]+` covers GUIDs and sanitized
6
- * refs. Called by every helper so a malicious `id` can't escape the
7
- * `orgs/` prefix. Mirrors `definitionPaths`' guard.
8
- */
9
- function assertSafeKey(value: string, label: string): void {
10
- if (!value || !/^[A-Za-z0-9._-]+$/.test(value) || value === '.' || value === '..') {
11
- throw new Error(`Unsafe ${label}: ${JSON.stringify(value)}`);
12
- }
13
- }
14
-
15
- /**
16
- * Storage keys for org-scoped assets. Every branding asset is stored as `.webp`
17
- * (uploads are rasterized through the shared transcoder), so the path per kind
18
- * is fixed — a re-upload overwrites in place via `IStorageProvider.put`'s
19
- * upsert. The filename IS the kind, so the layout is generic: a new branding
20
- * kind needs no new helper.
21
- *
22
- * Branding lives under `orgs/{id}/branding/` because it is a *public* asset
23
- * class — anyone, incl. logged-out viewers, may read a company logo. The
24
- * `branding/` segment keeps it distinguishable from future org-private blobs
25
- * under `orgs/{id}/private/` (e.g. pricing sheets), which are members-only.
26
- * The serving route and `getPublicUrl` decide public-vs-private by classifying
27
- * these prefixes — see `classifyAssetPath` / `ASSET_CLASSES`.
28
- */
29
- export const orgPaths = {
30
- asset: (id: string, kind: OrgAssetKind) => {
31
- assertSafeKey(id, 'orgId');
32
- assertSafeKey(kind, 'assetKind');
33
- return `orgs/${id}/branding/${kind}.webp`;
34
- },
35
- /** Members-only blob under the org's private tier. `name` includes the extension. */
36
- privateAsset: (id: string, name: string) => {
37
- assertSafeKey(id, 'orgId');
38
- assertSafeKey(name, 'assetName');
39
- return `orgs/${id}/private/${name}`;
40
- },
41
- prefix: (id: string) => {
42
- assertSafeKey(id, 'orgId');
43
- return `orgs/${id}/`;
44
- }
45
- } as const;
@@ -1,61 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /** Lowercase alphanumeric with internal hyphens, 3–63 chars. */
4
- export const SlugSchema = z
5
- .string()
6
- .min(3, 'Slug must be at least 3 characters')
7
- .max(63, 'Slug must be at most 63 characters')
8
- .regex(
9
- /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,
10
- 'Slug must be lowercase alphanumeric with hyphens (no leading/trailing/consecutive hyphens)'
11
- );
12
-
13
- export const OrgRoleSchema = z.enum(['owner', 'admin', 'member']);
14
- export type OrgRole = z.infer<typeof OrgRoleSchema>;
15
-
16
- /**
17
- * Kinds of org-scoped branding asset. Adding a new kind is a single entry here
18
- * — the storage path, upload route, store, and UI are all generic over it.
19
- */
20
- export const OrgAssetKindSchema = z.enum(['logo', 'favicon']);
21
- export type OrgAssetKind = z.infer<typeof OrgAssetKindSchema>;
22
-
23
- export const ALL_ORG_ASSET_KINDS: readonly OrgAssetKind[] = OrgAssetKindSchema.options;
24
-
25
- /** Org-scope permissions. A user may hold different sets in different orgs. */
26
- export const OrgPermissionSchema = z.enum([
27
- 'manage_org_members',
28
- 'manage_org_compute',
29
- 'manage_definitions',
30
- 'manage_projects'
31
- ]);
32
- export type OrgPermission = z.infer<typeof OrgPermissionSchema>;
33
-
34
- export const ALL_ORG_PERMISSIONS: readonly OrgPermission[] = OrgPermissionSchema.options;
35
-
36
- /** Governance perms — never grantable to `member`. */
37
- export const OWNER_ADMIN_ONLY_PERMISSIONS: readonly OrgPermission[] = [
38
- 'manage_org_members',
39
- 'manage_org_compute'
40
- ];
41
-
42
- export const MEMBER_ASSIGNABLE_PERMISSIONS: readonly OrgPermission[] = ALL_ORG_PERMISSIONS.filter(
43
- (p) => !OWNER_ADMIN_ONLY_PERMISSIONS.includes(p)
44
- );
45
-
46
- /** Applied when adding a member without an explicit permissions list. */
47
- export const DEFAULT_ORG_PERMISSIONS: Record<OrgRole, readonly OrgPermission[]> = {
48
- owner: [...ALL_ORG_PERMISSIONS],
49
- admin: [...ALL_ORG_PERMISSIONS],
50
- member: []
51
- };
52
-
53
- export const CreateOrgSchema = z.object({
54
- name: z.string().min(1, 'Organization name is required').max(128),
55
- slug: SlugSchema
56
- });
57
-
58
- export const UpdateOrgSchema = CreateOrgSchema.partial();
59
-
60
- export type CreateOrgInput = z.infer<typeof CreateOrgSchema>;
61
- export type UpdateOrgInput = z.infer<typeof UpdateOrgSchema>;
@@ -1,41 +0,0 @@
1
- import type { OrgRole, OrgPermission, OrgAssetKind } from './schemas.js';
2
-
3
- /**
4
- * Public URLs of an org's branding assets, keyed by kind. Each value is what
5
- * `IStorageProvider.getPublicUrl` returned for the stored blob; a kind is
6
- * absent when unset. One small map so adding a new asset kind never touches
7
- * the `Organization` shape, the migration, or the row mappers again.
8
- */
9
- export type OrgAssets = Partial<Record<OrgAssetKind, string>>;
10
-
11
- export interface Organization {
12
- id: string;
13
- name: string;
14
- /** URL-safe unique identifier, e.g. "acme-corp". */
15
- slug: string;
16
- /** Transferable; distinct from `createdBy`. */
17
- ownerId: string;
18
- /** Immutable even on ownership transfer. */
19
- createdBy: string;
20
- updatedBy: string;
21
- /** Branding assets (logo, favicon, …) by kind. Blobs live under `orgPaths.asset()`. */
22
- assets?: OrgAssets;
23
- createdAt: string;
24
- updatedAt: string;
25
- deletedAt?: string | null;
26
- }
27
-
28
- export interface OrgMember {
29
- orgId: string;
30
- userId: string;
31
- role: OrgRole;
32
- /**
33
- * `role` is the user-facing summary; `permissions` is what adapters check.
34
- * Seeded from `DEFAULT_ORG_PERMISSIONS[role]` when not specified.
35
- */
36
- permissions: OrgPermission[];
37
- joinedAt: string;
38
- updatedAt: string;
39
- updatedBy: string;
40
- deletedAt?: string | null;
41
- }
package/src/pagination.ts DELETED
@@ -1,45 +0,0 @@
1
- import type { DefinitionStatus } from './definitions/types.js';
2
-
3
- /**
4
- * Cursor-paginated list options. Adapters MUST provide sensible defaults
5
- * (limit=50, orderBy=createdAt desc) and clamp limit to a safe upper bound.
6
- *
7
- * Cursors are opaque — adapters may use row ids, offsets, or keyset
8
- * encodings. Callers must not inspect them.
9
- */
10
- export interface ListOptions {
11
- limit?: number;
12
- cursor?: string;
13
- orderBy?: 'createdAt' | 'updatedAt' | 'name';
14
- /** Default: 'desc'. */
15
- orderDir?: 'asc' | 'desc';
16
- }
17
-
18
- /** Definition-specific filtering on top of `ListOptions`. */
19
- export interface DefinitionListOptions extends Omit<ListOptions, 'orderBy'> {
20
- orderBy?: 'createdAt' | 'updatedAt' | 'name' | 'solveCount';
21
- /**
22
- * Include records with `status='pending'`. Default false — consumers don't
23
- * see half-written state unless they explicitly opt in.
24
- */
25
- includePending?: boolean;
26
- /**
27
- * Include records with `status='archived'`. Default false — archived
28
- * definitions are hidden from normal views; opt in for archive browsers.
29
- */
30
- includeArchived?: boolean;
31
- /**
32
- * Filter to specific editorial statuses. Omit for the default view (all
33
- * non-pending, non-archived). Pass `['published']` for the runner home.
34
- * When set, `includePending` / `includeArchived` are ignored.
35
- */
36
- statuses?: DefinitionStatus[];
37
- }
38
-
39
- export interface Page<T> {
40
- items: T[];
41
- nextCursor?: string;
42
- }
43
-
44
- export const DEFAULT_PAGE_LIMIT = 50;
45
- export const MAX_PAGE_LIMIT = 200;
@@ -1,3 +0,0 @@
1
- export type { PlatformPermission } from './types.js';
2
- export { PlatformPermissionSchema, ALL_PLATFORM_PERMISSIONS } from './types.js';
3
- export type { IPlatformPermissionStore } from './interface.js';
@@ -1,60 +0,0 @@
1
- import type { UserManagementResult } from '../auth/types.js';
2
- import type { RequestContext } from '../context.js';
3
- import type { PlatformPermission } from './types.js';
4
-
5
- /**
6
- * Per-user platform permissions. Lives at the data layer (not on
7
- * `IAuthProvider`) so external IdPs don't need to model Selva-specific
8
- * authorization.
9
- *
10
- * **The sole-`instance_admin` invariant is enforced here.** Any operation
11
- * that would leave zero users holding `instance_admin` MUST return
12
- * `'last_admin'`. Auth providers consult `countInstanceAdminsExcluding`
13
- * before any destructive op so the invariant holds across edits, deletes,
14
- * and disables.
15
- *
16
- * Reads are scoped to "self or admin"; writes restricted to `instance_admin`.
17
- * Pass `SYSTEM_CONTEXT` for trusted server flows (bootstrap, hooks).
18
- */
19
- export interface IPlatformPermissionStore {
20
- /**
21
- * Read permissions for a single user. Empty array for users with no
22
- * admin grants (the common case). Non-admin callers may only read self.
23
- */
24
- getFor(ctx: RequestContext, userId: string): Promise<PlatformPermission[]>;
25
-
26
- /** Batch-read for the admin user list. Admin-only. */
27
- getForBatch(
28
- ctx: RequestContext,
29
- userIds: readonly string[]
30
- ): Promise<Map<string, PlatformPermission[]>>;
31
-
32
- /**
33
- * Replace the user's permissions. Idempotent. Returns:
34
- * - `ok` — succeeded
35
- * - `not_found` — target user doesn't exist
36
- * - `last_admin` — refused; would leave zero `instance_admin` users
37
- *
38
- * `not_supported` is intentionally absent — every deployment must provide
39
- * a permission store.
40
- */
41
- set(
42
- ctx: RequestContext,
43
- userId: string,
44
- permissions: readonly PlatformPermission[]
45
- ): Promise<UserManagementResult>;
46
-
47
- /**
48
- * True if at least one *enabled* user holds `instance_admin`. Used by
49
- * first-run detection and the destructive-op invariant check. Adapters
50
- * MUST exclude disabled users (over-count is safer than under-count).
51
- */
52
- hasInstanceAdmin(ctx: RequestContext): Promise<boolean>;
53
-
54
- /**
55
- * Count *other* users holding `instance_admin`. `deleteUser` / `disableUser`
56
- * call this BEFORE the destructive op — `count === 0` ⇒ `'last_admin'`.
57
- * MUST exclude disabled users.
58
- */
59
- countInstanceAdminsExcluding(ctx: RequestContext, excludeUserId: string): Promise<number>;
60
- }
@@ -1,21 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * Platform-scope permissions. Reserved for Selva-staff and instance operators;
5
- * regular users hold an empty array.
6
- *
7
- * - `instance_admin` — superuser; implies every other permission.
8
- * - `manage_compute` — configure the instance-wide Rhino.Compute pool.
9
- * - `manage_instance_users` — disable/enable any user on the instance.
10
- * - `manage_updates` — run system updates.
11
- */
12
- export const PlatformPermissionSchema = z.enum([
13
- 'instance_admin',
14
- 'manage_compute',
15
- 'manage_instance_users',
16
- 'manage_updates'
17
- ]);
18
- export type PlatformPermission = z.infer<typeof PlatformPermissionSchema>;
19
-
20
- export const ALL_PLATFORM_PERMISSIONS: readonly PlatformPermission[] =
21
- PlatformPermissionSchema.options;
@@ -1,3 +0,0 @@
1
- export type { PlatformProjectGrant, PlatformProjectGranteeType } from './types.js';
2
- export type { IPlatformProjectGrantStore } from './interface.js';
3
- export { PlatformProjectGrantSchema, PlatformProjectGranteeTypeSchema } from './schemas.js';
@@ -1,27 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { PlatformProjectGrant } from './types.js';
3
-
4
- /**
5
- * Grants for `platform`-visibility projects. Enables `instance_admin` to
6
- * grant orgs or individual users view-only or view+solve access without
7
- * creating project membership rows.
8
- *
9
- * All deletes are hard-deletes — grants have no downstream references and
10
- * revocation is immediate.
11
- */
12
- export interface IPlatformProjectGrantStore {
13
- listByProject(ctx: RequestContext, projectId: string): Promise<PlatformProjectGrant[]>;
14
- create(ctx: RequestContext, grant: PlatformProjectGrant): Promise<void>;
15
- delete(ctx: RequestContext, id: string): Promise<void>;
16
- /**
17
- * Cascade hook: drop every grant for a project. Called by `deleteProject`.
18
- * No-op when none exist.
19
- */
20
- deleteByProject(ctx: RequestContext, projectId: string): Promise<void>;
21
- /**
22
- * Cascade hook: drop every org grant whose `granteeId === orgId`. Called by
23
- * `deleteOrg`. User grants are identity-scoped and remain. No-op when none
24
- * exist.
25
- */
26
- deleteByGranteeOrg(ctx: RequestContext, orgId: string): Promise<void>;
27
- }
@@ -1,13 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const PlatformProjectGranteeTypeSchema = z.enum(['org', 'user']);
4
-
5
- export const PlatformProjectGrantSchema = z.object({
6
- id: z.string(),
7
- projectId: z.string(),
8
- granteeType: PlatformProjectGranteeTypeSchema,
9
- granteeId: z.string(),
10
- canSolve: z.boolean(),
11
- createdBy: z.string(),
12
- createdAt: z.string()
13
- });
@@ -1,22 +0,0 @@
1
- export type PlatformProjectGranteeType = 'org' | 'user';
2
-
3
- /**
4
- * Grants view-only or view+solve access to a `platform`-visibility project
5
- * for an org or an individual user, without creating project membership rows.
6
- *
7
- * - `granteeType: 'org'` → applies to all members of that org
8
- * - `granteeType: 'user'` → applies to that specific user regardless of org
9
- * - `canSolve: false` → schema fetch / view only; submit-solve is blocked
10
- * - `canSolve: true` → view + solve
11
- *
12
- * Grants are hard-deleted on revocation — no soft-delete.
13
- */
14
- export interface PlatformProjectGrant {
15
- id: string;
16
- projectId: string;
17
- granteeType: PlatformProjectGranteeType;
18
- granteeId: string;
19
- canSolve: boolean;
20
- createdBy: string;
21
- createdAt: string;
22
- }
@@ -1,4 +0,0 @@
1
- export type { Project, ProjectMember } from './types.js';
2
- export type { IProjectStore } from './interface.js';
3
- export type { ProjectVisibility, ProjectRole } from './schemas.js';
4
- export { ProjectVisibilitySchema, ProjectRoleSchema, validateProjectFlags } from './schemas.js';
@@ -1,58 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { ListOptions, Page } from '../pagination.js';
3
- import type { Project, ProjectMember } from './types.js';
4
- import type { ProjectRole } from './schemas.js';
5
-
6
- /**
7
- * Projects + project members. Soft-delete cascades to members and definitions;
8
- * see also the cross-store cascade hook on `IPlatformProjectGrantStore`.
9
- */
10
- export interface IProjectStore {
11
- // Projects
12
- listProjects(ctx: RequestContext, orgId: string, opts?: ListOptions): Promise<Page<Project>>;
13
- getProject(ctx: RequestContext, id: string): Promise<Project | null>;
14
- getProjectBySlug(ctx: RequestContext, orgId: string, slug: string): Promise<Project | null>;
15
- createProject(ctx: RequestContext, project: Project): Promise<void>;
16
- updateProject(
17
- ctx: RequestContext,
18
- id: string,
19
- patch: Partial<
20
- Pick<Project, 'name' | 'slug' | 'description' | 'visibility' | 'autoJoinOnUpload'>
21
- >
22
- ): Promise<void>;
23
- /**
24
- * Soft-delete the project. Cascades to project members and definitions.
25
- * Definition versions and share links cascade through the definition delete.
26
- */
27
- deleteProject(ctx: RequestContext, id: string): Promise<void>;
28
- /**
29
- * Reactivate a previously soft-deleted project identified by its org + slug.
30
- * Clears `deleted_at` and reactivates the owner's `project_members` row.
31
- * Returns the live project, or `null` if no tombstone with that slug exists.
32
- *
33
- * Use this when `createProject` fails with a duplicate-key error on a slug
34
- * that was soft-deleted — the unconditional unique constraint blocks creation
35
- * even though `getProjectBySlug` returns null for tombstones.
36
- */
37
- reactivateProject(ctx: RequestContext, orgId: string, slug: string): Promise<Project | null>;
38
-
39
- // Project members
40
- listProjectMembers(
41
- ctx: RequestContext,
42
- projectId: string,
43
- opts?: ListOptions
44
- ): Promise<Page<ProjectMember>>;
45
- getProjectMember(
46
- ctx: RequestContext,
47
- projectId: string,
48
- userId: string
49
- ): Promise<ProjectMember | null>;
50
- addProjectMember(ctx: RequestContext, member: ProjectMember): Promise<void>;
51
- updateProjectMemberRole(
52
- ctx: RequestContext,
53
- projectId: string,
54
- userId: string,
55
- role: ProjectRole
56
- ): Promise<void>;
57
- removeProjectMember(ctx: RequestContext, projectId: string, userId: string): Promise<void>;
58
- }
@@ -1,28 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const ProjectVisibilitySchema = z.enum(['public', 'org', 'private', 'platform']);
4
- export type ProjectVisibility = z.infer<typeof ProjectVisibilitySchema>;
5
-
6
- export const ProjectRoleSchema = z.enum(['owner', 'editor', 'viewer']);
7
- export type ProjectRole = z.infer<typeof ProjectRoleSchema>;
8
-
9
- /** Cross-field check for PATCH flows. Call on the merged post-patch shape. */
10
- export function validateProjectFlags(merged: {
11
- visibility: ProjectVisibility;
12
- autoJoinOnUpload?: boolean;
13
- }): { path: string; message: string }[] {
14
- const issues: { path: string; message: string }[] = [];
15
- if (merged.autoJoinOnUpload && merged.visibility !== 'public') {
16
- issues.push({
17
- path: 'autoJoinOnUpload',
18
- message: 'autoJoinOnUpload requires visibility=public'
19
- });
20
- }
21
- if (merged.visibility === 'platform' && merged.autoJoinOnUpload) {
22
- issues.push({
23
- path: 'autoJoinOnUpload',
24
- message: 'autoJoinOnUpload is not allowed on platform projects'
25
- });
26
- }
27
- return issues;
28
- }
@@ -1,36 +0,0 @@
1
- import type { ProjectRole, ProjectVisibility } from './schemas.js';
2
-
3
- /**
4
- * Visibility:
5
- * - `public` — any authenticated user can view/solve (cross-org gated by `ALLOW_CROSS_ORG_PUBLIC`)
6
- * - `org` — any member of the parent organization
7
- * - `private` — only listed `ProjectMember`s
8
- * - `platform` — instance-admin-managed; access via `IPlatformProjectGrantStore`
9
- */
10
- export interface Project {
11
- id: string;
12
- orgId: string;
13
- name: string;
14
- slug: string;
15
- description?: string;
16
- visibility: ProjectVisibility;
17
- /** Transferable; distinct from `createdBy`. */
18
- ownerId: string;
19
- createdBy: string;
20
- updatedBy: string;
21
- /** Commons mode — only valid when `visibility='public'`. */
22
- autoJoinOnUpload: boolean;
23
- createdAt: string;
24
- updatedAt: string;
25
- deletedAt?: string | null;
26
- }
27
-
28
- export interface ProjectMember {
29
- projectId: string;
30
- userId: string;
31
- role: ProjectRole;
32
- joinedAt: string;
33
- updatedAt: string;
34
- updatedBy: string;
35
- deletedAt?: string | null;
36
- }
@@ -1,5 +0,0 @@
1
- export type { ShareLink } from './types.js';
2
- export type { IShareLinkStore } from './interface.js';
3
- export { DEFAULT_SHARE_LINK_MAX_SOLVES } from './types.js';
4
- export type { CreateShareLinkInput } from './schemas.js';
5
- export { CreateShareLinkInputSchema } from './schemas.js';
@@ -1,35 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { ListOptions, Page } from '../pagination.js';
3
- import type { ShareLink } from './types.js';
4
-
5
- /**
6
- * Per-definition tokens granting unauthenticated access to one
7
- * (definitionId, channel). The store sees only the HMAC hash; the raw token
8
- * is generated and shown to the minter at the route layer.
9
- *
10
- * Reads filter `revokedAt IS NULL` defensively.
11
- */
12
- export interface IShareLinkStore {
13
- /** Insert a new link. Caller has already hashed the raw token. */
14
- create(ctx: RequestContext, link: ShareLink): Promise<void>;
15
- /** Newest first by `createdAt`. Excludes revoked links. */
16
- listByDefinition(
17
- ctx: RequestContext,
18
- definitionId: string,
19
- opts?: ListOptions
20
- ): Promise<Page<ShareLink>>;
21
- getById(ctx: RequestContext, id: string): Promise<ShareLink | null>;
22
- /**
23
- * Lookup by HMAC hash. Returns null when the link doesn't exist OR is
24
- * revoked OR its parent definition is soft-deleted.
25
- */
26
- getByTokenHash(ctx: RequestContext, tokenHash: string): Promise<ShareLink | null>;
27
- /** Soft-delete (set `revokedAt`). Idempotent. */
28
- revoke(ctx: RequestContext, id: string): Promise<void>;
29
- /**
30
- * Atomic check-and-increment. Returns the new `solveCount`, or null when
31
- * the cap was reached. MUST be a single statement — read-then-write races
32
- * under load. `null` maxSolves means uncapped.
33
- */
34
- tryIncrementSolveCount(ctx: RequestContext, id: string): Promise<number | null>;
35
- }
@@ -1,21 +0,0 @@
1
- import { z } from 'zod';
2
- import { DefinitionChannelSchema } from '../definitions/schemas.js';
3
-
4
- /**
5
- * HTTP body for `POST /api/definitions/[guid]/share-links`. The route returns
6
- * the raw token exactly once.
7
- */
8
- export const CreateShareLinkInputSchema = z.object({
9
- channel: DefinitionChannelSchema.default('live'),
10
- name: z.string().max(128).optional(),
11
- allowSolve: z.boolean().default(true),
12
- /**
13
- * Null explicitly removes the cap. Omitted = apply default — null is the
14
- * minter's deliberate "uncap" choice.
15
- */
16
- maxSolves: z.number().int().min(1).nullable().optional(),
17
- /** Optional ISO timestamp. Resolution compares to `now()`. */
18
- expiresAt: z.iso.datetime().nullable().optional()
19
- });
20
-
21
- export type CreateShareLinkInput = z.infer<typeof CreateShareLinkInputSchema>;
@@ -1,34 +0,0 @@
1
- import type { DefinitionChannel } from '../definitions/types.js';
2
-
3
- /**
4
- * Per-definition token granting access to one (definitionId, channel)
5
- * without an account. The raw token is HMAC-hashed at rest; `tokenHash` is
6
- * the stored value. The plaintext token is shown to the minter exactly once.
7
- */
8
- export interface ShareLink {
9
- id: string;
10
- definitionId: string;
11
- channel: DefinitionChannel;
12
- /** HMAC of the raw token. Lookup hashes the supplied token, then matches. */
13
- tokenHash: string;
14
- /** Optional UX label, e.g. "Demo for Acme". */
15
- name?: string;
16
- createdBy: string;
17
- createdAt: string;
18
- /** Null = never expires. */
19
- expiresAt?: string | null;
20
- /** Non-null = revoked. Resolution checks IS NULL. */
21
- revokedAt?: string | null;
22
- /** false = view/schema only; true = solve allowed. */
23
- allowSolve: boolean;
24
- /** Per-link cap. Null = unlimited. */
25
- maxSolves?: number | null;
26
- solveCount: number;
27
- }
28
-
29
- /**
30
- * Default cap applied when the minter doesn't specify one. Tokens are
31
- * leak-prone by design (visible in iframes), so a default bounds the
32
- * worst-case denial-of-wallet damage.
33
- */
34
- export const DEFAULT_SHARE_LINK_MAX_SOLVES = 1000;