@rsconcept/rstool 0.5.2 → 0.6.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.
@@ -0,0 +1,125 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { EvalStatus, RSToolWrapperClient } from '../src';
5
+
6
+ import { A1_ID, D3_ID, S2_ID, S3_ID } from './kinship/constants';
7
+
8
+ /** Tuple marker in structured values. */
9
+ const TUPLE_ID = -111;
10
+
11
+ /**
12
+ * Minimal family so D3 (внучатые племянники) is non-empty:
13
+ * - 0,1 Иван/Мария — общие родители 2,3
14
+ * - 2 Пётр, 3 Анна — сиблинги
15
+ * - 4 Олег — сын Петра
16
+ * - 5 Дарья — дочь Анны (племянница Петра)
17
+ * - 6 Семён — сын Дарьи (внучатый племянник Петра)
18
+ */
19
+ const X1_BINDING = {
20
+ 0: 'Иван',
21
+ 1: 'Мария',
22
+ 2: 'Пётр',
23
+ 3: 'Анна',
24
+ 4: 'Олег',
25
+ 5: 'Дарья',
26
+ 6: 'Семён'
27
+ } as const;
28
+
29
+ /** S1: (parent, child) pairs — numeric indices into X1. */
30
+ const S1_VALUE = [
31
+ [TUPLE_ID, 0, 2],
32
+ [TUPLE_ID, 1, 2],
33
+ [TUPLE_ID, 0, 3],
34
+ [TUPLE_ID, 1, 3],
35
+ [TUPLE_ID, 2, 4],
36
+ [TUPLE_ID, 3, 5],
37
+ [TUPLE_ID, 5, 6]
38
+ ] as const;
39
+
40
+ /** Indices of men and women in the sample family (Иван, Пётр, Олег, Семён — м; остальные — ж). */
41
+ const S2_VALUE = [0, 2, 4, 6] as const;
42
+ const S3_VALUE = [1, 3, 5] as const;
43
+
44
+ async function run() {
45
+ const client = new RSToolWrapperClient({
46
+ cwd: resolve(process.cwd())
47
+ });
48
+
49
+ try {
50
+ await client.waitUntilReady();
51
+ const kinshipPath = resolve(process.cwd(), 'examples', 'kinship-rsform-session.json');
52
+ const kinshipJson = await readFile(kinshipPath, 'utf8');
53
+
54
+ const imported = await client.call<{ sessionId: string }>('importSession', {
55
+ payload: kinshipJson
56
+ });
57
+
58
+ await client.call('setConstituentaValues', {
59
+ sessionId: imported.sessionId,
60
+ input: {
61
+ items: [
62
+ { target: 1, value: X1_BINDING },
63
+ { target: 2, value: S1_VALUE },
64
+ { target: S2_ID, value: S2_VALUE },
65
+ { target: S3_ID, value: S3_VALUE }
66
+ ]
67
+ }
68
+ });
69
+
70
+ const recalculated = await client.call<{
71
+ items: { id: number; alias: string; value: unknown; status: number }[];
72
+ }>('recalculateModel', {
73
+ sessionId: imported.sessionId
74
+ });
75
+
76
+ const d3 = recalculated.items.find(item => item.id === D3_ID);
77
+ const d3Eval = await client.call<{ success: boolean; value: unknown; status: number }>('evaluateConstituenta', {
78
+ sessionId: imported.sessionId,
79
+ input: { constituentId: D3_ID }
80
+ });
81
+
82
+ const a1Eval = await client.call<{ success: boolean; value: unknown; status: number }>('evaluateConstituenta', {
83
+ sessionId: imported.sessionId,
84
+ input: { constituentId: A1_ID }
85
+ });
86
+
87
+ console.log('D3 recalculate:', d3);
88
+ console.log('D3 evaluate:', d3Eval);
89
+ console.log('A1 evaluate:', a1Eval);
90
+
91
+ if (
92
+ !d3Eval.success ||
93
+ d3Eval.status !== EvalStatus.HAS_DATA ||
94
+ !Array.isArray(d3Eval.value) ||
95
+ d3Eval.value.length === 0
96
+ ) {
97
+ console.error('Expected non-empty D3; got', d3Eval);
98
+ throw new Error(`Expected non-empty D3; got ${JSON.stringify(d3Eval)}`);
99
+ }
100
+
101
+ if (!a1Eval.success || a1Eval.status === EvalStatus.AXIOM_FALSE || a1Eval.value !== 1) {
102
+ console.error('Expected A1 card(X1)≤10 to hold; got', a1Eval);
103
+ throw new Error(`Expected A1 card(X1)≤10 to hold; got ${JSON.stringify(a1Eval)}`);
104
+ }
105
+
106
+ await client.call('commitStep', {
107
+ sessionId: imported.sessionId,
108
+ message: 'Модель kinship: D3 непуст; A1 card(X1)≤10 выполняется'
109
+ });
110
+
111
+ const exported = await client.call<string>('exportSession', {
112
+ sessionId: imported.sessionId
113
+ });
114
+ const outputPath = resolve(process.cwd(), 'examples', 'kinship-rsmodel-session.json');
115
+ await writeFile(outputPath, exported, 'utf8');
116
+ console.log(`Exported: ${outputPath}`);
117
+ } finally {
118
+ await client.close();
119
+ }
120
+ }
121
+
122
+ run().catch(error => {
123
+ console.error(error);
124
+ process.exit(1);
125
+ });
@@ -0,0 +1,90 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { CstType, RSToolWrapperClient, type AddOrUpdateConstituentaInput } from '../src';
5
+
6
+ async function run() {
7
+ const client = new RSToolWrapperClient({
8
+ cwd: resolve(process.cwd())
9
+ });
10
+
11
+ try {
12
+ await client.waitUntilReady();
13
+ const session = await client.call<{ sessionId: string; contractVersion: string }>('createSession');
14
+
15
+ const drafts: AddOrUpdateConstituentaInput[] = [
16
+ {
17
+ draft: {
18
+ id: 1,
19
+ alias: 'X1',
20
+ cstType: CstType.BASE,
21
+ definitionFormal: ''
22
+ }
23
+ },
24
+ {
25
+ draft: {
26
+ id: 2,
27
+ alias: 'C1',
28
+ cstType: CstType.CONSTANT,
29
+ definitionFormal: ''
30
+ }
31
+ },
32
+ {
33
+ draft: {
34
+ id: 3,
35
+ alias: 'S1',
36
+ cstType: CstType.STRUCTURED,
37
+ definitionFormal: 'ℬ(X1×X1)',
38
+ convention: 'Pairs (parent, child) over X1.'
39
+ }
40
+ },
41
+ {
42
+ draft: {
43
+ id: 4,
44
+ alias: 'D1',
45
+ cstType: CstType.TERM,
46
+ definitionFormal: 'Pr1(S1)'
47
+ }
48
+ },
49
+ {
50
+ draft: {
51
+ id: 5,
52
+ alias: 'A1',
53
+ cstType: CstType.AXIOM,
54
+ definitionFormal: '1=1'
55
+ }
56
+ }
57
+ ];
58
+
59
+ for (const input of drafts) {
60
+ const result = await client.call('addOrUpdateConstituenta', {
61
+ sessionId: session.sessionId,
62
+ input
63
+ });
64
+ console.log(
65
+ `Added ${input.draft.alias}:`,
66
+ (result as { diagnostics?: unknown[] }).diagnostics?.length ?? 0,
67
+ 'diagnostics'
68
+ );
69
+ }
70
+
71
+ await client.call('commitStep', {
72
+ sessionId: session.sessionId,
73
+ message: 'Built sample RSForm'
74
+ });
75
+
76
+ const exported = await client.call<string>('exportSession', {
77
+ sessionId: session.sessionId
78
+ });
79
+ const outputPath = resolve(process.cwd(), 'examples', 'sample-rsform-session.json');
80
+ await writeFile(outputPath, exported, 'utf8');
81
+ console.log(`Sample RSForm exported: ${outputPath}`);
82
+ } finally {
83
+ await client.close();
84
+ }
85
+ }
86
+
87
+ run().catch(error => {
88
+ console.error(error);
89
+ process.exit(1);
90
+ });
@@ -0,0 +1,106 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { CstType, RSToolWrapperClient, type AddOrUpdateConstituentaInput } from '../src';
5
+
6
+ /** Tuple marker in structured values. */
7
+ const TUPLE_ID = -111;
8
+
9
+ async function run() {
10
+ const client = new RSToolWrapperClient({
11
+ cwd: resolve(process.cwd())
12
+ });
13
+
14
+ try {
15
+ await client.waitUntilReady();
16
+ const session = await client.call<{ sessionId: string; contractVersion: string }>('createSession');
17
+
18
+ const drafts: AddOrUpdateConstituentaInput[] = [
19
+ {
20
+ draft: { id: 1, alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }
21
+ },
22
+ {
23
+ draft: { id: 2, alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }
24
+ },
25
+ {
26
+ draft: {
27
+ id: 3,
28
+ alias: 'S1',
29
+ cstType: CstType.STRUCTURED,
30
+ definitionFormal: 'ℬ(X1×X1)',
31
+ convention: 'Pairs (parent, child) over X1.'
32
+ }
33
+ },
34
+ {
35
+ draft: { id: 4, alias: 'D1', cstType: CstType.TERM, definitionFormal: 'Pr1(S1)' }
36
+ },
37
+ {
38
+ draft: { id: 5, alias: 'A1', cstType: CstType.AXIOM, definitionFormal: '1=1' }
39
+ }
40
+ ];
41
+
42
+ for (const input of drafts) {
43
+ const result = await client.call('addOrUpdateConstituenta', {
44
+ sessionId: session.sessionId,
45
+ input
46
+ });
47
+ console.log(
48
+ `Added ${input.draft.alias}:`,
49
+ (result as { diagnostics?: unknown[] }).diagnostics?.length ?? 0,
50
+ 'diagnostics'
51
+ );
52
+ }
53
+
54
+ const model = await client.call('setConstituentaValues', {
55
+ sessionId: session.sessionId,
56
+ input: {
57
+ items: [
58
+ { target: 1, value: { 0: 'alice', 1: 'bob' } },
59
+ { target: 2, value: { 0: 'zero', 1: 'one', 2: 'two' } },
60
+ { target: 3, value: [[TUPLE_ID, 0, 1]] }
61
+ ]
62
+ }
63
+ });
64
+ console.log('Model values set:', model);
65
+
66
+ const d1Eval = await client.call('evaluateConstituenta', {
67
+ sessionId: session.sessionId,
68
+ input: { constituentId: 4 }
69
+ });
70
+ console.log('D1 (Pr1(S1)) evaluation:', d1Eval);
71
+
72
+ const a1Eval = await client.call('evaluateConstituenta', {
73
+ sessionId: session.sessionId,
74
+ input: { constituentId: 5 }
75
+ });
76
+ console.log('A1 (1=1) evaluation:', a1Eval);
77
+
78
+ const recalculated = await client.call('recalculateModel', {
79
+ sessionId: session.sessionId
80
+ });
81
+ const recalculatedItems = (recalculated as { items: { alias: string; status: number }[] }).items;
82
+ console.log(
83
+ 'Recalculated model:',
84
+ recalculatedItems.map(item => ({ alias: item.alias, status: item.status }))
85
+ );
86
+
87
+ await client.call('commitStep', {
88
+ sessionId: session.sessionId,
89
+ message: 'Built sample RSModel'
90
+ });
91
+
92
+ const exported = await client.call<string>('exportSession', {
93
+ sessionId: session.sessionId
94
+ });
95
+ const outputPath = resolve(process.cwd(), 'examples', 'sample-rsmodel-session.json');
96
+ await writeFile(outputPath, exported, 'utf8');
97
+ console.log(`Sample RSModel exported: ${outputPath}`);
98
+ } finally {
99
+ await client.close();
100
+ }
101
+ }
102
+
103
+ run().catch(error => {
104
+ console.error(error);
105
+ process.exit(1);
106
+ });
@@ -0,0 +1,247 @@
1
+ import readline from 'node:readline/promises';
2
+ import { resolve } from 'node:path';
3
+ import { stdin as input, stdout as output } from 'node:process';
4
+
5
+ import { RSToolWrapperClient } from '../../src';
6
+
7
+ import { DEFAULT_SESSION_PATH } from './constants';
8
+ import { KinshipModelSession } from './session';
9
+ import {
10
+ formatS1,
11
+ formatX1,
12
+ formatA1Status,
13
+ genderLabel,
14
+ parseAddPersonArgs,
15
+ parseSetPersonArgs
16
+ } from './x1-actions';
17
+
18
+ const HELP = `
19
+ Команды изменения X1 (люди):
20
+
21
+ list показать X1 и связи S1
22
+ add <м|ж> <имя> добавить человека (пол: м, ж, m, f)
23
+ remove <имя> удалить человека (с пересчётом индексов и очисткой S1)
24
+ rename <старое> <новое> переименовать
25
+ set <м|ж> <имя> ... заменить X1 (пары пол+имя; S1 по именам)
26
+ set <имя1> <имя2> ... то же, если пол уже известен из сессии / примера
27
+ clear очистить X1 и S1
28
+ save [путь] сохранить сессию (по умолчанию — исходный файл)
29
+ help эта справка
30
+ exit выход без сохранения
31
+
32
+ Флаги запуска:
33
+ --session <path> файл сессии (по умолчанию: ${DEFAULT_SESSION_PATH})
34
+ --no-save не сохранять после команды
35
+ `.trim();
36
+
37
+ const READONLY_COMMANDS = new Set(['help', '?', 'list', 'show']);
38
+
39
+ function shouldAutoSave(command: string, autoSave: boolean): boolean {
40
+ return autoSave && command !== 'save' && !READONLY_COMMANDS.has(command);
41
+ }
42
+
43
+ interface CliOptions {
44
+ sessionPath: string;
45
+ autoSave: boolean;
46
+ command?: string;
47
+ args: string[];
48
+ }
49
+
50
+ function failCli(message: string): never {
51
+ console.error(message);
52
+ console.error('');
53
+ console.error(HELP);
54
+ process.exit(1);
55
+ }
56
+
57
+ function parseArgs(argv: string[]): CliOptions {
58
+ const options: CliOptions = {
59
+ sessionPath: DEFAULT_SESSION_PATH,
60
+ autoSave: true,
61
+ args: []
62
+ };
63
+
64
+ for (let index = 0; index < argv.length; index += 1) {
65
+ const token = argv[index];
66
+ if (token === '--session') {
67
+ const value = argv[index + 1];
68
+ if (value === undefined) {
69
+ failCli('Ошибка: после --session требуется путь к файлу сессии.');
70
+ }
71
+ if (value.startsWith('-')) {
72
+ failCli(`Ошибка: недопустимое значение для --session: «${value}».`);
73
+ }
74
+ options.sessionPath = value;
75
+ index += 1;
76
+ continue;
77
+ }
78
+ if (token === '--no-save') {
79
+ options.autoSave = false;
80
+ continue;
81
+ }
82
+ if (!options.command) {
83
+ options.command = token;
84
+ continue;
85
+ }
86
+ options.args.push(token);
87
+ }
88
+
89
+ return options;
90
+ }
91
+
92
+ async function printModel(session: KinshipModelSession): Promise<void> {
93
+ const binding = await session.getX1Binding();
94
+ const s1 = await session.getS1Value();
95
+ const genderByName = session.getGenderByName();
96
+ console.log('X1 (люди):');
97
+ console.log(formatX1(binding, genderByName));
98
+ console.log('');
99
+ console.log(formatA1Status(binding));
100
+ console.log('');
101
+ console.log('S1 (родитель → ребёнок):');
102
+ console.log(formatS1(binding, s1));
103
+ }
104
+
105
+ async function runCommand(session: KinshipModelSession, command: string, args: string[]): Promise<boolean> {
106
+ switch (command) {
107
+ case 'help':
108
+ case '?':
109
+ console.log(HELP);
110
+ return true;
111
+
112
+ case 'list':
113
+ case 'show':
114
+ await printModel(session);
115
+ return true;
116
+
117
+ case 'add': {
118
+ const { gender, name } = parseAddPersonArgs(args);
119
+ await session.addPerson(name, gender);
120
+ await session.commitStep(`X1: добавлен «${name}» (${genderLabel(gender)})`);
121
+ console.log(`Добавлен: ${name} (${genderLabel(gender)})`);
122
+ await printModel(session);
123
+ return true;
124
+ }
125
+
126
+ case 'remove':
127
+ case 'rm': {
128
+ const name = args.join(' ').trim();
129
+ if (!name) {
130
+ throw new Error('Укажите имя: remove <имя>');
131
+ }
132
+ await session.removePerson(name);
133
+ await session.commitStep(`X1: удалён «${name}»`);
134
+ console.log(`Удалён: ${name}`);
135
+ await printModel(session);
136
+ return true;
137
+ }
138
+
139
+ case 'rename': {
140
+ const [oldName, ...rest] = args;
141
+ const newName = rest.join(' ').trim();
142
+ if (!oldName || !newName) {
143
+ throw new Error('Укажите имена: rename <старое> <новое>');
144
+ }
145
+ await session.renamePerson(oldName, newName);
146
+ await session.commitStep(`X1: «${oldName}» → «${newName}»`);
147
+ console.log(`Переименовано: ${oldName} → ${newName}`);
148
+ await printModel(session);
149
+ return true;
150
+ }
151
+
152
+ case 'set': {
153
+ const { specs } = parseSetPersonArgs(args, session.getGenderByName());
154
+ await session.setX1People(specs);
155
+ await session.commitStep(`X1: задан список (${specs.length})`);
156
+ const summary = specs.map(spec => `${spec.name} (${genderLabel(spec.gender)})`).join(', ');
157
+ console.log(`Задан X1: ${summary}`);
158
+ await printModel(session);
159
+ return true;
160
+ }
161
+
162
+ case 'clear': {
163
+ await session.clearX1();
164
+ await session.commitStep('X1 и S1 очищены');
165
+ console.log('X1 и S1 очищены');
166
+ await printModel(session);
167
+ return true;
168
+ }
169
+
170
+ case 'save': {
171
+ const savedPath = await session.save(args[0]);
172
+ console.log(`Сохранено: ${savedPath}`);
173
+ return true;
174
+ }
175
+
176
+ case 'exit':
177
+ case 'quit':
178
+ return false;
179
+
180
+ default:
181
+ throw new Error(`Неизвестная команда: ${command}\n\n${HELP}`);
182
+ }
183
+ }
184
+
185
+ async function runInteractive(session: KinshipModelSession, autoSave: boolean): Promise<void> {
186
+ const rl = readline.createInterface({ input, output });
187
+ console.log('Kinship model CLI — изменение X1');
188
+ console.log(`Сессия: ${resolve(process.cwd(), DEFAULT_SESSION_PATH)}`);
189
+ console.log('Введите help для списка команд.\n');
190
+
191
+ try {
192
+ for (;;) {
193
+ const line = (await rl.question('kinship> ')).trim();
194
+ if (!line) {
195
+ continue;
196
+ }
197
+ const [command, ...args] = line.split(/\s+/);
198
+ if (command === 'exit' || command === 'quit') {
199
+ break;
200
+ }
201
+ try {
202
+ await runCommand(session, command, args);
203
+ if (shouldAutoSave(command, autoSave)) {
204
+ const savedPath = await session.save();
205
+ console.log(`(автосохранение: ${savedPath})`);
206
+ }
207
+ } catch (error) {
208
+ console.error(error instanceof Error ? error.message : error);
209
+ }
210
+ console.log('');
211
+ }
212
+ } finally {
213
+ rl.close();
214
+ }
215
+ }
216
+
217
+ async function main(): Promise<void> {
218
+ const options = parseArgs(process.argv.slice(2));
219
+ const client = new RSToolWrapperClient({ cwd: resolve(process.cwd()) });
220
+
221
+ try {
222
+ await client.waitUntilReady();
223
+ const session = await KinshipModelSession.open(client, options.sessionPath);
224
+
225
+ if (!options.command) {
226
+ await runInteractive(session, options.autoSave);
227
+ return;
228
+ }
229
+
230
+ const shouldContinue = await runCommand(session, options.command, options.args);
231
+ if (!shouldContinue) {
232
+ return;
233
+ }
234
+
235
+ if (shouldAutoSave(options.command, options.autoSave)) {
236
+ const savedPath = await session.save();
237
+ console.log(`Сохранено: ${savedPath}`);
238
+ }
239
+ } finally {
240
+ await client.close();
241
+ }
242
+ }
243
+
244
+ main().catch(error => {
245
+ console.error(error instanceof Error ? error.message : error);
246
+ process.exit(1);
247
+ });
@@ -0,0 +1,12 @@
1
+ /** Constituent ids in kinship RSForm / RSModel sessions. */
2
+ export const X1_ID = 1;
3
+ export const S1_ID = 2;
4
+ export const S2_ID = 13;
5
+ export const S3_ID = 14;
6
+ export const D3_ID = 11;
7
+ export const A1_ID = 12;
8
+
9
+ /** Tuple marker in structured S1 values (frontend `TUPLE_ID`). */
10
+ export const TUPLE_ID = -111;
11
+
12
+ export const DEFAULT_SESSION_PATH = 'examples/kinship-rsmodel-session.json';