@selvajs/platform 0.14.2 → 0.15.0-beta.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 (196) hide show
  1. package/LICENSE +1 -1
  2. package/dist/auth.d.ts +60 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +12 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/compute.d.ts +47 -0
  7. package/dist/compute.d.ts.map +1 -0
  8. package/dist/compute.js +11 -0
  9. package/dist/compute.js.map +1 -0
  10. package/dist/computeServer/index.d.ts +2 -0
  11. package/dist/computeServer/index.d.ts.map +1 -1
  12. package/dist/computeServer/index.js +6 -0
  13. package/dist/computeServer/index.js.map +1 -1
  14. package/dist/computeServer/interface.d.ts +10 -0
  15. package/dist/computeServer/interface.d.ts.map +1 -1
  16. package/dist/computeServer/secrets.d.ts +53 -0
  17. package/dist/computeServer/secrets.d.ts.map +1 -0
  18. package/dist/computeServer/secrets.js +67 -0
  19. package/dist/computeServer/secrets.js.map +1 -0
  20. package/dist/context.d.ts +7 -0
  21. package/dist/context.d.ts.map +1 -1
  22. package/dist/context.js +13 -0
  23. package/dist/context.js.map +1 -1
  24. package/dist/data/interface.d.ts +33 -0
  25. package/dist/data/interface.d.ts.map +1 -1
  26. package/dist/definitions/interface.d.ts +18 -0
  27. package/dist/definitions/interface.d.ts.map +1 -1
  28. package/dist/definitions/schemas.d.ts +1 -0
  29. package/dist/definitions/schemas.d.ts.map +1 -1
  30. package/dist/definitions/schemas.js +3 -0
  31. package/dist/definitions/schemas.js.map +1 -1
  32. package/dist/definitions/service.d.ts +62 -0
  33. package/dist/definitions/service.d.ts.map +1 -0
  34. package/dist/definitions/service.js +149 -0
  35. package/dist/definitions/service.js.map +1 -0
  36. package/dist/definitions/types.d.ts +24 -1
  37. package/dist/definitions/types.d.ts.map +1 -1
  38. package/dist/definitions.d.ts +146 -0
  39. package/dist/definitions.d.ts.map +1 -0
  40. package/dist/definitions.js +23 -0
  41. package/dist/definitions.js.map +1 -0
  42. package/dist/errors/interface.d.ts +44 -0
  43. package/dist/errors/interface.d.ts.map +1 -0
  44. package/dist/errors/interface.js +9 -0
  45. package/dist/errors/interface.js.map +1 -0
  46. package/dist/events/interface.d.ts +23 -0
  47. package/dist/events/interface.d.ts.map +1 -1
  48. package/dist/events/interface.js.map +1 -1
  49. package/dist/index.d.ts +14 -5
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -2
  52. package/dist/index.js.map +1 -1
  53. package/dist/invites/schemas.d.ts +24 -0
  54. package/dist/invites/schemas.d.ts.map +1 -0
  55. package/dist/invites/schemas.js +17 -0
  56. package/dist/invites/schemas.js.map +1 -0
  57. package/dist/metrics/interface.d.ts +6 -0
  58. package/dist/metrics/interface.d.ts.map +1 -1
  59. package/dist/metrics/interface.js.map +1 -1
  60. package/dist/organizations/index.d.ts +4 -3
  61. package/dist/organizations/index.d.ts.map +1 -1
  62. package/dist/organizations/index.js +2 -1
  63. package/dist/organizations/index.js.map +1 -1
  64. package/dist/organizations/interface.d.ts +1 -1
  65. package/dist/organizations/interface.d.ts.map +1 -1
  66. package/dist/organizations/paths.d.ts +22 -0
  67. package/dist/organizations/paths.d.ts.map +1 -0
  68. package/dist/organizations/paths.js +43 -0
  69. package/dist/organizations/paths.js.map +1 -0
  70. package/dist/organizations/schemas.d.ts +10 -0
  71. package/dist/organizations/schemas.d.ts.map +1 -1
  72. package/dist/organizations/schemas.js +6 -0
  73. package/dist/organizations/schemas.js.map +1 -1
  74. package/dist/organizations/types.d.ts +10 -1
  75. package/dist/organizations/types.d.ts.map +1 -1
  76. package/dist/organizations.d.ts +93 -0
  77. package/dist/organizations.d.ts.map +1 -0
  78. package/dist/organizations.js +14 -0
  79. package/dist/organizations.js.map +1 -0
  80. package/dist/solveCache/interface.d.ts +86 -0
  81. package/dist/solveCache/interface.d.ts.map +1 -0
  82. package/dist/solveCache/interface.js +12 -0
  83. package/dist/solveCache/interface.js.map +1 -0
  84. package/dist/storage/assetClasses.d.ts +59 -0
  85. package/dist/storage/assetClasses.d.ts.map +1 -0
  86. package/dist/storage/assetClasses.js +86 -0
  87. package/dist/storage/assetClasses.js.map +1 -0
  88. package/dist/storage/cacheBust.d.ts +21 -0
  89. package/dist/storage/cacheBust.d.ts.map +1 -0
  90. package/dist/storage/cacheBust.js +29 -0
  91. package/dist/storage/cacheBust.js.map +1 -0
  92. package/dist/storage/image.d.ts +6 -1
  93. package/dist/storage/image.d.ts.map +1 -1
  94. package/dist/storage/image.js +8 -3
  95. package/dist/storage/image.js.map +1 -1
  96. package/dist/storage/index.d.ts +3 -0
  97. package/dist/storage/index.d.ts.map +1 -1
  98. package/dist/storage/index.js +2 -0
  99. package/dist/storage/index.js.map +1 -1
  100. package/dist/testing/adapterSuite.d.ts +33 -0
  101. package/dist/testing/adapterSuite.d.ts.map +1 -0
  102. package/dist/testing/adapterSuite.js +166 -0
  103. package/dist/testing/adapterSuite.js.map +1 -0
  104. package/dist/testing/suites/definitionStoreSuite.d.ts.map +1 -1
  105. package/dist/testing/suites/definitionStoreSuite.js +87 -0
  106. package/dist/testing/suites/definitionStoreSuite.js.map +1 -1
  107. package/dist/testing/suites/eventSinkSuite.js +1 -0
  108. package/dist/testing/suites/eventSinkSuite.js.map +1 -1
  109. package/dist/testing/suites/orgStoreSuite.d.ts.map +1 -1
  110. package/dist/testing/suites/orgStoreSuite.js +12 -0
  111. package/dist/testing/suites/orgStoreSuite.js.map +1 -1
  112. package/dist/testing/suites/platformPermissionStoreSuite.d.ts +9 -0
  113. package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +1 -1
  114. package/dist/testing/suites/platformPermissionStoreSuite.js +34 -1
  115. package/dist/testing/suites/platformPermissionStoreSuite.js.map +1 -1
  116. package/dist/testing/suites/runner.d.ts +21 -0
  117. package/dist/testing/suites/runner.d.ts.map +1 -0
  118. package/dist/testing/suites/runner.js +7 -0
  119. package/dist/testing/suites/runner.js.map +1 -0
  120. package/dist/testing/suites/storageProviderSuite.d.ts.map +1 -1
  121. package/dist/testing/suites/storageProviderSuite.js +18 -0
  122. package/dist/testing/suites/storageProviderSuite.js.map +1 -1
  123. package/package.json +9 -24
  124. package/src/access/index.ts +0 -23
  125. package/src/access/rules.ts +0 -276
  126. package/src/auth/index.ts +0 -8
  127. package/src/auth/interface.ts +0 -279
  128. package/src/auth/types.ts +0 -32
  129. package/src/bindings/index.ts +0 -2
  130. package/src/bindings/interface.ts +0 -61
  131. package/src/computeServer/index.ts +0 -17
  132. package/src/computeServer/interface.ts +0 -62
  133. package/src/computeServer/types.ts +0 -74
  134. package/src/computeServer/utils.ts +0 -112
  135. package/src/config.ts +0 -99
  136. package/src/context.ts +0 -66
  137. package/src/data/index.ts +0 -1
  138. package/src/data/interface.ts +0 -70
  139. package/src/definitions/index.ts +0 -26
  140. package/src/definitions/interface.ts +0 -78
  141. package/src/definitions/paths.ts +0 -31
  142. package/src/definitions/schemas.ts +0 -64
  143. package/src/definitions/types.ts +0 -111
  144. package/src/errors.ts +0 -16
  145. package/src/events/audit.ts +0 -67
  146. package/src/events/index.ts +0 -9
  147. package/src/events/interface.ts +0 -102
  148. package/src/index.ts +0 -219
  149. package/src/invites/index.ts +0 -2
  150. package/src/invites/interface.ts +0 -38
  151. package/src/invites/types.ts +0 -39
  152. package/src/metrics/interface.ts +0 -86
  153. package/src/organizations/index.ts +0 -14
  154. package/src/organizations/interface.ts +0 -68
  155. package/src/organizations/schemas.ts +0 -52
  156. package/src/organizations/types.ts +0 -31
  157. package/src/pagination.ts +0 -45
  158. package/src/permissions/index.ts +0 -3
  159. package/src/permissions/interface.ts +0 -60
  160. package/src/permissions/types.ts +0 -21
  161. package/src/platformProjects/index.ts +0 -3
  162. package/src/platformProjects/interface.ts +0 -27
  163. package/src/platformProjects/schemas.ts +0 -13
  164. package/src/platformProjects/types.ts +0 -22
  165. package/src/projects/index.ts +0 -4
  166. package/src/projects/interface.ts +0 -58
  167. package/src/projects/schemas.ts +0 -28
  168. package/src/projects/types.ts +0 -36
  169. package/src/shareLinks/index.ts +0 -5
  170. package/src/shareLinks/interface.ts +0 -35
  171. package/src/shareLinks/schemas.ts +0 -21
  172. package/src/shareLinks/types.ts +0 -34
  173. package/src/storage/image.ts +0 -77
  174. package/src/storage/index.ts +0 -9
  175. package/src/storage/interface.ts +0 -27
  176. package/src/testing/index.ts +0 -47
  177. package/src/testing/suites/authProviderSuite.ts +0 -207
  178. package/src/testing/suites/computeServerStoreSuite.ts +0 -195
  179. package/src/testing/suites/definitionStoreSuite.ts +0 -585
  180. package/src/testing/suites/emailLinkAuthSuite.ts +0 -83
  181. package/src/testing/suites/eventSinkSuite.ts +0 -379
  182. package/src/testing/suites/helpers.ts +0 -87
  183. package/src/testing/suites/inviteStoreSuite.ts +0 -143
  184. package/src/testing/suites/orgStoreSuite.ts +0 -412
  185. package/src/testing/suites/platformPermissionStoreSuite.ts +0 -196
  186. package/src/testing/suites/platformProjectGrantStoreSuite.ts +0 -166
  187. package/src/testing/suites/projectStoreSuite.ts +0 -381
  188. package/src/testing/suites/shareLinkStoreSuite.ts +0 -203
  189. package/src/testing/suites/solveMetricSinkSuite.ts +0 -135
  190. package/src/testing/suites/storageProviderSuite.ts +0 -175
  191. package/src/testing/suites/userProfileStoreSuite.ts +0 -134
  192. package/src/userProfile/index.ts +0 -3
  193. package/src/userProfile/interface.ts +0 -48
  194. package/src/userProfile/types.ts +0 -24
  195. package/src/utils/audit.ts +0 -46
  196. package/src/utils/index.ts +0 -2
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,66 +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
-
6
- /**
7
- * Per-request identity + scope passed to every data provider call. Built
8
- * once per HTTP request from the authenticated session.
9
- *
10
- * `actingOrgId` is the org the user is currently acting as — NOT "an org the
11
- * user is a member of." Tenancy checks compare it to the resource's `orgId`.
12
- *
13
- * `instance_admin` implies every other permission, everywhere — encoded by
14
- * `hasPermission`. `system: true` is a trusted server-internal call;
15
- * adapters with RLS treat it as fully authorized.
16
- */
17
- export interface RequestContext {
18
- /** Empty string for system contexts. */
19
- userId: string;
20
- /** Undefined for instance-admin global reads and pre-org routes. */
21
- actingOrgId?: string;
22
- platformPermissions: PlatformPermission[];
23
- /** Empty when `actingOrgId` is undefined. */
24
- orgPermissions: OrgPermission[];
25
- /** Never derive from a user session. */
26
- system?: true;
27
- /**
28
- * Opaque adapter payload. The Supabase adapter passes the user JWT through
29
- * for RLS; local ignores it.
30
- */
31
- adapterContext?: unknown;
32
- }
33
-
34
- /**
35
- * For server-internal operations outside any HTTP request (bootstrap,
36
- * janitors, migrations). Treated as fully authorized.
37
- */
38
- export const SYSTEM_CONTEXT: RequestContext = {
39
- userId: '',
40
- platformPermissions: [...ALL_PLATFORM_PERMISSIONS],
41
- orgPermissions: [...ALL_ORG_PERMISSIONS],
42
- system: true
43
- };
44
-
45
- /**
46
- * Does NOT cross-check `ctx.actingOrgId` — the caller builds a context scoped
47
- * to the org they're acting on. An org permission against a ctx without
48
- * `actingOrgId` returns false unless `instance_admin` or `system`.
49
- */
50
- export function hasPermission(
51
- ctx: RequestContext,
52
- permission: PlatformPermission | OrgPermission
53
- ): boolean {
54
- if (ctx.system) return true;
55
- if (ctx.platformPermissions.includes('instance_admin')) return true;
56
- if (isPlatformPermission(permission)) {
57
- return ctx.platformPermissions.includes(permission);
58
- }
59
- return ctx.orgPermissions.includes(permission);
60
- }
61
-
62
- const PLATFORM_PERMISSION_VALUES = new Set<string>(ALL_PLATFORM_PERMISSIONS);
63
-
64
- function isPlatformPermission(p: string): p is PlatformPermission {
65
- return PLATFORM_PERMISSION_VALUES.has(p);
66
- }
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,78 +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
- /** Atomic +1 on the solve counter. No-op if the record doesn't exist. */
34
- incrementSolveCount(ctx: RequestContext, guid: string): Promise<void>;
35
-
36
- // Versions (immutable rows)
37
- createVersion(ctx: RequestContext, version: DefinitionVersion): Promise<void>;
38
- /** Newest first by `versionNumber`. */
39
- listVersions(
40
- ctx: RequestContext,
41
- definitionId: string,
42
- opts?: ListOptions
43
- ): Promise<Page<DefinitionVersion>>;
44
- getVersion(ctx: RequestContext, versionId: string): Promise<DefinitionVersion | null>;
45
-
46
- /**
47
- * Set the cached compute-extracted schema on a version row. Used by the
48
- * upload path (writes the schema validated up-front) and the temporary
49
- * solve-time backfill bridge (see selva/specs/SchemaCaching.md). No-op if
50
- * the version doesn't exist.
51
- */
52
- setVersionSchema(ctx: RequestContext, versionId: string, schema: UISchema): Promise<void>;
53
- /**
54
- * Throws 409 if the version is referenced by `liveVersionId` or
55
- * `draftVersionId`. Caller deletes the blob separately.
56
- */
57
- deleteVersion(ctx: RequestContext, versionId: string): Promise<void>;
58
-
59
- /** Atomically point `liveVersionId` at a target version of this definition. */
60
- setLiveVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
61
- setDraftVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
62
-
63
- /**
64
- * Atomic `'pending'` → `'draft'` bootstrap. Sets BOTH `liveVersionId` and
65
- * `draftVersionId` to `versionId` and flips `status` to `'draft'` in a
66
- * single update — used by the create flow so a mid-flight failure can't
67
- * leave the record half-promoted (status='draft' with a null channel
68
- * pointer, or status='pending' with channels set).
69
- *
70
- * Validates that `versionId` belongs to `definitionId` (404 if not).
71
- *
72
- * Does NOT emit `definition.published`. The bootstrap is covered by the
73
- * parent's `definition.created` + `definition_version.created` pair;
74
- * `definition.published` is reserved for explicit publish ops via
75
- * `setLiveVersion`.
76
- */
77
- attachInitialVersion(ctx: RequestContext, definitionId: string, versionId: string): Promise<void>;
78
- }
@@ -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';
package/src/errors.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Thrown by providers to signal an expected failure that the API layer
3
- * should forward with a specific status. Unknown errors should propagate as
4
- * plain `Error` and become 500s.
5
- *
6
- * Conventional values: 400, 401, 403, 404, 409, 422, 429, 500.
7
- */
8
- export class ProviderError extends Error {
9
- readonly statusCode: number;
10
-
11
- constructor(message: string, statusCode: number = 400) {
12
- super(message);
13
- this.name = 'ProviderError';
14
- this.statusCode = statusCode;
15
- }
16
- }
@@ -1,67 +0,0 @@
1
- import type { RequestContext } from '../context.js';
2
- import type { DomainEvent, DomainEventType } from './interface.js';
3
-
4
- /**
5
- * Read-side counterpart to `IEventSink`. Persisted by the sink, queried by
6
- * operator-facing UIs (admin audit log) and future analytics consumers.
7
- *
8
- * Kept on its own interface so:
9
- * - dev-mode `NoopEventSink` doesn't need a fake `query` returning empty;
10
- * `auditQuery` simply stays undefined on the data provider.
11
- * - future webhook-dispatcher sinks aren't forced to implement a query path.
12
- * - the contract here can throw on backend failure without violating the
13
- * sink's "MUST NOT throw" rule.
14
- *
15
- * Authorisation is the caller's job — implementations read from a privileged
16
- * client (Supabase service-role, etc.) and assume the route layer has already
17
- * gated on `instance_admin`.
18
- */
19
-
20
- export interface AuditEventRow {
21
- /** Stable row id from the persistent store (e.g. uuid). */
22
- id: string;
23
- /** ISO 8601. The persisted timestamp, not when the originating mutation occurred. */
24
- occurredAt: string;
25
- type: DomainEventType;
26
- actorId: string;
27
- /** The full `DomainEvent` payload as recorded. */
28
- data: DomainEvent;
29
- }
30
-
31
- /**
32
- * Keyset cursor. `(occurredAt, id)` lexicographic — `occurredAt` alone isn't
33
- * unique under bursty writes, so a tie-breaker is required to avoid skipped
34
- * rows on page boundaries.
35
- */
36
- export interface AuditCursor {
37
- occurredAt: string;
38
- id: string;
39
- }
40
-
41
- export interface AuditQueryFilters {
42
- /** Restrict to these event types. Empty / undefined = all types. */
43
- types?: readonly DomainEventType[];
44
- /** Restrict to events emitted by this actor id (or `'system'`). */
45
- actorId?: string;
46
- /** Inclusive lower bound on `occurredAt`. ISO 8601. */
47
- sinceIso?: string;
48
- /** Inclusive upper bound on `occurredAt`. ISO 8601. */
49
- untilIso?: string;
50
- /**
51
- * Page size. Implementations clamp to a sane upper bound (≤200) — callers
52
- * shouldn't be able to request unbounded reads.
53
- */
54
- limit?: number;
55
- /** Keyset cursor from a previous page's `nextCursor`. */
56
- cursor?: AuditCursor;
57
- }
58
-
59
- export interface AuditQueryResult {
60
- rows: AuditEventRow[];
61
- /** Null when there are no more rows past this page. */
62
- nextCursor: AuditCursor | null;
63
- }
64
-
65
- export interface IAuditQuery {
66
- list(ctx: RequestContext, filters: AuditQueryFilters): Promise<AuditQueryResult>;
67
- }
@@ -1,9 +0,0 @@
1
- export type { DomainEvent, DomainEventType, IEventSink } from './interface.js';
2
- export { actorFrom, NoopEventSink } from './interface.js';
3
- export type {
4
- AuditEventRow,
5
- AuditCursor,
6
- AuditQueryFilters,
7
- AuditQueryResult,
8
- IAuditQuery
9
- } from './audit.js';