@selvajs/platform 0.21.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/dist/access/index.d.ts +1 -2
- package/dist/access/index.js +1 -2
- package/dist/access/rules.d.ts +11 -12
- package/dist/access/rules.js +27 -23
- package/dist/apiTokens/index.d.ts +2 -3
- package/dist/apiTokens/index.js +2 -3
- package/dist/apiTokens/interface.d.ts +0 -1
- package/dist/apiTokens/interface.js +0 -1
- package/dist/apiTokens/scope.d.ts +3 -2
- package/dist/apiTokens/scope.js +9 -5
- package/dist/apiTokens/types.d.ts +7 -2
- package/dist/apiTokens/types.js +10 -5
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +0 -1
- package/dist/auth/interface.d.ts +4 -23
- package/dist/auth/interface.js +0 -1
- package/dist/auth/types.d.ts +4 -2
- package/dist/auth/types.js +0 -1
- package/dist/bindings/index.d.ts +0 -1
- package/dist/bindings/index.js +0 -1
- package/dist/bindings/interface.d.ts +0 -1
- package/dist/bindings/interface.js +0 -1
- package/dist/computeServer/index.d.ts +1 -7
- package/dist/computeServer/index.js +2 -8
- package/dist/computeServer/interface.d.ts +9 -6
- package/dist/computeServer/interface.js +0 -1
- package/dist/computeServer/public.d.ts +6 -0
- package/dist/computeServer/public.js +2 -0
- package/dist/computeServer/secrets.d.ts +12 -6
- package/dist/computeServer/secrets.js +35 -15
- package/dist/computeServer/types.d.ts +5 -2
- package/dist/computeServer/types.js +4 -1
- package/dist/computeServer/utils.d.ts +17 -6
- package/dist/computeServer/utils.js +31 -21
- package/dist/config.d.ts +1 -2
- package/dist/config.js +0 -1
- package/dist/context.d.ts +12 -1
- package/dist/context.js +51 -2
- package/dist/data/index.d.ts +0 -1
- package/dist/data/index.js +0 -1
- package/dist/data/interface.d.ts +0 -1
- package/dist/data/interface.js +0 -1
- package/dist/definitions/index.d.ts +2 -3
- package/dist/definitions/index.js +2 -3
- package/dist/definitions/interface.d.ts +0 -1
- package/dist/definitions/interface.js +0 -1
- package/dist/definitions/paths.d.ts +0 -1
- package/dist/definitions/paths.js +1 -12
- package/dist/definitions/schemas.d.ts +2 -21
- package/dist/definitions/schemas.js +10 -19
- package/dist/definitions/types.d.ts +12 -4
- package/dist/definitions/types.js +1 -3
- package/dist/errors/interface.d.ts +1 -2
- package/dist/errors/interface.js +0 -1
- package/dist/errors.d.ts +8 -1
- package/dist/errors.js +11 -1
- package/dist/events/audit.d.ts +1 -2
- package/dist/events/audit.js +0 -1
- package/dist/events/index.d.ts +0 -1
- package/dist/events/index.js +0 -1
- package/dist/events/interface.d.ts +0 -1
- package/dist/events/interface.js +0 -1
- package/dist/index.d.ts +18 -61
- package/dist/index.js +18 -24
- package/dist/invites/index.d.ts +0 -1
- package/dist/invites/index.js +0 -1
- package/dist/invites/interface.d.ts +5 -6
- package/dist/invites/interface.js +0 -1
- package/dist/invites/types.d.ts +0 -1
- package/dist/invites/types.js +0 -1
- package/dist/logging/interface.d.ts +0 -1
- package/dist/logging/interface.js +0 -1
- package/dist/metrics/interface.d.ts +0 -1
- package/dist/metrics/interface.js +0 -1
- package/dist/notifications/index.d.ts +0 -1
- package/dist/notifications/index.js +0 -1
- package/dist/notifications/interface.d.ts +1 -3
- package/dist/notifications/interface.js +1 -3
- package/dist/notifications/types.d.ts +0 -1
- package/dist/notifications/types.js +0 -1
- package/dist/organizations/index.d.ts +2 -3
- package/dist/organizations/index.js +1 -2
- package/dist/organizations/interface.d.ts +3 -5
- package/dist/organizations/interface.js +0 -1
- package/dist/organizations/paths.d.ts +1 -6
- package/dist/organizations/paths.js +2 -20
- package/dist/organizations/schemas.d.ts +5 -7
- package/dist/organizations/schemas.js +20 -13
- package/dist/organizations/types.d.ts +0 -1
- package/dist/organizations/types.js +0 -1
- package/dist/pagination.d.ts +11 -3
- package/dist/pagination.js +15 -1
- package/dist/permissions/index.d.ts +2 -2
- package/dist/permissions/index.js +2 -2
- package/dist/permissions/interface.d.ts +4 -4
- package/dist/permissions/interface.js +0 -1
- package/dist/permissions/types.d.ts +0 -1
- package/dist/permissions/types.js +0 -1
- package/dist/platformProjects/index.d.ts +1 -3
- package/dist/platformProjects/index.js +1 -2
- package/dist/platformProjects/interface.d.ts +0 -1
- package/dist/platformProjects/interface.js +0 -1
- package/dist/platformProjects/schemas.d.ts +0 -1
- package/dist/platformProjects/schemas.js +0 -1
- package/dist/platformProjects/types.d.ts +3 -2
- package/dist/platformProjects/types.js +0 -1
- package/dist/projects/index.d.ts +0 -1
- package/dist/projects/index.js +0 -1
- package/dist/projects/interface.d.ts +0 -1
- package/dist/projects/interface.js +0 -1
- package/dist/projects/schemas.d.ts +0 -1
- package/dist/projects/schemas.js +0 -1
- package/dist/projects/types.d.ts +0 -1
- package/dist/projects/types.js +0 -1
- package/dist/shareLinks/index.d.ts +0 -1
- package/dist/shareLinks/index.js +0 -1
- package/dist/shareLinks/interface.d.ts +6 -3
- package/dist/shareLinks/interface.js +0 -1
- package/dist/shareLinks/schemas.d.ts +0 -1
- package/dist/shareLinks/schemas.js +4 -2
- package/dist/shareLinks/types.d.ts +0 -1
- package/dist/shareLinks/types.js +0 -1
- package/dist/solveCache/interface.d.ts +2 -3
- package/dist/solveCache/interface.js +0 -1
- package/dist/storage/assetClasses.d.ts +4 -8
- package/dist/storage/assetClasses.js +6 -21
- package/dist/storage/cacheBust.d.ts +0 -1
- package/dist/storage/cacheBust.js +5 -3
- package/dist/storage/image.d.ts +8 -5
- package/dist/storage/image.js +74 -12
- package/dist/storage/index.d.ts +2 -4
- package/dist/storage/index.js +2 -3
- package/dist/storage/interface.d.ts +11 -3
- package/dist/storage/interface.js +0 -1
- package/dist/storage/safeKey.d.ts +7 -0
- package/dist/storage/safeKey.js +12 -0
- package/dist/testing/index.d.ts +5 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/suites/apiTokenStoreSuite.d.ts +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js +0 -1
- package/dist/testing/suites/auditQuerySuite.d.ts +18 -0
- package/dist/testing/suites/auditQuerySuite.js +73 -0
- package/dist/testing/suites/authProviderSuite.d.ts +14 -3
- package/dist/testing/suites/authProviderSuite.js +57 -32
- package/dist/testing/suites/computeServerStoreSuite.d.ts +1 -2
- package/dist/testing/suites/computeServerStoreSuite.js +68 -4
- package/dist/testing/suites/definitionStoreSuite.d.ts +2 -1
- package/dist/testing/suites/definitionStoreSuite.js +48 -16
- package/dist/testing/suites/emailLinkAuthSuite.d.ts +2 -5
- package/dist/testing/suites/emailLinkAuthSuite.js +1 -9
- package/dist/testing/suites/eventSinkSuite.d.ts +0 -1
- package/dist/testing/suites/eventSinkSuite.js +9 -1
- package/dist/testing/suites/helpers.d.ts +5 -1
- package/dist/testing/suites/helpers.js +7 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts +0 -1
- package/dist/testing/suites/inviteStoreSuite.js +17 -7
- package/dist/testing/suites/oauthAuthSuite.d.ts +18 -0
- package/dist/testing/suites/oauthAuthSuite.js +26 -0
- package/dist/testing/suites/orgStoreSuite.d.ts +5 -1
- package/dist/testing/suites/orgStoreSuite.js +26 -7
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts +0 -5
- package/dist/testing/suites/platformPermissionStoreSuite.js +12 -63
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js +0 -5
- package/dist/testing/suites/projectStoreSuite.d.ts +5 -1
- package/dist/testing/suites/projectStoreSuite.js +6 -6
- package/dist/testing/suites/shareLinkStoreSuite.d.ts +2 -1
- package/dist/testing/suites/shareLinkStoreSuite.js +22 -25
- package/dist/testing/suites/solveMetricSinkSuite.d.ts +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js +2 -2
- package/dist/testing/suites/storageProviderSuite.d.ts +0 -1
- package/dist/testing/suites/storageProviderSuite.js +15 -10
- package/dist/testing/suites/userProfileStoreSuite.d.ts +1 -2
- package/dist/testing/suites/userProfileStoreSuite.js +42 -7
- package/dist/userProfile/index.d.ts +2 -2
- package/dist/userProfile/index.js +2 -2
- package/dist/userProfile/interface.d.ts +5 -6
- package/dist/userProfile/interface.js +0 -1
- package/dist/userProfile/types.d.ts +2 -1
- package/dist/userProfile/types.js +2 -1
- package/dist/utils/audit.d.ts +0 -1
- package/dist/utils/audit.js +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/package.json +7 -6
- package/dist/access/index.d.ts.map +0 -1
- package/dist/access/index.js.map +0 -1
- package/dist/access/rules.d.ts.map +0 -1
- package/dist/access/rules.js.map +0 -1
- package/dist/apiTokens/index.d.ts.map +0 -1
- package/dist/apiTokens/index.js.map +0 -1
- package/dist/apiTokens/interface.d.ts.map +0 -1
- package/dist/apiTokens/interface.js.map +0 -1
- package/dist/apiTokens/scope.d.ts.map +0 -1
- package/dist/apiTokens/scope.js.map +0 -1
- package/dist/apiTokens/types.d.ts.map +0 -1
- package/dist/apiTokens/types.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/interface.d.ts.map +0 -1
- package/dist/auth/interface.js.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/bindings/index.d.ts.map +0 -1
- package/dist/bindings/index.js.map +0 -1
- package/dist/bindings/interface.d.ts.map +0 -1
- package/dist/bindings/interface.js.map +0 -1
- package/dist/computeServer/index.d.ts.map +0 -1
- package/dist/computeServer/index.js.map +0 -1
- package/dist/computeServer/interface.d.ts.map +0 -1
- package/dist/computeServer/interface.js.map +0 -1
- package/dist/computeServer/secrets.d.ts.map +0 -1
- package/dist/computeServer/secrets.js.map +0 -1
- package/dist/computeServer/types.d.ts.map +0 -1
- package/dist/computeServer/types.js.map +0 -1
- package/dist/computeServer/utils.d.ts.map +0 -1
- package/dist/computeServer/utils.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js.map +0 -1
- package/dist/data/index.d.ts.map +0 -1
- package/dist/data/index.js.map +0 -1
- package/dist/data/interface.d.ts.map +0 -1
- package/dist/data/interface.js.map +0 -1
- package/dist/definitions/index.d.ts.map +0 -1
- package/dist/definitions/index.js.map +0 -1
- package/dist/definitions/interface.d.ts.map +0 -1
- package/dist/definitions/interface.js.map +0 -1
- package/dist/definitions/paths.d.ts.map +0 -1
- package/dist/definitions/paths.js.map +0 -1
- package/dist/definitions/schemas.d.ts.map +0 -1
- package/dist/definitions/schemas.js.map +0 -1
- package/dist/definitions/types.d.ts.map +0 -1
- package/dist/definitions/types.js.map +0 -1
- package/dist/errors/interface.d.ts.map +0 -1
- package/dist/errors/interface.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/events/audit.d.ts.map +0 -1
- package/dist/events/audit.js.map +0 -1
- package/dist/events/index.d.ts.map +0 -1
- package/dist/events/index.js.map +0 -1
- package/dist/events/interface.d.ts.map +0 -1
- package/dist/events/interface.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invites/index.d.ts.map +0 -1
- package/dist/invites/index.js.map +0 -1
- package/dist/invites/interface.d.ts.map +0 -1
- package/dist/invites/interface.js.map +0 -1
- package/dist/invites/types.d.ts.map +0 -1
- package/dist/invites/types.js.map +0 -1
- package/dist/logging/interface.d.ts.map +0 -1
- package/dist/logging/interface.js.map +0 -1
- package/dist/metrics/interface.d.ts.map +0 -1
- package/dist/metrics/interface.js.map +0 -1
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/index.js.map +0 -1
- package/dist/notifications/interface.d.ts.map +0 -1
- package/dist/notifications/interface.js.map +0 -1
- package/dist/notifications/types.d.ts.map +0 -1
- package/dist/notifications/types.js.map +0 -1
- package/dist/organizations/index.d.ts.map +0 -1
- package/dist/organizations/index.js.map +0 -1
- package/dist/organizations/interface.d.ts.map +0 -1
- package/dist/organizations/interface.js.map +0 -1
- package/dist/organizations/paths.d.ts.map +0 -1
- package/dist/organizations/paths.js.map +0 -1
- package/dist/organizations/schemas.d.ts.map +0 -1
- package/dist/organizations/schemas.js.map +0 -1
- package/dist/organizations/types.d.ts.map +0 -1
- package/dist/organizations/types.js.map +0 -1
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js.map +0 -1
- package/dist/permissions/index.d.ts.map +0 -1
- package/dist/permissions/index.js.map +0 -1
- package/dist/permissions/interface.d.ts.map +0 -1
- package/dist/permissions/interface.js.map +0 -1
- package/dist/permissions/types.d.ts.map +0 -1
- package/dist/permissions/types.js.map +0 -1
- package/dist/platformProjects/index.d.ts.map +0 -1
- package/dist/platformProjects/index.js.map +0 -1
- package/dist/platformProjects/interface.d.ts.map +0 -1
- package/dist/platformProjects/interface.js.map +0 -1
- package/dist/platformProjects/schemas.d.ts.map +0 -1
- package/dist/platformProjects/schemas.js.map +0 -1
- package/dist/platformProjects/types.d.ts.map +0 -1
- package/dist/platformProjects/types.js.map +0 -1
- package/dist/projects/index.d.ts.map +0 -1
- package/dist/projects/index.js.map +0 -1
- package/dist/projects/interface.d.ts.map +0 -1
- package/dist/projects/interface.js.map +0 -1
- package/dist/projects/schemas.d.ts.map +0 -1
- package/dist/projects/schemas.js.map +0 -1
- package/dist/projects/types.d.ts.map +0 -1
- package/dist/projects/types.js.map +0 -1
- package/dist/shareLinks/index.d.ts.map +0 -1
- package/dist/shareLinks/index.js.map +0 -1
- package/dist/shareLinks/interface.d.ts.map +0 -1
- package/dist/shareLinks/interface.js.map +0 -1
- package/dist/shareLinks/schemas.d.ts.map +0 -1
- package/dist/shareLinks/schemas.js.map +0 -1
- package/dist/shareLinks/types.d.ts.map +0 -1
- package/dist/shareLinks/types.js.map +0 -1
- package/dist/solveCache/interface.d.ts.map +0 -1
- package/dist/solveCache/interface.js.map +0 -1
- package/dist/storage/assetClasses.d.ts.map +0 -1
- package/dist/storage/assetClasses.js.map +0 -1
- package/dist/storage/cacheBust.d.ts.map +0 -1
- package/dist/storage/cacheBust.js.map +0 -1
- package/dist/storage/image.d.ts.map +0 -1
- package/dist/storage/image.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/interface.d.ts.map +0 -1
- package/dist/storage/interface.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js.map +0 -1
- package/dist/testing/suites/authProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/authProviderSuite.js.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.js.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.js.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.d.ts.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.js.map +0 -1
- package/dist/testing/suites/eventSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/eventSinkSuite.js.map +0 -1
- package/dist/testing/suites/helpers.d.ts.map +0 -1
- package/dist/testing/suites/helpers.js.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.js.map +0 -1
- package/dist/testing/suites/orgStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/orgStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js.map +0 -1
- package/dist/testing/suites/projectStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/projectStoreSuite.js.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.js.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js.map +0 -1
- package/dist/testing/suites/storageProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/storageProviderSuite.js.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.js.map +0 -1
- package/dist/userProfile/index.d.ts.map +0 -1
- package/dist/userProfile/index.js.map +0 -1
- package/dist/userProfile/interface.d.ts.map +0 -1
- package/dist/userProfile/interface.js.map +0 -1
- package/dist/userProfile/types.d.ts.map +0 -1
- package/dist/userProfile/types.js.map +0 -1
- package/dist/utils/audit.d.ts.map +0 -1
- package/dist/utils/audit.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
|
@@ -2,12 +2,19 @@ import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
|
2
2
|
const ALGO = 'aes-256-gcm';
|
|
3
3
|
const IV_BYTES = 12;
|
|
4
4
|
const TAG_BYTES = 16;
|
|
5
|
-
const
|
|
5
|
+
const PREFIX_V1 = 'enc:v1:';
|
|
6
|
+
const PREFIX_V2 = 'enc:v2:';
|
|
7
|
+
export function isEncryptedSecret(value) {
|
|
8
|
+
return value.startsWith(PREFIX_V1) || value.startsWith(PREFIX_V2);
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* AES-256-GCM envelope for at-rest secrets (e.g. compute API keys).
|
|
8
12
|
*
|
|
9
|
-
* Wire format: `enc:
|
|
10
|
-
*
|
|
13
|
+
* Wire format: `enc:v2:<base64(iv|tag|ciphertext)>`. `v2` authenticates the
|
|
14
|
+
* record the secret belongs to as AAD, so ciphertext copied onto another record
|
|
15
|
+
* fails to decrypt instead of handing the plaintext to that record's URL.
|
|
16
|
+
* `enc:v1:` envelopes have no AAD and still read: a store rewrites them as `v2`
|
|
17
|
+
* on its next save.
|
|
11
18
|
*
|
|
12
19
|
* Defends against backup leaks, accidental file/DB sharing, and read-only
|
|
13
20
|
* storage access — not against an attacker who also has the master key
|
|
@@ -16,11 +23,11 @@ const PREFIX = 'enc:v1:';
|
|
|
16
23
|
*
|
|
17
24
|
* Pure `node:crypto`, no fs or DB coupling, so the local and Supabase
|
|
18
25
|
* compute-server stores share this one implementation.
|
|
26
|
+
*
|
|
27
|
+
* Pass `aad` to bind the secret to its record. {@link decryptSecret} then needs the
|
|
28
|
+
* same value.
|
|
19
29
|
*/
|
|
20
|
-
export function
|
|
21
|
-
return value.startsWith(PREFIX);
|
|
22
|
-
}
|
|
23
|
-
export function encryptSecret(plaintext, key) {
|
|
30
|
+
export function encryptSecret(plaintext, key, aad) {
|
|
24
31
|
if (key.length !== 32) {
|
|
25
32
|
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
26
33
|
}
|
|
@@ -29,23 +36,33 @@ export function encryptSecret(plaintext, key) {
|
|
|
29
36
|
}
|
|
30
37
|
const iv = randomBytes(IV_BYTES);
|
|
31
38
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
39
|
+
if (aad !== undefined)
|
|
40
|
+
cipher.setAAD(Buffer.from(aad, 'utf8'));
|
|
32
41
|
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
33
42
|
const tag = cipher.getAuthTag();
|
|
34
|
-
|
|
43
|
+
const prefix = aad === undefined ? PREFIX_V1 : PREFIX_V2;
|
|
44
|
+
return prefix + Buffer.concat([iv, tag, ciphertext]).toString('base64');
|
|
35
45
|
}
|
|
36
|
-
|
|
46
|
+
/** `aad` is ignored for an `enc:v1:` envelope and required for an `enc:v2:` one. */
|
|
47
|
+
export function decryptSecret(envelope, key, aad) {
|
|
37
48
|
if (key.length !== 32) {
|
|
38
49
|
throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
|
|
39
50
|
}
|
|
40
51
|
if (!isEncryptedSecret(envelope)) {
|
|
41
52
|
throw new Error('Value is not an encrypted secret envelope');
|
|
42
53
|
}
|
|
43
|
-
const
|
|
54
|
+
const bound = envelope.startsWith(PREFIX_V2);
|
|
55
|
+
if (bound && aad === undefined) {
|
|
56
|
+
throw new Error('This secret is bound to a record: pass its id');
|
|
57
|
+
}
|
|
58
|
+
const buf = Buffer.from(envelope.slice(PREFIX_V2.length), 'base64');
|
|
44
59
|
const iv = buf.subarray(0, IV_BYTES);
|
|
45
60
|
const tag = buf.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
|
|
46
61
|
const ciphertext = buf.subarray(IV_BYTES + TAG_BYTES);
|
|
47
62
|
const decipher = createDecipheriv(ALGO, key, iv);
|
|
48
63
|
decipher.setAuthTag(tag);
|
|
64
|
+
if (bound)
|
|
65
|
+
decipher.setAAD(Buffer.from(aad, 'utf8'));
|
|
49
66
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
@@ -55,12 +72,15 @@ export function decryptSecret(envelope, key) {
|
|
|
55
72
|
* ciphertext nothing can decrypt.
|
|
56
73
|
*/
|
|
57
74
|
export function decodeSecretKey(raw) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const key = raw.trim();
|
|
76
|
+
if (/^[0-9a-fA-F]{64}$/.test(key))
|
|
77
|
+
return Buffer.from(key, 'hex');
|
|
78
|
+
// `Buffer.from` skips what it can't decode, so only a round trip proves the
|
|
79
|
+
// whole string was base64 rather than 32 bytes of it.
|
|
80
|
+
const buf = Buffer.from(key, 'base64');
|
|
81
|
+
if (buf.length === 32 && buf.toString('base64').replace(/=+$/, '') === key.replace(/=+$/, '')) {
|
|
62
82
|
return buf;
|
|
83
|
+
}
|
|
63
84
|
throw new Error('SELVA_AT_REST_KEY must be 32 bytes encoded as 64-char hex or base64. ' +
|
|
64
85
|
"Generate one with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"");
|
|
65
86
|
}
|
|
66
|
-
//# sourceMappingURL=secrets.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export declare const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
|
|
2
|
+
export declare const COMPUTE_TIMEOUT_MIN_MS = 1000;
|
|
3
|
+
export declare const COMPUTE_TIMEOUT_MAX_MS = 300000;
|
|
4
|
+
export declare const COMPUTE_RETRY_MAX = 5;
|
|
1
5
|
/**
|
|
2
6
|
* Compute servers belong to one of two scopes — never both.
|
|
3
7
|
*
|
|
@@ -29,7 +33,7 @@ interface ComputeServerCommon {
|
|
|
29
33
|
* they'd only throw away.
|
|
30
34
|
*/
|
|
31
35
|
hasApiKey?: boolean;
|
|
32
|
-
/** Default:
|
|
36
|
+
/** Default: {@link COMPUTE_TIMEOUT_DEFAULT_MS}. */
|
|
33
37
|
timeoutMs?: number;
|
|
34
38
|
/** Default: 0. */
|
|
35
39
|
retryCount?: number;
|
|
@@ -68,4 +72,3 @@ export interface ComputeConfig {
|
|
|
68
72
|
export declare function isPlatformServer(s: ComputeServerConfig): s is PlatformComputeServer;
|
|
69
73
|
export declare function isOrgServer(s: ComputeServerConfig): s is OrgComputeServer;
|
|
70
74
|
export {};
|
|
71
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
|
|
2
|
+
export const COMPUTE_TIMEOUT_MIN_MS = 1000;
|
|
3
|
+
export const COMPUTE_TIMEOUT_MAX_MS = 300000;
|
|
4
|
+
export const COMPUTE_RETRY_MAX = 5;
|
|
1
5
|
// ============================================================================
|
|
2
6
|
// Type guards
|
|
3
7
|
// ============================================================================
|
|
@@ -7,4 +11,3 @@ export function isPlatformServer(s) {
|
|
|
7
11
|
export function isOrgServer(s) {
|
|
8
12
|
return s.scope === 'org';
|
|
9
13
|
}
|
|
10
|
-
//# sourceMappingURL=types.js.map
|
|
@@ -10,18 +10,27 @@ export declare function serversVisibleTo(config: ComputeConfig, orgId: string |
|
|
|
10
10
|
* default. Returns `undefined` if neither is set or visible.
|
|
11
11
|
*/
|
|
12
12
|
export declare function defaultServerIdFor(config: ComputeConfig, orgId: string | null | undefined): string | undefined;
|
|
13
|
+
export interface ResolveOptions {
|
|
14
|
+
/** Per-definition pin. Falls through silently if not visible. */
|
|
15
|
+
definitionPin?: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* From servers already filtered for visibility, the pin if it is among them, then
|
|
19
|
+
* the default, then the first. Only for a caller holding the filtered list: use
|
|
20
|
+
* {@link resolveServerForOrg} when you have the config.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pickServer<T extends {
|
|
23
|
+
id: string;
|
|
24
|
+
}>(visible: readonly T[], defaultId: string | null | undefined, pin: string | null | undefined): T | undefined;
|
|
13
25
|
/**
|
|
14
26
|
* Resolves a definition's compute server, in order:
|
|
15
27
|
* 1. Definition pin (`computeServerId`) if visible to the project's org.
|
|
16
28
|
* 2. `orgDefaults[orgId]` if set.
|
|
17
29
|
* 3. Global `defaultServerId`.
|
|
30
|
+
* 4. The first visible server, mainly before a default is chosen.
|
|
18
31
|
*
|
|
19
32
|
* Throws if no usable server exists.
|
|
20
33
|
*/
|
|
21
|
-
export interface ResolveOptions {
|
|
22
|
-
/** Per-definition pin. Falls through silently if not visible. */
|
|
23
|
-
definitionPin?: string | null;
|
|
24
|
-
}
|
|
25
34
|
export declare function resolveServerForOrg(config: ComputeConfig, orgId: string | null | undefined, opts?: ResolveOptions): ComputeServerConfig;
|
|
26
35
|
/**
|
|
27
36
|
* The config as one org may see it: visible servers only, `orgDefaults` reduced
|
|
@@ -36,5 +45,7 @@ export declare function scopeConfigToOrg(config: ComputeConfig, orgId: string):
|
|
|
36
45
|
/** Lookup by id, ignoring scope or visibility — for admin contexts that display a server outside the visibility filter. */
|
|
37
46
|
export declare function findServerById(config: ComputeConfig, id: string): ComputeServerConfig | undefined;
|
|
38
47
|
export declare function platformServers(config: ComputeConfig): import("./types.js").PlatformComputeServer[];
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
/** What a compute settings form should send for a typed timeout. */
|
|
49
|
+
export declare function clampTimeoutMs(value: unknown): number;
|
|
50
|
+
/** What a compute settings form should send for a typed retry count. */
|
|
51
|
+
export declare function clampRetryCount(value: unknown): number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { COMPUTE_RETRY_MAX, COMPUTE_TIMEOUT_DEFAULT_MS, COMPUTE_TIMEOUT_MAX_MS, COMPUTE_TIMEOUT_MIN_MS, isPlatformServer } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Servers an org can see in pickers and use for solves:
|
|
4
4
|
* platform servers shared with this org (or `'all'`, or the global default)
|
|
@@ -36,25 +36,30 @@ export function defaultServerIdFor(config, orgId) {
|
|
|
36
36
|
}
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* From servers already filtered for visibility, the pin if it is among them, then
|
|
41
|
+
* the default, then the first. Only for a caller holding the filtered list: use
|
|
42
|
+
* {@link resolveServerForOrg} when you have the config.
|
|
43
|
+
*/
|
|
44
|
+
export function pickServer(visible, defaultId, pin) {
|
|
45
|
+
// A pin can go stale when an admin un-shares the server, so it falls through.
|
|
46
|
+
return visible.find((s) => s.id === pin) ?? visible.find((s) => s.id === defaultId) ?? visible[0];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves a definition's compute server, in order:
|
|
50
|
+
* 1. Definition pin (`computeServerId`) if visible to the project's org.
|
|
51
|
+
* 2. `orgDefaults[orgId]` if set.
|
|
52
|
+
* 3. Global `defaultServerId`.
|
|
53
|
+
* 4. The first visible server, mainly before a default is chosen.
|
|
54
|
+
*
|
|
55
|
+
* Throws if no usable server exists.
|
|
56
|
+
*/
|
|
39
57
|
export function resolveServerForOrg(config, orgId, opts = {}) {
|
|
40
58
|
const visible = serversVisibleTo(config, orgId);
|
|
41
|
-
|
|
59
|
+
const server = pickServer(visible, defaultServerIdFor(config, orgId), opts.definitionPin);
|
|
60
|
+
if (!server)
|
|
42
61
|
throw new Error('No compute servers are configured or visible for this org.');
|
|
43
|
-
|
|
44
|
-
if (opts.definitionPin) {
|
|
45
|
-
const pinned = visible.find((s) => s.id === opts.definitionPin);
|
|
46
|
-
if (pinned)
|
|
47
|
-
return pinned;
|
|
48
|
-
// Not visible — admin may have un-shared the server since the pin was set. Fall through.
|
|
49
|
-
}
|
|
50
|
-
const defaultId = defaultServerIdFor(config, orgId);
|
|
51
|
-
if (defaultId) {
|
|
52
|
-
const found = visible.find((s) => s.id === defaultId);
|
|
53
|
-
if (found)
|
|
54
|
-
return found;
|
|
55
|
-
}
|
|
56
|
-
// Last resort: first visible server. Mainly hit in tests and initial setup before a default is chosen.
|
|
57
|
-
return visible[0];
|
|
62
|
+
return server;
|
|
58
63
|
}
|
|
59
64
|
/**
|
|
60
65
|
* The config as one org may see it: visible servers only, `orgDefaults` reduced
|
|
@@ -74,7 +79,7 @@ export function scopeConfigToOrg(config, orgId) {
|
|
|
74
79
|
defaultServerId: config.defaultServerId && visibleIds.has(config.defaultServerId)
|
|
75
80
|
? config.defaultServerId
|
|
76
81
|
: undefined,
|
|
77
|
-
orgDefaults: orgDefault ? { [orgId]: orgDefault } : {}
|
|
82
|
+
orgDefaults: orgDefault && visibleIds.has(orgDefault) ? { [orgId]: orgDefault } : {}
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
/** Lookup by id, ignoring scope or visibility — for admin contexts that display a server outside the visibility filter. */
|
|
@@ -87,7 +92,12 @@ export function findServerById(config, id) {
|
|
|
87
92
|
export function platformServers(config) {
|
|
88
93
|
return config.servers.filter(isPlatformServer);
|
|
89
94
|
}
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
/** What a compute settings form should send for a typed timeout. */
|
|
96
|
+
export function clampTimeoutMs(value) {
|
|
97
|
+
const ms = Number(value) || COMPUTE_TIMEOUT_DEFAULT_MS;
|
|
98
|
+
return Math.min(COMPUTE_TIMEOUT_MAX_MS, Math.max(COMPUTE_TIMEOUT_MIN_MS, ms));
|
|
99
|
+
}
|
|
100
|
+
/** What a compute settings form should send for a typed retry count. */
|
|
101
|
+
export function clampRetryCount(value) {
|
|
102
|
+
return Math.min(COMPUTE_RETRY_MAX, Math.max(0, Number(value) || 0));
|
|
92
103
|
}
|
|
93
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/config.d.ts
CHANGED
|
@@ -60,5 +60,4 @@ export interface SelvaConfig {
|
|
|
60
60
|
export type SelvaConfigFactory = (env: Record<string, string | undefined>) => SelvaConfig;
|
|
61
61
|
export declare function isFlagEnabled(config: SelvaConfig, flag: keyof SelvaFlags): boolean;
|
|
62
62
|
/** Identity function for type inference and IDE autocomplete. */
|
|
63
|
-
export declare function defineConfig
|
|
64
|
-
//# sourceMappingURL=config.d.ts.map
|
|
63
|
+
export declare function defineConfig<T extends SelvaConfig | SelvaConfigFactory>(config: T): T;
|
package/dist/config.js
CHANGED
package/dist/context.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface RequestContext {
|
|
|
37
37
|
apiScope?: {
|
|
38
38
|
tokenId: string;
|
|
39
39
|
scopes: readonly ApiScope[];
|
|
40
|
+
expiresAt: string;
|
|
40
41
|
};
|
|
41
42
|
/** Opaque adapter payload — the Supabase adapter passes the user JWT through for RLS; local ignores it. */
|
|
42
43
|
adapterContext?: unknown;
|
|
@@ -61,10 +62,20 @@ export declare function isShareContext(ctx: RequestContext): boolean;
|
|
|
61
62
|
* definition on one channel, never instance authority.
|
|
62
63
|
*/
|
|
63
64
|
export declare function assertNotShareContext(ctx: RequestContext, what: string): void;
|
|
65
|
+
/** May `ctx` read `userId`'s platform permissions? Throws `ProviderError(403)` if not. */
|
|
66
|
+
export declare function assertCanReadPermissions(ctx: RequestContext, userId: string): void;
|
|
67
|
+
/** May `ctx` read several users' platform permissions? Throws `ProviderError(403)` if not. */
|
|
68
|
+
export declare function assertCanReadPermissionsBatch(ctx: RequestContext): void;
|
|
69
|
+
/** May `ctx` change platform permissions? Throws `ProviderError(403)` if not. */
|
|
70
|
+
export declare function assertCanManagePermissions(ctx: RequestContext): void;
|
|
71
|
+
/**
|
|
72
|
+
* For provider authors: may `ctx` read or change `userId`'s profile? Throws
|
|
73
|
+
* `ProviderError(403)` if not.
|
|
74
|
+
*/
|
|
75
|
+
export declare function assertCanAccessProfile(ctx: RequestContext, userId: string): void;
|
|
64
76
|
/**
|
|
65
77
|
* Returns the org the caller is acting as, or throws `ProviderError(403)`.
|
|
66
78
|
* Centralizes this guard so every store that scopes rows to an acting org fails
|
|
67
79
|
* the same consistent way, instead of leaking `org_id = undefined` into a query.
|
|
68
80
|
*/
|
|
69
81
|
export declare function requireActingOrg(ctx: RequestContext): string;
|
|
70
|
-
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.js
CHANGED
|
@@ -14,7 +14,7 @@ export const SYSTEM_CONTEXT = {
|
|
|
14
14
|
* `actingOrgId` returns false unless `instance_admin` or `system`.
|
|
15
15
|
*/
|
|
16
16
|
export function hasPermission(ctx, permission) {
|
|
17
|
-
if (ctx.system)
|
|
17
|
+
if (ctx.system && !isShareContext(ctx))
|
|
18
18
|
return true;
|
|
19
19
|
if (ctx.platformPermissions.includes('instance_admin'))
|
|
20
20
|
return true;
|
|
@@ -41,6 +41,56 @@ export function assertNotShareContext(ctx, what) {
|
|
|
41
41
|
throw new ProviderError(`Forbidden: a share link cannot ${what}`, 403);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
// For provider authors: `IPlatformPermissionStore` adapters whose backing store is
|
|
45
|
+
// too coarse to enforce this themselves. Reading one row and reading many agree, and
|
|
46
|
+
// read access is not authority: `set` stays `instance_admin`-only.
|
|
47
|
+
/** May `ctx` read `userId`'s platform permissions? Throws `ProviderError(403)` if not. */
|
|
48
|
+
export function assertCanReadPermissions(ctx, userId) {
|
|
49
|
+
assertNotShareContext(ctx, 'read permissions');
|
|
50
|
+
if (ctx.system)
|
|
51
|
+
return;
|
|
52
|
+
if (ctx.userId === userId)
|
|
53
|
+
return;
|
|
54
|
+
if (hasPermission(ctx, 'instance_admin'))
|
|
55
|
+
return;
|
|
56
|
+
if (hasPermission(ctx, 'manage_instance_users'))
|
|
57
|
+
return;
|
|
58
|
+
throw new ProviderError('Forbidden: cannot read another user’s permissions', 403);
|
|
59
|
+
}
|
|
60
|
+
/** May `ctx` read several users' platform permissions? Throws `ProviderError(403)` if not. */
|
|
61
|
+
export function assertCanReadPermissionsBatch(ctx) {
|
|
62
|
+
assertNotShareContext(ctx, 'read permissions');
|
|
63
|
+
if (ctx.system)
|
|
64
|
+
return;
|
|
65
|
+
if (hasPermission(ctx, 'instance_admin'))
|
|
66
|
+
return;
|
|
67
|
+
if (hasPermission(ctx, 'manage_instance_users'))
|
|
68
|
+
return;
|
|
69
|
+
throw new ProviderError('Forbidden: instance admin or manage_instance_users required', 403);
|
|
70
|
+
}
|
|
71
|
+
/** May `ctx` change platform permissions? Throws `ProviderError(403)` if not. */
|
|
72
|
+
export function assertCanManagePermissions(ctx) {
|
|
73
|
+
assertNotShareContext(ctx, 'manage permissions');
|
|
74
|
+
if (ctx.system)
|
|
75
|
+
return;
|
|
76
|
+
if (hasPermission(ctx, 'instance_admin'))
|
|
77
|
+
return;
|
|
78
|
+
throw new ProviderError('Forbidden: instance admin required', 403);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* For provider authors: may `ctx` read or change `userId`'s profile? Throws
|
|
82
|
+
* `ProviderError(403)` if not.
|
|
83
|
+
*/
|
|
84
|
+
export function assertCanAccessProfile(ctx, userId) {
|
|
85
|
+
assertNotShareContext(ctx, 'access user profiles');
|
|
86
|
+
if (ctx.system)
|
|
87
|
+
return;
|
|
88
|
+
if (ctx.userId === userId)
|
|
89
|
+
return;
|
|
90
|
+
if (hasPermission(ctx, 'instance_admin'))
|
|
91
|
+
return;
|
|
92
|
+
throw new ProviderError('Forbidden: cannot access another user’s profile', 403);
|
|
93
|
+
}
|
|
44
94
|
const PLATFORM_PERMISSION_VALUES = new Set(ALL_PLATFORM_PERMISSIONS);
|
|
45
95
|
function isPlatformPermission(p) {
|
|
46
96
|
return PLATFORM_PERMISSION_VALUES.has(p);
|
|
@@ -56,4 +106,3 @@ export function requireActingOrg(ctx) {
|
|
|
56
106
|
}
|
|
57
107
|
return ctx.actingOrgId;
|
|
58
108
|
}
|
|
59
|
-
//# sourceMappingURL=context.js.map
|
package/dist/data/index.d.ts
CHANGED
package/dist/data/index.js
CHANGED
package/dist/data/interface.d.ts
CHANGED
package/dist/data/interface.js
CHANGED
|
@@ -2,8 +2,7 @@ export type { DefinitionFileExt, DefinitionListItem, DefinitionRecord, Definitio
|
|
|
2
2
|
export { toDefinitionListItem } from './types.js';
|
|
3
3
|
export type { IDefinitionStore } from './interface.js';
|
|
4
4
|
export type { UISchema } from '@selvajs/schemas';
|
|
5
|
-
export { GH_EXTENSIONS,
|
|
5
|
+
export { GH_EXTENSIONS, COVER_IMAGE_CONTENT_TYPES } from './types.js';
|
|
6
6
|
export { definitionPaths } from './paths.js';
|
|
7
7
|
export type { UpdateMetadataInput } from './schemas.js';
|
|
8
|
-
export {
|
|
9
|
-
//# sourceMappingURL=index.d.ts.map
|
|
8
|
+
export { PublishVersionInputSchema, CreateDefinitionInputSchema, UpdateMetadataInputSchema, GuidSchema, toSourceDocumentId } from './schemas.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { toDefinitionListItem } from './types.js';
|
|
2
|
-
export { GH_EXTENSIONS,
|
|
2
|
+
export { GH_EXTENSIONS, COVER_IMAGE_CONTENT_TYPES } from './types.js';
|
|
3
3
|
export { definitionPaths } from './paths.js';
|
|
4
|
-
export {
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
|
4
|
+
export { PublishVersionInputSchema, CreateDefinitionInputSchema, UpdateMetadataInputSchema, GuidSchema, toSourceDocumentId } from './schemas.js';
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Path-segment safety check. Blocks traversal, separators, NUL, and empty
|
|
3
|
-
* segments. Allowed alphabet `[A-Za-z0-9._-]+` covers GUIDs and sanitized
|
|
4
|
-
* refs. Called by every helper so a malicious `guid` can't escape the
|
|
5
|
-
* `definitions/` prefix.
|
|
6
|
-
*/
|
|
7
|
-
function assertSafeKey(value, label) {
|
|
8
|
-
if (!value || !/^[A-Za-z0-9._-]+$/.test(value) || value === '.' || value === '..') {
|
|
9
|
-
throw new Error(`Unsafe ${label}: ${JSON.stringify(value)}`);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
1
|
+
import { assertSafeKey } from '../storage/safeKey.js';
|
|
12
2
|
export const definitionPaths = {
|
|
13
3
|
version: (guid, versionNumber, ext) => {
|
|
14
4
|
assertSafeKey(guid, 'guid');
|
|
@@ -26,4 +16,3 @@ export const definitionPaths = {
|
|
|
26
16
|
return `definitions/${guid}/`;
|
|
27
17
|
}
|
|
28
18
|
};
|
|
29
|
-
//# sourceMappingURL=paths.js.map
|
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const UUID_REGEX: RegExp;
|
|
3
3
|
export declare const GuidSchema: z.ZodString;
|
|
4
|
+
/** A Grasshopper document GUID as stores keep and compare it (lowercase), or null for anything else. */
|
|
5
|
+
export declare function toSourceDocumentId(value: unknown): string | null;
|
|
4
6
|
export declare const DefinitionStatusSchema: z.ZodEnum<{
|
|
5
7
|
draft: "draft";
|
|
6
8
|
published: "published";
|
|
7
9
|
archived: "archived";
|
|
8
10
|
}>;
|
|
9
|
-
export declare const DefinitionFileExtSchema: z.ZodEnum<{
|
|
10
|
-
gh: "gh";
|
|
11
|
-
ghx: "ghx";
|
|
12
|
-
}>;
|
|
13
|
-
export declare const DefinitionVersionSchema: z.ZodObject<{
|
|
14
|
-
id: z.ZodString;
|
|
15
|
-
definitionId: z.ZodString;
|
|
16
|
-
versionNumber: z.ZodNumber;
|
|
17
|
-
fileExt: z.ZodEnum<{
|
|
18
|
-
gh: "gh";
|
|
19
|
-
ghx: "ghx";
|
|
20
|
-
}>;
|
|
21
|
-
fileKey: z.ZodString;
|
|
22
|
-
originalFilename: z.ZodOptional<z.ZodString>;
|
|
23
|
-
uploadedBy: z.ZodString;
|
|
24
|
-
uploadedAt: z.ZodString;
|
|
25
|
-
changeNote: z.ZodOptional<z.ZodString>;
|
|
26
|
-
schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
-
schemaExtractedAt: z.ZodOptional<z.ZodString>;
|
|
28
|
-
}, z.core.$strip>;
|
|
29
11
|
export declare const DefinitionChannelSchema: z.ZodEnum<{
|
|
30
12
|
draft: "draft";
|
|
31
13
|
live: "live";
|
|
@@ -66,4 +48,3 @@ export type UpdateMetadataInput = z.infer<typeof UpdateMetadataInputSchema>;
|
|
|
66
48
|
export declare const PublishVersionInputSchema: z.ZodDefault<z.ZodObject<{
|
|
67
49
|
versionId: z.ZodOptional<z.ZodString>;
|
|
68
50
|
}, z.core.$strip>>;
|
|
69
|
-
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -4,30 +4,22 @@ export const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9
|
|
|
4
4
|
// regex is what keeps `__proto__` and `constructor` out of a key position.
|
|
5
5
|
// Relaxing it to a looser string re-opens prototype pollution in every store
|
|
6
6
|
// that does `config.definitions[guid]`.
|
|
7
|
-
|
|
7
|
+
//
|
|
8
|
+
// Lowercases because local keys by raw string while Postgres compares `uuid`
|
|
9
|
+
// columns case-insensitively, so one GUID in two cases is two records on local.
|
|
10
|
+
export const GuidSchema = z.string().toLowerCase().regex(UUID_REGEX, 'Invalid GUID format');
|
|
11
|
+
/** A Grasshopper document GUID as stores keep and compare it (lowercase), or null for anything else. */
|
|
12
|
+
export function toSourceDocumentId(value) {
|
|
13
|
+
return typeof value === 'string' && UUID_REGEX.test(value) ? value.toLowerCase() : null;
|
|
14
|
+
}
|
|
8
15
|
// Intentionally omits `pending` (see `DefinitionStatus` in ./types.ts): it's a
|
|
9
16
|
// create-internal state `DefinitionService.create` writes during the
|
|
10
17
|
// metadata-first window, and a client must never hand-set it through an update.
|
|
11
18
|
export const DefinitionStatusSchema = z.enum(['draft', 'published', 'archived']);
|
|
12
|
-
export const DefinitionFileExtSchema = z.enum(['gh', 'ghx']);
|
|
13
|
-
export const DefinitionVersionSchema = z.object({
|
|
14
|
-
id: GuidSchema,
|
|
15
|
-
definitionId: GuidSchema,
|
|
16
|
-
versionNumber: z.number().int().min(1),
|
|
17
|
-
fileExt: DefinitionFileExtSchema,
|
|
18
|
-
fileKey: z.string().min(1),
|
|
19
|
-
originalFilename: z.string().max(256).optional(),
|
|
20
|
-
uploadedBy: z.string().min(1),
|
|
21
|
-
uploadedAt: z.string(),
|
|
22
|
-
changeNote: z.string().max(1000).optional(),
|
|
23
|
-
// Rhino.Compute validates structural correctness at upload; this only asserts it's an object.
|
|
24
|
-
schema: z.record(z.string(), z.unknown()).optional(),
|
|
25
|
-
schemaExtractedAt: z.string().optional()
|
|
26
|
-
});
|
|
27
19
|
export const DefinitionChannelSchema = z.enum(['live', 'draft']);
|
|
28
20
|
/** HTTP-body validator for new-definition input. */
|
|
29
21
|
export const CreateDefinitionInputSchema = z.object({
|
|
30
|
-
displayName: z.string().min(1, 'Display name is required').max(256),
|
|
22
|
+
displayName: z.string().trim().min(1, 'Display name is required').max(256),
|
|
31
23
|
description: z.string().max(2000).optional(),
|
|
32
24
|
coverImage: z.string().max(2048).optional(),
|
|
33
25
|
category: z.string().max(128).optional(),
|
|
@@ -42,7 +34,7 @@ export const CreateDefinitionInputSchema = z.object({
|
|
|
42
34
|
* - value — set
|
|
43
35
|
*/
|
|
44
36
|
export const UpdateMetadataInputSchema = z.object({
|
|
45
|
-
displayName: z.string().min(1, 'Display name is required').max(256).optional(),
|
|
37
|
+
displayName: z.string().trim().min(1, 'Display name is required').max(256).optional(),
|
|
46
38
|
description: z.string().max(2000).nullable().optional(),
|
|
47
39
|
coverImage: z.string().max(2048).nullable().optional(),
|
|
48
40
|
category: z.string().max(128).nullable().optional(),
|
|
@@ -60,4 +52,3 @@ export const PublishVersionInputSchema = z
|
|
|
60
52
|
versionId: GuidSchema.optional()
|
|
61
53
|
})
|
|
62
54
|
.default({});
|
|
63
|
-
//# sourceMappingURL=schemas.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { UISchema } from '@selvajs/schemas';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { DefinitionChannelSchema } from './schemas.js';
|
|
2
4
|
export type DefinitionFileExt = 'gh' | 'ghx';
|
|
3
5
|
export declare const GH_EXTENSIONS: string[];
|
|
4
|
-
export declare const COVER_IMAGE_EXTENSIONS: string[];
|
|
5
|
-
export declare const ALLOWED_UPLOAD_EXTENSIONS: string[];
|
|
6
6
|
export declare const COVER_IMAGE_CONTENT_TYPES: Record<string, string>;
|
|
7
7
|
/**
|
|
8
8
|
* - `pending` — internal: metadata written, blob upload may be in flight.
|
|
@@ -81,6 +81,12 @@ export interface DefinitionRecord {
|
|
|
81
81
|
*/
|
|
82
82
|
liveVersionId: string | null;
|
|
83
83
|
draftVersionId: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* The Grasshopper document GUID (lowercase) from the schema Compute
|
|
86
|
+
* extracted at the last upload that carried one. A copied `.gh` keeps its
|
|
87
|
+
* GUID, so this suggests a match and never proves one.
|
|
88
|
+
*/
|
|
89
|
+
sourceDocumentId?: string;
|
|
84
90
|
createdAt: string;
|
|
85
91
|
updatedAt: string;
|
|
86
92
|
/** Soft-delete marker, independent of `status`: a store filters this out regardless of `archived`. */
|
|
@@ -107,6 +113,8 @@ export interface DefinitionListItem {
|
|
|
107
113
|
status: DefinitionStatus;
|
|
108
114
|
solveCount: number;
|
|
109
115
|
liveVersionId: string | null;
|
|
116
|
+
/** See {@link DefinitionRecord.sourceDocumentId}. */
|
|
117
|
+
sourceDocumentId: string | null;
|
|
110
118
|
updatedAt: string;
|
|
111
119
|
}
|
|
112
120
|
export declare function toDefinitionListItem(r: DefinitionRecord): DefinitionListItem;
|
|
@@ -132,6 +140,6 @@ export interface DefinitionRecordPatch {
|
|
|
132
140
|
solveCacheLimit?: number | null;
|
|
133
141
|
status?: DefinitionStatus;
|
|
134
142
|
ownerId?: string;
|
|
143
|
+
sourceDocumentId?: string | null;
|
|
135
144
|
}
|
|
136
|
-
export type DefinitionChannel =
|
|
137
|
-
//# sourceMappingURL=types.d.ts.map
|
|
145
|
+
export type DefinitionChannel = z.infer<typeof DefinitionChannelSchema>;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export const GH_EXTENSIONS = ['.gh', '.ghx'];
|
|
2
|
-
export const COVER_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
|
|
3
|
-
export const ALLOWED_UPLOAD_EXTENSIONS = [...GH_EXTENSIONS, ...COVER_IMAGE_EXTENSIONS];
|
|
4
2
|
export const COVER_IMAGE_CONTENT_TYPES = {
|
|
5
3
|
'.jpg': 'image/jpeg',
|
|
6
4
|
'.jpeg': 'image/jpeg',
|
|
@@ -20,7 +18,7 @@ export function toDefinitionListItem(r) {
|
|
|
20
18
|
status: r.status,
|
|
21
19
|
solveCount: r.solveCount,
|
|
22
20
|
liveVersionId: r.liveVersionId,
|
|
21
|
+
sourceDocumentId: r.sourceDocumentId ?? null,
|
|
23
22
|
updatedAt: r.updatedAt
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
|
-
//# sourceMappingURL=types.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Structured context attached to a reported error. All fields optional — a
|
|
3
3
|
* report can fire before any request context is built (e.g. an
|
|
4
4
|
* `unhandledRejection` with no active request). Deliberately NOT a
|
|
5
|
-
* `RequestContext`: reporters must never receive the opaque `
|
|
5
|
+
* `RequestContext`: reporters must never receive the opaque `adapterContext`, so
|
|
6
6
|
* only non-sensitive routing/identity fields are surfaced here.
|
|
7
7
|
*/
|
|
8
8
|
export interface ErrorContext {
|
|
@@ -37,4 +37,3 @@ export interface IErrorReporter {
|
|
|
37
37
|
export declare class NoopErrorReporter implements IErrorReporter {
|
|
38
38
|
capture(_error: unknown, _context?: ErrorContext): void;
|
|
39
39
|
}
|
|
40
|
-
//# sourceMappingURL=interface.d.ts.map
|
package/dist/errors/interface.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
* Thrown by providers to signal an expected failure the API layer forwards with
|
|
3
3
|
* a specific status. Unknown errors should propagate as plain `Error` and become 500s.
|
|
4
4
|
*/
|
|
5
|
+
declare const BRAND: unique symbol;
|
|
5
6
|
export declare class ProviderError extends Error {
|
|
6
7
|
readonly statusCode: number;
|
|
8
|
+
readonly [BRAND] = true;
|
|
9
|
+
/**
|
|
10
|
+
* Providers depend on this package, so a version mismatch installs two copies
|
|
11
|
+
* and plain `instanceof` would turn a 403 from one into a 500 in the other.
|
|
12
|
+
*/
|
|
13
|
+
static [Symbol.hasInstance](value: unknown): boolean;
|
|
7
14
|
constructor(message: string, statusCode?: number);
|
|
8
15
|
}
|
|
9
|
-
|
|
16
|
+
export {};
|