@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
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { type CheckedTable, type RowTable, type Rows, type RowsOptions } from './rows.ts';
|
|
2
|
+
import { type UserRefs } from './user.ts';
|
|
3
|
+
import type { AccessTarget, CommandActor, SystemRoles } from '../rbac/index.ts';
|
|
4
|
+
import type { ServiceTarget } from '../service/index.ts';
|
|
5
|
+
import type { ScreenTarget } from '../screen/index.ts';
|
|
6
|
+
import type { VersionsTarget } from '../versions/index.ts';
|
|
7
|
+
import type { StorageTarget } from '../storage/storage.ts';
|
|
8
|
+
import { buildAsas, type HttpPlug } from '../asas/build.ts';
|
|
9
|
+
import type { AuthConfig } from '../asas/build.ts';
|
|
10
|
+
import type { AuditHandler, Derives, DerivedShapes } from '../asas/runner/steps.ts';
|
|
11
|
+
import type { Where } from './where.ts';
|
|
12
|
+
import { defineGlobal, defineKayan } from './define.ts';
|
|
13
|
+
import type { Kayan, ResolvedField, ResolvedKayan } from './types.ts';
|
|
14
|
+
import type { Kind } from '../base/kinds.ts';
|
|
15
|
+
import type { Rule, RuleValue } from '../base/rules.ts';
|
|
16
|
+
import type { StandardSchemaV1 } from './standard-schema.ts';
|
|
17
|
+
import { type ArkanOf, type ArkanRefusals, type Rukn } from './rukn.ts';
|
|
18
|
+
type RuleHandler<Rules, Name extends keyof Rules, Built, Key extends string> = (args: {
|
|
19
|
+
[K in Key]: Built;
|
|
20
|
+
} & {
|
|
21
|
+
value: RuleValue<Rules[Name]>;
|
|
22
|
+
field: ResolvedField;
|
|
23
|
+
}) => Built;
|
|
24
|
+
/** What a target's command receives: the built project, and the words after the command, `--check` among them. */
|
|
25
|
+
export type CommandInput = {
|
|
26
|
+
asas: {
|
|
27
|
+
destroy(): Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
flags: readonly string[];
|
|
30
|
+
};
|
|
31
|
+
/** What a command answers: the exit code the bin exits with, 0 for nothing to say. */
|
|
32
|
+
export type Command = (input: CommandInput) => Promise<number>;
|
|
33
|
+
export type Target<Kinds extends Record<string, Kind> = Record<string, Kind>, Rules extends Record<string, Rule> = Record<string, Rule>, Built = unknown, Key extends string = 'shape'> = {
|
|
34
|
+
readonly capability?: 'schema';
|
|
35
|
+
/** What this target is, by the constructor that made it: `'drizzle'`, `'zod'`, `'screen'`. Kinds' own implementations are keyed by it. */
|
|
36
|
+
readonly type?: string;
|
|
37
|
+
/** The commands this target answers, run as `mithaq <target> <name>` and swept with `--check` by `mithaq generate --check`. */
|
|
38
|
+
readonly commands?: Record<string, Command>;
|
|
39
|
+
/** The kinds this target builds an id of, so a fourth fails the build rather than the pushed schema. */
|
|
40
|
+
readonly idKinds?: readonly (keyof Kinds & string)[];
|
|
41
|
+
kinds: {
|
|
42
|
+
[Name in keyof Kinds]: (field: ResolvedField, ...rest: never[]) => Built;
|
|
43
|
+
};
|
|
44
|
+
rules: {
|
|
45
|
+
[Name in keyof Rules]: RuleHandler<Rules, Name, Built, Key>;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/** A target that shapes a field, and assembles the shapes a kayan's fields derive, so core never composes a schema itself. */
|
|
49
|
+
export type SchemaTarget<Kinds extends Record<string, Kind> = Record<string, Kind>, Rules extends Record<string, Rule> = Record<string, Rule>, Built = unknown, Schema extends StandardSchemaV1 = StandardSchemaV1> = Target<Kinds, Rules, Built> & {
|
|
50
|
+
/** Declared, never sniffed, the same way a store declares its own. */
|
|
51
|
+
readonly capability: 'schema';
|
|
52
|
+
/** The create, update, row and other shapes a kayan's fields derive. */
|
|
53
|
+
derive<F extends Record<string, ResolvedField>>(fields: F): DerivedShapes<Schema>;
|
|
54
|
+
/** The same mapping written as a type, because a conditional type cannot read `derive`'s generic signature for one kayan. Never set at runtime. */
|
|
55
|
+
readonly derives?: Derives;
|
|
56
|
+
};
|
|
57
|
+
type RuntimeSchema = {
|
|
58
|
+
readonly capability?: 'schema';
|
|
59
|
+
readonly type?: string;
|
|
60
|
+
readonly derives?: Derives;
|
|
61
|
+
readonly commands?: Record<string, Command>;
|
|
62
|
+
readonly idKinds?: readonly string[];
|
|
63
|
+
kinds: Record<string, (field: ResolvedField, ...rest: never[]) => unknown>;
|
|
64
|
+
rules: Record<string, (...args: never[]) => unknown>;
|
|
65
|
+
derive(fields: Record<string, ResolvedField>): DerivedShapes;
|
|
66
|
+
};
|
|
67
|
+
type RuntimeStore = Omit<Store, 'kinds' | 'rules'> & {
|
|
68
|
+
kinds: RuntimeSchema['kinds'];
|
|
69
|
+
rules: RuntimeSchema['rules'];
|
|
70
|
+
};
|
|
71
|
+
type RuntimeAccess = Omit<AccessTarget, 'access' | 'roles' | 'rolesOf'> & {
|
|
72
|
+
access: RowTable;
|
|
73
|
+
roles: SystemRoles;
|
|
74
|
+
rolesOf?: (user: never) => readonly string[];
|
|
75
|
+
};
|
|
76
|
+
type RuntimeService = ServiceTarget;
|
|
77
|
+
export type RuntimeTarget = RuntimeAccess | RuntimeSchema | RuntimeScreen | RuntimeService | RuntimeStore | StorageTarget | TenantTarget | VersionsTarget;
|
|
78
|
+
/** The field a tenant-owned kayan places: one row of `RelationTo`, written by the application on create and never after. */
|
|
79
|
+
export type TenantField<RelationTo extends string = string> = {
|
|
80
|
+
readonly kind: 'relationship';
|
|
81
|
+
readonly relationTo: RelationTo;
|
|
82
|
+
readonly rules: {
|
|
83
|
+
readonly required: true;
|
|
84
|
+
readonly index: true;
|
|
85
|
+
readonly providedBy: {
|
|
86
|
+
readonly create: 'application';
|
|
87
|
+
readonly update: 'never';
|
|
88
|
+
};
|
|
89
|
+
readonly onDelete: 'restrict';
|
|
90
|
+
};
|
|
91
|
+
readonly targets: {
|
|
92
|
+
readonly tenant: true;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Which kayan a row's tenant is, and the field a tenant-owned kayan places. A kayan carrying
|
|
97
|
+
* a field bound through `targets.tenant` is tenant-owned; one carrying none is shared.
|
|
98
|
+
*/
|
|
99
|
+
export type TenantTarget<RelationTo extends string = string> = {
|
|
100
|
+
readonly capability: 'tenant';
|
|
101
|
+
readonly relationTo: RelationTo;
|
|
102
|
+
readonly onDelete: 'restrict';
|
|
103
|
+
readonly field: TenantField<RelationTo>;
|
|
104
|
+
/**
|
|
105
|
+
* Whether this actor is platform staff in the trusted platform app, who may read every
|
|
106
|
+
* tenant's rows with `tenant: 'all'` and never write through it. Absent, no one may.
|
|
107
|
+
*/
|
|
108
|
+
readonly platform?: (args: {
|
|
109
|
+
user: unknown;
|
|
110
|
+
}) => boolean | Promise<boolean>;
|
|
111
|
+
};
|
|
112
|
+
type RuntimeScreen = ScreenTarget;
|
|
113
|
+
/** A target that shapes a field: a store or a schema target. */
|
|
114
|
+
export type FieldTarget = RuntimeSchema | RuntimeStore;
|
|
115
|
+
/** The runtime shape of the one target that says who may do what. */
|
|
116
|
+
export type AccessCapability = RuntimeAccess;
|
|
117
|
+
/**
|
|
118
|
+
* A target that also holds the rows: a database adapter, or anything else a write can land in.
|
|
119
|
+
* The writes take the transaction, so everything one write does commits or rolls back together.
|
|
120
|
+
*/
|
|
121
|
+
export type Store<Kinds extends Record<string, Kind> = Record<string, Kind>, Rules extends Record<string, Rule> = Record<string, Rule>, Built = unknown, Tx = unknown, Row extends Record<string, unknown> = Record<string, unknown>> = Omit<Target<Kinds, Rules, Built, 'column'>, 'capability'> & {
|
|
122
|
+
/** Declared, never sniffed: a target says which of the two things it is. */
|
|
123
|
+
readonly capability: 'store';
|
|
124
|
+
/** `'drizzle'` where this store's `db` and `tables` are drizzle's, which local auth reads its kayan through. */
|
|
125
|
+
readonly sql?: 'drizzle';
|
|
126
|
+
/** Builds what this store needs for one build, answering the store that build uses from then on. */
|
|
127
|
+
init(kayan: ResolvedKayan[]): void | Store<Kinds, Rules, Built, Tx, Row>;
|
|
128
|
+
/** Reaches the database and, unless `readOnly`, prepares it. `readOnly` writes nothing. */
|
|
129
|
+
connect(args?: {
|
|
130
|
+
readOnly?: boolean;
|
|
131
|
+
}): Promise<void>;
|
|
132
|
+
/** Run `fn` in one transaction. What it returns is what the write returns. */
|
|
133
|
+
transaction<Result>(fn: (tx: Tx) => Promise<Result>): Promise<Result>;
|
|
134
|
+
insert(args: {
|
|
135
|
+
tx: Tx;
|
|
136
|
+
kayan: string;
|
|
137
|
+
data: Record<string, unknown>;
|
|
138
|
+
}): Promise<Row>;
|
|
139
|
+
update(args: {
|
|
140
|
+
tx: Tx;
|
|
141
|
+
kayan: string;
|
|
142
|
+
id: unknown;
|
|
143
|
+
data: Record<string, unknown>;
|
|
144
|
+
where?: Where;
|
|
145
|
+
}): Promise<Row>;
|
|
146
|
+
remove(args: {
|
|
147
|
+
tx: Tx;
|
|
148
|
+
kayan: string;
|
|
149
|
+
id: unknown;
|
|
150
|
+
where?: Where;
|
|
151
|
+
}): Promise<Row>;
|
|
152
|
+
/** Required, so no store can exist that the managed reads cannot use. */
|
|
153
|
+
find(args: {
|
|
154
|
+
kayan: string;
|
|
155
|
+
args: {
|
|
156
|
+
limit: number;
|
|
157
|
+
/** A store must hold the rows it returns until the transaction ends: a step that compares a row, such as `expectedUpdatedAt`, relies on it. */
|
|
158
|
+
lock?: 'update';
|
|
159
|
+
page: number;
|
|
160
|
+
sort: {
|
|
161
|
+
field: string;
|
|
162
|
+
direction: 'asc' | 'desc';
|
|
163
|
+
};
|
|
164
|
+
where?: Where;
|
|
165
|
+
};
|
|
166
|
+
tx?: Tx;
|
|
167
|
+
}): Promise<{
|
|
168
|
+
docs: Row[];
|
|
169
|
+
totalDocs: number;
|
|
170
|
+
}>;
|
|
171
|
+
/** Counts matching rows without loading them. Without it, `find` with a limit of `0` is asked. */
|
|
172
|
+
count?(args: {
|
|
173
|
+
kayan: string;
|
|
174
|
+
args: {
|
|
175
|
+
where?: Where;
|
|
176
|
+
};
|
|
177
|
+
tx?: Tx;
|
|
178
|
+
}): Promise<number>;
|
|
179
|
+
destroy(): Promise<void>;
|
|
180
|
+
};
|
|
181
|
+
/** A target that holds rows. Takes `undefined`, so a caller can name the bad target in its own throw. */
|
|
182
|
+
export declare const isStore: (target: RuntimeTarget | undefined) => target is RuntimeStore;
|
|
183
|
+
/** A target that says who may do what. */
|
|
184
|
+
export declare const isAccess: (target: RuntimeTarget | undefined) => target is RuntimeAccess;
|
|
185
|
+
/** A place a build draws its kayan. */
|
|
186
|
+
export declare const isScreen: (target: RuntimeTarget | undefined) => target is RuntimeScreen;
|
|
187
|
+
/** The runner. */
|
|
188
|
+
export declare const isService: (target: RuntimeTarget | undefined) => target is RuntimeService;
|
|
189
|
+
/** A kayan's versions. */
|
|
190
|
+
export declare const isVersions: (target: RuntimeTarget | undefined) => target is VersionsTarget;
|
|
191
|
+
/** The target that says which kayan a row's tenant is. */
|
|
192
|
+
export declare const isTenant: (target: RuntimeTarget | undefined) => target is TenantTarget;
|
|
193
|
+
/**
|
|
194
|
+
* A field bound through `targets.tenant`, with the tenant target's own field under it, so one
|
|
195
|
+
* written by hand takes every key and rule it leaves out. Any other field is handed back as it is.
|
|
196
|
+
*/
|
|
197
|
+
export declare const tenantDefaults: <F extends {
|
|
198
|
+
targets?: Record<string, unknown>;
|
|
199
|
+
rules?: Record<string, unknown>;
|
|
200
|
+
}>(language: Language, field: F) => F;
|
|
201
|
+
/** Where a kayan's files are kept. */
|
|
202
|
+
export declare const isStorage: (target: RuntimeTarget | undefined) => target is StorageTarget;
|
|
203
|
+
export declare const serviceTargetOf: (language: Language) => RuntimeService | undefined;
|
|
204
|
+
export declare const accessTargetOf: (language: Language) => RuntimeAccess | undefined;
|
|
205
|
+
export declare const languageRows: (language: Language) => Rows | undefined;
|
|
206
|
+
/** The language: the arkan, the locales and one entry per target. */
|
|
207
|
+
export type Language = {
|
|
208
|
+
kinds: Record<string, Kind>;
|
|
209
|
+
rules: Record<string, Rule>;
|
|
210
|
+
targets: Record<string, RuntimeTarget>;
|
|
211
|
+
/** The arkan the kinds, rules and some targets came from. */
|
|
212
|
+
arkan?: readonly Rukn[];
|
|
213
|
+
/**
|
|
214
|
+
* The locales every piece of copy is written in. Every label then needs one string per locale,
|
|
215
|
+
* so a half-translated project fails to compile. mithaq resolves no locale.
|
|
216
|
+
*/
|
|
217
|
+
locales?: readonly string[];
|
|
218
|
+
};
|
|
219
|
+
export declare const serviceKeys: readonly ['beforeSteps', 'afterSteps', 'afterCommit', 'authorize', 'softDelete', 'store', 'schema', 'audit', 'depth'];
|
|
220
|
+
type StoreKeys<Targets> = {
|
|
221
|
+
[Name in keyof Targets]: Targets[Name] extends {
|
|
222
|
+
capability: 'store';
|
|
223
|
+
} ? Name : never;
|
|
224
|
+
}[keyof Targets] & string;
|
|
225
|
+
type SchemaKeys<Targets> = {
|
|
226
|
+
[Name in keyof Targets]: Targets[Name] extends {
|
|
227
|
+
capability: 'access' | 'screen' | 'service' | 'storage' | 'store' | 'tenant' | 'versions';
|
|
228
|
+
} ? never : Name;
|
|
229
|
+
}[keyof Targets] & string;
|
|
230
|
+
type Owned<Kinds, Name extends keyof Kinds, K, T> = Name extends keyof K ? Name : T extends {
|
|
231
|
+
readonly type: infer Type extends string;
|
|
232
|
+
} ? Kinds[Name] extends {
|
|
233
|
+
readonly targets: {
|
|
234
|
+
readonly [P in Type]: unknown;
|
|
235
|
+
};
|
|
236
|
+
} ? never : Name : Name;
|
|
237
|
+
type CheckedTarget<Kinds extends Record<string, Kind>, Rules extends Record<string, Rule>, Targets, T> = T extends {
|
|
238
|
+
capability: 'access' | 'storage' | 'tenant' | 'versions';
|
|
239
|
+
} ? T : T extends {
|
|
240
|
+
capability: 'service';
|
|
241
|
+
} ? T & Omit<ServiceTarget, 'store' | 'schema'> & {
|
|
242
|
+
store: StoreKeys<Targets>;
|
|
243
|
+
schema: SchemaKeys<Targets>;
|
|
244
|
+
/** The audit sink, which takes every actor the runner hands it: the request's and `mithaq rbac assign`'s. */
|
|
245
|
+
audit?: AuditHandler<ActorOf<{
|
|
246
|
+
targets: Targets;
|
|
247
|
+
}> | CommandActor>;
|
|
248
|
+
http?: never;
|
|
249
|
+
orpc?: never;
|
|
250
|
+
} : T extends {
|
|
251
|
+
capability: 'screen';
|
|
252
|
+
controls: infer K;
|
|
253
|
+
} ? T & {
|
|
254
|
+
controls: {
|
|
255
|
+
[Name in keyof Kinds as string extends keyof K ? never : Owned<Kinds, Name, K, T>]: Name extends keyof K ? K[Name] : never;
|
|
256
|
+
} & {
|
|
257
|
+
[Name in string extends keyof K ? never : Exclude<keyof K, keyof Kinds>]: never;
|
|
258
|
+
};
|
|
259
|
+
} : T extends {
|
|
260
|
+
kinds: infer K;
|
|
261
|
+
rules: infer R;
|
|
262
|
+
} ? T & {
|
|
263
|
+
kinds: {
|
|
264
|
+
[Name in keyof Kinds as string extends keyof K ? never : Owned<Kinds, Name, K, T>]: Name extends keyof K ? K[Name] : never;
|
|
265
|
+
} & {
|
|
266
|
+
[Name in string extends keyof K ? never : Exclude<keyof K, keyof Kinds>]: never;
|
|
267
|
+
};
|
|
268
|
+
rules: {
|
|
269
|
+
[Name in keyof Rules as string extends keyof R ? never : Name]: Name extends keyof R ? R[Name] extends (args: infer Args) => infer Built ? (args: Omit<Args, 'value' | 'field'> & {
|
|
270
|
+
value: RuleValue<Rules[Name]>;
|
|
271
|
+
field: ResolvedField;
|
|
272
|
+
}) => Built : never : never;
|
|
273
|
+
};
|
|
274
|
+
} & (T extends {
|
|
275
|
+
idKinds: readonly unknown[];
|
|
276
|
+
} ? {
|
|
277
|
+
idKinds: readonly (keyof Kinds & string)[];
|
|
278
|
+
} : unknown) : never;
|
|
279
|
+
type CheckedLanguage<L> = L extends {
|
|
280
|
+
arkan: infer A extends readonly Rukn[];
|
|
281
|
+
targets: infer Targets extends Record<string, RuntimeTarget>;
|
|
282
|
+
} ? [ArkanOf<A, 'kinds'>, ArkanOf<A, 'rules'>] extends [infer Kinds extends Record<string, Kind>, infer Rules extends Record<string, Rule>] ? L & {
|
|
283
|
+
kinds?: never;
|
|
284
|
+
rules?: never;
|
|
285
|
+
targets: Targets & {
|
|
286
|
+
[Name in keyof Targets]: CheckedTarget<Kinds, Rules, Targets & ArkanOf<A, 'targets'>, Targets[Name]>;
|
|
287
|
+
};
|
|
288
|
+
} & {
|
|
289
|
+
[Key in Extract<keyof L, (typeof serviceKeys)[number] | 'operations'>]: never;
|
|
290
|
+
} & {
|
|
291
|
+
[Stray in {
|
|
292
|
+
[Name in keyof Kinds]: `kind "${Name & string}" accepts rule "${Exclude<Kinds[Name]['rules'][number], keyof Rules> & string}", which no rukn brings`;
|
|
293
|
+
}[keyof Kinds]]: never;
|
|
294
|
+
} & ArkanRefusals<A, Targets> & {
|
|
295
|
+
[Misnamed in {
|
|
296
|
+
[Name in keyof Targets]: [Targets[Name]] extends [never] ? never : Targets[Name] extends {
|
|
297
|
+
capability: 'tenant';
|
|
298
|
+
} ? Name extends 'tenant' ? never : `target "${Name & string}" declares capability "tenant" and is named "tenant" in targets` : never;
|
|
299
|
+
}[keyof Targets]]: never;
|
|
300
|
+
} : never : never;
|
|
301
|
+
/** The language the arkan make: their kinds and rules, and their targets beside the project's. */
|
|
302
|
+
type Composed<L> = L extends {
|
|
303
|
+
arkan: infer A extends readonly Rukn[];
|
|
304
|
+
targets: infer Targets;
|
|
305
|
+
} ? L & {
|
|
306
|
+
kinds: ArkanOf<A, 'kinds'>;
|
|
307
|
+
rules: ArkanOf<A, 'rules'>;
|
|
308
|
+
targets: Targets & ArkanOf<A, 'targets'>;
|
|
309
|
+
} : never;
|
|
310
|
+
/** The access target a language declared, or nothing where it declared none. */
|
|
311
|
+
type AccessOf<L> = L extends {
|
|
312
|
+
targets: infer Targets;
|
|
313
|
+
} ? Extract<Targets[keyof Targets], {
|
|
314
|
+
capability: 'access';
|
|
315
|
+
}> : never;
|
|
316
|
+
type RolesOf<L> = [AccessOf<L>] extends [never] ? string : AccessOf<L> extends {
|
|
317
|
+
roles: infer Roles;
|
|
318
|
+
} ? '*' | (keyof Roles & string) : string;
|
|
319
|
+
type ActorOf<L> = NonNullable<AccessOf<L> extends {
|
|
320
|
+
rolesOf?: infer Reader;
|
|
321
|
+
} ? Reader : never> extends (user: infer Actor) => unknown ? Actor : Record<string, never>;
|
|
322
|
+
type Defined<L extends Language> = L & {
|
|
323
|
+
defineKayan: ReturnType<typeof defineKayan<L>>;
|
|
324
|
+
/** `defineKayan` with the operations pinned: a kayan of one row. */
|
|
325
|
+
defineGlobal: ReturnType<typeof defineGlobal<L>>;
|
|
326
|
+
buildAsas: <const KayanList extends readonly Kayan[], const GlobalList extends readonly Kayan[] = readonly [], Auth extends AuthConfig = AuthConfig, Http extends HttpPlug = HttpPlug>(args: Parameters<typeof buildAsas<L, KayanList, GlobalList, Auth, Http>>[1]) => ReturnType<typeof buildAsas<L, KayanList, GlobalList, Auth, Http>>;
|
|
327
|
+
/** `rows`, with its role keys checked against the language's `roles`. */
|
|
328
|
+
rows: <const Table extends RowTable>(table: Table & CheckedTable<Table, RolesOf<L>>, options?: RowsOptions<ActorOf<L>>) => Rows<Table>;
|
|
329
|
+
/** `user`, with its keys checked against the actor this language's `rolesOf` takes. */
|
|
330
|
+
user: UserRefs<ActorOf<L>>;
|
|
331
|
+
};
|
|
332
|
+
/** The object `defineMithaq` received, given the `defineKayan` it returned, or nothing. */
|
|
333
|
+
export declare const receivedLanguage: (define: unknown) => unknown;
|
|
334
|
+
export declare function defineMithaq<const L>(language: L & CheckedLanguage<L>): Defined<Extract<Composed<L>, Language>>;
|
|
335
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { rows } from './rows.js';
|
|
2
|
+
import { actorPermissions } from '../asas/actor.js';
|
|
3
|
+
import { user } from './user.js';
|
|
4
|
+
import { buildAsas } from '../asas/build.js';
|
|
5
|
+
import { defineGlobal, defineKayan } from './define.js';
|
|
6
|
+
import { composeArkan } from './rukn.js';
|
|
7
|
+
/** A target that holds rows. Takes `undefined`, so a caller can name the bad target in its own throw. */
|
|
8
|
+
export const isStore = (target) => target?.capability === 'store';
|
|
9
|
+
/** A target that says who may do what. */
|
|
10
|
+
export const isAccess = (target) => target?.capability === 'access';
|
|
11
|
+
/** A place a build draws its kayan. */
|
|
12
|
+
export const isScreen = (target) => target?.capability === 'screen';
|
|
13
|
+
/** The runner. */
|
|
14
|
+
export const isService = (target) => target?.capability === 'service';
|
|
15
|
+
/** A kayan's versions. */
|
|
16
|
+
export const isVersions = (target) => target?.capability === 'versions';
|
|
17
|
+
/** The target that says which kayan a row's tenant is. */
|
|
18
|
+
export const isTenant = (target) => target?.capability === 'tenant';
|
|
19
|
+
/**
|
|
20
|
+
* A field bound through `targets.tenant`, with the tenant target's own field under it, so one
|
|
21
|
+
* written by hand takes every key and rule it leaves out. Any other field is handed back as it is.
|
|
22
|
+
*/
|
|
23
|
+
export const tenantDefaults = (language, field) => {
|
|
24
|
+
const target = language.targets.tenant;
|
|
25
|
+
if (field.targets?.tenant === undefined || !isTenant(target))
|
|
26
|
+
return field;
|
|
27
|
+
return { ...target.field, ...field, rules: { ...target.field?.rules, ...field.rules } };
|
|
28
|
+
};
|
|
29
|
+
/** Where a kayan's files are kept. */
|
|
30
|
+
export const isStorage = (target) => target?.capability === 'storage';
|
|
31
|
+
// The one service target of a build; `buildAsas` refuses a build without one.
|
|
32
|
+
export const serviceTargetOf = (language) => Object.values(language.targets).find((target) => isService(target));
|
|
33
|
+
/** The compiled rows of the language's access target, kept off the public shape. */
|
|
34
|
+
const LANGUAGE_ROWS = Symbol('mithaq.language.rows');
|
|
35
|
+
// The one access target of a build, or nothing; `buildAsas` refuses two.
|
|
36
|
+
export const accessTargetOf = (language) => Object.values(language.targets).find((target) => isAccess(target));
|
|
37
|
+
// Compiled once by `defineMithaq`; every kayan that keeps the tenant field inherits it.
|
|
38
|
+
export const languageRows = (language) => language[LANGUAGE_ROWS];
|
|
39
|
+
// Keys of the service target, refused on the root by name.
|
|
40
|
+
export const serviceKeys = ['beforeSteps', 'afterSteps', 'afterCommit', 'authorize', 'softDelete', 'store', 'schema', 'audit', 'depth'];
|
|
41
|
+
// Keyed on the returned `defineKayan`, which every project exports, so `mithaq lock`
|
|
42
|
+
// reads the language without the language carrying a key for it.
|
|
43
|
+
const received = new WeakMap();
|
|
44
|
+
/** The object `defineMithaq` received, given the `defineKayan` it returned, or nothing. */
|
|
45
|
+
export const receivedLanguage = (define) => typeof define === 'function' ? received.get(define) : undefined;
|
|
46
|
+
export function defineMithaq(declared) {
|
|
47
|
+
// Refused rather than merged: kinds and rules come from the arkan alone.
|
|
48
|
+
const root = ['kinds', 'rules'].find((key) => Object.hasOwn(declared, key));
|
|
49
|
+
if (root) {
|
|
50
|
+
throw new Error(`The language declares "${root}" at its root, and kinds and rules come from its arkan alone: lay down arkan: [base], and bring your own in a rukn of your own, defineRukn({ name, ${root} }).`);
|
|
51
|
+
}
|
|
52
|
+
const language = { ...declared, ...composeArkan(declared) };
|
|
53
|
+
// `holds` is passed whether or not the language declares an access target: an actor
|
|
54
|
+
// carries keys only where a target resolved them.
|
|
55
|
+
const target = accessTargetOf(language);
|
|
56
|
+
const options = {
|
|
57
|
+
holds: actorPermissions,
|
|
58
|
+
...(target?.rolesOf ? { roles: target.rolesOf } : {}),
|
|
59
|
+
};
|
|
60
|
+
const compiled = {
|
|
61
|
+
...language,
|
|
62
|
+
...(target && Object.keys(target.access).length ? { [LANGUAGE_ROWS]: rows(target.access, options) } : {}),
|
|
63
|
+
};
|
|
64
|
+
const build = ((args) => buildAsas(compiled, args));
|
|
65
|
+
const define = defineKayan(compiled);
|
|
66
|
+
received.set(define, declared);
|
|
67
|
+
return {
|
|
68
|
+
...compiled,
|
|
69
|
+
defineKayan: define,
|
|
70
|
+
defineGlobal: defineGlobal(compiled),
|
|
71
|
+
buildAsas: build,
|
|
72
|
+
// A call that names no reader keeps the language's.
|
|
73
|
+
rows: ((table, own) => rows(table, { ...options, ...own })),
|
|
74
|
+
user: user,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type KeyObject } from 'node:crypto';
|
|
2
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
3
|
+
[key: string]: Json;
|
|
4
|
+
};
|
|
5
|
+
/** A machine whose key may sign this project's lock. */
|
|
6
|
+
export type Signer = {
|
|
7
|
+
publicKey: string;
|
|
8
|
+
label: string;
|
|
9
|
+
addedAt: string;
|
|
10
|
+
};
|
|
11
|
+
export type Seal = {
|
|
12
|
+
project: string;
|
|
13
|
+
signers: Signer[];
|
|
14
|
+
previous?: string;
|
|
15
|
+
signature: string;
|
|
16
|
+
sealedAt: string;
|
|
17
|
+
};
|
|
18
|
+
/** A lock's `seal` is `'pending'` until a person first approves `mithaq lock`. */
|
|
19
|
+
export type Lock = {
|
|
20
|
+
_notice?: string;
|
|
21
|
+
language: Json;
|
|
22
|
+
seal: Seal | 'pending';
|
|
23
|
+
};
|
|
24
|
+
/** The first thing anyone reads in `mithaq.lock`. It signs nothing. */
|
|
25
|
+
export declare const notice = "\u0645\u064A\u062B\u0627\u0642: do not change the vocabulary of defineMithaq (src/mithaq.ts), which is its locales, kinds, rules, targets and root keys, or this file unless the project's developer asked for that change. When the vocabulary must change, tell the developer what and why, then run `mithaq lock` and let them approve it in the popup it shows.";
|
|
26
|
+
export declare const covenant = "\u0645\u064A\u062B\u0627\u0642: the language changed and is not signed. Only the project's owner may approve a language change. Stop here, tell the owner what you changed and why, and ask them to run `mithaq lock`.";
|
|
27
|
+
/**
|
|
28
|
+
* What the lock records of the object `defineMithaq` received: the locales, each kind's label, message
|
|
29
|
+
* and accepted rules, each rule's message and excludes, each target's name and capability, and the root keys.
|
|
30
|
+
*/
|
|
31
|
+
export declare const vocabularyOf: (received: unknown) => Json;
|
|
32
|
+
/** The lock as its file holds it: canonical, two-space indented, one trailing newline. */
|
|
33
|
+
export declare const formatLock: (lock: Lock) => string;
|
|
34
|
+
/** Each path at which two canonical languages differ, as a line a person reads. */
|
|
35
|
+
export declare const changedPaths: ({ was, now, path }: {
|
|
36
|
+
was: Json | undefined;
|
|
37
|
+
now: Json | undefined;
|
|
38
|
+
path?: string;
|
|
39
|
+
}) => string[];
|
|
40
|
+
/**
|
|
41
|
+
* Signs a canonical language with this machine's key, over `base`'s project and
|
|
42
|
+
* signers, adding this machine to the signers when it is not one of them yet.
|
|
43
|
+
*/
|
|
44
|
+
export declare const sealLanguage: ({ language, key, base, label, }: {
|
|
45
|
+
language: Json;
|
|
46
|
+
key: KeyObject;
|
|
47
|
+
base?: Pick<Seal, 'project' | 'signers'>;
|
|
48
|
+
label?: string;
|
|
49
|
+
}) => Lock;
|
|
50
|
+
/** Where a lock stands against the current language: the paths that changed, and its seal's state. */
|
|
51
|
+
export declare const verifyLock: (language: Json, lock: Lock) => {
|
|
52
|
+
changed: string[];
|
|
53
|
+
seal: 'signed' | 'pending' | 'missing' | 'invalid';
|
|
54
|
+
};
|
|
55
|
+
/** `mithaq lock --check`. A project with no `mithaq.lock` passes with one notice; one with a lock is held to it. */
|
|
56
|
+
export declare const checkLock: (language: Json, text: string | undefined) => {
|
|
57
|
+
code: 0 | 1;
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
/** Asks a person to type `code` back, beside `summary`. The code is never written to this process's output. */
|
|
61
|
+
export type Approve = (summary: string, code: number) => Promise<string | undefined>;
|
|
62
|
+
export declare const popup: Approve;
|
|
63
|
+
export declare const warnLock: (project: URL) => Promise<void>;
|
|
64
|
+
/** `mithaq lock` and `mithaq lock --check`, run from the project root. `approve` and `keys` are for tests. */
|
|
65
|
+
export declare const run: ({ project, noun, flags, approve, keys, }: {
|
|
66
|
+
project: URL;
|
|
67
|
+
noun: string | undefined;
|
|
68
|
+
flags: readonly string[];
|
|
69
|
+
approve?: Approve;
|
|
70
|
+
keys?: string;
|
|
71
|
+
}) => Promise<number>;
|