@vexcms/core 0.0.19 → 0.1.0-alpha.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.
- package/LICENSE +202 -0
- package/dist/.build +1 -0
- package/dist/access/buildChecks.d.ts +58 -0
- package/dist/access/buildChecks.d.ts.map +1 -0
- package/dist/access/canAccessAdminPanel.d.ts +53 -0
- package/dist/access/canAccessAdminPanel.d.ts.map +1 -0
- package/dist/access/compileConstraints.d.ts +155 -0
- package/dist/access/compileConstraints.d.ts.map +1 -0
- package/dist/access/config.d.ts +62 -0
- package/dist/access/config.d.ts.map +1 -0
- package/dist/access/constants.d.ts +95 -0
- package/dist/access/constants.d.ts.map +1 -0
- package/dist/access/constraintTypes.d.ts +337 -0
- package/dist/access/constraintTypes.d.ts.map +1 -0
- package/dist/access/createAccessQueryBuilder.d.ts +59 -0
- package/dist/access/createAccessQueryBuilder.d.ts.map +1 -0
- package/dist/access/createFilterConstraintBuilder.d.ts +26 -0
- package/dist/access/createFilterConstraintBuilder.d.ts.map +1 -0
- package/dist/access/createIndexConstraintBuilder.d.ts +40 -0
- package/dist/access/createIndexConstraintBuilder.d.ts.map +1 -0
- package/dist/access/hasPermission.d.ts +85 -0
- package/dist/access/hasPermission.d.ts.map +1 -0
- package/dist/access/index.d.ts +13 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/pickQueryIndex.d.ts +42 -0
- package/dist/access/pickQueryIndex.d.ts.map +1 -0
- package/dist/access/resolveAccessRule.d.ts +85 -0
- package/dist/access/resolveAccessRule.d.ts.map +1 -0
- package/dist/access/types.d.ts +738 -0
- package/dist/access/types.d.ts.map +1 -0
- package/dist/access/validateAccessConstraints.d.ts +57 -0
- package/dist/access/validateAccessConstraints.d.ts.map +1 -0
- package/dist/api/client.d.ts +20 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +117 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/convex.d.ts +296 -0
- package/dist/api/convex.d.ts.map +1 -0
- package/dist/api/create/client.d.ts +50 -0
- package/dist/api/create/client.d.ts.map +1 -0
- package/dist/api/create/server.d.ts +43 -0
- package/dist/api/create/server.d.ts.map +1 -0
- package/dist/api/depth.d.ts +39 -0
- package/dist/api/depth.d.ts.map +1 -0
- package/dist/api/find/client.d.ts +90 -0
- package/dist/api/find/client.d.ts.map +1 -0
- package/dist/api/find/server.d.ts +73 -0
- package/dist/api/find/server.d.ts.map +1 -0
- package/dist/api/get/client.d.ts +56 -0
- package/dist/api/get/client.d.ts.map +1 -0
- package/dist/api/get/server.d.ts +49 -0
- package/dist/api/get/server.d.ts.map +1 -0
- package/dist/api/globals/find.client.d.ts +20 -0
- package/dist/api/globals/find.client.d.ts.map +1 -0
- package/dist/api/globals/find.server.d.ts +29 -0
- package/dist/api/globals/find.server.d.ts.map +1 -0
- package/dist/api/globals/get.client.d.ts +46 -0
- package/dist/api/globals/get.client.d.ts.map +1 -0
- package/dist/api/globals/get.server.d.ts +55 -0
- package/dist/api/globals/get.server.d.ts.map +1 -0
- package/dist/api/globals/types.d.ts +65 -0
- package/dist/api/globals/types.d.ts.map +1 -0
- package/dist/api/globals/upsert.client.d.ts +24 -0
- package/dist/api/globals/upsert.client.d.ts.map +1 -0
- package/dist/api/globals/upsert.server.d.ts +47 -0
- package/dist/api/globals/upsert.server.d.ts.map +1 -0
- package/dist/api/populate.d.ts +23 -0
- package/dist/api/populate.d.ts.map +1 -0
- package/dist/api/remove/client.d.ts +58 -0
- package/dist/api/remove/client.d.ts.map +1 -0
- package/dist/api/remove/server.d.ts +65 -0
- package/dist/api/remove/server.d.ts.map +1 -0
- package/dist/api/search/client.d.ts +58 -0
- package/dist/api/search/client.d.ts.map +1 -0
- package/dist/api/search/server.d.ts +54 -0
- package/dist/api/search/server.d.ts.map +1 -0
- package/dist/api/server.d.ts +370 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +33 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api/types.d.ts +525 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/update/client.d.ts +53 -0
- package/dist/api/update/client.d.ts.map +1 -0
- package/dist/api/update/server.d.ts +47 -0
- package/dist/api/update/server.d.ts.map +1 -0
- package/dist/api/utils.d.ts +57 -0
- package/dist/api/utils.d.ts.map +1 -0
- package/dist/auth/mergeCollections.d.ts +28 -0
- package/dist/auth/mergeCollections.d.ts.map +1 -0
- package/dist/auth/types.d.ts +84 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/chunk-2DU7JDEX.js +1877 -0
- package/dist/chunk-2DU7JDEX.js.map +1 -0
- package/dist/chunk-2IB2DCN7.js +75 -0
- package/dist/chunk-2IB2DCN7.js.map +1 -0
- package/dist/collections/config.d.ts +28 -0
- package/dist/collections/config.d.ts.map +1 -0
- package/dist/collections/constants.d.ts +27 -0
- package/dist/collections/constants.d.ts.map +1 -0
- package/dist/collections/index.d.ts +7 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/indexFields.d.ts +20 -0
- package/dist/collections/indexFields.d.ts.map +1 -0
- package/dist/collections/interfaceGen.d.ts +59 -0
- package/dist/collections/interfaceGen.d.ts.map +1 -0
- package/dist/collections/internal/index.d.ts +2 -0
- package/dist/collections/internal/index.d.ts.map +1 -0
- package/dist/collections/types.d.ts +282 -0
- package/dist/collections/types.d.ts.map +1 -0
- package/dist/collections/utils.d.ts +71 -0
- package/dist/collections/utils.d.ts.map +1 -0
- package/dist/collections/validator.d.ts +73 -0
- package/dist/collections/validator.d.ts.map +1 -0
- package/dist/config/config.d.ts +43 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/sanitizeConfig.d.ts +82 -0
- package/dist/config/sanitizeConfig.d.ts.map +1 -0
- package/dist/config/types.d.ts +316 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/fields/array/config.d.ts +57 -0
- package/dist/fields/array/config.d.ts.map +1 -0
- package/dist/fields/array/index.d.ts +5 -0
- package/dist/fields/array/index.d.ts.map +1 -0
- package/dist/fields/array/inputSchema.d.ts +32 -0
- package/dist/fields/array/inputSchema.d.ts.map +1 -0
- package/dist/fields/array/types.d.ts +127 -0
- package/dist/fields/array/types.d.ts.map +1 -0
- package/dist/fields/array/validator.d.ts +30 -0
- package/dist/fields/array/validator.d.ts.map +1 -0
- package/dist/fields/baseTypes.d.ts +251 -0
- package/dist/fields/baseTypes.d.ts.map +1 -0
- package/dist/fields/blocks/config.d.ts +63 -0
- package/dist/fields/blocks/config.d.ts.map +1 -0
- package/dist/fields/blocks/index.d.ts +5 -0
- package/dist/fields/blocks/index.d.ts.map +1 -0
- package/dist/fields/blocks/inputSchema.d.ts +22 -0
- package/dist/fields/blocks/inputSchema.d.ts.map +1 -0
- package/dist/fields/blocks/types.d.ts +253 -0
- package/dist/fields/blocks/types.d.ts.map +1 -0
- package/dist/fields/blocks/validator.d.ts +32 -0
- package/dist/fields/blocks/validator.d.ts.map +1 -0
- package/dist/fields/checkbox/config.d.ts +46 -0
- package/dist/fields/checkbox/config.d.ts.map +1 -0
- package/dist/fields/checkbox/index.d.ts +5 -0
- package/dist/fields/checkbox/index.d.ts.map +1 -0
- package/dist/fields/checkbox/inputSchema.d.ts +27 -0
- package/dist/fields/checkbox/inputSchema.d.ts.map +1 -0
- package/dist/fields/checkbox/types.d.ts +67 -0
- package/dist/fields/checkbox/types.d.ts.map +1 -0
- package/dist/fields/checkbox/validator.d.ts +34 -0
- package/dist/fields/checkbox/validator.d.ts.map +1 -0
- package/dist/fields/color/config.d.ts +49 -0
- package/dist/fields/color/config.d.ts.map +1 -0
- package/dist/fields/color/convert.d.ts +72 -0
- package/dist/fields/color/convert.d.ts.map +1 -0
- package/dist/fields/color/formats.d.ts +38 -0
- package/dist/fields/color/formats.d.ts.map +1 -0
- package/dist/fields/color/index.d.ts +8 -0
- package/dist/fields/color/index.d.ts.map +1 -0
- package/dist/fields/color/inputSchema.d.ts +29 -0
- package/dist/fields/color/inputSchema.d.ts.map +1 -0
- package/dist/fields/color/types.d.ts +100 -0
- package/dist/fields/color/types.d.ts.map +1 -0
- package/dist/fields/color/utils.d.ts +100 -0
- package/dist/fields/color/utils.d.ts.map +1 -0
- package/dist/fields/color/validator.d.ts +27 -0
- package/dist/fields/color/validator.d.ts.map +1 -0
- package/dist/fields/constants.d.ts +108 -0
- package/dist/fields/constants.d.ts.map +1 -0
- package/dist/fields/date/config.d.ts +57 -0
- package/dist/fields/date/config.d.ts.map +1 -0
- package/dist/fields/date/index.d.ts +5 -0
- package/dist/fields/date/index.d.ts.map +1 -0
- package/dist/fields/date/inputSchema.d.ts +29 -0
- package/dist/fields/date/inputSchema.d.ts.map +1 -0
- package/dist/fields/date/types.d.ts +147 -0
- package/dist/fields/date/types.d.ts.map +1 -0
- package/dist/fields/date/validator.d.ts +34 -0
- package/dist/fields/date/validator.d.ts.map +1 -0
- package/dist/fields/group/config.d.ts +47 -0
- package/dist/fields/group/config.d.ts.map +1 -0
- package/dist/fields/group/index.d.ts +5 -0
- package/dist/fields/group/index.d.ts.map +1 -0
- package/dist/fields/group/inputSchema.d.ts +27 -0
- package/dist/fields/group/inputSchema.d.ts.map +1 -0
- package/dist/fields/group/types.d.ts +105 -0
- package/dist/fields/group/types.d.ts.map +1 -0
- package/dist/fields/group/validator.d.ts +41 -0
- package/dist/fields/group/validator.d.ts.map +1 -0
- package/dist/fields/index.d.ts +20 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/inputSchemas/index.d.ts +23 -0
- package/dist/fields/inputSchemas/index.d.ts.map +1 -0
- package/dist/fields/inputSchemas/utils.d.ts +25 -0
- package/dist/fields/inputSchemas/utils.d.ts.map +1 -0
- package/dist/fields/interfaceGen.d.ts +15 -0
- package/dist/fields/interfaceGen.d.ts.map +1 -0
- package/dist/fields/number/config.d.ts +48 -0
- package/dist/fields/number/config.d.ts.map +1 -0
- package/dist/fields/number/index.d.ts +5 -0
- package/dist/fields/number/index.d.ts.map +1 -0
- package/dist/fields/number/inputSchema.d.ts +23 -0
- package/dist/fields/number/inputSchema.d.ts.map +1 -0
- package/dist/fields/number/types.d.ts +100 -0
- package/dist/fields/number/types.d.ts.map +1 -0
- package/dist/fields/number/validator.d.ts +39 -0
- package/dist/fields/number/validator.d.ts.map +1 -0
- package/dist/fields/relationship/config.d.ts +45 -0
- package/dist/fields/relationship/config.d.ts.map +1 -0
- package/dist/fields/relationship/index.d.ts +5 -0
- package/dist/fields/relationship/index.d.ts.map +1 -0
- package/dist/fields/relationship/inputSchema.d.ts +31 -0
- package/dist/fields/relationship/inputSchema.d.ts.map +1 -0
- package/dist/fields/relationship/types.d.ts +126 -0
- package/dist/fields/relationship/types.d.ts.map +1 -0
- package/dist/fields/relationship/validator.d.ts +27 -0
- package/dist/fields/relationship/validator.d.ts.map +1 -0
- package/dist/fields/select/config.d.ts +59 -0
- package/dist/fields/select/config.d.ts.map +1 -0
- package/dist/fields/select/index.d.ts +5 -0
- package/dist/fields/select/index.d.ts.map +1 -0
- package/dist/fields/select/inputSchema.d.ts +28 -0
- package/dist/fields/select/inputSchema.d.ts.map +1 -0
- package/dist/fields/select/types.d.ts +89 -0
- package/dist/fields/select/types.d.ts.map +1 -0
- package/dist/fields/select/validator.d.ts +31 -0
- package/dist/fields/select/validator.d.ts.map +1 -0
- package/dist/fields/text/config.d.ts +48 -0
- package/dist/fields/text/config.d.ts.map +1 -0
- package/dist/fields/text/index.d.ts +5 -0
- package/dist/fields/text/index.d.ts.map +1 -0
- package/dist/fields/text/inputSchema.d.ts +24 -0
- package/dist/fields/text/inputSchema.d.ts.map +1 -0
- package/dist/fields/text/types.d.ts +132 -0
- package/dist/fields/text/types.d.ts.map +1 -0
- package/dist/fields/text/validator.d.ts +39 -0
- package/dist/fields/text/validator.d.ts.map +1 -0
- package/dist/fields/types.d.ts +135 -0
- package/dist/fields/types.d.ts.map +1 -0
- package/dist/fields/upload/config.d.ts +24 -0
- package/dist/fields/upload/config.d.ts.map +1 -0
- package/dist/fields/upload/index.d.ts +5 -0
- package/dist/fields/upload/index.d.ts.map +1 -0
- package/dist/fields/upload/inputSchema.d.ts +17 -0
- package/dist/fields/upload/inputSchema.d.ts.map +1 -0
- package/dist/fields/upload/types.d.ts +78 -0
- package/dist/fields/upload/types.d.ts.map +1 -0
- package/dist/fields/upload/validator.d.ts +16 -0
- package/dist/fields/upload/validator.d.ts.map +1 -0
- package/dist/fields/url/config.d.ts +49 -0
- package/dist/fields/url/config.d.ts.map +1 -0
- package/dist/fields/url/index.d.ts +5 -0
- package/dist/fields/url/index.d.ts.map +1 -0
- package/dist/fields/url/inputSchema.d.ts +31 -0
- package/dist/fields/url/inputSchema.d.ts.map +1 -0
- package/dist/fields/url/types.d.ts +70 -0
- package/dist/fields/url/types.d.ts.map +1 -0
- package/dist/fields/url/validator.d.ts +37 -0
- package/dist/fields/url/validator.d.ts.map +1 -0
- package/dist/fields/utils.d.ts +47 -0
- package/dist/fields/utils.d.ts.map +1 -0
- package/dist/fields/validators/index.d.ts +23 -0
- package/dist/fields/validators/index.d.ts.map +1 -0
- package/dist/fields/validators/utils.d.ts +24 -0
- package/dist/fields/validators/utils.d.ts.map +1 -0
- package/dist/framework.d.ts +227 -0
- package/dist/framework.d.ts.map +1 -0
- package/dist/globals/config/config.d.ts +1 -0
- package/dist/globals/config/config.d.ts.map +1 -0
- package/dist/globals/config/types.d.ts +1 -0
- package/dist/globals/config/types.d.ts.map +1 -0
- package/dist/globals/config.d.ts +47 -0
- package/dist/globals/config.d.ts.map +1 -0
- package/dist/globals/index.d.ts +5 -0
- package/dist/globals/index.d.ts.map +1 -0
- package/dist/globals/interfaceGen.d.ts +33 -0
- package/dist/globals/interfaceGen.d.ts.map +1 -0
- package/dist/globals/types.d.ts +175 -0
- package/dist/globals/types.d.ts.map +1 -0
- package/dist/globals/utils.d.ts +37 -0
- package/dist/globals/utils.d.ts.map +1 -0
- package/dist/index.d.ts +15 -3165
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2101 -3470
- package/dist/index.js.map +1 -1
- package/dist/media/api/index.d.ts +5 -0
- package/dist/media/api/index.d.ts.map +1 -0
- package/dist/media/api/mutations.d.ts +76 -0
- package/dist/media/api/mutations.d.ts.map +1 -0
- package/dist/media/api/queries.d.ts +28 -0
- package/dist/media/api/queries.d.ts.map +1 -0
- package/dist/media/api/server.d.ts +68 -0
- package/dist/media/api/server.d.ts.map +1 -0
- package/dist/media/api/types.d.ts +171 -0
- package/dist/media/api/types.d.ts.map +1 -0
- package/dist/media/config.d.ts +25 -0
- package/dist/media/config.d.ts.map +1 -0
- package/dist/media/index.d.ts +6 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/types.d.ts +332 -0
- package/dist/media/types.d.ts.map +1 -0
- package/dist/media/utils.d.ts +35 -0
- package/dist/media/utils.d.ts.map +1 -0
- package/dist/media/validators.d.ts +61 -0
- package/dist/media/validators.d.ts.map +1 -0
- package/dist/schema/generateVexSchema.d.ts +36 -0
- package/dist/schema/generateVexSchema.d.ts.map +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/migrate.d.ts +73 -0
- package/dist/schema/migrate.d.ts.map +1 -0
- package/dist/types/generateVexTypes.d.ts +41 -0
- package/dist/types/generateVexTypes.d.ts.map +1 -0
- package/dist/types/generated.d.ts +382 -0
- package/dist/types/generated.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils.d.ts +57 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +67 -24
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { CollectionConfig } from "../types";
|
|
2
|
+
import { GlobalConfig } from "../globals";
|
|
3
|
+
import type { AuthOrgDocument, AuthUserDocument, DocumentBySlug, GlobalDocumentBySlug, CollectionSlug, IndexFieldsBySlug, IndexNameFor } from "../types/generated";
|
|
4
|
+
import { ADMIN_CUSTOM_SUBJECTS, WILDCARD_KEY, type PermissionMode, type CrudAction, type DraftAction, type AdminCustomSubjectSlug, QueryAction } from "./constants";
|
|
5
|
+
import { ConvexError } from "convex/values";
|
|
6
|
+
import type { Expression, FilterBuilder, IndexRange, IndexRangeBuilder } from "convex/server";
|
|
7
|
+
import type { AccessConditionResult, DeclaredDoc, AccessPredicateBuilder, AccessQueryBuilder } from "./constraintTypes";
|
|
8
|
+
/**
|
|
9
|
+
* Any config that may contribute a resource subject: a collection or a global.
|
|
10
|
+
* Structural — the slug literal (and `versions.drafts`, when present) is all
|
|
11
|
+
* the type system reads from it.
|
|
12
|
+
*/
|
|
13
|
+
export type AccessResource = CollectionConfig | GlobalConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Single permission check result — boolean shorthand (all/none) or a
|
|
16
|
+
* field-mode object restricting the check to named fields.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Props passed to a permission callback.
|
|
21
|
+
*
|
|
22
|
+
* The `data` key exists only for data-carrying subjects; the `organization`
|
|
23
|
+
* key exists only when `orgCollectionSlug` is configured. Built with
|
|
24
|
+
* intersections (not conditional property types) so the keys are truly
|
|
25
|
+
* absent — not present-but-`never` — when unavailable.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam TData - Document type for the subject; `never` when the subject has no data.
|
|
28
|
+
* @typeParam TUser - User document shape (registry lookup on the user collection slug).
|
|
29
|
+
* @typeParam TOrg - Organization document shape; `never` when not configured.
|
|
30
|
+
*/
|
|
31
|
+
export type PermissionCallbackProps<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>> = {
|
|
32
|
+
user: TUser;
|
|
33
|
+
} & ([TData] extends [never] ? unknown : {
|
|
34
|
+
data: TData;
|
|
35
|
+
}) & ([TOrg] extends [never] ? unknown : {
|
|
36
|
+
organization: TOrg;
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* The plain permission check shapes shared by every action: a static boolean or a
|
|
40
|
+
* callback. The object form is {@link ConstrainedPermissionCheck}; this is the leaf a
|
|
41
|
+
* rule's optional `filter` property also accepts.
|
|
42
|
+
*
|
|
43
|
+
* A callback returning `undefined` is treated as deny — "inconclusive" must never
|
|
44
|
+
* read as an implicit allow. @internal
|
|
45
|
+
*/
|
|
46
|
+
type BasePermissionCheck<TData, TUser, TOrg> = boolean | ((props: PermissionCallbackProps<TData, TUser, TOrg>) => boolean | undefined);
|
|
47
|
+
/** A range callback as applied to a Convex query. @internal */
|
|
48
|
+
export type IndexRangeFn = (q: IndexRangeBuilder<any, any, 0>) => IndexRange;
|
|
49
|
+
/**
|
|
50
|
+
* A compiled access filter as applied to a Convex query — the output of
|
|
51
|
+
* `resolveAccessConstraint`.
|
|
52
|
+
*
|
|
53
|
+
* A thunk rather than an `Expression<boolean>` because a filter expression can only
|
|
54
|
+
* be built from the query's OWN `FilterBuilder`, which the resolver never sees: the
|
|
55
|
+
* caller supplies it inside `.filter((q) => …)`. Mirrors {@link IndexRangeFn}, which
|
|
56
|
+
* is deferred for the same reason and uses the same `any` table-info convention.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export type AccessFilterFn = (q: FilterBuilder<any>) => Expression<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* A concrete index a query will use: the output of `resolveAccessIndex` and,
|
|
63
|
+
* after arbitration, of `pickQueryIndex`. Both stages produce the same shape,
|
|
64
|
+
* so there is one type for both.
|
|
65
|
+
*
|
|
66
|
+
* Distinct from what the user *authors*: a rule declares its index inside
|
|
67
|
+
* `q.withIndex(name, (ix) => …)`, whose range is a function **of the caller**.
|
|
68
|
+
* Resolving one binds the caller in, leaving the plain
|
|
69
|
+
* `(q) => …` builder here and widening `name` to `string` — by resolve time
|
|
70
|
+
* the resource generic is gone. `AccessIndex` is the template; this is that
|
|
71
|
+
* template applied to one caller.
|
|
72
|
+
*
|
|
73
|
+
* `range` is optional because after arbitration the winner may be a *caller's*
|
|
74
|
+
* `withIndex`, and a caller may legitimately name an index with no range purely
|
|
75
|
+
* to order results. `buildQuery` already branches on this
|
|
76
|
+
* (`api/find/server.ts` — `range ? withIndex(name, range) : withIndex(name)`),
|
|
77
|
+
* so an absent range needs no placeholder.
|
|
78
|
+
*
|
|
79
|
+
* An access-sourced index always carries one, guaranteed by
|
|
80
|
+
* {@link AccessQueryBuilder.withIndex} requiring its range callback rather than
|
|
81
|
+
* restated as a second type here — a range-less access index would scan the whole
|
|
82
|
+
* table, since every document has an index entry (fields that are missing are
|
|
83
|
+
* indexed as `undefined`).
|
|
84
|
+
*/
|
|
85
|
+
export interface QueryIndex {
|
|
86
|
+
/** Index name to query. */
|
|
87
|
+
name: string;
|
|
88
|
+
/** Range to apply; omitted for an ordering-only caller index. */
|
|
89
|
+
range?: IndexRangeFn;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Props for a rule's `constraints` callback: the caller, optionally the
|
|
93
|
+
* organization, and `q` — the builder the rule records onto. No `data`:
|
|
94
|
+
* constraints run once per query, before any document is read (contrast
|
|
95
|
+
* {@link PermissionCallbackProps}, which the sibling `filter` property uses).
|
|
96
|
+
*
|
|
97
|
+
* `q`'s TYPE is what gates index pushdown per action (DD 14). A query-shaped
|
|
98
|
+
* action gets an {@link AccessQueryBuilder} — `filter` plus `withIndex`; every
|
|
99
|
+
* other action gets an {@link AccessPredicateBuilder}, the same shape minus
|
|
100
|
+
* `withIndex`, so a rule reads identically either way and `q.withIndex` is simply
|
|
101
|
+
* absent where there is no query to narrow. One property, two builder types, and
|
|
102
|
+
* nothing to discriminate at the object level.
|
|
103
|
+
*
|
|
104
|
+
* @typeParam TData - Document type constraints are typed against.
|
|
105
|
+
* @typeParam TUser - User document shape.
|
|
106
|
+
* @typeParam TOrg - Organization document shape; `never` if not configured.
|
|
107
|
+
* @typeParam TQ - The builder for this action: `AccessQueryBuilder` on query
|
|
108
|
+
* actions, `AccessPredicateBuilder` on mutations.
|
|
109
|
+
*/
|
|
110
|
+
export type ConstraintsCallbackProps<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>, TQ = AccessPredicateBuilder<TData>> = {
|
|
111
|
+
user: TUser;
|
|
112
|
+
q: TQ;
|
|
113
|
+
} & ([TOrg] extends [never] ? unknown : {
|
|
114
|
+
organization: TOrg;
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* A rule's constraint-recording callback.
|
|
118
|
+
*
|
|
119
|
+
* ONE signature returning `boolean | ConstraintResult`, never a union of two
|
|
120
|
+
* function types: verified against `tsc` that a union of differently-shaped
|
|
121
|
+
* callbacks breaks contextual typing of the destructured `props` — every
|
|
122
|
+
* parameter infers `any` instead of TypeScript picking a member. `boolean` is
|
|
123
|
+
* primitive and `ConstraintResult` is nominal, so discriminating the RESULT at
|
|
124
|
+
* runtime (`typeof result === "boolean"`) stays unambiguous.
|
|
125
|
+
*
|
|
126
|
+
* @typeParam TData - Document type constraints are typed against.
|
|
127
|
+
* @typeParam TUser - User document shape.
|
|
128
|
+
* @typeParam TOrg - Organization document shape; `never` if not configured.
|
|
129
|
+
* @typeParam TQ - The builder for this action.
|
|
130
|
+
*/
|
|
131
|
+
export type ConstraintsCallback<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>, TQ = AccessPredicateBuilder<TData>> = (props: ConstraintsCallbackProps<TData, TUser, TOrg, TQ>) => boolean | AccessConditionResult;
|
|
132
|
+
/**
|
|
133
|
+
* The constraint-builder object form of a permission check — ONE shape for every
|
|
134
|
+
* action.
|
|
135
|
+
*
|
|
136
|
+
* `constraints` narrows what gets read: compiled to a `withIndex` range when the
|
|
137
|
+
* rule called `q.withIndex(…)`, otherwise to a `.filter()` expression, and in
|
|
138
|
+
* either case interpreted per-document as a JS predicate
|
|
139
|
+
* (`compileConstraints`). `filter` is an OPTIONAL additional per-document check
|
|
140
|
+
* for what constraints cannot express — array membership, string operations,
|
|
141
|
+
* cross-table reads, all outside `FilterBuilder`'s surface, so they stay
|
|
142
|
+
* callbacks permanently.
|
|
143
|
+
*
|
|
144
|
+
* `filter` augments `constraints`; it never replaces it. A rule that declares
|
|
145
|
+
* only `constraints` is already checked per-document too, via
|
|
146
|
+
* `constraintsToPredicate` in `hasPermission`. A bare `filter`-only shape is
|
|
147
|
+
* therefore rejected — a callback with no descriptor cannot narrow a query, so
|
|
148
|
+
* write it as a bare callback instead and accept the full scan knowingly.
|
|
149
|
+
*
|
|
150
|
+
* Index pushdown is opted into INSIDE the callback
|
|
151
|
+
* ({@link AccessQueryBuilder.withIndex}), not by a sibling property. That is
|
|
152
|
+
* what lets `q` bind to one index's real field tuple and check field ORDER at
|
|
153
|
+
* compile time; a sibling property cannot be seen by its neighbour's callback
|
|
154
|
+
* type.
|
|
155
|
+
*
|
|
156
|
+
* @typeParam TData - Document type for the subject.
|
|
157
|
+
* @typeParam TUser - User document shape.
|
|
158
|
+
* @typeParam TOrg - Organization document shape; `never` if not configured.
|
|
159
|
+
* @typeParam TQ - The builder for this action.
|
|
160
|
+
*/
|
|
161
|
+
export interface ConstrainedPermissionCheck<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>, TQ = AccessPredicateBuilder<TData>> {
|
|
162
|
+
/** Narrows what gets read. Required — see the type doc. */
|
|
163
|
+
constraints: ConstraintsCallback<TData, TUser, TOrg, TQ>;
|
|
164
|
+
/** Optional per-document check augmenting `constraints`. Never a substitute. */
|
|
165
|
+
filter?: BasePermissionCheck<TData, TUser, TOrg>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Every check shape valid on **any** action, query-shaped or not: the plain leaf
|
|
169
|
+
* shapes plus the constraint-builder object form (DD 14).
|
|
170
|
+
*
|
|
171
|
+
* This is the tier `RolePermissions` hands a non-query action. The only difference
|
|
172
|
+
* from {@link PermissionCheck} is `q`'s type: a query action's `q` has `withIndex`,
|
|
173
|
+
* this one's does not.
|
|
174
|
+
*
|
|
175
|
+
* **Why `ConstrainedPermissionCheck` sits here rather than inside
|
|
176
|
+
* {@link BasePermissionCheck}.** Both are now valid on every action, so folding
|
|
177
|
+
* them looks tempting. It cannot be done: `BasePermissionCheck` is also the type of
|
|
178
|
+
* the `filter` property *inside* both object forms, so folding the composite into
|
|
179
|
+
* it makes `filter` able to hold another whole constrained check —
|
|
180
|
+
* `{ constraints, filter: { constraints, filter: … } }` — an infinite regress the
|
|
181
|
+
* compiler accepts and `hasPermission`'s resolver has no meaning for. `filter` is
|
|
182
|
+
* the per-document escape hatch for what constraints cannot express; a constraints
|
|
183
|
+
* object is not a leaf check. "Base" stays the irreducible shapes — a value or a
|
|
184
|
+
* function — and the composites point at it, never the other way round.
|
|
185
|
+
*
|
|
186
|
+
* @typeParam TData - Document type for the subject.
|
|
187
|
+
* @typeParam TUser - User document shape.
|
|
188
|
+
* @typeParam TOrg - Organization document shape; `never` if not configured.
|
|
189
|
+
*/
|
|
190
|
+
export type AnyActionPermissionCheck<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>> = BasePermissionCheck<TData, TUser, TOrg> | ConstrainedPermissionCheck<TData, TUser, TOrg, AccessPredicateBuilder<TData>>;
|
|
191
|
+
/**
|
|
192
|
+
* A single permission check on a **query-shaped** action: the plain leaf shapes,
|
|
193
|
+
* and the constraint form with `q` upgraded to an {@link AccessQueryBuilder} so
|
|
194
|
+
* `q.withIndex(…)` is available.
|
|
195
|
+
*
|
|
196
|
+
* The ONLY difference from {@link AnyActionPermissionCheck} is `q`'s type. Index
|
|
197
|
+
* pushdown is gated by giving a query action a builder
|
|
198
|
+
* that HAS `withIndex` and a mutation one that does not — so writing
|
|
199
|
+
* `q.withIndex(…)` on a create is a missing-method error at the exact call, rather
|
|
200
|
+
* than a whole-object shape rejection pointing at the wrong line (DD 14).
|
|
201
|
+
*
|
|
202
|
+
* A callback returning `undefined` is treated as deny.
|
|
203
|
+
*
|
|
204
|
+
* @typeParam TData - Document type for the subject.
|
|
205
|
+
* @typeParam TUser - User document shape.
|
|
206
|
+
* @typeParam TOrg - Organization document shape; `never` if not configured.
|
|
207
|
+
* @typeParam TIndexFields - The resource's index name → field tuple map, which
|
|
208
|
+
* `q.withIndex` resolves against.
|
|
209
|
+
*/
|
|
210
|
+
export type PermissionCheck<TData = unknown, TUser = Record<string, unknown>, TOrg = Record<string, unknown>, TIndexFields extends Record<string, readonly string[]> = Record<string, readonly string[]>> = BasePermissionCheck<TData, TUser, TOrg> | ConstrainedPermissionCheck<TData, TUser, TOrg, AccessQueryBuilder<TData, TIndexFields>>;
|
|
211
|
+
/**
|
|
212
|
+
* One entry in the subject registry: the action union, the data shape passed
|
|
213
|
+
* to callbacks, and the index registry `q.withIndex` resolves against.
|
|
214
|
+
*/
|
|
215
|
+
export interface SubjectEntry {
|
|
216
|
+
/** Union of actions this subject supports. */
|
|
217
|
+
action: string;
|
|
218
|
+
/** Actions on this subject whose `q` carries `withIndex`. `never` when none do. */
|
|
219
|
+
queryAction: string;
|
|
220
|
+
/** Document/context type; `never` for subjects without data. */
|
|
221
|
+
data: unknown;
|
|
222
|
+
/** Union of access-index names declared on this resource; `never` for non-indexable subjects. */
|
|
223
|
+
indexes: string;
|
|
224
|
+
/**
|
|
225
|
+
* The resource's declared indexes as name → field tuple, in declaration order.
|
|
226
|
+
* `{}` for subjects with no table to index (custom resources, admin subjects),
|
|
227
|
+
* which makes `q.withIndex` uncallable there rather than absent.
|
|
228
|
+
*
|
|
229
|
+
* Carries TUPLES, not just the names in `indexes`, because
|
|
230
|
+
* {@link AccessQueryBuilder.withIndex} binds `q` to one index's real field order
|
|
231
|
+
* — that is what makes positional constraint typing reachable.
|
|
232
|
+
*/
|
|
233
|
+
indexFields: Record<string, readonly string[]>;
|
|
234
|
+
}
|
|
235
|
+
/** Extract the slug literal from a resource config. @internal */
|
|
236
|
+
type ExtractSlug<T> = T extends {
|
|
237
|
+
slug: infer S extends string;
|
|
238
|
+
} ? S : never;
|
|
239
|
+
/**
|
|
240
|
+
* Document type for a slug via the generated registry (collections, then
|
|
241
|
+
* globals; wide fallback pre-generation). @internal
|
|
242
|
+
*/
|
|
243
|
+
type InferDocTypeFromSlug<S extends string> = S extends keyof DocumentBySlug ? DocumentBySlug[S] : S extends keyof GlobalDocumentBySlug ? GlobalDocumentBySlug[S] : Record<string, unknown>;
|
|
244
|
+
/**
|
|
245
|
+
* Document type for a resource config via its slug literal. @internal
|
|
246
|
+
*/
|
|
247
|
+
type InferDocType<T> = T extends {
|
|
248
|
+
slug: infer S extends string;
|
|
249
|
+
} ? InferDocTypeFromSlug<S> : Record<string, unknown>;
|
|
250
|
+
/**
|
|
251
|
+
* The document type a permission callback receives for resource `S`.
|
|
252
|
+
*
|
|
253
|
+
* Exported because a project writing its own composable access checks has to name
|
|
254
|
+
* this type. Accepts any slug string: {@link InferDocTypeFromSlug} resolves
|
|
255
|
+
* collections, then globals, then falls back wide, so a global slug works here too.
|
|
256
|
+
*
|
|
257
|
+
* @typeParam S - Resource slug.
|
|
258
|
+
*/
|
|
259
|
+
export type AccessDocFor<S extends string> = DeclaredDoc<InferDocTypeFromSlug<S>>;
|
|
260
|
+
/**
|
|
261
|
+
* Resource `S`'s index name → field tuple map, which `q.withIndex` resolves against.
|
|
262
|
+
*
|
|
263
|
+
* @typeParam S - Resource slug.
|
|
264
|
+
*/
|
|
265
|
+
export type AccessIndexFieldsFor<S extends string> = S extends keyof IndexFieldsBySlug ? IndexFieldsBySlug[S] : Record<string, readonly string[]>;
|
|
266
|
+
/**
|
|
267
|
+
* The exact check type `permissions[role][S][queryAction]` accepts.
|
|
268
|
+
*
|
|
269
|
+
* This is the return type for a project-defined helper on a query-shaped action
|
|
270
|
+
* (`read`, `readDrafts`) — the helper hands back a check, so nothing wraps it and the
|
|
271
|
+
* call site reads identically to writing the check inline.
|
|
272
|
+
*
|
|
273
|
+
* @typeParam S - Resource slug.
|
|
274
|
+
* @typeParam TUser - The project's user document type.
|
|
275
|
+
* @typeParam TOrg - The project's organization document type, or `never`.
|
|
276
|
+
*/
|
|
277
|
+
/**
|
|
278
|
+
* The field union a helper can use to READ a value off `AccessDocFor<S>`.
|
|
279
|
+
*
|
|
280
|
+
* A bare `keyof`, deliberately: a parameter bounded by it is *provably* a key of the
|
|
281
|
+
* document, so `data[field]` needs no cast. Wrapping it in a widening conditional —
|
|
282
|
+
* "fall back to `string` when the registry is unaugmented" — breaks exactly that,
|
|
283
|
+
* because TypeScript cannot prove an unreduced conditional is a key, and the read
|
|
284
|
+
* fails with "Type 'F' cannot be used to index type".
|
|
285
|
+
*
|
|
286
|
+
* The cost of staying indexable: for a slug the registry does not know, the document
|
|
287
|
+
* is the wide fallback whose index signature `DeclaredDoc` strips, leaving this
|
|
288
|
+
* `never`. A field-reading helper therefore only types after `vex generate` — already
|
|
289
|
+
* true of the rest of authoring an access config, since slugs and index names come
|
|
290
|
+
* from the same registry.
|
|
291
|
+
*
|
|
292
|
+
* @typeParam S - Resource slug.
|
|
293
|
+
*/
|
|
294
|
+
export type AccessDocFieldFor<S extends string> = keyof AccessDocFor<S> & string;
|
|
295
|
+
/**
|
|
296
|
+
* Fields on `S` that LEAD a declared index.
|
|
297
|
+
*
|
|
298
|
+
* Only a leading field can open an index range, so a helper that pushes a comparison
|
|
299
|
+
* into an index must restrict its field argument to these — otherwise it silently
|
|
300
|
+
* degrades to a full scan.
|
|
301
|
+
*
|
|
302
|
+
* @typeParam S - Resource slug.
|
|
303
|
+
*/
|
|
304
|
+
export type AccessIndexedFieldFor<S extends string> = {
|
|
305
|
+
[N in keyof AccessIndexFieldsFor<S>]: AccessIndexFieldsFor<S>[N] extends readonly [
|
|
306
|
+
infer F,
|
|
307
|
+
...unknown[]
|
|
308
|
+
] ? F : never;
|
|
309
|
+
}[keyof AccessIndexFieldsFor<S>] & string;
|
|
310
|
+
/**
|
|
311
|
+
* The index on `S` whose leading field is `F`.
|
|
312
|
+
*
|
|
313
|
+
* Lets a helper's runtime index lookup be typed as the index name it resolves to,
|
|
314
|
+
* rather than a bare `string` that `q.withIndex` refuses.
|
|
315
|
+
*
|
|
316
|
+
* @typeParam S - Resource slug.
|
|
317
|
+
* @typeParam F - A field that leads an index on `S`.
|
|
318
|
+
*/
|
|
319
|
+
export type AccessIndexNameFor<S extends string, F> = {
|
|
320
|
+
[N in keyof AccessIndexFieldsFor<S>]: AccessIndexFieldsFor<S>[N] extends readonly [
|
|
321
|
+
F,
|
|
322
|
+
...unknown[]
|
|
323
|
+
] ? N : never;
|
|
324
|
+
}[keyof AccessIndexFieldsFor<S>] & string;
|
|
325
|
+
/**
|
|
326
|
+
* The stored value type of field `F` on `S`.
|
|
327
|
+
*
|
|
328
|
+
* @typeParam S - Resource slug.
|
|
329
|
+
* @typeParam F - Field name.
|
|
330
|
+
*/
|
|
331
|
+
export type AccessFieldValueFor<S extends string, F> = F extends keyof AccessDocFor<S> ? AccessDocFor<S>[F] : never;
|
|
332
|
+
/**
|
|
333
|
+
* Indexed fields on `S` whose stored value admits `V`.
|
|
334
|
+
*
|
|
335
|
+
* Narrows a value-specific helper — a "published only" read, say — to the fields that
|
|
336
|
+
* can actually hold that value, so naming the wrong field is a compile error rather
|
|
337
|
+
* than a comparison that silently matches nothing. Handles both a scalar field and an
|
|
338
|
+
* array-valued one (`select` and relationship fields store arrays).
|
|
339
|
+
*
|
|
340
|
+
* @typeParam S - Resource slug.
|
|
341
|
+
* @typeParam V - The value the field must be able to hold.
|
|
342
|
+
*/
|
|
343
|
+
export type AccessIndexedFieldWithValue<S extends string, V> = {
|
|
344
|
+
[F in AccessIndexedFieldFor<S>]: NonNullable<AccessFieldValueFor<S, F>> extends readonly (infer E)[] ? [V] extends [E] ? F : never : [V] extends [NonNullable<AccessFieldValueFor<S, F>>] ? F : never;
|
|
345
|
+
}[AccessIndexedFieldFor<S>];
|
|
346
|
+
/**
|
|
347
|
+
* What a check builder needs from a resource config: the slug, to bind `S`, and the
|
|
348
|
+
* fields, to recover an index NAME from a field name at runtime.
|
|
349
|
+
*
|
|
350
|
+
* Structural rather than `CollectionConfig` so a `GlobalConfig` satisfies it too.
|
|
351
|
+
*
|
|
352
|
+
* @typeParam S - Resource slug.
|
|
353
|
+
*/
|
|
354
|
+
export type AccessResourceRef<S extends string> = {
|
|
355
|
+
readonly fields: Readonly<Record<string, {
|
|
356
|
+
readonly index?: string;
|
|
357
|
+
readonly type?: string;
|
|
358
|
+
}>>;
|
|
359
|
+
readonly slug: S;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* The check type `permissions[role][S][queryAction]` accepts, with the project's user
|
|
363
|
+
* and organization documents resolved from the generated registry.
|
|
364
|
+
*
|
|
365
|
+
* `vex generate` emits the slugs `defineAccess` was configured with
|
|
366
|
+
* ({@link AuthSlugs}), so the project's user and organization documents resolve from
|
|
367
|
+
* the registry — nothing needs passing in, and a project's helpers need no local
|
|
368
|
+
* type aliases at all. `AccessMutationCheck` is the non-query counterpart: its `q`
|
|
369
|
+
* is an {@link AccessPredicateBuilder}, no `withIndex`, because a single-document
|
|
370
|
+
* authorization has no range to narrow (DD 14).
|
|
371
|
+
*
|
|
372
|
+
* @typeParam S - Resource slug.
|
|
373
|
+
*/
|
|
374
|
+
export type AccessCheck<S extends string> = PermissionCheck<AccessDocFor<S>, AuthUserDocument, AuthOrgDocument, AccessIndexFieldsFor<S>>;
|
|
375
|
+
/**
|
|
376
|
+
* The check type a NON-query action (`create`/`update`/`delete`, custom actions)
|
|
377
|
+
* accepts, with user and organization resolved from the registry.
|
|
378
|
+
*
|
|
379
|
+
* @typeParam S - Resource slug.
|
|
380
|
+
*/
|
|
381
|
+
export type AccessMutationCheck<S extends string> = AnyActionPermissionCheck<AccessDocFor<S>, AuthUserDocument, AuthOrgDocument>;
|
|
382
|
+
/**
|
|
383
|
+
* Index-name union for a resource config via its slug literal, from the
|
|
384
|
+
* generated {@link IndexNameFor} registry (wide `string` fallback
|
|
385
|
+
* pre-generation). @internal
|
|
386
|
+
*/
|
|
387
|
+
type ExtractIndexNames<T> = T extends {
|
|
388
|
+
slug: infer S extends string;
|
|
389
|
+
} ? IndexNameFor<S> : string;
|
|
390
|
+
/**
|
|
391
|
+
* The declared-index field-tuple map for a resource config's slug — the shape
|
|
392
|
+
* {@link AccessQueryBuilder.withIndex} resolves against. `{}` pre-generation, so
|
|
393
|
+
* `withIndex` accepts nothing rather than accepting anything. @internal
|
|
394
|
+
*/
|
|
395
|
+
type ExtractIndexFields<T> = T extends {
|
|
396
|
+
slug: infer S extends string;
|
|
397
|
+
} ? S extends keyof IndexFieldsBySlug ? IndexFieldsBySlug[S] : PreGenerationIndexFields : PreGenerationIndexFields;
|
|
398
|
+
/**
|
|
399
|
+
* Pre-generation fallback for {@link ExtractIndexFields}: any index name, and a
|
|
400
|
+
* FIXED-LENGTH tuple of eight `string` slots per index.
|
|
401
|
+
*
|
|
402
|
+
* The length matters more than it looks. `ConstraintBuilder` terminates its chain
|
|
403
|
+
* on `PlusOne<N> extends TFields["length"]`, and a plain `readonly string[]` has
|
|
404
|
+
* `length: number` — which that test treats as already satisfied, so the chain would
|
|
405
|
+
* end after ONE constraint. A fixed-length tuple keeps `length` a numeric literal,
|
|
406
|
+
* so chaining behaves the same before and after `vex generate`; only field NAMES and
|
|
407
|
+
* per-field value types widen. Eight comfortably exceeds any realistic compound
|
|
408
|
+
* index. @internal
|
|
409
|
+
*/
|
|
410
|
+
type PreGenerationIndexFields = Record<string, readonly [string, string, string, string, string, string, string, string]>;
|
|
411
|
+
/** True when a resource config declares `versions.drafts: true`. @internal */
|
|
412
|
+
type HasDrafts<T> = T extends {
|
|
413
|
+
versions?: {
|
|
414
|
+
drafts?: infer D extends boolean;
|
|
415
|
+
};
|
|
416
|
+
} ? D extends true ? true : false : false;
|
|
417
|
+
/**
|
|
418
|
+
* Subject entry synthesized for the user/organization collections.
|
|
419
|
+
*
|
|
420
|
+
* Resolved from the slug via the generated registry, so these subjects exist with
|
|
421
|
+
* full document and index typing WITHOUT the collection being passed in
|
|
422
|
+
* `resources` — the adapter owns those tables and merges them later, inside
|
|
423
|
+
* `defineConfig`. CRUD-only: auth tables are never versioned. @internal
|
|
424
|
+
*/
|
|
425
|
+
type AuthSubjectEntry<S extends string, TCustomActions> = {
|
|
426
|
+
action: CrudAction | CustomActionsFor<S, TCustomActions>["query"] | CustomActionsFor<S, TCustomActions>["mutation"];
|
|
427
|
+
queryAction: QueryAction | CustomActionsFor<S, TCustomActions>["query"];
|
|
428
|
+
data: DeclaredDoc<InferDocTypeFromSlug<S>>;
|
|
429
|
+
indexes: IndexNameFor<S>;
|
|
430
|
+
indexFields: AccessIndexFieldsFor<S>;
|
|
431
|
+
};
|
|
432
|
+
/**
|
|
433
|
+
* The user subject, plus the organization subject when one is configured.
|
|
434
|
+
*
|
|
435
|
+
* The `string extends S` guards are load-bearing: for a non-literal slug the mapped
|
|
436
|
+
* type would become an INDEX SIGNATURE, making every subject key valid in
|
|
437
|
+
* `RolePermissions` and silently destroying typo-checking across the whole matrix.
|
|
438
|
+
* @internal
|
|
439
|
+
*/
|
|
440
|
+
type AuthSubjects<TUserSlug extends CollectionSlug, TOrgSlug extends CollectionSlug | undefined, TCustomActions> = (string extends TUserSlug ? unknown : {
|
|
441
|
+
[K in TUserSlug]: AuthSubjectEntry<K, TCustomActions>;
|
|
442
|
+
}) & (TOrgSlug extends string ? string extends TOrgSlug ? unknown : {
|
|
443
|
+
[K in TOrgSlug]: AuthSubjectEntry<K, TCustomActions>;
|
|
444
|
+
} : unknown);
|
|
445
|
+
/**
|
|
446
|
+
* The complete subject registry: resources (keyed by slug, CRUD + conditional
|
|
447
|
+
* draft actions), custom resources, and the core built-in subjects from
|
|
448
|
+
* {@link ADMIN_CUSTOM_SUBJECTS}.
|
|
449
|
+
*
|
|
450
|
+
* @typeParam TResources - Structural resource tuple (`{ slug, versions? }`).
|
|
451
|
+
* @typeParam TCustomResources - Custom resource declarations.
|
|
452
|
+
* @typeParam TUserSlug - User collection slug
|
|
453
|
+
* @typeParam TOrgSlug - Organization collection slug
|
|
454
|
+
* @typeParam TCustomActions extends Record<string, CustomActionsInput> = {}, // NEW
|
|
455
|
+
*/
|
|
456
|
+
export type SubjectMap<TResources extends readonly AccessResource[] = AccessResource[], TCustomResources extends Record<string, CustomResourceInput> = Record<string, CustomResourceInput>, TUserSlug extends CollectionSlug = CollectionSlug, TOrgSlug extends CollectionSlug | undefined = undefined, TCustomActions extends Partial<Record<TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>, CustomActionsInput>> = {}> = {
|
|
457
|
+
[R in TResources[number] as ExtractSlug<R>]: {
|
|
458
|
+
action: CrudAction | (HasDrafts<R> extends true ? DraftAction : never) | CustomActionsFor<ExtractSlug<R>, TCustomActions>["query"] | CustomActionsFor<ExtractSlug<R>, TCustomActions>["mutation"];
|
|
459
|
+
queryAction: QueryAction | CustomActionsFor<ExtractSlug<R>, TCustomActions>["query"];
|
|
460
|
+
data: DeclaredDoc<InferDocType<R>>;
|
|
461
|
+
indexes: ExtractIndexNames<R>;
|
|
462
|
+
indexFields: ExtractIndexFields<R>;
|
|
463
|
+
};
|
|
464
|
+
} & {
|
|
465
|
+
[K in keyof TCustomResources]: {
|
|
466
|
+
action: TCustomResources[K]["actions"][number];
|
|
467
|
+
queryAction: never;
|
|
468
|
+
data: TCustomResources[K]["data"] extends DataTypeCarrier<infer D> ? D : never;
|
|
469
|
+
indexes: never;
|
|
470
|
+
indexFields: {};
|
|
471
|
+
};
|
|
472
|
+
} & {
|
|
473
|
+
[K in AdminCustomSubjectSlug]: {
|
|
474
|
+
action: (typeof ADMIN_CUSTOM_SUBJECTS)[K]["actions"][number];
|
|
475
|
+
queryAction: never;
|
|
476
|
+
data: never;
|
|
477
|
+
indexes: never;
|
|
478
|
+
indexFields: {};
|
|
479
|
+
};
|
|
480
|
+
} & Omit<AuthSubjects<TUserSlug, TOrgSlug, TCustomActions>, ExtractSlug<TResources[number]> | keyof TCustomResources | AdminCustomSubjectSlug>;
|
|
481
|
+
/**
|
|
482
|
+
* Phantom carrier for a custom resource's `data` type. Created by
|
|
483
|
+
* {@link dataType}; never inspected at runtime.
|
|
484
|
+
*/
|
|
485
|
+
export interface DataTypeCarrier<T = never> {
|
|
486
|
+
readonly __phantom?: T;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Declares the data type callbacks (and `hasPermission` callers) receive for a
|
|
490
|
+
* custom resource.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```ts
|
|
494
|
+
* customResources: {
|
|
495
|
+
* reviews: { actions: ["approve", "reject"], data: dataType<{ queue: string }>() },
|
|
496
|
+
* }
|
|
497
|
+
* ```
|
|
498
|
+
* @returns a plain object '{}'
|
|
499
|
+
*/
|
|
500
|
+
export declare function dataType<T>(): DataTypeCarrier<T>;
|
|
501
|
+
/** Custom actions for one resource, split by whether `q` gets `withIndex`. */
|
|
502
|
+
export type CustomActionsInput = {
|
|
503
|
+
query?: readonly string[];
|
|
504
|
+
mutation?: readonly string[];
|
|
505
|
+
};
|
|
506
|
+
/** The custom query/mutation action unions declared for slug `S`. @internal */
|
|
507
|
+
type CustomActionsFor<S extends string, TCA> = S extends keyof TCA ? {
|
|
508
|
+
query: TCA[S] extends {
|
|
509
|
+
query: readonly (infer Q extends string)[];
|
|
510
|
+
} ? Q : never;
|
|
511
|
+
mutation: TCA[S] extends {
|
|
512
|
+
mutation: readonly (infer M extends string)[];
|
|
513
|
+
} ? M : never;
|
|
514
|
+
} : {
|
|
515
|
+
query: never;
|
|
516
|
+
mutation: never;
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* A custom (non-collection) subject declaration: its action list and an
|
|
520
|
+
* optional typed data carrier. One canonical form — no array shorthand.
|
|
521
|
+
*/
|
|
522
|
+
export type CustomResourceInput = {
|
|
523
|
+
actions: readonly string[];
|
|
524
|
+
data?: DataTypeCarrier<unknown>;
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* Per-role permission matrix, typed against the resolved {@link SubjectMap}.
|
|
528
|
+
*
|
|
529
|
+
* Each subject key accepts `boolean` (all actions) or a per-action map whose
|
|
530
|
+
* keys are that subject's action union plus the action-level wildcard
|
|
531
|
+
* ({@link WILDCARD_KEY}).
|
|
532
|
+
*
|
|
533
|
+
* **Only `withIndex` is gated on {@link QueryAction}** (DD 14). A query-shaped
|
|
534
|
+
* action gets the full {@link PermissionCheck}, whose `q` carries `withIndex` —
|
|
535
|
+
* there is a query to narrow.
|
|
536
|
+
* Every other action gets the plain shapes plus
|
|
537
|
+
* {@link ConstrainedPermissionCheck}: a create/update/delete has no query to
|
|
538
|
+
* narrow, but its constraints are still meaningful, interpreted per-document via
|
|
539
|
+
* `constraintsToPredicate`. Restricting the object form to query actions is what
|
|
540
|
+
* used to force a read+update pair to express one predicate twice — once as a
|
|
541
|
+
* constraint, once as a hand-written callback — which is the dual-expression
|
|
542
|
+
* footgun this design removes.
|
|
543
|
+
*
|
|
544
|
+
* The action-level wildcard stays plain: it spans actions of mixed shape, so a
|
|
545
|
+
* constraint written there could not be typed against one document consistently.
|
|
546
|
+
* The role-level wildcard is boolean-only.
|
|
547
|
+
* Precedence: explicit action > subject wildcard > role wildcard > `defaults`.
|
|
548
|
+
*
|
|
549
|
+
* @typeParam TSubjects - The resolved {@link SubjectMap}.
|
|
550
|
+
* @typeParam TUser - User document shape.
|
|
551
|
+
* @typeParam TOrg - Organization document shape, or `never`.
|
|
552
|
+
*/
|
|
553
|
+
export type RolePermissions<TSubjects extends Record<string, SubjectEntry>, TUser = Record<string, unknown>, TOrg = never, TUserSlug extends CollectionSlug = CollectionSlug, TOrgSlug extends CollectionSlug | undefined = never> = {
|
|
554
|
+
[S in keyof TSubjects | TUserSlug | (TOrgSlug extends string ? TOrgSlug : never)]?: boolean | ({
|
|
555
|
+
[A in TSubjects[S]["action"]]?: A extends QueryAction ? PermissionCheck<TSubjects[S]["data"], TUser, TOrg, TSubjects[S]["indexFields"]> : A extends Exclude<CrudAction | DraftAction, QueryAction> | typeof WILDCARD_KEY ? AnyActionPermissionCheck<TSubjects[S]["data"], TUser, TOrg> : A extends TSubjects[S]["queryAction"] ? PermissionCheck<TSubjects[S]["data"], TUser, TOrg, TSubjects[S]["indexFields"]> : AnyActionPermissionCheck<TSubjects[S]["data"], TUser, TOrg>;
|
|
556
|
+
} & {
|
|
557
|
+
[W in typeof WILDCARD_KEY]?: AnyActionPermissionCheck<TSubjects[S]["data"], TUser, TOrg>;
|
|
558
|
+
});
|
|
559
|
+
} & {
|
|
560
|
+
[W in typeof WILDCARD_KEY]?: boolean;
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Input shape for the `defineAccess` builder.
|
|
564
|
+
*
|
|
565
|
+
* @typeParam TRoles - Tuple of role name literals.
|
|
566
|
+
* @typeParam TResources - Structural resource tuple (`{ slug, versions? }`).
|
|
567
|
+
* @typeParam TCustomResources - Custom resource declarations.
|
|
568
|
+
* @typeParam TUserCollection - `{ slug }` shape naming the user collection.
|
|
569
|
+
* @typeParam TOrgCollection - `{ slug }` shape naming the org collection; `undefined` if absent.
|
|
570
|
+
*
|
|
571
|
+
* @see {@link VexAccessConfig} for the resolved runtime shape.
|
|
572
|
+
*/
|
|
573
|
+
export interface VexAccessConfigInput<TRoles extends readonly string[], TResources extends readonly AccessResource[] = readonly AccessResource[], TCustomResources extends Record<string, CustomResourceInput> = {}, TUserSlug extends CollectionSlug = CollectionSlug, TOrgSlug extends CollectionSlug | undefined = undefined, TCustomActions extends Partial<Record<TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>, CustomActionsInput>> = {}> {
|
|
574
|
+
/** Default: `true`. Turn access control on or off. */
|
|
575
|
+
enabled?: boolean;
|
|
576
|
+
/**
|
|
577
|
+
* OPTIONAL. Role applied when a caller's roles resolve empty — no session,
|
|
578
|
+
* or an anonymous user (e.g. Better Auth anonymous plugin) whose
|
|
579
|
+
* `userRolesField` is unset. Explicit roles always win over this fallback.
|
|
580
|
+
* Omitted → empty roles deny, exactly as before.
|
|
581
|
+
*/
|
|
582
|
+
anonRole?: TRoles[number];
|
|
583
|
+
/** Role identifiers; keys of the `permissions` matrix. */
|
|
584
|
+
roles: TRoles;
|
|
585
|
+
/** Collections/globals contributing subjects, keyed by slug. */
|
|
586
|
+
resources: TResources;
|
|
587
|
+
/**
|
|
588
|
+
* Extra actions per resource, beyond CRUD. Keys must be declared resource slugs
|
|
589
|
+
* (or the user/org collection slugs).
|
|
590
|
+
*
|
|
591
|
+
* Three constituents, each load-bearing:
|
|
592
|
+
*
|
|
593
|
+
* 1. `TCustomActions` — the inference site; carries the caller's literal declaration
|
|
594
|
+
* into the phantom `SubjectMap`.
|
|
595
|
+
* 2. `Partial<Record<slugs, …>>` — the COMPLETION source. Key completions come from
|
|
596
|
+
* the property's contextual type, and a bare unresolved type parameter offers
|
|
597
|
+
* nothing; this constituent names the slug union directly, which is resolvable in
|
|
598
|
+
* the first inference round (`resources` and the slugs are not context-sensitive).
|
|
599
|
+
* Redundant for checking — the bound already constrains values.
|
|
600
|
+
* 3. The exactness map — the TYPO guard. The `Partial<Record<…>>` bound cannot reject
|
|
601
|
+
* a bad key on its own: an all-optional target is a "weak type", so TypeScript
|
|
602
|
+
* errors only when the object shares ZERO keys with it — one valid entry beside a
|
|
603
|
+
* garbage key passes, because structural subtyping permits extra properties and
|
|
604
|
+
* per-key excess checking does not fire against a generic-constrained inference.
|
|
605
|
+
* Mapping every key NOT in the slug union to `never` makes the garbage entry's
|
|
606
|
+
* value unassignable AT ITS OWN KEY, restoring both the error and its location.
|
|
607
|
+
*/
|
|
608
|
+
customActions?: TCustomActions & Partial<Record<TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>, CustomActionsInput>> & {
|
|
609
|
+
[K in Exclude<keyof TCustomActions, TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>>]: never;
|
|
610
|
+
};
|
|
611
|
+
/**
|
|
612
|
+
* Custom, non-resource subjects with arbitrary action unions and optional
|
|
613
|
+
* typed data. Example: `{ apiKeys: { actions: ["create", "revoke"] } }`.
|
|
614
|
+
*/
|
|
615
|
+
customResources?: TCustomResources;
|
|
616
|
+
/**
|
|
617
|
+
* Slug of the collection whose documents are `user` in callbacks. A plain
|
|
618
|
+
* slug string — the full collection often does not exist at authoring time
|
|
619
|
+
* (auth-adapter collections merge later, inside `defineConfig`); the
|
|
620
|
+
* document type resolves from the generated registry by slug.
|
|
621
|
+
*/
|
|
622
|
+
userCollectionSlug: TUserSlug;
|
|
623
|
+
/**
|
|
624
|
+
* REQUIRED. The field on the user document that holds the user's role(s).
|
|
625
|
+
* Value may be `string` or `string[]`; `hasPermission` normalizes both.
|
|
626
|
+
* Callers never pass roles separately — they always ride the user document.
|
|
627
|
+
*/
|
|
628
|
+
userRolesField: string;
|
|
629
|
+
/**
|
|
630
|
+
* Slug of the organization collection. When present, `organization` is
|
|
631
|
+
* available (typed via the registry) in every permission callback; when
|
|
632
|
+
* omitted, callbacks have no `organization` key.
|
|
633
|
+
*/
|
|
634
|
+
orgCollectionSlug?: TOrgSlug;
|
|
635
|
+
/**
|
|
636
|
+
* Permission matrix: role → subject → check. See {@link RolePermissions}
|
|
637
|
+
* for shapes and wildcard semantics.
|
|
638
|
+
*/
|
|
639
|
+
permissions: Record<TRoles[number], RolePermissions<SubjectMap<TResources, TCustomResources, TUserSlug, TOrgSlug, TCustomActions>, InferDocTypeFromSlug<TUserSlug>, TOrgSlug extends string ? InferDocTypeFromSlug<TOrgSlug> : never, TUserSlug, TOrgSlug>>;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Resolved access configuration returned by `defineAccess` — the runtime
|
|
643
|
+
* shape consumed by `hasPermission`.
|
|
644
|
+
*
|
|
645
|
+
* Deliberately VALUE-LEVEL TYPE-ERASED: every call-site guarantee
|
|
646
|
+
* (`resource`/`action` unions, callback `data` types, field keys) rides the
|
|
647
|
+
* phantom `TSubjects` parameter, while the stored fields are wide. This is
|
|
648
|
+
* what lets any concrete config assign to plain `VexAccessConfig` (e.g. the
|
|
649
|
+
* `access` field on `VexConfig`) — a fully-generic config type would be
|
|
650
|
+
* unassignable to any common supertype, because permission callbacks are
|
|
651
|
+
* contravariant in their `data` parameter.
|
|
652
|
+
*
|
|
653
|
+
* @typeParam TSubjects - Phantom {@link SubjectMap} carried for `hasPermission` inference.
|
|
654
|
+
*/
|
|
655
|
+
export interface VexAccessConfig<TSubjects extends Record<string, SubjectEntry> = Record<string, SubjectEntry>, TResources extends readonly AccessResource[] = readonly AccessResource[], TUserSlug extends CollectionSlug = CollectionSlug, TOrgSlug extends CollectionSlug | undefined = CollectionSlug | undefined, TCustomActions extends Partial<Record<TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>, CustomActionsInput>> = Partial<Record<TResources[number]["slug"] | TUserSlug | Extract<TOrgSlug, string>, CustomActionsInput>>> {
|
|
656
|
+
/** Default: `true`. Turn access control on or off. */
|
|
657
|
+
enabled: boolean;
|
|
658
|
+
/**
|
|
659
|
+
* The allback role when there is no user.
|
|
660
|
+
*/
|
|
661
|
+
anonRole?: string;
|
|
662
|
+
/** Role names known to the system. */
|
|
663
|
+
roles: readonly string[];
|
|
664
|
+
/** Collections/globals contributing subjects, keyed by slug. */
|
|
665
|
+
resources: TResources;
|
|
666
|
+
/**
|
|
667
|
+
* Custom actions per subject slug, as declared in `defineAccess`. Carried on the
|
|
668
|
+
* RESOLVED config purely so request-time code can tell a declared verb from a typo:
|
|
669
|
+
* an undeclared action resolves through `defaultPermissionMode` (default `allow`),
|
|
670
|
+
* so without this record a misspelled `access.action` silently widens access.
|
|
671
|
+
*/
|
|
672
|
+
customActions?: TCustomActions;
|
|
673
|
+
/**
|
|
674
|
+
* Posture for undeclared role/subject/action combinations. Always
|
|
675
|
+
* {@link PERMISSION_MODES.deny} — `defineAccess` pins it and no input field sets it.
|
|
676
|
+
*
|
|
677
|
+
* Retained as a field rather than inlined because `hasPermission` and
|
|
678
|
+
* `resolveAccessRule` branch on it, and an allow posture is expressible as a
|
|
679
|
+
* role-level `"*": true` if it is ever wanted back — per-role and greppable,
|
|
680
|
+
* which a global default never was.
|
|
681
|
+
*
|
|
682
|
+
* @internal
|
|
683
|
+
*/
|
|
684
|
+
defaultPermissionMode: PermissionMode;
|
|
685
|
+
/** Slug of the user collection. */
|
|
686
|
+
userCollectionSlug: TUserSlug;
|
|
687
|
+
/** Field on the user document holding role(s) (`string | string[]`). */
|
|
688
|
+
userRolesField: string;
|
|
689
|
+
/** Slug of the organization collection, when configured. */
|
|
690
|
+
orgCollectionSlug?: TOrgSlug;
|
|
691
|
+
/**
|
|
692
|
+
* The permission matrix as authored (checks may be booleans, field-mode
|
|
693
|
+
* objects, or callbacks). Type-erased for storage; `defineAccess` fully
|
|
694
|
+
* type-checks it at authoring time.
|
|
695
|
+
*/
|
|
696
|
+
permissions: Record<string, Record<string, unknown>>;
|
|
697
|
+
/**
|
|
698
|
+
* Phantom field carrying {@link SubjectMap} for inference. Optional and
|
|
699
|
+
* never assigned at runtime.
|
|
700
|
+
*/
|
|
701
|
+
readonly __subjects?: TSubjects;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Thrown by `hasPermission` when `throwOnDenied: true` and access is denied.
|
|
705
|
+
* Carries the subject and action.
|
|
706
|
+
*/
|
|
707
|
+
export declare class VexAccessError extends ConvexError<{
|
|
708
|
+
code: "ACCESS_DENIED";
|
|
709
|
+
resource: string;
|
|
710
|
+
action: string;
|
|
711
|
+
message: string;
|
|
712
|
+
}> {
|
|
713
|
+
/** The subject on which access was denied. */
|
|
714
|
+
resource: string;
|
|
715
|
+
/** The denied action. */
|
|
716
|
+
action: string;
|
|
717
|
+
/**
|
|
718
|
+
* @param options — Structured denial context.
|
|
719
|
+
* @param options.message — Human-readable error message.
|
|
720
|
+
* @param options.resource — Subject name.
|
|
721
|
+
* @param options.action — Action name.
|
|
722
|
+
*/
|
|
723
|
+
constructor(options: {
|
|
724
|
+
message?: string;
|
|
725
|
+
resource: string;
|
|
726
|
+
action: string;
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Thrown by `defineAccess` on hard configuration errors (custom resource key
|
|
731
|
+
* colliding with a resource slug; empty `actions` array).
|
|
732
|
+
*/
|
|
733
|
+
export declare class VexAccessConfigError extends Error {
|
|
734
|
+
/** @param message — Human-readable description of the configuration error. */
|
|
735
|
+
constructor(message: string);
|
|
736
|
+
}
|
|
737
|
+
export {};
|
|
738
|
+
//# sourceMappingURL=types.d.ts.map
|