@drhomet/mithaq 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSES/payload.txt +22 -0
- package/LICENSES/ponytail.txt +21 -0
- package/dist/asas/actor.d.ts +96 -0
- package/dist/asas/actor.js +114 -0
- package/dist/asas/build.d.ts +231 -0
- package/dist/asas/build.js +787 -0
- package/dist/asas/cli/mithaq.d.ts +2 -0
- package/dist/asas/cli/mithaq.js +145 -0
- package/dist/asas/cli/upgrade.d.ts +20 -0
- package/dist/asas/cli/upgrade.js +147 -0
- package/dist/asas/http/fetch.d.ts +23 -0
- package/dist/asas/http/fetch.js +115 -0
- package/dist/asas/http/orpc.d.ts +43 -0
- package/dist/asas/http/orpc.js +135 -0
- package/dist/asas/runner/bulk.d.ts +20 -0
- package/dist/asas/runner/bulk.js +191 -0
- package/dist/asas/runner/errors.d.ts +60 -0
- package/dist/asas/runner/errors.js +59 -0
- package/dist/asas/runner/read.d.ts +68 -0
- package/dist/asas/runner/read.js +579 -0
- package/dist/asas/runner/runtime.d.ts +21 -0
- package/dist/asas/runner/runtime.js +8 -0
- package/dist/asas/runner/steps.d.ts +400 -0
- package/dist/asas/runner/steps.js +271 -0
- package/dist/asas/runner/tenant.d.ts +54 -0
- package/dist/asas/runner/tenant.js +213 -0
- package/dist/asas/runner/write.d.ts +444 -0
- package/dist/asas/runner/write.js +791 -0
- package/dist/auth/better-auth/access.d.ts +15 -0
- package/dist/auth/better-auth/access.js +71 -0
- package/dist/auth/better-auth/generate.d.ts +29 -0
- package/dist/auth/better-auth/generate.js +310 -0
- package/dist/auth/better-auth/index.d.ts +92 -0
- package/dist/auth/better-auth/index.js +81 -0
- package/dist/auth/better-auth/tenant.d.ts +18 -0
- package/dist/auth/better-auth/tenant.js +26 -0
- package/dist/auth/local/index.d.ts +285 -0
- package/dist/auth/local/index.js +736 -0
- package/dist/base/decimal.d.ts +9 -0
- package/dist/base/decimal.js +16 -0
- package/dist/base/fields.d.ts +123 -0
- package/dist/base/fields.js +51 -0
- package/dist/base/index.d.ts +46 -0
- package/dist/base/index.js +5 -0
- package/dist/base/kinds.d.ts +54 -0
- package/dist/base/kinds.js +78 -0
- package/dist/base/rules.d.ts +73 -0
- package/dist/base/rules.js +80 -0
- package/dist/drizzle/index.d.ts +438 -0
- package/dist/drizzle/index.js +832 -0
- package/dist/drizzle/kinds.d.ts +27 -0
- package/dist/drizzle/kinds.js +41 -0
- package/dist/drizzle/migrate.d.ts +133 -0
- package/dist/drizzle/migrate.js +1285 -0
- package/dist/drizzle/proxy/index.d.ts +1 -0
- package/dist/drizzle/proxy/index.js +1 -0
- package/dist/drizzle/proxy/node-postgres.d.ts +1 -0
- package/dist/drizzle/proxy/node-postgres.js +1 -0
- package/dist/drizzle/proxy/pg-core.d.ts +1 -0
- package/dist/drizzle/proxy/pg-core.js +1 -0
- package/dist/drizzle/rules.d.ts +336 -0
- package/dist/drizzle/rules.js +44 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +36 -0
- package/dist/mithaq/define.d.ts +288 -0
- package/dist/mithaq/define.js +43 -0
- package/dist/mithaq/generate-messages.d.ts +38 -0
- package/dist/mithaq/generate-messages.js +234 -0
- package/dist/mithaq/language.d.ts +335 -0
- package/dist/mithaq/language.js +76 -0
- package/dist/mithaq/lock.d.ts +71 -0
- package/dist/mithaq/lock.js +329 -0
- package/dist/mithaq/messages.d.ts +55 -0
- package/dist/mithaq/messages.js +106 -0
- package/dist/mithaq/refusal.d.ts +6 -0
- package/dist/mithaq/refusal.js +6 -0
- package/dist/mithaq/resolve.d.ts +10 -0
- package/dist/mithaq/resolve.js +954 -0
- package/dist/mithaq/rows.d.ts +87 -0
- package/dist/mithaq/rows.js +174 -0
- package/dist/mithaq/rukn.d.ts +72 -0
- package/dist/mithaq/rukn.js +15 -0
- package/dist/mithaq/standard-schema.d.ts +38 -0
- package/dist/mithaq/standard-schema.js +2 -0
- package/dist/mithaq/types.d.ts +358 -0
- package/dist/mithaq/types.js +15 -0
- package/dist/mithaq/user.d.ts +19 -0
- package/dist/mithaq/user.js +16 -0
- package/dist/mithaq/where.d.ts +32 -0
- package/dist/mithaq/where.js +87 -0
- package/dist/rbac/index.d.ts +105 -0
- package/dist/rbac/index.js +182 -0
- package/dist/rbac/steps.d.ts +22 -0
- package/dist/rbac/steps.js +161 -0
- package/dist/rich-text/index.d.ts +27 -0
- package/dist/rich-text/index.js +58 -0
- package/dist/rich-text/rich-text.d.ts +159 -0
- package/dist/rich-text/rich-text.js +372 -0
- package/dist/screen/client.d.ts +153 -0
- package/dist/screen/client.js +68 -0
- package/dist/screen/generate.d.ts +14 -0
- package/dist/screen/generate.js +226 -0
- package/dist/screen/index.d.ts +479 -0
- package/dist/screen/index.js +645 -0
- package/dist/screen/react.d.ts +95 -0
- package/dist/screen/react.js +58 -0
- package/dist/screen/studio.d.ts +24 -0
- package/dist/screen/studio.js +68 -0
- package/dist/service/index.d.ts +63 -0
- package/dist/service/index.js +15 -0
- package/dist/storage/allowed-type.d.ts +5 -0
- package/dist/storage/allowed-type.js +7 -0
- package/dist/storage/index.d.ts +43 -0
- package/dist/storage/index.js +44 -0
- package/dist/storage/storage.d.ts +384 -0
- package/dist/storage/storage.js +405 -0
- package/dist/versions/index.d.ts +37 -0
- package/dist/versions/index.js +8 -0
- package/dist/versions/versions.d.ts +104 -0
- package/dist/versions/versions.js +160 -0
- package/dist/zod/index.d.ts +184 -0
- package/dist/zod/index.js +121 -0
- package/dist/zod/kinds.d.ts +34 -0
- package/dist/zod/kinds.js +49 -0
- package/dist/zod/rules.d.ts +117 -0
- package/dist/zod/rules.js +67 -0
- package/package.json +63 -4
- package/r/mithaq-cells.json +20 -0
- package/r/mithaq-controls.json +37 -0
- package/r/mithaq-directional-icon.json +15 -0
- package/r/mithaq-expected-updated-at.json +15 -0
- package/r/mithaq-field-shell.json +18 -0
- package/r/mithaq-filter-operators.json +15 -0
- package/r/mithaq-form.json +33 -0
- package/r/mithaq-list-columns.json +23 -0
- package/r/mithaq-list-filters.json +24 -0
- package/r/mithaq-list-pagination.json +24 -0
- package/r/mithaq-list-query.json +18 -0
- package/r/mithaq-list-selection.json +19 -0
- package/r/mithaq-list.json +33 -0
- package/r/mithaq-nav.json +24 -0
- package/r/mithaq-nodes.json +34 -0
- package/r/mithaq-relationship-cell.json +20 -0
- package/r/mithaq-relationship-control.json +20 -0
- package/r/mithaq-rich-text.json +40 -0
- package/r/mithaq-screens-host.json +96 -0
- package/r/mithaq-storage.json +44 -0
- package/r/mithaq-upload.json +29 -0
- package/r/registry.json +550 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 drhomet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-2026 Payload CMS, LLC <info@payloadcms.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DietrichGebert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Assigned } from '../rbac/index.ts';
|
|
2
|
+
import type { ResolvedKayan } from '../mithaq/types.ts';
|
|
3
|
+
import type { OperationEffect } from './runner/steps.ts';
|
|
4
|
+
export type AuthStrategyArgs = {
|
|
5
|
+
headers: Headers;
|
|
6
|
+
canSetHeaders?: boolean;
|
|
7
|
+
isGraphQL?: boolean;
|
|
8
|
+
strategyName?: string;
|
|
9
|
+
};
|
|
10
|
+
export type AuthStrategyResult<Actor = unknown> = {
|
|
11
|
+
responseHeaders?: Headers;
|
|
12
|
+
user: Actor | null;
|
|
13
|
+
};
|
|
14
|
+
/** An auth strategy: `authenticate` resolves a request's headers to an actor, or to `null`. */
|
|
15
|
+
export type AuthStrategy<Actor = unknown> = {
|
|
16
|
+
name: string;
|
|
17
|
+
authenticate(args: AuthStrategyArgs): Promise<AuthStrategyResult<Actor>> | AuthStrategyResult<Actor>;
|
|
18
|
+
/** What the HTTP layer mounts at `/api/auth/<strategy name>/*`. */
|
|
19
|
+
handler?(request: Request): Promise<Response>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* What a kayan's `auth` is: a shipped provider declaration, `local(...)` or `betterAuth(...)`,
|
|
23
|
+
* which `buildAsas` builds, or a strategy the project wrote.
|
|
24
|
+
*/
|
|
25
|
+
export type KayanAuth = AuthStrategy | {
|
|
26
|
+
provider: 'better-auth' | 'local';
|
|
27
|
+
};
|
|
28
|
+
export declare const isAuthDeclaration: (auth: KayanAuth) => auth is {
|
|
29
|
+
provider: 'better-auth' | 'local';
|
|
30
|
+
};
|
|
31
|
+
type TrustedActor = {
|
|
32
|
+
activeOrganizationId?: unknown;
|
|
33
|
+
collection: string;
|
|
34
|
+
id?: unknown;
|
|
35
|
+
memberRole?: unknown;
|
|
36
|
+
organizations?: readonly ({
|
|
37
|
+
id?: unknown;
|
|
38
|
+
} & Record<string, unknown>)[];
|
|
39
|
+
role?: unknown;
|
|
40
|
+
permissions?: true | ReadonlySet<string>;
|
|
41
|
+
roles?: readonly string[];
|
|
42
|
+
strategy?: unknown;
|
|
43
|
+
admin?: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** Record the build's admin answer on an actor a strategy returned; an unmarked object holds none. */
|
|
46
|
+
export declare const markAdminAnswer: (actor: unknown, admin: boolean) => boolean;
|
|
47
|
+
/** Whether the build answered this actor as its administrator. */
|
|
48
|
+
export declare const isAdminActor: (actor: unknown) => boolean;
|
|
49
|
+
export declare const markAuthActor: <Actor extends object>(actor: Actor) => Actor;
|
|
50
|
+
/**
|
|
51
|
+
* The permission keys the access target resolved for this actor, `true` for every key,
|
|
52
|
+
* or nothing where no target graded. Read from the build's snapshot, never the actor object.
|
|
53
|
+
*/
|
|
54
|
+
export declare const actorPermissions: (actor: unknown) => true | ReadonlySet<string> | undefined;
|
|
55
|
+
/** The role slugs the access target resolved for this actor, read from the build's snapshot. */
|
|
56
|
+
export declare const actorRoles: (actor: unknown) => readonly string[] | undefined;
|
|
57
|
+
export declare const authActor: (actor: unknown) => TrustedActor | null;
|
|
58
|
+
export declare const actor: <Actor>({ strategies, headers, onError, }: {
|
|
59
|
+
strategies: readonly AuthStrategy<Actor>[];
|
|
60
|
+
headers: Headers;
|
|
61
|
+
onError: (error: unknown) => void;
|
|
62
|
+
}) => Promise<Actor | null>;
|
|
63
|
+
/**
|
|
64
|
+
* What the build's access target answers for one actor: the slugs assigned in the tenant
|
|
65
|
+
* and every key they hold, or nothing where the build declares no access target.
|
|
66
|
+
*/
|
|
67
|
+
export type AssignedResolver = (args: {
|
|
68
|
+
tenant: unknown;
|
|
69
|
+
member: unknown;
|
|
70
|
+
/** The actor being made, before it is marked. */
|
|
71
|
+
actor: object;
|
|
72
|
+
}) => Promise<Assigned | undefined> | Assigned | undefined;
|
|
73
|
+
export declare const resolveAssigned: ({ resolver, onError, ...args }: Parameters<AssignedResolver>[0] & {
|
|
74
|
+
resolver: AssignedResolver | undefined;
|
|
75
|
+
onError: (error: unknown) => void;
|
|
76
|
+
}) => Promise<Assigned | undefined>;
|
|
77
|
+
export type LocalAuthPermissions = {
|
|
78
|
+
kayan: readonly ResolvedKayan[];
|
|
79
|
+
can(args: {
|
|
80
|
+
kayan: ResolvedKayan;
|
|
81
|
+
effect: OperationEffect;
|
|
82
|
+
actor: unknown;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
allowed: boolean;
|
|
85
|
+
operations: Record<string, boolean>;
|
|
86
|
+
}>;
|
|
87
|
+
canAccessAdmin(actor: unknown): Promise<boolean>;
|
|
88
|
+
screens(actor: unknown): Promise<readonly string[]>;
|
|
89
|
+
};
|
|
90
|
+
export declare const accessReport: (permissions: LocalAuthPermissions, actor: unknown) => Promise<{
|
|
91
|
+
canAccessAdmin: boolean;
|
|
92
|
+
collections: Record<string, unknown>;
|
|
93
|
+
screens: readonly string[];
|
|
94
|
+
permissions?: string | string[] | undefined;
|
|
95
|
+
}>;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Asks for `authenticate`, not `provider`: a project's own strategy may carry a
|
|
2
|
+
// `provider` key of its own meaning.
|
|
3
|
+
export const isAuthDeclaration = (auth) => !('authenticate' in auth);
|
|
4
|
+
const trustedActors = new WeakMap();
|
|
5
|
+
/** Record the build's admin answer on an actor a strategy returned; an unmarked object holds none. */
|
|
6
|
+
export const markAdminAnswer = (actor, admin) => {
|
|
7
|
+
const trusted = authActor(actor);
|
|
8
|
+
if (trusted)
|
|
9
|
+
trusted.admin = admin;
|
|
10
|
+
return admin;
|
|
11
|
+
};
|
|
12
|
+
/** Whether the build answered this actor as its administrator. */
|
|
13
|
+
export const isAdminActor = (actor) => authActor(actor)?.admin === true;
|
|
14
|
+
export const markAuthActor = (actor) => {
|
|
15
|
+
const source = actor;
|
|
16
|
+
const claimed = source.permissions;
|
|
17
|
+
trustedActors.set(actor, {
|
|
18
|
+
activeOrganizationId: source.activeOrganizationId,
|
|
19
|
+
collection: source.collection,
|
|
20
|
+
id: source.id,
|
|
21
|
+
memberRole: source.memberRole,
|
|
22
|
+
organizations: source.organizations?.map((organization) => ({ ...organization })),
|
|
23
|
+
role: source.role,
|
|
24
|
+
// Present decides, absent stands aside. Any other shape is a store that answered
|
|
25
|
+
// unreadably and holds no key: reading it as absent would fail open.
|
|
26
|
+
permissions: 'permissions' in source
|
|
27
|
+
? claimed === '*'
|
|
28
|
+
? true
|
|
29
|
+
: new Set(Array.isArray(claimed) && claimed.every((key) => typeof key === 'string') ? claimed : [])
|
|
30
|
+
: undefined,
|
|
31
|
+
roles: Array.isArray(source.roles) && source.roles.every((one) => typeof one === 'string')
|
|
32
|
+
? [...source.roles]
|
|
33
|
+
: undefined,
|
|
34
|
+
strategy: source.strategy,
|
|
35
|
+
});
|
|
36
|
+
return actor;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The permission keys the access target resolved for this actor, `true` for every key,
|
|
40
|
+
* or nothing where no target graded. Read from the build's snapshot, never the actor object.
|
|
41
|
+
*/
|
|
42
|
+
export const actorPermissions = (actor) => authActor(actor)?.permissions;
|
|
43
|
+
/** The role slugs the access target resolved for this actor, read from the build's snapshot. */
|
|
44
|
+
export const actorRoles = (actor) => authActor(actor)?.roles;
|
|
45
|
+
export const authActor = (actor) => actor && typeof actor === 'object' ? trustedActors.get(actor) ?? null : null;
|
|
46
|
+
// First non-null actor wins; a strategy that throws is handed to `onError` and skipped.
|
|
47
|
+
export const actor = async ({ strategies, headers, onError, }) => {
|
|
48
|
+
for (const strategy of strategies) {
|
|
49
|
+
try {
|
|
50
|
+
const found = await strategy.authenticate({
|
|
51
|
+
canSetHeaders: true,
|
|
52
|
+
headers,
|
|
53
|
+
strategyName: strategy.name,
|
|
54
|
+
});
|
|
55
|
+
if (found.user)
|
|
56
|
+
return found.user;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
onError(new Error(`Auth strategy "${strategy.name}" failed.`, { cause: error }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
};
|
|
64
|
+
// A throw must not leave: the strategy loop would turn a store outage into
|
|
65
|
+
// "not signed in" instead of a 403. A failed resolver holds no key and `onError` is told.
|
|
66
|
+
export const resolveAssigned = async ({ resolver, onError, ...args }) => {
|
|
67
|
+
if (!resolver)
|
|
68
|
+
return undefined;
|
|
69
|
+
try {
|
|
70
|
+
return await resolver(args);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
onError(new Error('The access target failed to resolve, so this actor holds no role and no key.', { cause: error }));
|
|
74
|
+
return { permissions: [], roles: [] };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// Reports and never grants. A kayan the actor may not read is left out, and so is a
|
|
78
|
+
// field none of `read`, `create` and `update` holds: the report discloses what it names.
|
|
79
|
+
export const accessReport = async (permissions, actor) => {
|
|
80
|
+
// First: the build records its answer on the actor, and a field callback below reads it.
|
|
81
|
+
const canAccessAdmin = await permissions.canAccessAdmin(actor);
|
|
82
|
+
const collections = Object.create(null);
|
|
83
|
+
for (const kayan of permissions.kayan) {
|
|
84
|
+
const effects = ['create', 'read', 'update', 'delete'];
|
|
85
|
+
const allowed = {};
|
|
86
|
+
const operations = {};
|
|
87
|
+
for (const effect of effects) {
|
|
88
|
+
const answer = await permissions.can({ kayan, effect, actor });
|
|
89
|
+
allowed[effect] = answer.allowed;
|
|
90
|
+
Object.assign(operations, answer.operations);
|
|
91
|
+
}
|
|
92
|
+
if (!allowed.read)
|
|
93
|
+
continue;
|
|
94
|
+
const fields = {};
|
|
95
|
+
for (const field of Object.values(kayan.fields)) {
|
|
96
|
+
const answers = {};
|
|
97
|
+
for (const effect of ['create', 'read', 'update']) {
|
|
98
|
+
const own = field.access?.[effect];
|
|
99
|
+
answers[effect] = allowed[effect] && (!own || !!(await own({ user: actor })));
|
|
100
|
+
}
|
|
101
|
+
if (answers.create || answers.read || answers.update)
|
|
102
|
+
fields[field.name] = answers;
|
|
103
|
+
}
|
|
104
|
+
collections[kayan.name] = { ...allowed, fields, operations };
|
|
105
|
+
}
|
|
106
|
+
// The actor's own keys, so a screen can answer a provider action such as `user.ban` without trying it.
|
|
107
|
+
const held = actorPermissions(actor);
|
|
108
|
+
return {
|
|
109
|
+
canAccessAdmin,
|
|
110
|
+
collections,
|
|
111
|
+
screens: await permissions.screens(actor),
|
|
112
|
+
...(held === undefined ? {} : { permissions: held === true ? '*' : [...held] }),
|
|
113
|
+
};
|
|
114
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { type AuthStrategy, type AuthStrategyResult } from './actor.ts';
|
|
2
|
+
import { betterAuthStrategy } from '../auth/better-auth/index.ts';
|
|
3
|
+
import { type LocalAuthActor, type ProtectedLocalField } from '../auth/local/index.ts';
|
|
4
|
+
import { type OperationEffect } from './runner/steps.ts';
|
|
5
|
+
import { type Globals, type Procedures, type Row, type Writes } from './runner/write.ts';
|
|
6
|
+
import { type ActorBoundAccess, type Kayan, type RuntimeField, type ServiceOf } from '../mithaq/types.ts';
|
|
7
|
+
import { type Language } from '../mithaq/language.ts';
|
|
8
|
+
import type { StandardSchemaV1 } from '../mithaq/standard-schema.ts';
|
|
9
|
+
import type { BuiltTables } from '../drizzle/index.ts';
|
|
10
|
+
import { type ScreenClient } from '../screen/index.ts';
|
|
11
|
+
import { type MessageRegistry, type MessagesResolver } from '../mithaq/messages.ts';
|
|
12
|
+
type UniqueNames<List extends readonly Kayan[], Seen = never> = List extends readonly [
|
|
13
|
+
infer Head extends Kayan,
|
|
14
|
+
...infer Tail extends readonly Kayan[]
|
|
15
|
+
] ? Head['name'] extends Seen ? false : UniqueNames<Tail, Seen | Head['name']> : true;
|
|
16
|
+
type BetterAuthServer = Parameters<typeof betterAuthStrategy>[0]['server'];
|
|
17
|
+
type ActorFor<L extends Language, KayanList extends readonly Kayan[]> = [Extract<AuthStrategies<L, KayanList>, AuthStrategy>] extends [never] ? unknown : Awaited<ReturnType<Extract<Extract<AuthStrategies<L, KayanList>, AuthStrategy>, AuthStrategy>['authenticate']>> extends AuthStrategyResult<infer Result> ? Result : never;
|
|
18
|
+
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => (T extends B ? 1 : 2) ? true : false;
|
|
19
|
+
type FirstArg<Fn> = Fn extends (...args: infer Args) => unknown ? Args extends [] ? never : Args[0] : never;
|
|
20
|
+
type UserFromArg<Arg> = Arg extends {
|
|
21
|
+
user: infer User;
|
|
22
|
+
} ? NonNullable<User> : unknown;
|
|
23
|
+
type IdFromArg<Arg> = Arg extends {
|
|
24
|
+
id?: infer Id;
|
|
25
|
+
} ? Id : unknown;
|
|
26
|
+
type DataFromArg<Arg> = Arg extends {
|
|
27
|
+
data?: infer Data;
|
|
28
|
+
} ? Data : unknown;
|
|
29
|
+
type DocFromArg<Arg> = Arg extends {
|
|
30
|
+
doc?: infer Doc;
|
|
31
|
+
} ? Doc : unknown;
|
|
32
|
+
type CallbackRequirement<Fn> = [FirstArg<Fn>] extends [never] ? never : {
|
|
33
|
+
actor: UserFromArg<FirstArg<Fn>>;
|
|
34
|
+
data: DataFromArg<FirstArg<Fn>>;
|
|
35
|
+
doc: DocFromArg<FirstArg<Fn>>;
|
|
36
|
+
id: IdFromArg<FirstArg<Fn>>;
|
|
37
|
+
};
|
|
38
|
+
type AccessRequirement<K> = K extends {
|
|
39
|
+
access?: infer Access;
|
|
40
|
+
} ? NonNullable<Access> extends infer A ? '__mithaqAccessActor' extends keyof A ? A extends ActorBoundAccess<infer Actor> ? {
|
|
41
|
+
actor: Actor;
|
|
42
|
+
data: unknown;
|
|
43
|
+
doc: unknown;
|
|
44
|
+
id: unknown;
|
|
45
|
+
} | CallbackRequirement<A extends {
|
|
46
|
+
create?: infer Fn;
|
|
47
|
+
} ? NonNullable<Fn> : never> | CallbackRequirement<A extends {
|
|
48
|
+
read?: infer Fn;
|
|
49
|
+
} ? NonNullable<Fn> : never> | CallbackRequirement<A extends {
|
|
50
|
+
update?: infer Fn;
|
|
51
|
+
} ? NonNullable<Fn> : never> | CallbackRequirement<A extends {
|
|
52
|
+
delete?: infer Fn;
|
|
53
|
+
} ? NonNullable<Fn> : never> : never : never : never : never;
|
|
54
|
+
type FieldAccessRequirement<Fields> = Fields extends Record<string, RuntimeField> ? Fields[keyof Fields] extends infer Field ? Field extends {
|
|
55
|
+
access?: infer Access;
|
|
56
|
+
} ? {} extends Pick<Field, 'access'> ? never : NonNullable<NonNullable<Access>[keyof NonNullable<Access>]> extends infer Fn ? '__mithaqAccessActor' extends keyof NonNullable<Fn> ? NonNullable<Fn> extends ActorBoundAccess<infer Actor> ? {
|
|
57
|
+
actor: Actor;
|
|
58
|
+
data: unknown;
|
|
59
|
+
doc: unknown;
|
|
60
|
+
id: unknown;
|
|
61
|
+
} | CallbackRequirement<NonNullable<Fn>> | ('__mithaqFieldData' extends keyof NonNullable<Fn> ? NonNullable<Fn> extends {
|
|
62
|
+
__mithaqFieldData: infer Data;
|
|
63
|
+
__mithaqFieldId: infer Id;
|
|
64
|
+
} ? {
|
|
65
|
+
actor: Actor;
|
|
66
|
+
data: unknown;
|
|
67
|
+
doc: Data;
|
|
68
|
+
id: Id;
|
|
69
|
+
} : never : never) : never : never : never : never : never : never;
|
|
70
|
+
type BroadRecord = Record<string, unknown>;
|
|
71
|
+
type IsUnknown<T> = Equal<T, unknown>;
|
|
72
|
+
type IsBroadRecord<T> = Equal<T, BroadRecord>;
|
|
73
|
+
type MatchesDoc<Expected, Actual> = IsUnknown<Expected> extends true ? true : IsBroadRecord<Expected> extends true ? true : Equal<Expected, Actual>;
|
|
74
|
+
type MatchesData<Expected, Actual> = IsUnknown<Expected> extends true ? true : IsBroadRecord<Expected> extends true ? true : [Partial<Actual>] extends [Expected] ? true : false;
|
|
75
|
+
type MatchesId<Expected, Actual> = IsUnknown<Expected> extends true ? true : Equal<Expected, Actual>;
|
|
76
|
+
type MismatchName<One, Required, Actor, L extends Language, KayanList extends readonly Kayan[]> = Required extends {
|
|
77
|
+
actor: infer Needed;
|
|
78
|
+
data: infer Data;
|
|
79
|
+
doc: infer Doc;
|
|
80
|
+
id: infer Id;
|
|
81
|
+
} ? [Actor] extends [never] ? One extends {
|
|
82
|
+
name: infer Name;
|
|
83
|
+
} ? Name : never : [Actor] extends [Needed] ? One extends Kayan ? MatchesDoc<Doc, Row<One['fields'], L, KayanList>> extends true ? MatchesData<Data, Row<One['fields'], L, KayanList>> extends true ? 'id' extends keyof Row<One['fields'], L, KayanList> ? MatchesId<Id, Row<One['fields'], L, KayanList>['id']> extends true ? never : One extends {
|
|
84
|
+
name: infer Name;
|
|
85
|
+
} ? Name : never : One extends {
|
|
86
|
+
name: infer Name;
|
|
87
|
+
} ? Name : never : One extends {
|
|
88
|
+
name: infer Name;
|
|
89
|
+
} ? Name : never : One extends {
|
|
90
|
+
name: infer Name;
|
|
91
|
+
} ? Name : never : never : One extends {
|
|
92
|
+
name: infer Name;
|
|
93
|
+
} ? Name : never : never;
|
|
94
|
+
type AccessActorMismatch<KayanList extends readonly Kayan[], L extends Language, Actor> = KayanList[number] extends infer One ? MismatchName<One, AccessRequirement<One> | (One extends {
|
|
95
|
+
fields: infer Fields;
|
|
96
|
+
} ? FieldAccessRequirement<Fields> : never), Actor, L, KayanList> : never;
|
|
97
|
+
type AdminAccessRequirement<Auth> = Auth extends {
|
|
98
|
+
admin?: infer Admin;
|
|
99
|
+
} ? NonNullable<Admin> extends {
|
|
100
|
+
access?: infer Fn;
|
|
101
|
+
} ? CallbackRequirement<NonNullable<Fn>> : never : never;
|
|
102
|
+
type AdminAccessMismatch<Auth, Actor> = MismatchName<{
|
|
103
|
+
name: 'auth.admin.access';
|
|
104
|
+
}, AdminAccessRequirement<Auth>, Actor, never, never>;
|
|
105
|
+
type AdminAccessShapeMismatch<Auth> = Auth extends {
|
|
106
|
+
admin?: infer Admin;
|
|
107
|
+
} ? NonNullable<Admin> extends {
|
|
108
|
+
access: infer Fn;
|
|
109
|
+
} ? NonNullable<Fn> extends (...args: infer _Args) => infer Return ? Return extends boolean | Promise<boolean> ? never : 'auth.admin.access' : 'auth.admin.access' : never : never;
|
|
110
|
+
/** The keys this project has, in the two shapes `buildAsas` publishes them in. */
|
|
111
|
+
export type Vocabulary = {
|
|
112
|
+
catalog: readonly string[];
|
|
113
|
+
statements: Readonly<Record<string, readonly string[]>>;
|
|
114
|
+
};
|
|
115
|
+
/** The build's auth: the admin kayan whose actor passes default access, and where failures are reported. */
|
|
116
|
+
export type AuthConfig = {
|
|
117
|
+
/** The admin kayan: whose actor passes default access. */
|
|
118
|
+
admin?: {
|
|
119
|
+
kayan: Kayan;
|
|
120
|
+
access?: unknown;
|
|
121
|
+
};
|
|
122
|
+
/** Handed a strategy that threw and an access target that could not answer, each as an `Error` with the cause. Required where any kayan carries auth. */
|
|
123
|
+
onError: (error: unknown) => void;
|
|
124
|
+
};
|
|
125
|
+
type PublicLocalUser<K extends Kayan, L extends Language, KayanList extends readonly Kayan[]> = Omit<Row<K['fields'], L, KayanList>, ProtectedLocalField>;
|
|
126
|
+
/** One strategy per kayan carrying `auth`, typed by that provider's or that strategy's own actor. */
|
|
127
|
+
type AuthStrategies<L extends Language, KayanList extends readonly Kayan[]> = KayanList[number] extends infer One ? One extends Kayan ? One['auth'] extends infer Declared ? Declared extends {
|
|
128
|
+
provider: 'local';
|
|
129
|
+
} ? AuthStrategy<LocalAuthActor<One['name'], PublicLocalUser<One, L, KayanList>>> : Declared extends {
|
|
130
|
+
provider: 'better-auth';
|
|
131
|
+
server: (...args: never[]) => infer Server;
|
|
132
|
+
} ? Server extends BetterAuthServer ? ReturnType<typeof betterAuthStrategy<Server, One['name']>> : never : Declared extends AuthStrategy ? Declared : never : never : never : never;
|
|
133
|
+
/** One built operation in the flat table `buildAsas` publishes: one entry per operation of every kayan and global, in declared order. */
|
|
134
|
+
export type Operation = {
|
|
135
|
+
/** The kayan or global the operation belongs to. */
|
|
136
|
+
kayan: string;
|
|
137
|
+
/** The operation's own name, which is also the action an access row grants. */
|
|
138
|
+
name: string;
|
|
139
|
+
effect: OperationEffect;
|
|
140
|
+
/** True for a global: its read takes no id and its update makes the row. */
|
|
141
|
+
single: boolean;
|
|
142
|
+
/** What a caller may send. A global's update publishes its update shape without the id, so a first update short of a required field is refused by the runner. */
|
|
143
|
+
input: StandardSchemaV1;
|
|
144
|
+
/** Runs the operation with the actor the request resolved and the context the plug's `context` function built. */
|
|
145
|
+
call: (input: unknown, options: ServedOptions) => Promise<unknown>;
|
|
146
|
+
};
|
|
147
|
+
/** What a plug hands a served call beside its input. */
|
|
148
|
+
export type ServedOptions = {
|
|
149
|
+
actor: unknown;
|
|
150
|
+
context?: Record<string, unknown>;
|
|
151
|
+
/** `?draft=true` on a kayan that keeps drafts. Nothing else a client sends reaches a call as an option. */
|
|
152
|
+
draft?: boolean;
|
|
153
|
+
autosave?: boolean;
|
|
154
|
+
/** `?tenant=mine` or `?tenant=all` on a read. A write sent one is refused. */
|
|
155
|
+
tenant?: string;
|
|
156
|
+
};
|
|
157
|
+
/** The operation table: the flat array a plug iterates, and `procedures`, a type-only view keyed by kayan then operation. */
|
|
158
|
+
export interface OperationTable<View = unknown> extends ReadonlyArray<Operation> {
|
|
159
|
+
/** Type only. There is no such property on the array at runtime. */
|
|
160
|
+
readonly procedures?: View;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* What an HTTP plug hands back: `router`, which a typed client is built from, and `handle`,
|
|
164
|
+
* which answers `null` where no route of its own matched so the next plug is asked.
|
|
165
|
+
*/
|
|
166
|
+
export type HttpPlug = {
|
|
167
|
+
/** The plug's own name, unique in the list. */
|
|
168
|
+
name: string;
|
|
169
|
+
router: unknown;
|
|
170
|
+
/** `actor` is a thunk, so a request no route of this plug matches costs no session lookup. */
|
|
171
|
+
handle: (request: Request, actor: () => Promise<unknown>) => Promise<Response | null>;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Every declared thing of one build, kayan and global alike, which is the
|
|
175
|
+
* registry a relationship is resolved against.
|
|
176
|
+
*/
|
|
177
|
+
type Declared<KayanList extends readonly Kayan[], GlobalList extends readonly Kayan[]> = readonly (KayanList[number] | GlobalList[number])[];
|
|
178
|
+
export declare const buildAsas: <const L extends Language, const KayanList extends readonly Kayan[], const GlobalList extends readonly Kayan[] = readonly [], Auth extends AuthConfig = AuthConfig, Http extends HttpPlug = HttpPlug>(language: L, { kayan, global, auth, http, messages, statements: declaredStatements, }: {
|
|
179
|
+
kayan: KayanList & (UniqueNames<KayanList> extends true ? unknown : {
|
|
180
|
+
duplicateKayanNames: never;
|
|
181
|
+
}) & (AccessActorMismatch<KayanList, L, ActorFor<L, KayanList>> | AdminAccessMismatch<Auth, ActorFor<L, KayanList>> | AdminAccessShapeMismatch<Auth> extends never ? unknown : {
|
|
182
|
+
authAccessActorMismatch: AccessActorMismatch<KayanList, L, ActorFor<L, KayanList>> | AdminAccessMismatch<Auth, ActorFor<L, KayanList>> | AdminAccessShapeMismatch<Auth>;
|
|
183
|
+
});
|
|
184
|
+
/** The globals, each a kayan of one row. They share the kayan namespace. */
|
|
185
|
+
global?: GlobalList;
|
|
186
|
+
auth?: Auth;
|
|
187
|
+
/**
|
|
188
|
+
* The HTTP plugs, asked in order, so the order decides who owns a prefix two claim. Required:
|
|
189
|
+
* `fetchHttp()` takes no dependency; `orpcHttp()` first gives `asas.router` its typed client.
|
|
190
|
+
*/
|
|
191
|
+
http: readonly [(operations: OperationTable<Procedures<KayanList, GlobalList, Declared<KayanList, GlobalList>, L>>) => Http, ...((operations: OperationTable<Procedures<KayanList, GlobalList, Declared<KayanList, GlobalList>, L>>) => HttpPlug)[]];
|
|
192
|
+
/** The project's own copy for the labels mithaq ships, as one resolver whose return shape mithaq declares. */
|
|
193
|
+
messages?: MessagesResolver<L>;
|
|
194
|
+
/** Keys no declaration derives, such as an auth provider's own actions, added to the vocabulary under the resources they name. */
|
|
195
|
+
statements?: Readonly<Record<string, readonly string[]>>;
|
|
196
|
+
}) => {
|
|
197
|
+
/** Every permission key this project has: one `<kayan>.<operation>` per operation, one `<kayan>.fields.<field>` per field, one `screen.<screen>` per screen. */
|
|
198
|
+
catalog: readonly string[];
|
|
199
|
+
/** Every scope of copy this project has. A getter: only the CLI reads it, and it calls the project's own resolver. */
|
|
200
|
+
readonly messages: MessageRegistry;
|
|
201
|
+
/** The same catalog in Better Auth's shape, for `createAccessControl(asas.statements)`. */
|
|
202
|
+
statements: Readonly<Record<string, readonly string[]>>;
|
|
203
|
+
/** Every built operation, flat. Its `procedures` view is a type, keyed by kayan then operation. */
|
|
204
|
+
operations: OperationTable<Procedures<KayanList, GlobalList, Declared<KayanList, GlobalList>, L>>;
|
|
205
|
+
/** The router the first plug built, which a typed client is built from; `undefined` for `fetchHttp()`. */
|
|
206
|
+
router: Http['router'];
|
|
207
|
+
/** Every target as the language passed it, so `asas.targets.drizzle.db` is drizzle. A screen also carries its projection under `client`. */
|
|
208
|
+
targets: { [Name in keyof L['targets']]: L['targets'][Name] extends {
|
|
209
|
+
tables: unknown;
|
|
210
|
+
} ? Omit<L['targets'][Name], 'tables'> & {
|
|
211
|
+
tables: BuiltTables<Declared<KayanList, GlobalList>, Name, ServiceOf<L>['store'], L['kinds']>;
|
|
212
|
+
} : L['targets'][Name] extends {
|
|
213
|
+
capability: 'screen';
|
|
214
|
+
} ? L['targets'][Name] & {
|
|
215
|
+
client: ScreenClient;
|
|
216
|
+
} : L['targets'][Name]; };
|
|
217
|
+
kayan: Writes<KayanList, Declared<KayanList, GlobalList>, L, ActorFor<L, KayanList>>;
|
|
218
|
+
/** One row per global, under `findOne` and `update`. */
|
|
219
|
+
global: Globals<GlobalList, Declared<KayanList, GlobalList>, L, ActorFor<L, KayanList>>;
|
|
220
|
+
/** The strategies in the order the loop tries them, and the loop itself. */
|
|
221
|
+
auth: {
|
|
222
|
+
strategies: Extract<AuthStrategies<L, KayanList>, AuthStrategy>[];
|
|
223
|
+
actor: (headers: Headers) => Promise<ActorFor<L, KayanList> | null>;
|
|
224
|
+
};
|
|
225
|
+
handler: (request: Request) => Promise<Response>;
|
|
226
|
+
connect: ({ readOnly }?: {
|
|
227
|
+
readOnly?: boolean;
|
|
228
|
+
}) => Promise<void>;
|
|
229
|
+
destroy: () => Promise<void>;
|
|
230
|
+
};
|
|
231
|
+
export {};
|