@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,112 +0,0 @@
1
- import {
2
- isOrgServer,
3
- isPlatformServer,
4
- type ComputeConfig,
5
- type ComputeServerConfig
6
- } from './types.js';
7
-
8
- /**
9
- * Servers an org can see in pickers and use for solves. Architecture spec §4.8.
10
- *
11
- * = platform servers shared with this org (or `'all'`, or the global default)
12
- * ∪ org-private servers owned by this org
13
- *
14
- * The global default is always included — it's the floor of the system.
15
- */
16
- export function serversVisibleTo(
17
- config: ComputeConfig,
18
- orgId: string | null | undefined
19
- ): ComputeServerConfig[] {
20
- return config.servers.filter((s) => {
21
- if (isPlatformServer(s)) {
22
- if (s.id === config.defaultServerId) return true;
23
- if (s.sharedWith === 'all') return true;
24
- if (orgId && s.sharedWith.includes(orgId)) return true;
25
- return false;
26
- }
27
- // Org-private — visible only to its owner org.
28
- return !!orgId && s.ownerOrgId === orgId;
29
- });
30
- }
31
-
32
- /**
33
- * Resolve the default server id for an org. Per-org override → global default.
34
- * Returns `undefined` if neither is set or the chosen id is not visible.
35
- */
36
- export function defaultServerIdFor(
37
- config: ComputeConfig,
38
- orgId: string | null | undefined
39
- ): string | undefined {
40
- const visible = serversVisibleTo(config, orgId);
41
- const visibleIds = new Set(visible.map((s) => s.id));
42
-
43
- if (orgId) {
44
- const orgChoice = config.orgDefaults?.[orgId];
45
- if (orgChoice && visibleIds.has(orgChoice)) return orgChoice;
46
- }
47
- if (config.defaultServerId && visibleIds.has(config.defaultServerId)) {
48
- return config.defaultServerId;
49
- }
50
- return undefined;
51
- }
52
-
53
- /**
54
- * Resolve a definition's compute server. Architecture spec §4.8 resolution order:
55
- * 1. Definition pin (`computeServerId`) if visible to the project's org.
56
- * 2. `orgDefaults[orgId]` if set.
57
- * 3. Global `defaultServerId`.
58
- *
59
- * Pure — throws if no usable server exists.
60
- */
61
- export interface ResolveOptions {
62
- /** Per-definition pin. Falls through silently if not visible. */
63
- definitionPin?: string | null;
64
- }
65
-
66
- export function resolveServerForOrg(
67
- config: ComputeConfig,
68
- orgId: string | null | undefined,
69
- opts: ResolveOptions = {}
70
- ): ComputeServerConfig {
71
- const visible = serversVisibleTo(config, orgId);
72
- if (visible.length === 0) {
73
- throw new Error('No compute servers are configured or visible for this org.');
74
- }
75
-
76
- if (opts.definitionPin) {
77
- const pinned = visible.find((s) => s.id === opts.definitionPin);
78
- if (pinned) return pinned;
79
- // Fall through — admin may have un-shared the server since the pin
80
- // was set.
81
- }
82
-
83
- const defaultId = defaultServerIdFor(config, orgId);
84
- if (defaultId) {
85
- const found = visible.find((s) => s.id === defaultId);
86
- if (found) return found;
87
- }
88
-
89
- // Last resort — return the first visible server. Mostly applies in tests
90
- // and during initial setup before a default is chosen.
91
- return visible[0];
92
- }
93
-
94
- /**
95
- * Lookup by id, regardless of scope or visibility. Useful in admin contexts
96
- * that need to display a server independently of the visibility filter.
97
- */
98
- export function findServerById(config: ComputeConfig, id: string): ComputeServerConfig | undefined {
99
- return config.servers.find((s) => s.id === id);
100
- }
101
-
102
- // ============================================================================
103
- // Filters
104
- // ============================================================================
105
-
106
- export function platformServers(config: ComputeConfig) {
107
- return config.servers.filter(isPlatformServer);
108
- }
109
-
110
- export function orgServersFor(config: ComputeConfig, orgId: string) {
111
- return config.servers.filter((s) => isOrgServer(s) && s.ownerOrgId === orgId);
112
- }
package/src/config.ts DELETED
@@ -1,99 +0,0 @@
1
- import type { IAuthProvider } from './auth/interface.js';
2
- import type { IDataProvider } from './data/interface.js';
3
- import type { IStorageProvider } from './storage/interface.js';
4
- import type { IEventSink } from './events/interface.js';
5
- import type { ISolveMetricSink } from './metrics/interface.js';
6
- import type { IBindingResolver } from './bindings/interface.js';
7
-
8
- /**
9
- * - `single`: one org per deployment. Setup creates it; `ctx.actingOrgId`
10
- * resolves to that org for every authenticated user.
11
- * - `multi`: orgs are first-class. Setup creates only a platform admin; orgs
12
- * are created by users. `ctx.actingOrgId` resolves per-request.
13
- */
14
- export type TenancyMode = 'single' | 'multi';
15
-
16
- /**
17
- * White-label branding. All fields optional; the selva app applies sensible
18
- * "Selva" defaults for anything omitted. Use this to rebrand the instance
19
- * (header name, footer copyright, landing-page copy, page titles) without
20
- * forking the UI.
21
- */
22
- export interface SelvaBranding {
23
- /** Product name shown in header, footer, page titles. Default: "Selva". */
24
- name?: string;
25
- /** Footer copyright owner. Default: same as `name`. */
26
- copyrightName?: string;
27
- /** Landing-page tagline under the product name. */
28
- tagline?: string;
29
- /** Meta description for SEO / social previews. */
30
- description?: string;
31
- }
32
-
33
- /** Opt-in platform features. All default false when absent. */
34
- export interface SelvaFlags {
35
- /** Projects with `visibility='public'` visible across orgs on the instance. */
36
- ALLOW_CROSS_ORG_PUBLIC?: boolean;
37
- /** Orgs may override the instance compute pool with their own server. */
38
- ALLOW_ORG_COMPUTE_OVERRIDE?: boolean;
39
- /** Authenticated users may create new orgs. */
40
- ALLOW_ORG_CREATION?: boolean;
41
- /**
42
- * Per-definition share links — anonymous external access via tokenized URLs.
43
- * When off, share-link admin routes reject mint/list/revoke and the
44
- * token-resolution path stops honouring any existing tokens.
45
- */
46
- ENABLE_SHARING?: boolean;
47
- /**
48
- * Platform projects — projects owned by instance admins and granted to orgs
49
- * or individual users without normal membership. When off, the
50
- * `/admin/projects` surface 404s, platform-visibility projects are hidden
51
- * from every list, and access rules treat them as inaccessible. Existing
52
- * data is preserved; flipping back on restores access.
53
- */
54
- ENABLE_PLATFORM_PROJECTS?: boolean;
55
- }
56
-
57
- export interface SelvaConfig {
58
- tenancy?: TenancyMode;
59
- flags?: SelvaFlags;
60
- branding?: SelvaBranding;
61
- auth: IAuthProvider;
62
- /**
63
- * Orgs, projects, members, definitions, compute config, user profiles,
64
- * and platform permissions. All database-layer concerns live here.
65
- */
66
- data: IDataProvider;
67
- /** Blob storage — .gh/.ghx files, archived versions, images. */
68
- storage: IStorageProvider;
69
- /** Optional. Defaults to `NoopEventSink`. */
70
- events?: IEventSink;
71
- /**
72
- * Optional. Records per-solve timing (wall time of the Rhino.Compute solve
73
- * call). Defaults to `NoopSolveMetricSink`, which discards every metric.
74
- * Supply a real sink to persist or aggregate solve durations.
75
- */
76
- solveMetrics?: ISolveMetricSink;
77
- /**
78
- * Optional. Resolves values for inputs marked as `source.kind === 'server'`
79
- * in the schema. Defaults to `NoopBindingResolver`, which returns nothing
80
- * — combined with the schema's `onMissing: 'fail'` default, that causes
81
- * any solve involving a server-resolved input to error loudly until a real
82
- * resolver is configured. Hosts that want server-resolved inputs must supply one.
83
- */
84
- bindingResolver?: IBindingResolver;
85
- }
86
-
87
- export type SelvaConfigFactory = (env: Record<string, string | undefined>) => SelvaConfig;
88
-
89
- /** Safe accessor — omitted flags resolve to false. */
90
- export function isFlagEnabled(config: SelvaConfig, flag: keyof SelvaFlags): boolean {
91
- return Boolean(config.flags?.[flag]);
92
- }
93
-
94
- /** For TypeScript inference / IDE autocomplete only, Vite-style. */
95
- export function defineConfig(
96
- config: SelvaConfig | SelvaConfigFactory
97
- ): SelvaConfig | SelvaConfigFactory {
98
- return config;
99
- }
package/src/context.ts DELETED
@@ -1,80 +0,0 @@
1
- import type { PlatformPermission } from './permissions/types.js';
2
- import { ALL_PLATFORM_PERMISSIONS } from './permissions/types.js';
3
- import type { OrgPermission } from './organizations/schemas.js';
4
- import { ALL_ORG_PERMISSIONS } from './organizations/schemas.js';
5
- import { ProviderError } from './errors.js';
6
-
7
- /**
8
- * Per-request identity + scope passed to every data provider call. Built
9
- * once per HTTP request from the authenticated session.
10
- *
11
- * `actingOrgId` is the org the user is currently acting as — NOT "an org the
12
- * user is a member of." Tenancy checks compare it to the resource's `orgId`.
13
- *
14
- * `instance_admin` implies every other permission, everywhere — encoded by
15
- * `hasPermission`. `system: true` is a trusted server-internal call;
16
- * adapters with RLS treat it as fully authorized.
17
- */
18
- export interface RequestContext {
19
- /** Empty string for system contexts. */
20
- userId: string;
21
- /** Undefined for instance-admin global reads and pre-org routes. */
22
- actingOrgId?: string;
23
- platformPermissions: PlatformPermission[];
24
- /** Empty when `actingOrgId` is undefined. */
25
- orgPermissions: OrgPermission[];
26
- /** Never derive from a user session. */
27
- system?: true;
28
- /**
29
- * Opaque adapter payload. The Supabase adapter passes the user JWT through
30
- * for RLS; local ignores it.
31
- */
32
- adapterContext?: unknown;
33
- }
34
-
35
- /**
36
- * For server-internal operations outside any HTTP request (bootstrap,
37
- * janitors, migrations). Treated as fully authorized.
38
- */
39
- export const SYSTEM_CONTEXT: RequestContext = {
40
- userId: '',
41
- platformPermissions: [...ALL_PLATFORM_PERMISSIONS],
42
- orgPermissions: [...ALL_ORG_PERMISSIONS],
43
- system: true
44
- };
45
-
46
- /**
47
- * Does NOT cross-check `ctx.actingOrgId` — the caller builds a context scoped
48
- * to the org they're acting on. An org permission against a ctx without
49
- * `actingOrgId` returns false unless `instance_admin` or `system`.
50
- */
51
- export function hasPermission(
52
- ctx: RequestContext,
53
- permission: PlatformPermission | OrgPermission
54
- ): boolean {
55
- if (ctx.system) return true;
56
- if (ctx.platformPermissions.includes('instance_admin')) return true;
57
- if (isPlatformPermission(permission)) {
58
- return ctx.platformPermissions.includes(permission);
59
- }
60
- return ctx.orgPermissions.includes(permission);
61
- }
62
-
63
- const PLATFORM_PERMISSION_VALUES = new Set<string>(ALL_PLATFORM_PERMISSIONS);
64
-
65
- function isPlatformPermission(p: string): p is PlatformPermission {
66
- return PLATFORM_PERMISSION_VALUES.has(p);
67
- }
68
-
69
- /**
70
- * Return the org the caller is acting as, or throw `ProviderError(403)` when
71
- * the context has none. Stores that scope every row to an acting org repeat
72
- * this guard; centralizing it keeps the failure a single, consistent 403
73
- * rather than a `null` that leaks into a query as `org_id = undefined`.
74
- */
75
- export function requireActingOrg(ctx: RequestContext): string {
76
- if (!ctx.actingOrgId) {
77
- throw new ProviderError('No acting organization in context', 403);
78
- }
79
- return ctx.actingOrgId;
80
- }
package/src/data/index.ts DELETED
@@ -1 +0,0 @@
1
- export type { IDataProvider } from './interface.js';
@@ -1,70 +0,0 @@
1
- /**
2
- * `IDataProvider` is the composition root that bundles every store an adapter
3
- * implements. Individual interfaces (`IOrgStore`, `IProjectStore`, …) live
4
- * next to their types in their respective subdirs — only the composition
5
- * lives here.
6
- *
7
- * Every store method takes a `RequestContext` first. **The query itself is
8
- * the security boundary** — adapters MUST scope reads/writes by `ctx`. An
9
- * unauthorized caller sees an empty page, `null`, or a `ProviderError`.
10
- *
11
- * Access-control predicates live in `@selvajs/platform/access` (pure rules).
12
- * Stores do storage; rules do rules.
13
- */
14
-
15
- import type { IOrgStore } from '../organizations/interface.js';
16
- import type { IProjectStore } from '../projects/interface.js';
17
- import type { IDefinitionStore } from '../definitions/interface.js';
18
- import type { IComputeServerStore } from '../computeServer/interface.js';
19
- import type { IShareLinkStore } from '../shareLinks/interface.js';
20
- import type { IPlatformProjectGrantStore } from '../platformProjects/interface.js';
21
- import type { IInviteStore } from '../invites/interface.js';
22
- import type { IUserProfileStore } from '../userProfile/interface.js';
23
- import type { IPlatformPermissionStore } from '../permissions/interface.js';
24
- import type { IAuditQuery } from '../events/audit.js';
25
- import type { RequestContext } from '../context.js';
26
-
27
- export interface IDataProvider {
28
- orgs: IOrgStore;
29
- projects: IProjectStore;
30
- definitions: IDefinitionStore;
31
- computeServer: IComputeServerStore;
32
- invites: IInviteStore;
33
- shareLinks: IShareLinkStore;
34
- userProfile: IUserProfileStore;
35
- permissions: IPlatformPermissionStore;
36
- platformProjectGrants: IPlatformProjectGrantStore;
37
- /**
38
- * Read-side for the persisted event log. Optional — providers whose event
39
- * sink is a noop (local-provider) leave this undefined and the
40
- * `/admin/audit` UI degrades to its "no backend wired" state.
41
- */
42
- auditQuery?: IAuditQuery;
43
-
44
- /**
45
- * Idempotently register a user in the data layer. Called by `hooks.server.ts`
46
- * after `verifyToken` succeeds, so the data layer always has a row for the
47
- * authenticated user regardless of which auth provider issued the ID.
48
- *
49
- * Adapters with a DB-side trigger that auto-seeds on auth signup (Supabase)
50
- * make this a no-op. Adapters that own a separate user-data table without a
51
- * trigger (local-provider) upsert here.
52
- *
53
- * MUST be safe to call concurrently and on every request. MUST NOT throw on
54
- * a duplicate userId.
55
- */
56
- ensureUser(ctx: RequestContext, userId: string): Promise<void>;
57
-
58
- /**
59
- * Cascade hook called after the auth provider deletes a user. Removes any
60
- * data-layer rows tied to that user (the user-data row, anything else
61
- * keyed by `userId` that should die with the identity).
62
- *
63
- * Adapters whose data-layer FKs cascade on delete (Supabase via
64
- * `on delete cascade` against `auth.users`) no-op. Adapters that own
65
- * separate tables (local-provider) clean up here.
66
- *
67
- * MUST tolerate a missing user (idempotent — caller may retry).
68
- */
69
- onUserDeleted(ctx: RequestContext, userId: string): Promise<void>;
70
- }
@@ -1,26 +0,0 @@
1
- export type {
2
- DefinitionFileExt,
3
- DefinitionRecord,
4
- DefinitionRecordPatch,
5
- DefinitionStatus,
6
- DefinitionVersion,
7
- DefinitionChannel
8
- } from './types.js';
9
- export type { IDefinitionStore } from './interface.js';
10
- export type { UISchema } from '@selvajs/schemas';
11
- export {
12
- GH_EXTENSIONS,
13
- COVER_IMAGE_EXTENSIONS,
14
- ALLOWED_UPLOAD_EXTENSIONS,
15
- COVER_IMAGE_CONTENT_TYPES
16
- } from './types.js';
17
- export { definitionPaths } from './paths.js';
18
- export type { UpdateMetadataInput } from './schemas.js';
19
- export {
20
- DefinitionChannelSchema,
21
- PublishVersionInputSchema,
22
- CreateDefinitionInputSchema,
23
- UpdateMetadataInputSchema,
24
- GuidSchema,
25
- UUID_REGEX
26
- } from './schemas.js';
@@ -1,87 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { ListOptions, DefinitionListOptions, Page } from '../pagination.js';
3
- import type { UISchema } from '@selvajs/schemas';
4
- import type { DefinitionRecord, DefinitionRecordPatch, DefinitionVersion } from './types.js';
5
-
6
- /**
7
- * Definition metadata + version store. Blob contents live in `IStorageProvider`;
8
- * this interface tracks the parent record plus immutable version rows and the
9
- * `live` / `draft` channel pointers.
10
- */
11
- export interface IDefinitionStore {
12
- // Definitions
13
- list(ctx: RequestContext, opts?: DefinitionListOptions): Promise<Page<DefinitionRecord>>;
14
- listByProject(
15
- ctx: RequestContext,
16
- projectId: string,
17
- opts?: DefinitionListOptions
18
- ): Promise<Page<DefinitionRecord>>;
19
- /**
20
- * List definitions whose parent project has `visibility === 'public'`.
21
- * Pass `orgId` to restrict to one org; omit for cross-org listing within
22
- * whatever tenant boundary the adapter already enforces.
23
- */
24
- listPublic(
25
- ctx: RequestContext,
26
- opts?: DefinitionListOptions & { orgId?: string }
27
- ): Promise<Page<DefinitionRecord>>;
28
- get(ctx: RequestContext, guid: string): Promise<DefinitionRecord | null>;
29
- create(ctx: RequestContext, record: DefinitionRecord): Promise<void>;
30
- update(ctx: RequestContext, guid: string, patch: DefinitionRecordPatch): Promise<void>;
31
- delete(ctx: RequestContext, guid: string): Promise<void>;
32
-
33
- /**
34
- * Cascade hook: soft-delete every live definition in a project. Called by
35
- * `IProjectStore.deleteProject` — a deleted project must never keep serving
36
- * its definitions (they surface in the library/public listings independent
37
- * of the project row). No-op when the project has none. Mirrors the
38
- * `deleteByProject` cascade hooks on `IPlatformProjectGrantStore`.
39
- */
40
- deleteByProject(ctx: RequestContext, projectId: string): Promise<void>;
41
-
42
- /** Atomic +1 on the solve counter. No-op if the record doesn't exist. */
43
- incrementSolveCount(ctx: RequestContext, guid: string): Promise<void>;
44
-
45
- // Versions (immutable rows)
46
- createVersion(ctx: RequestContext, version: DefinitionVersion): Promise<void>;
47
- /** Newest first by `versionNumber`. */
48
- listVersions(
49
- ctx: RequestContext,
50
- definitionId: string,
51
- opts?: ListOptions
52
- ): Promise<Page<DefinitionVersion>>;
53
- getVersion(ctx: RequestContext, versionId: string): Promise<DefinitionVersion | null>;
54
-
55
- /**
56
- * Set the cached compute-extracted schema on a version row. Used by the
57
- * upload path (writes the schema validated up-front) and the temporary
58
- * solve-time backfill bridge (see selva/specs/SchemaCaching.md). No-op if
59
- * the version doesn't exist.
60
- */
61
- setVersionSchema(ctx: RequestContext, versionId: string, schema: UISchema): Promise<void>;
62
- /**
63
- * Throws 409 if the version is referenced by `liveVersionId` or
64
- * `draftVersionId`. Caller deletes the blob separately.
65
- */
66
- deleteVersion(ctx: RequestContext, versionId: string): Promise<void>;
67
-
68
- /** Atomically point `liveVersionId` at a target version of this definition. */
69
- setLiveVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
70
- setDraftVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
71
-
72
- /**
73
- * Atomic `'pending'` → `'draft'` bootstrap. Sets BOTH `liveVersionId` and
74
- * `draftVersionId` to `versionId` and flips `status` to `'draft'` in a
75
- * single update — used by the create flow so a mid-flight failure can't
76
- * leave the record half-promoted (status='draft' with a null channel
77
- * pointer, or status='pending' with channels set).
78
- *
79
- * Validates that `versionId` belongs to `definitionId` (404 if not).
80
- *
81
- * Does NOT emit `definition.published`. The bootstrap is covered by the
82
- * parent's `definition.created` + `definition_version.created` pair;
83
- * `definition.published` is reserved for explicit publish ops via
84
- * `setLiveVersion`.
85
- */
86
- attachInitialVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
87
- }
@@ -1,31 +0,0 @@
1
- import type { DefinitionFileExt } from './types.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 `guid` can't escape the
7
- * `definitions/` prefix.
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
- export const definitionPaths = {
16
- version: (guid: string, versionNumber: number, ext: DefinitionFileExt) => {
17
- assertSafeKey(guid, 'guid');
18
- if (!Number.isInteger(versionNumber) || versionNumber < 1) {
19
- throw new Error(`Unsafe versionNumber: ${versionNumber}`);
20
- }
21
- return `definitions/${guid}/versions/v${versionNumber}.${ext}`;
22
- },
23
- image: (guid: string) => {
24
- assertSafeKey(guid, 'guid');
25
- return `definitions/${guid}/cover.webp`;
26
- },
27
- prefix: (guid: string) => {
28
- assertSafeKey(guid, 'guid');
29
- return `definitions/${guid}/`;
30
- }
31
- } as const;
@@ -1,64 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
-
5
- export const GuidSchema = z.string().regex(UUID_REGEX, 'Invalid GUID format');
6
-
7
- export const DefinitionStatusSchema = z.enum(['draft', 'published', 'archived']);
8
-
9
- export const DefinitionFileExtSchema = z.enum(['gh', 'ghx']);
10
-
11
- export const DefinitionVersionSchema = z.object({
12
- id: GuidSchema,
13
- definitionId: GuidSchema,
14
- versionNumber: z.number().int().min(1),
15
- fileExt: DefinitionFileExtSchema,
16
- fileKey: z.string().min(1),
17
- originalFilename: z.string().max(256).optional(),
18
- uploadedBy: z.string().min(1),
19
- uploadedAt: z.string(),
20
- changeNote: z.string().max(1000).optional(),
21
- // Cached compute-extracted UI schema. Structural correctness is validated by
22
- // Rhino.Compute at upload, not here — this only asserts it's an object.
23
- schema: z.record(z.string(), z.unknown()).optional(),
24
- schemaExtractedAt: z.string().optional()
25
- });
26
-
27
- export const DefinitionChannelSchema = z.enum(['live', 'draft']);
28
-
29
- /** HTTP-body validator for new-definition input. */
30
- export const CreateDefinitionInputSchema = z.object({
31
- displayName: z.string().min(1, 'Display name is required').max(256),
32
- description: z.string().max(2000).optional(),
33
- coverImage: z.string().max(2048).optional(),
34
- category: z.string().max(128).optional(),
35
- tags: z.array(z.string().max(64)).max(20).optional(),
36
- projectId: GuidSchema,
37
- computeServerId: GuidSchema.optional()
38
- });
39
-
40
- /**
41
- * PATCH validator. Field semantics match `DefinitionRecordPatch`:
42
- * - missing / `undefined` — leave unchanged
43
- * - `null` — clear (only on nullable fields)
44
- * - value — set
45
- */
46
- export const UpdateMetadataInputSchema = z.object({
47
- displayName: z.string().min(1, 'Display name is required').max(256).optional(),
48
- description: z.string().max(2000).nullable().optional(),
49
- coverImage: z.string().max(2048).nullable().optional(),
50
- category: z.string().max(128).nullable().optional(),
51
- tags: z.array(z.string().max(64)).max(20).nullable().optional(),
52
- projectId: GuidSchema.optional(),
53
- computeServerId: GuidSchema.nullable().optional(),
54
- status: DefinitionStatusSchema.optional()
55
- });
56
-
57
- export type UpdateMetadataInput = z.infer<typeof UpdateMetadataInputSchema>;
58
-
59
- /** Body for `POST /api/definitions/[guid]/publish`. */
60
- export const PublishVersionInputSchema = z
61
- .object({
62
- versionId: GuidSchema.optional()
63
- })
64
- .default({});
@@ -1,111 +0,0 @@
1
- import type { UISchema } from '@selvajs/schemas';
2
-
3
- export type DefinitionFileExt = 'gh' | 'ghx';
4
-
5
- export const GH_EXTENSIONS: string[] = ['.gh', '.ghx'];
6
- export const COVER_IMAGE_EXTENSIONS: string[] = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
7
- export const ALLOWED_UPLOAD_EXTENSIONS: string[] = [...GH_EXTENSIONS, ...COVER_IMAGE_EXTENSIONS];
8
-
9
- export const COVER_IMAGE_CONTENT_TYPES: Record<string, string> = {
10
- '.jpg': 'image/jpeg',
11
- '.jpeg': 'image/jpeg',
12
- '.png': 'image/png',
13
- '.webp': 'image/webp',
14
- '.gif': 'image/gif'
15
- };
16
-
17
- /**
18
- * - `pending` — internal: metadata written, blob upload may be in flight.
19
- * - `draft` — work in progress, not visible to runners.
20
- * - `published` — live, visible to all with solve access.
21
- * - `archived` — retired but preserved. Versions and channel pointers remain
22
- * intact; restore via `update({ status: 'draft' | 'published' })`.
23
- *
24
- * List endpoints filter `pending` and `archived` by default (opt in via
25
- * `DefinitionListOptions.includePending` / `includeArchived`, or pass an
26
- * explicit `statuses` filter).
27
- */
28
- export type DefinitionStatus = 'pending' | 'draft' | 'published' | 'archived';
29
-
30
- /**
31
- * Immutable snapshot of a definition's `.gh` file at a point in time. New
32
- * uploads create new versions; rollback re-points the parent's
33
- * `liveVersionId` (it never mutates the version itself).
34
- */
35
- export interface DefinitionVersion {
36
- id: string;
37
- definitionId: string;
38
- /** Monotonic integer starting at 1; never reused. */
39
- versionNumber: number;
40
- fileExt: DefinitionFileExt;
41
- /** Full storage key. Opaque to callers. */
42
- fileKey: string;
43
- originalFilename?: string;
44
- uploadedBy: string;
45
- uploadedAt: string;
46
- /** Free-form note describing what changed in this version. */
47
- changeNote?: string;
48
- /**
49
- * Compute-extracted UI schema, cached at upload so the render path doesn't
50
- * re-fetch it from Rhino.Compute on every load. Optional only because of the
51
- * lazy-backfill bridge for pre-existing versions (see
52
- * selva/specs/SchemaCaching.md); new uploads always set it.
53
- */
54
- schema?: UISchema;
55
- /** ISO timestamp of when `schema` was extracted. */
56
- schemaExtractedAt?: string;
57
- }
58
-
59
- export interface DefinitionRecord {
60
- guid: string;
61
- projectId: string;
62
- /** Current owner — separate from `createdBy` so ownership can transfer. */
63
- ownerId: string;
64
- createdBy: string;
65
- updatedBy: string;
66
- /** Falls back to the org default, then the platform default. */
67
- computeServerId?: string;
68
- displayName: string;
69
- description?: string;
70
- category?: string;
71
- tags?: string[];
72
- /** Provider-dependent public URL — must be safe to send to clients. */
73
- coverImage?: string;
74
- status: DefinitionStatus;
75
- solveCount: number;
76
- /**
77
- * Both null only during the brief `pending` window between metadata create
78
- * and v1 upload; otherwise both reference live `DefinitionVersion` rows.
79
- */
80
- liveVersionId: string | null;
81
- draftVersionId: string | null;
82
- createdAt: string;
83
- updatedAt: string;
84
- deletedAt?: string | null;
85
- }
86
-
87
- /**
88
- * Patch omits immutable fields and provider-managed ones (use
89
- * `setLiveVersion`/`setDraftVersion` for the latter).
90
- *
91
- * - `undefined` — leave unchanged
92
- * - `null` — clear (only on nullable fields)
93
- * - value — set
94
- *
95
- * Use `incrementSolveCount` for atomic +1 bumps instead of patching `solveCount`.
96
- */
97
- export interface DefinitionRecordPatch {
98
- displayName?: string;
99
- description?: string | null;
100
- category?: string | null;
101
- tags?: string[] | null;
102
- coverImage?: string | null;
103
- projectId?: string;
104
- computeServerId?: string | null;
105
- status?: DefinitionStatus;
106
- /** Ownership transfer. */
107
- ownerId?: string;
108
- }
109
-
110
- /** Channel for solve dispatch. */
111
- export type DefinitionChannel = 'live' | 'draft';