letopis 1.4.2 → 3.0.0
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/AGENT-CHEATSHEET.md +516 -398
- package/CHANGELOG.md +739 -0
- package/LICENSE +75 -21
- package/LICENSE-COMMERCIAL.md +49 -0
- package/README.md +1312 -1190
- package/api-contract.json +381 -0
- package/dist/acl.d.ts +81 -1
- package/dist/acl.js +281 -13
- package/dist/admin.d.ts +11 -141
- package/dist/admin.js +28 -186
- package/dist/auth.d.ts +20 -5
- package/dist/auth.js +80 -21
- package/dist/cache.d.ts +8 -1
- package/dist/cache.js +30 -8
- package/dist/chain.d.ts +46 -48
- package/dist/chain.js +556 -223
- package/dist/cli.js +73 -96
- package/dist/demo/booking.d.ts +352 -36
- package/dist/describe.d.ts +47 -0
- package/dist/describe.js +195 -0
- package/dist/errors.d.ts +77 -13
- package/dist/errors.js +231 -30
- package/dist/index.d.ts +62 -74
- package/dist/index.js +312 -155
- package/dist/internal.d.ts +7 -0
- package/dist/jsonschema.js +18 -17
- package/dist/load.d.ts +29 -17
- package/dist/load.js +235 -190
- package/dist/log.d.ts +121 -0
- package/dist/log.js +633 -0
- package/dist/mcp.d.ts +5 -7
- package/dist/mcp.js +194 -225
- package/dist/messages/access.d.ts +90 -0
- package/dist/messages/access.js +113 -0
- package/dist/messages/api.d.ts +237 -0
- package/dist/messages/api.js +304 -0
- package/dist/messages/boot.d.ts +201 -0
- package/dist/messages/boot.js +256 -0
- package/dist/messages/codes.d.ts +172 -0
- package/dist/messages/codes.js +173 -0
- package/dist/messages/index.d.ts +98 -0
- package/dist/messages/index.js +121 -0
- package/dist/messages/log.d.ts +229 -0
- package/dist/messages/log.js +102 -0
- package/dist/messages/mcp.d.ts +306 -0
- package/dist/messages/mcp.js +387 -0
- package/dist/messages/model.d.ts +256 -0
- package/dist/messages/model.js +314 -0
- package/dist/messages/query.d.ts +141 -0
- package/dist/messages/query.js +171 -0
- package/dist/messages/sql-access.d.ts +70 -0
- package/dist/messages/sql-access.js +87 -0
- package/dist/messages/sql-core.d.ts +164 -0
- package/dist/messages/sql-core.js +198 -0
- package/dist/messages/sql-time.d.ts +86 -0
- package/dist/messages/sql-time.js +111 -0
- package/dist/messages/sql-write.d.ts +246 -0
- package/dist/messages/sql-write.js +270 -0
- package/dist/messages/time.d.ts +23 -0
- package/dist/messages/time.js +29 -0
- package/dist/messages/types.d.ts +27 -0
- package/dist/messages/types.js +5 -0
- package/dist/messages/write.d.ts +452 -0
- package/dist/messages/write.js +572 -0
- package/dist/model.d.ts +77 -60
- package/dist/model.js +43 -34
- package/dist/ops.d.ts +35 -0
- package/dist/ops.js +101 -0
- package/dist/pglite.d.ts +0 -9
- package/dist/pglite.js +3 -1
- package/dist/registry.d.ts +3 -19
- package/dist/registry.js +3 -16
- package/dist/signals.d.ts +1 -5
- package/dist/signals.js +1 -5
- package/dist/sql.d.ts +9 -1
- package/dist/sql.js +175 -107
- package/dist/sync.d.ts +10 -4
- package/dist/sync.js +90 -17
- package/dist/tx.d.ts +21 -2
- package/dist/tx.js +76 -13
- package/dist/typed.d.ts +252 -53
- package/dist/types.d.ts +93 -90
- package/dist/types.js +17 -14
- package/dist/up.d.ts +56 -35
- package/dist/up.js +229 -83
- package/dist/uuid.d.ts +8 -0
- package/dist/uuid.js +14 -6
- package/dist/validate.d.ts +13 -1
- package/dist/validate.js +82 -33
- package/dist/watch.d.ts +10 -16
- package/dist/watch.js +36 -77
- package/dist/write.d.ts +87 -17
- package/dist/write.js +441 -206
- package/llms.txt +48 -31
- package/package.json +13 -14
- package/sql/10-core.sql +7 -50
- package/sql/15-errors.sql +64 -10
- package/sql/20-context.sql +18 -9
- package/sql/30-validate.sql +79 -30
- package/sql/40-class.sql +41 -115
- package/sql/50-acl.sql +8 -13
- package/sql/60-write.sql +220 -676
- package/sql/70-read.sql +11 -13
- package/sql/80-auth.sql +109 -42
- package/sql/90-time.sql +108 -947
- package/sql/95-seed.system.sql +7 -14
- package/sql/99-revision.sql +6 -1
- package/sql/README.md +32 -24
- package/sql/migrate/0009-restore-rev.tenants-name.sql +10 -0
- package/sql/migrate/0010-impersonation-no-account.sql +5 -0
- package/sql/migrate/0011-key-lock.otp-enabled.sql +7 -0
- package/sql/migrate/README.md +18 -0
- package/AGENT-CHEATSHEET.en.md +0 -488
- package/MIGRATION.md +0 -190
- package/README.en.md +0 -2910
- package/dist/import.d.ts +0 -185
- package/dist/import.js +0 -820
- package/dist/project.d.ts +0 -359
- package/dist/project.js +0 -1258
- package/sql/75-history.sql +0 -436
- package/sql/88-derived.sql +0 -1236
- package/sql/migrate/0006-changed-paths.sql +0 -3
- package/sql/migrate/0007-secret-account-fk.sql +0 -12
package/dist/model.d.ts
CHANGED
|
@@ -17,18 +17,20 @@ export type OnDelete = 'restrict' | 'cascade' | 'unset';
|
|
|
17
17
|
export interface ClassMeta {
|
|
18
18
|
[key: string]: unknown;
|
|
19
19
|
}
|
|
20
|
-
/** Политика хранения истории (§2.11). */
|
|
21
|
-
export interface HistoryPolicy {
|
|
22
|
-
all: string;
|
|
23
|
-
daily?: string;
|
|
24
|
-
weekly?: string;
|
|
25
|
-
monthly?: string;
|
|
26
|
-
yearly?: string;
|
|
27
|
-
tz?: string;
|
|
28
|
-
}
|
|
29
20
|
type Target = AnyModel | string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
21
|
+
/** Имя класса цели: у модели — её имя, у строки — сама строка. */
|
|
22
|
+
type TargetName<T> = T extends string ? T : T extends {
|
|
23
|
+
readonly name: infer N extends string;
|
|
24
|
+
} ? N : string;
|
|
25
|
+
/**
|
|
26
|
+
* Обязателен ли множественный конец по `min` (для типов): `min` больше нуля — да, нет `min` или ноль — нет, число,
|
|
27
|
+
* известное только во время выполнения, — неизвестно (`boolean`).
|
|
28
|
+
*/
|
|
29
|
+
type MinRequired<O> = O extends {
|
|
30
|
+
min: infer N;
|
|
31
|
+
} ? (N extends 0 | undefined ? false : number extends N ? boolean : true) : false;
|
|
32
|
+
/** Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. `T` — имена классов целей (только для типов). */
|
|
33
|
+
export declare class End<Many extends boolean = boolean, Required extends boolean = boolean, T extends string = string> {
|
|
32
34
|
readonly targets: readonly Target[];
|
|
33
35
|
readonly many: Many;
|
|
34
36
|
readonly required: Required;
|
|
@@ -36,6 +38,8 @@ export declare class End<Many extends boolean = boolean, Required extends boolea
|
|
|
36
38
|
readonly max?: number;
|
|
37
39
|
readonly onDeleteRule?: OnDelete;
|
|
38
40
|
readonly metaData?: Record<string, unknown>;
|
|
41
|
+
/** Только для вывода типов: имена классов целей — строгие переходы и слоты цепочек (план 2.0, этап 4, п. 1). */
|
|
42
|
+
readonly __targets?: T;
|
|
39
43
|
constructor(targets: readonly Target[], opts: {
|
|
40
44
|
many: Many;
|
|
41
45
|
required: Required;
|
|
@@ -46,22 +50,25 @@ export declare class End<Many extends boolean = boolean, Required extends boolea
|
|
|
46
50
|
});
|
|
47
51
|
private with;
|
|
48
52
|
/** Необязательный конец. */
|
|
49
|
-
optional(): End<Many, false>;
|
|
53
|
+
optional(): End<Many, false, T>;
|
|
50
54
|
/** Правило при удалении цели: restrict (по умолчанию), cascade, unset. */
|
|
51
|
-
onDelete(rule: OnDelete): End<Many, Required>;
|
|
55
|
+
onDelete(rule: OnDelete): End<Many, Required, T>;
|
|
52
56
|
/** Метаданные конца (§2.13). */
|
|
53
|
-
meta(meta: Record<string, unknown>): End<Many, Required>;
|
|
57
|
+
meta(meta: Record<string, unknown>): End<Many, Required, T>;
|
|
54
58
|
/** Имена классов целей. */
|
|
55
59
|
classNames(): string[];
|
|
56
60
|
}
|
|
57
61
|
/** Одиночный конец; несколько классов — союз: `one(Service, Product)`. По умолчанию обязательный. */
|
|
58
|
-
export declare function one(...targets:
|
|
59
|
-
/**
|
|
60
|
-
|
|
62
|
+
export declare function one<const T extends readonly Target[]>(...targets: T): End<false, true, TargetName<T[number]>>;
|
|
63
|
+
/**
|
|
64
|
+
* Множественный конец: список целей в одном конце (§4 предложения). Обязателен при `min` больше нуля — `unset()` у него
|
|
65
|
+
* в цепочке типизированного подключения нет.
|
|
66
|
+
*/
|
|
67
|
+
export declare function many<const T extends Target | readonly Target[], const O extends {
|
|
61
68
|
min?: number;
|
|
62
69
|
max?: number;
|
|
63
70
|
onDelete?: OnDelete;
|
|
64
|
-
}): End<true,
|
|
71
|
+
} = Record<never, never>>(target: T, opts?: O): End<true, MinRequired<O>, TargetName<T extends readonly (infer X)[] ? X : T>>;
|
|
65
72
|
type EndsInput = Record<string, End | AnyModel>;
|
|
66
73
|
type EndOf<E> = E extends End<infer M, infer R> ? End<M, R> : End<false, true>;
|
|
67
74
|
type LinkValue<E> = EndOf<E> extends End<true, boolean> ? string[] : string;
|
|
@@ -79,39 +86,14 @@ export interface ClassDef<S extends z.ZodRawShape = z.ZodRawShape, E extends End
|
|
|
79
86
|
fields?: z.ZodObject<S> | S;
|
|
80
87
|
key?: readonly string[];
|
|
81
88
|
onDelete?: Partial<Record<keyof E & string, OnDelete>>;
|
|
82
|
-
|
|
89
|
+
/** Вести ли журнал версий (по умолчанию да). */
|
|
90
|
+
history?: boolean;
|
|
83
91
|
ownerDefault?: 'tenant' | 'actor';
|
|
84
92
|
cache?: {
|
|
85
93
|
ttl?: number;
|
|
86
94
|
};
|
|
87
95
|
meta?: ClassMeta;
|
|
88
|
-
/** Производный класс (план 1.1, §2.4): строки пишет только проектор с этим именем; наследуется. */
|
|
89
|
-
derivedBy?: string;
|
|
90
|
-
/**
|
|
91
|
-
* Агрегаты в базе (план 1.1, §2.4): поле — сумма поля (`sum`) или число (`count: true`) живых строк класса `of` того
|
|
92
|
-
* же арендатора, чей одиночный конец `via` ведёт на строку; `where` — JSON Schema над данными строки-источника.
|
|
93
|
-
* Поле считает база, приложение его не пишет. Наследуется, потомок заменяет целиком.
|
|
94
|
-
*/
|
|
95
|
-
aggregate?: Record<string, AggregateDef>;
|
|
96
96
|
}
|
|
97
|
-
/** Описание одного агрегатного поля: `sum` или `count: true`; `of` — класс-источник (имя или модель). */
|
|
98
|
-
export type AggregateDef = {
|
|
99
|
-
sum: string;
|
|
100
|
-
count?: never;
|
|
101
|
-
of: string | {
|
|
102
|
-
readonly name: string;
|
|
103
|
-
};
|
|
104
|
-
via: string;
|
|
105
|
-
where?: JsonSchema;
|
|
106
|
-
} | {
|
|
107
|
-
count: true;
|
|
108
|
-
sum?: never;
|
|
109
|
-
of: string | {
|
|
110
|
-
readonly name: string;
|
|
111
|
-
};
|
|
112
|
-
via: string;
|
|
113
|
-
where?: JsonSchema;
|
|
114
|
-
};
|
|
115
97
|
/** Описание класса в строке `Class` (метасхема `class_metaschema()` в lib/sql/40-class.sql). */
|
|
116
98
|
export interface ClassDescription {
|
|
117
99
|
name: string;
|
|
@@ -133,20 +115,12 @@ export interface ClassDescription {
|
|
|
133
115
|
meta?: Record<string, unknown>;
|
|
134
116
|
}>;
|
|
135
117
|
key?: string[];
|
|
136
|
-
history?: boolean
|
|
118
|
+
history?: boolean;
|
|
137
119
|
ownerDefault?: 'tenant' | 'actor';
|
|
138
120
|
cache?: {
|
|
139
121
|
ttl?: number;
|
|
140
122
|
};
|
|
141
123
|
meta?: ClassMeta;
|
|
142
|
-
derivedBy?: string;
|
|
143
|
-
aggregate?: Record<string, {
|
|
144
|
-
sum?: string;
|
|
145
|
-
count?: true;
|
|
146
|
-
of: string;
|
|
147
|
-
via: string;
|
|
148
|
-
where?: JsonSchema;
|
|
149
|
-
}>;
|
|
150
124
|
}
|
|
151
125
|
/** Правило refine, которое не попадёт в базу: проверяется только в библиотеке. */
|
|
152
126
|
export interface LostRule {
|
|
@@ -155,7 +129,36 @@ export interface LostRule {
|
|
|
155
129
|
}
|
|
156
130
|
type DataOf<P, S extends z.ZodRawShape> = P extends Model<string, infer PD, unknown> ? Omit<PD, keyof z.output<z.ZodObject<S>>> & z.output<z.ZodObject<S>> : z.output<z.ZodObject<S>>;
|
|
157
131
|
type AllLinks<P, E extends EndsInput> = P extends Model<string, unknown, infer PL> ? Omit<PL, keyof E> & LinksOf<E> : LinksOf<E>;
|
|
158
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Концы класса для строгих типов цепочек (план 2.0, этап 4, п. 1): роль → классы целей, множественный ли, обязательный
|
|
134
|
+
* ли; с концами предков (потомок заменяет конец целиком). Тот же вид выдаёт `letopis types` (generateTypes, sync.ts).
|
|
135
|
+
*/
|
|
136
|
+
type EndSpecOf<V> = V extends End<infer Mn, infer R, infer T> ? {
|
|
137
|
+
targets: T;
|
|
138
|
+
many: Mn;
|
|
139
|
+
required: R;
|
|
140
|
+
} : V extends {
|
|
141
|
+
readonly name: infer N extends string;
|
|
142
|
+
} ? {
|
|
143
|
+
targets: N;
|
|
144
|
+
many: false;
|
|
145
|
+
required: true;
|
|
146
|
+
} : never;
|
|
147
|
+
type EndsSpecOf<E> = {
|
|
148
|
+
[K in keyof E]: EndSpecOf<E[K]>;
|
|
149
|
+
};
|
|
150
|
+
type AllEnds<P, E extends EndsInput> = P extends Model<string, unknown, unknown, string, infer PE, string> ? Omit<PE, keyof E> & EndsSpecOf<E> : EndsSpecOf<E>;
|
|
151
|
+
/** Имя класса и его предков — как `ancestors` реестра (семейства и переходы строгих типов). */
|
|
152
|
+
type Lineage<N extends string, P> = N | (P extends Model<infer PN, unknown, unknown, string, unknown, infer PP> ? PN | PP : never);
|
|
153
|
+
/** Концы описания класса: `ends` или пусто. */
|
|
154
|
+
type EndsIn<D> = D extends {
|
|
155
|
+
ends?: infer E extends EndsInput;
|
|
156
|
+
} ? E : Record<never, never>;
|
|
157
|
+
/**
|
|
158
|
+
* Модель класса. `N` — имя, `D` — данные строки, `L` — концы строки, `A` — алиас, `E` — концы класса (роль → классы
|
|
159
|
+
* целей, множественный ли, обязательный ли), `P` — имя класса и его предков: всё это — для строгих типов цепочек.
|
|
160
|
+
*/
|
|
161
|
+
export interface Model<N extends string = string, D = unknown, L = unknown, A extends string = string, E = unknown, P extends string = N> {
|
|
159
162
|
readonly name: N;
|
|
160
163
|
readonly kind: 'hub' | 'link';
|
|
161
164
|
readonly alias?: A;
|
|
@@ -180,9 +183,11 @@ export interface Model<N extends string = string, D = unknown, L = unknown, A ex
|
|
|
180
183
|
readonly __types?: {
|
|
181
184
|
data: D;
|
|
182
185
|
links: L;
|
|
186
|
+
ends: E;
|
|
187
|
+
ancestors: P;
|
|
183
188
|
};
|
|
184
189
|
}
|
|
185
|
-
export type AnyModel = Model<string, any, any, any>;
|
|
190
|
+
export type AnyModel = Model<string, any, any, any, any, any>;
|
|
186
191
|
/** Тип данных строки класса: `Infer<typeof Master>`. */
|
|
187
192
|
export type Infer<M> = M extends Model<string, infer D, unknown> ? D : never;
|
|
188
193
|
/** Тип концов строки класса. */
|
|
@@ -191,13 +196,25 @@ type ShapeOf<F> = F extends z.ZodObject<infer S> ? S : F extends z.ZodRawShape ?
|
|
|
191
196
|
type AliasOf<D> = D extends {
|
|
192
197
|
alias: infer A extends string;
|
|
193
198
|
} ? A : string;
|
|
199
|
+
/** Предок из описания класса (`extends`); нет — unknown. */
|
|
200
|
+
type ExtendsOf<D> = D extends {
|
|
201
|
+
extends?: infer P;
|
|
202
|
+
} ? P : unknown;
|
|
203
|
+
/** Описание класса без полей — третьим аргументом короткой формы. */
|
|
204
|
+
type ClassOpts = Omit<ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>, 'fields'>;
|
|
205
|
+
/** Тип модели по имени, описанию и собственным полям: данные и концы — с предками. */
|
|
206
|
+
type ModelOf<N extends string, D, S extends z.ZodRawShape> = Model<N, DataOf<ExtendsOf<D>, S>, AllLinks<ExtendsOf<D>, EndsIn<D>>, AliasOf<D>, AllEnds<ExtendsOf<D>, EndsIn<D>>, Lineage<N, ExtendsOf<D>>>;
|
|
194
207
|
/**
|
|
195
208
|
* Описывает класс объектов (хаб): поля, ключ, концы и свойства класса. Класс-связь объектов со своими данными
|
|
196
209
|
* описывает `link()`.
|
|
197
210
|
*/
|
|
198
|
-
export declare function hub<N extends string, S extends z.ZodRawShape>(name: N, shape: S, opts:
|
|
199
|
-
export declare function hub<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D):
|
|
211
|
+
export declare function hub<N extends string, S extends z.ZodRawShape, const O extends ClassOpts>(name: N, shape: S, opts: O): ModelOf<N, O, S>;
|
|
212
|
+
export declare function hub<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D): ModelOf<N, D, ShapeOf<D['fields']>>;
|
|
213
|
+
/** Короткая форма без опций: hub('Имя', { поле: z… }) — поля класса (полная форма { fields: z.object(…) } — выше). */
|
|
214
|
+
export declare function hub<N extends string, S extends z.ZodRawShape>(name: N, shape: S): ModelOf<N, {}, S>;
|
|
200
215
|
/** Связь — отношение между объектами со своими данными (бронь, навык). */
|
|
201
|
-
export declare function link<N extends string, S extends z.ZodRawShape>(name: N, shape: S, opts:
|
|
202
|
-
export declare function link<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D):
|
|
216
|
+
export declare function link<N extends string, S extends z.ZodRawShape, const O extends ClassOpts>(name: N, shape: S, opts: O): ModelOf<N, O, S>;
|
|
217
|
+
export declare function link<N extends string, const D extends ClassDef<z.ZodRawShape, EndsInput, AnyModel | undefined>>(name: N, def?: D): ModelOf<N, D, ShapeOf<D['fields']>>;
|
|
218
|
+
/** Короткая форма без опций: link('Имя', { поле: z… }) — поля класса (полная форма { fields: z.object(…) } — выше). */
|
|
219
|
+
export declare function link<N extends string, S extends z.ZodRawShape>(name: N, shape: S): ModelOf<N, {}, S>;
|
|
203
220
|
export { normalizeSchema, translatePattern, type JsonSchema } from './jsonschema.js';
|
package/dist/model.js
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Поля, концы и ключ наследуются по правилу 0.21 «потомок заменяет поле целиком» (`extends`).
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import {
|
|
14
|
+
import { lerr } from './errors.js';
|
|
15
15
|
import { normalizeSchema } from './jsonschema.js';
|
|
16
|
-
|
|
16
|
+
import { RESERVED_CLASS_NAMES } from './types.js';
|
|
17
|
+
/** Конец класса: `one(X)`, `one(A, B)`, `many(X, { min, max })`. `T` — имена классов целей (только для типов). */
|
|
17
18
|
export class End {
|
|
18
19
|
targets;
|
|
19
20
|
many;
|
|
@@ -24,7 +25,7 @@ export class End {
|
|
|
24
25
|
metaData;
|
|
25
26
|
constructor(targets, opts) {
|
|
26
27
|
if (!targets.length)
|
|
27
|
-
throw
|
|
28
|
+
throw lerr('model.end_no_targets');
|
|
28
29
|
this.targets = targets;
|
|
29
30
|
this.many = opts.many;
|
|
30
31
|
this.required = opts.required;
|
|
@@ -58,25 +59,31 @@ export class End {
|
|
|
58
59
|
export function one(...targets) {
|
|
59
60
|
return new End(targets, { many: false, required: true });
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Множественный конец: список целей в одном конце (§4 предложения). Обязателен при `min` больше нуля — `unset()` у него
|
|
64
|
+
* в цепочке типизированного подключения нет.
|
|
65
|
+
*/
|
|
62
66
|
export function many(target, opts = {}) {
|
|
63
67
|
const targets = Array.isArray(target) ? target : [target];
|
|
64
68
|
return new End(targets, { many: true, required: (opts.min ?? 0) > 0, min: opts.min, max: opts.max, onDelete: opts.onDelete });
|
|
65
69
|
}
|
|
66
70
|
const NAME = /^[A-Za-zА-Яа-яЁё_][A-Za-zА-Яа-яЁё0-9_]{0,62}$/;
|
|
67
71
|
/** Ключи полной формы описания класса (ClassDef). */
|
|
68
|
-
const DEF_KEYS = new Set(['alias', 'extends', 'abstract', 'ends', 'fields', 'key', 'onDelete', 'history', 'ownerDefault', 'cache', 'meta'
|
|
72
|
+
const DEF_KEYS = new Set(['alias', 'extends', 'abstract', 'ends', 'fields', 'key', 'onDelete', 'history', 'ownerDefault', 'cache', 'meta']);
|
|
69
73
|
function isZod(v) {
|
|
70
74
|
return typeof v === 'object' && v !== null && '_zod' in v;
|
|
71
75
|
}
|
|
72
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Пути правил Zod, которых нет в JSON Schema: refine/superRefine (`refine`) и преобразования — `overwrite` (`.trim()`,
|
|
78
|
+
* `.toLowerCase()`, `.toUpperCase()`, `.normalize()` …) и `transform` (`.transform()`, `z.preprocess()`, `.pipe()`).
|
|
79
|
+
* Преобразования база и валидатор letopis не исполняют: их syncModels отклоняет (план 2.1, A11).
|
|
80
|
+
*/
|
|
73
81
|
function collectLost(schema, path, out) {
|
|
74
82
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
83
|
const def = schema._zod.def;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
84
|
+
collectChecks(schema, path, out);
|
|
85
|
+
if (def.type === 'transform')
|
|
86
|
+
out.push({ path, kind: 'transform' });
|
|
80
87
|
if (def.type === 'object')
|
|
81
88
|
for (const [k, v] of Object.entries(def.shape))
|
|
82
89
|
collectLost(v, `${path}.${k}`, out);
|
|
@@ -91,9 +98,24 @@ function collectLost(schema, path, out) {
|
|
|
91
98
|
if (def.innerType)
|
|
92
99
|
collectLost(def.innerType, path, out);
|
|
93
100
|
}
|
|
101
|
+
/** Проверки самой схемы (без вложенных): refine/superRefine/check — `refine`, `overwrite` — преобразование. */
|
|
102
|
+
function collectChecks(schema, path, out) {
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
for (const c of schema._zod.def.checks ?? []) {
|
|
105
|
+
const check = c?._zod?.def?.check;
|
|
106
|
+
if (check === 'custom')
|
|
107
|
+
out.push({ path, kind: 'refine' });
|
|
108
|
+
else if (check === 'overwrite')
|
|
109
|
+
out.push({ path, kind: 'overwrite' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
94
112
|
function makeModel(kind, name, defArg, optsArg) {
|
|
95
113
|
if (!NAME.test(name))
|
|
96
|
-
throw
|
|
114
|
+
throw lerr('model.class_name_invalid', { name });
|
|
115
|
+
// имя метода цепочки или корня db: шаг с таким именем до хаба не дойдёт (план 2.0, этап 4, п. 2; 7.10 №3); связь с
|
|
116
|
+
// таким именем отклоняет база
|
|
117
|
+
if (kind === 'hub' && RESERVED_CLASS_NAMES.includes(name))
|
|
118
|
+
throw lerr('model.hub_name_reserved', { name });
|
|
97
119
|
// короткая форма: hub(name, { поле: z…, … }, { key, meta, … })
|
|
98
120
|
let def;
|
|
99
121
|
// { fields: z.object(…) } без других ключей описания — полная форма, а не класс с единственным полем fields (план 2.0,
|
|
@@ -111,12 +133,12 @@ function makeModel(kind, name, defArg, optsArg) {
|
|
|
111
133
|
const ownShape = !fields ? {} : fields instanceof z.ZodObject ? fields.shape : fields;
|
|
112
134
|
for (const k of Object.keys(ownShape)) {
|
|
113
135
|
if (k.startsWith('$'))
|
|
114
|
-
throw
|
|
136
|
+
throw lerr('model.field_name_dollar', { cls: name, field: k });
|
|
115
137
|
}
|
|
116
138
|
const ownEnds = {};
|
|
117
139
|
for (const [role, v] of Object.entries(def.ends ?? {})) {
|
|
118
140
|
if (!NAME.test(role))
|
|
119
|
-
throw
|
|
141
|
+
throw lerr('model.end_name_invalid', { cls: name, role });
|
|
120
142
|
let end = v instanceof End ? v : one(v);
|
|
121
143
|
const rule = def.onDelete?.[role];
|
|
122
144
|
if (rule)
|
|
@@ -136,28 +158,21 @@ function makeModel(kind, name, defArg, optsArg) {
|
|
|
136
158
|
if (zd.type === 'default')
|
|
137
159
|
zd = zd.innerType._zod.def;
|
|
138
160
|
if (zd.type !== 'string')
|
|
139
|
-
throw
|
|
161
|
+
throw lerr('model.key_field_type', { cls: name, field: k });
|
|
140
162
|
}
|
|
141
163
|
else if (ends[k]) {
|
|
142
164
|
if (ends[k].many || !ends[k].required)
|
|
143
|
-
throw
|
|
165
|
+
throw lerr('model.key_end_type', { cls: name, end: k });
|
|
144
166
|
}
|
|
145
167
|
else {
|
|
146
|
-
throw
|
|
168
|
+
throw lerr('model.key_unknown_name', { cls: name, name: k });
|
|
147
169
|
}
|
|
148
170
|
}
|
|
149
171
|
for (const [role, e] of Object.entries(ends)) {
|
|
150
172
|
if (e.onDeleteRule === 'unset' && (e.required || (e.min ?? 0) > 0)) {
|
|
151
|
-
throw
|
|
173
|
+
throw lerr('model.unset_required_end', { cls: name, end: role });
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
|
-
// агрегаты (план 1.1, §2.4): остальное — роль, поле суммы, цикл — сверяет база по описаниям источников
|
|
155
|
-
for (const [field, a] of Object.entries(def.aggregate ?? {})) {
|
|
156
|
-
if (!(field in shape))
|
|
157
|
-
throw new LetopisError('invalid_class', `${name}: агрегатного поля ${field} нет среди полей класса`);
|
|
158
|
-
if ((a.sum === undefined) === (a.count === undefined))
|
|
159
|
-
throw new LetopisError('invalid_class', `${name}: агрегат ${field} — ровно одно из sum и count: true`);
|
|
160
|
-
}
|
|
161
176
|
// строгий верхний уровень — как в базе (resolved.schema: additionalProperties false)
|
|
162
177
|
const schema = z.strictObject(shape);
|
|
163
178
|
const model = {
|
|
@@ -217,20 +232,14 @@ function makeModel(kind, name, defArg, optsArg) {
|
|
|
217
232
|
desc.cache = def.cache;
|
|
218
233
|
if (def.meta)
|
|
219
234
|
desc.meta = def.meta;
|
|
220
|
-
if (def.derivedBy)
|
|
221
|
-
desc.derivedBy = def.derivedBy;
|
|
222
|
-
if (def.aggregate) {
|
|
223
|
-
desc.aggregate = Object.fromEntries(Object.entries(def.aggregate).map(([field, a]) => [field, {
|
|
224
|
-
...(a.sum !== undefined ? { sum: a.sum } : { count: true }),
|
|
225
|
-
of: typeof a.of === 'string' ? a.of : a.of.name,
|
|
226
|
-
via: a.via,
|
|
227
|
-
...(a.where ? { where: a.where } : {}),
|
|
228
|
-
}]));
|
|
229
|
-
}
|
|
230
235
|
return desc;
|
|
231
236
|
},
|
|
232
237
|
lostRules() {
|
|
233
238
|
const out = [];
|
|
239
|
+
// проверки z.object(…).refine/superRefine/check — на уровне объекта (между полями): в описание класса идёт
|
|
240
|
+
// только shape, поэтому они тоже потеряны — путь — имя класса
|
|
241
|
+
if (fields instanceof z.ZodObject)
|
|
242
|
+
collectChecks(fields, name, out);
|
|
234
243
|
for (const [k, v] of Object.entries(ownShape))
|
|
235
244
|
collectLost(v, `${name}.${k}`, out);
|
|
236
245
|
return out;
|
package/dist/ops.d.ts
CHANGED
|
@@ -81,3 +81,38 @@ export declare const not: (v: Scalar | Op) => Op;
|
|
|
81
81
|
* вместо всего фильтра шага, а не значением поля; условия внутри одного фильтра соединяются через «и».
|
|
82
82
|
*/
|
|
83
83
|
export declare const or: (...filters: Record<string, unknown>[]) => Op;
|
|
84
|
+
/**
|
|
85
|
+
* Значение ключа в JSON-фильтре MCP: `value` — скаляр, `list` — массив скаляров, `range` — массив из двух скаляров,
|
|
86
|
+
* `text` — строка, `flag` — true или false, `true` — только true, `negation` — оператор или скаляр, `filters` — массив
|
|
87
|
+
* фильтров вместо всего фильтра шага (`$or`). По нему mcp.ts проверяет значение, а таблицы показывают форму.
|
|
88
|
+
*/
|
|
89
|
+
export type OpArg = 'value' | 'list' | 'range' | 'text' | 'flag' | 'true' | 'negation' | 'filters';
|
|
90
|
+
/** Оператор фильтра в реестре. */
|
|
91
|
+
export interface OperatorSpec {
|
|
92
|
+
/** Имя фабрики в TypeScript: `import { gt } from 'letopis'`. */
|
|
93
|
+
readonly ts: string;
|
|
94
|
+
/** Сама фабрика. */
|
|
95
|
+
readonly make: (...args: never[]) => Op;
|
|
96
|
+
/** Ключ в JSON-фильтре MCP (`letopis_query`): `{ "price": { "$gt": 1000 } }`. */
|
|
97
|
+
readonly mcp: string;
|
|
98
|
+
/** Значение ключа MCP. */
|
|
99
|
+
readonly arg: OpArg;
|
|
100
|
+
/** Смысл для таблиц: README, шпаргалка, глава «Чтение» и справочник сайта. */
|
|
101
|
+
readonly ru: string;
|
|
102
|
+
}
|
|
103
|
+
/** Все 18 операторов в порядке таблиц. */
|
|
104
|
+
export declare const OPERATORS: readonly OperatorSpec[];
|
|
105
|
+
/** Вызов фабрики в таблицах: `between(a, b)`. */
|
|
106
|
+
export declare function opCall(o: OperatorSpec): string;
|
|
107
|
+
/** Ключ MCP со значением в таблицах: `{ "$between": [a, b] }`. */
|
|
108
|
+
export declare function opJson(o: OperatorSpec): string;
|
|
109
|
+
/** Ключ MCP с формой значения для описания letopis_query: `$between [a, b]`; у value, text и negation — только ключ. */
|
|
110
|
+
export declare function opHint(o: OperatorSpec): string;
|
|
111
|
+
/**
|
|
112
|
+
* Таблица операторов в Markdown — README, шпаргалка и справочник сайта: TypeScript, MCP, смысл; `mcp: false` — без
|
|
113
|
+
* колонки MCP (глава «Чтение» сайта: там цепочки на TypeScript). Вертикальная черта в ячейке экранируется, иначе она
|
|
114
|
+
* разрезала бы строку таблицы.
|
|
115
|
+
*/
|
|
116
|
+
export declare function operatorTable({ mcp }?: {
|
|
117
|
+
mcp?: boolean;
|
|
118
|
+
}): string;
|
package/dist/ops.js
CHANGED
|
@@ -82,3 +82,104 @@ export const not = (v) => op('not', v);
|
|
|
82
82
|
* вместо всего фильтра шага, а не значением поля; условия внутри одного фильтра соединяются через «и».
|
|
83
83
|
*/
|
|
84
84
|
export const or = (...filters) => op('or', filters);
|
|
85
|
+
/** Все 18 операторов в порядке таблиц. */
|
|
86
|
+
export const OPERATORS = [
|
|
87
|
+
{
|
|
88
|
+
ts: 'ne', make: ne, mcp: '$ne', arg: 'value',
|
|
89
|
+
ru: 'Не равно. Подходят и строки без поля или с `null`; `ne(null)` — строки, где у поля есть значение',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
ts: 'gt', make: gt, mcp: '$gt', arg: 'value',
|
|
93
|
+
ru: 'Больше. Сравнение — по типу поля из описания класса (числа — как числа, даты — как даты), поле без описания — как текст; строки без поля не подходят',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
ts: 'gte', make: gte, mcp: '$gte', arg: 'value',
|
|
97
|
+
ru: 'Больше или равно; сравнение — как у `gt`',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
ts: 'lt', make: lt, mcp: '$lt', arg: 'value',
|
|
101
|
+
ru: 'Меньше; сравнение — как у `gt`',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
ts: 'lte', make: lte, mcp: '$lte', arg: 'value',
|
|
105
|
+
ru: 'Меньше или равно; сравнение — как у `gt`',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
ts: 'between', make: between, mcp: '$between', arg: 'range',
|
|
109
|
+
ru: 'От `a` до `b` включительно; сравнение — как у `gt`. Если `a` больше `b`, не подходит ничего',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
ts: 'inList', make: inList, mcp: '$in', arg: 'list',
|
|
113
|
+
ru: 'Одно из значений списка. Пустой список не находит ничего; `null` в списке не находит строк без значения — для них `isNull`',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
ts: 'like', make: like, mcp: '$like', arg: 'text',
|
|
117
|
+
ru: 'Текст по шаблону с учётом регистра: `%` — любые символы, в том числе ни одного, `_` — ровно один; буквальные `%` и `_` — с обратной косой чертой',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
ts: 'ilike', make: ilike, mcp: '$ilike', arg: 'text',
|
|
121
|
+
ru: 'Как `like`, но без учёта регистра',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
ts: 'starts', make: starts, mcp: '$starts', arg: 'text',
|
|
125
|
+
ru: 'Текст начинается с `s`, с учётом регистра. Это `like(s + \'%\')`: `%` и `_` внутри `s` не экранируются и работают как в шаблоне',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
ts: 'ends', make: ends, mcp: '$ends', arg: 'text',
|
|
129
|
+
ru: 'Текст заканчивается на `s`, с учётом регистра. Это `like(\'%\' + s)`: `%` и `_` внутри `s` не экранируются и работают как в шаблоне',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
ts: 'has', make: has, mcp: '$has', arg: 'value',
|
|
133
|
+
ru: 'Поле-массив содержит элемент `v`. Тип важен: `has(5)` найдёт число 5, но не строку `\'5\'`',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
ts: 'hasAny', make: hasAny, mcp: '$hasAny', arg: 'list',
|
|
137
|
+
ru: 'Поле-массив содержит хотя бы один элемент списка. Находит только строковые элементы; пустой список не находит ничего',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
ts: 'hasAll', make: hasAll, mcp: '$hasAll', arg: 'list',
|
|
141
|
+
ru: 'Поле-массив содержит все элементы списка, в любом порядке. Тип важен, как у `has`; пустой список подходит любой строке, даже без поля',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
ts: 'exists', make: exists, mcp: '$exists', arg: 'flag',
|
|
145
|
+
ru: 'Поле есть в данных (`true`, по умолчанию) или его нет (`false`); значение не важно — поле с `null` тоже есть',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
ts: 'isNull', make: isNull, mcp: '$isNull', arg: 'true',
|
|
149
|
+
ru: 'В поле `null` или поля нет совсем; отличить одно от другого помогает `exists`',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
ts: 'not', make: not, mcp: '$not', arg: 'negation',
|
|
153
|
+
ru: 'Отрицание: `x` — оператор (`not(gte(1000))` — меньше 1000 или поля нет) или простое значение — тогда это «не равно», как `ne`',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
ts: 'or', make: or, mcp: '$or', arg: 'filters',
|
|
157
|
+
ru: 'Хотя бы один из фильтров. Ставится вместо всего фильтра шага, а не значением поля; условия внутри одного фильтра соединяются через «и»',
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
/** Вызов фабрики в таблицах: `between(a, b)`. */
|
|
161
|
+
export function opCall(o) {
|
|
162
|
+
const args = { value: 'v', list: '[v, …]', range: 'a, b', text: 's', flag: 'b = true', true: '', negation: 'x', filters: 'f1, f2, …' };
|
|
163
|
+
return `${o.ts}(${args[o.arg]})`;
|
|
164
|
+
}
|
|
165
|
+
/** Ключ MCP со значением в таблицах: `{ "$between": [a, b] }`. */
|
|
166
|
+
export function opJson(o) {
|
|
167
|
+
const vals = { value: 'v', list: '[v, …]', range: '[a, b]', text: '"s"', flag: 'b', true: 'true', negation: 'x', filters: '[f1, f2, …]' };
|
|
168
|
+
return `{ "${o.mcp}": ${vals[o.arg]} }`;
|
|
169
|
+
}
|
|
170
|
+
/** Ключ MCP с формой значения для описания letopis_query: `$between [a, b]`; у value, text и negation — только ключ. */
|
|
171
|
+
export function opHint(o) {
|
|
172
|
+
const vals = { list: '[..]', range: '[a, b]', flag: 'true|false', true: 'true', filters: '[filter, …]' };
|
|
173
|
+
return vals[o.arg] ? `${o.mcp} ${vals[o.arg]}` : o.mcp;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Таблица операторов в Markdown — README, шпаргалка и справочник сайта: TypeScript, MCP, смысл; `mcp: false` — без
|
|
177
|
+
* колонки MCP (глава «Чтение» сайта: там цепочки на TypeScript). Вертикальная черта в ячейке экранируется, иначе она
|
|
178
|
+
* разрезала бы строку таблицы.
|
|
179
|
+
*/
|
|
180
|
+
export function operatorTable({ mcp = true } = {}) {
|
|
181
|
+
const cell = (s) => s.replaceAll('|', '\\|');
|
|
182
|
+
const head = mcp ? '| TypeScript | MCP | Смысл |' : '| TypeScript | Смысл |';
|
|
183
|
+
const rows = OPERATORS.map((o) => `| ${[`\`${opCall(o)}\``, ...(mcp ? [`\`${opJson(o)}\``] : []), o.ru].map(cell).join(' | ')} |`);
|
|
184
|
+
return [head, mcp ? '|---|---|---|' : '|---|---|', ...rows].join('\n');
|
|
185
|
+
}
|
package/dist/pglite.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* База для разработки без установленного PostgreSQL (решение 6 точки А): PGlite (PostgreSQL 18 в
|
|
3
|
-
* WebAssembly) с pgcrypto, доступный postgres.js через pglite-socket. Одно соединение: годится для
|
|
4
|
-
* разработки и быстрых тестов логики, но не для гонок и не вместо postgres:18 в CI; LISTEN из
|
|
5
|
-
* второго соединения нет — connect({ listen: false, max: 1 }).
|
|
6
|
-
*
|
|
7
|
-
* Пакеты @electric-sql/pglite и @electric-sql/pglite-socket — необязательные peer-зависимости:
|
|
8
|
-
* основная точка входа их не импортирует, up({ pglite }) загружает их по требованию.
|
|
9
|
-
*/
|
|
10
1
|
export interface PgliteOptions {
|
|
11
2
|
/** Каталог данных (по умолчанию — в памяти, данные теряются при остановке). */
|
|
12
3
|
dataDir?: string;
|
package/dist/pglite.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Пакеты @electric-sql/pglite и @electric-sql/pglite-socket — необязательные peer-зависимости:
|
|
8
8
|
* основная точка входа их не импортирует, up({ pglite }) загружает их по требованию.
|
|
9
9
|
*/
|
|
10
|
+
import { lmsg } from './errors.js';
|
|
10
11
|
/** Запустить PGlite с pgcrypto и сокетом для postgres.js. */
|
|
11
12
|
export async function startPglite(opts = {}) {
|
|
12
13
|
const load = async (name) => {
|
|
@@ -14,7 +15,8 @@ export async function startPglite(opts = {}) {
|
|
|
14
15
|
return await import(name);
|
|
15
16
|
}
|
|
16
17
|
catch {
|
|
17
|
-
|
|
18
|
+
// обычный Error, как у up(): подходящего кода нет
|
|
19
|
+
throw new Error(`letopis.up: ${lmsg('boot.pglite_package', { name })}`);
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
22
|
const { PGlite } = await load('@electric-sql/pglite');
|
package/dist/registry.d.ts
CHANGED
|
@@ -9,18 +9,7 @@ export interface EndInfo {
|
|
|
9
9
|
onDelete?: 'restrict' | 'cascade' | 'unset';
|
|
10
10
|
meta?: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Агрегат в базе (план 1.1, §2.4): поле — сумма поля sum или число (count) живых строк класса of с потомками того же
|
|
14
|
-
* арендатора, чей одиночный конец via ведёт на строку; where — JSON Schema над данными строки-источника.
|
|
15
|
-
*/
|
|
16
|
-
export interface AggregateInfo {
|
|
17
|
-
sum?: string;
|
|
18
|
-
count?: true;
|
|
19
|
-
of: string;
|
|
20
|
-
via: string;
|
|
21
|
-
where?: JsonSchema;
|
|
22
|
-
}
|
|
23
|
-
/** Тип листа данных для фильтров, сортировки и агрегатов (замена fieldTypeOf 0.21). */
|
|
12
|
+
/** Тип листа данных для фильтров, сортировки и агрегатов. */
|
|
24
13
|
export type LeafType = 'text' | 'numeric' | 'boolean' | 'timestamptz' | 'date' | 'uuid' | 'jsonb';
|
|
25
14
|
export interface ClassInfo {
|
|
26
15
|
name: string;
|
|
@@ -34,16 +23,13 @@ export interface ClassInfo {
|
|
|
34
23
|
schema: JsonSchema;
|
|
35
24
|
ends: Record<string, EndInfo>;
|
|
36
25
|
key: string[];
|
|
37
|
-
history:
|
|
26
|
+
/** Ведёт ли класс журнал версий (`history: false` — нет). */
|
|
27
|
+
history: boolean;
|
|
38
28
|
ownerDefault: 'tenant' | 'actor';
|
|
39
29
|
cache?: {
|
|
40
30
|
ttl?: number;
|
|
41
31
|
};
|
|
42
32
|
meta: Record<string, unknown>;
|
|
43
|
-
/** Производный класс: строки пишет только проектор с этим именем (план 1.1, §2.4). */
|
|
44
|
-
derivedBy?: string;
|
|
45
|
-
/** Агрегатные поля: их считает база, приложение не пишет (план 1.1, §2.4). */
|
|
46
|
-
aggregate?: Record<string, AggregateInfo>;
|
|
47
33
|
/** Валидатор на TypeScript (компилируется при первом обращении). */
|
|
48
34
|
validator(): Validator;
|
|
49
35
|
}
|
|
@@ -55,8 +41,6 @@ interface ClassRow {
|
|
|
55
41
|
resolved: Record<string, unknown>;
|
|
56
42
|
};
|
|
57
43
|
}
|
|
58
|
-
/** Агрегатные поля класса: их считает база (план 1.1, §2.4). */
|
|
59
|
-
export declare const aggregateFields: (cls: Pick<ClassInfo, "aggregate">) => string[];
|
|
60
44
|
export declare class Registry {
|
|
61
45
|
private byName;
|
|
62
46
|
private byAlias;
|