@hzhangxyz/ddss 0.0.29 → 0.0.30-alpha1

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.
Files changed (3) hide show
  1. package/README.md +30 -15
  2. package/dist/index.js +219 -270
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Distributed Deductive System Sorts (DDSS)
2
2
 
3
- DDSS is a distributed deductive system with a scalable architecture. It currently supports distributed engines including forward-chaining, E-graph, and more.
3
+ DDSS is a distributed deductive system with a scalable architecture, providing identical implementations in both **Python** and **TypeScript**. It currently supports distributed engines including forward-chaining, E-graph, and more.
4
4
 
5
5
  ## Design Philosophy
6
6
 
7
7
  DDSS adopts a modular architecture that decomposes the deductive system into independent but collaborative sub-systems:
8
8
 
9
- 1. **Separation of Concerns**: Each module focuses on a specific reasoning task
10
- 2. **Concurrent Execution**: All modules collaborate asynchronously through a shared database, fully utilizing system resources
11
- 3. **Persistent Storage**: Uses a database to store facts and ideas, ensuring data consistency
9
+ 1. **Separation of Concerns**: Each module focuses on a specific reasoning task.
10
+ 2. **Concurrent Execution**: All modules collaborate asynchronously through a shared database, fully utilizing system resources.
11
+ 3. **Persistent Storage**: Uses a database to store facts and ideas, ensuring data consistency.
12
12
 
13
13
  The system uses a database as the central hub, with two tables (`facts` and `ideas`) for interaction between sub-systems:
14
14
 
@@ -18,34 +18,49 @@ The system uses a database as the central hub, with two tables (`facts` and `ide
18
18
 
19
19
  ## Modules
20
20
 
21
- - **Input** (`ddss/input.py`): Interactive input interface with BNF syntax parsing
22
- - **Output** (`ddss/output.py`): Real-time display of facts and ideas from the database
23
- - **Load** (`ddss/load.py`): Batch import of facts from standard input
24
- - **Dump** (`ddss/dump.py`): Export all facts and ideas to output
25
- - **DS** (`ddss/ds.py`): Forward-chaining deductive search engine
26
- - **Egg** (`ddss/egg.py`): E-graph based equality reasoning engine
21
+ The system consists of the following modules, implemented symmetrically in `ddss/*.py` and `ddss/*.ts`:
22
+
23
+ - **Input** (`ddss/input.py`, `ddss/input.ts`): Interactive input interface with BNF syntax parsing
24
+ - **Output** (`ddss/output.py`, `ddss/output.ts`): Real-time display of facts and ideas from the database
25
+ - **Load** (`ddss/load.py`, `ddss/load.ts`): Batch import of facts from standard input
26
+ - **Dump** (`ddss/dump.py`, `ddss/dump.ts`): Export all facts and ideas to output
27
+ - **DS** (`ddss/ds.py`, `ddss/ds.ts`): Forward-chaining deductive search engine
28
+ - **Egg** (`ddss/egg.py`, `ddss/egg.ts`): E-graph based equality reasoning engine
27
29
 
28
30
  ## Installation
29
31
 
30
- ### Using uvx (Recommended)
32
+ You can choose either the Python or TypeScript version. Both provide the same `ddss` command-line interface.
31
33
 
32
- The simplest way is to run with `uvx`:
34
+ ### Python Version
33
35
 
36
+ #### Using uvx (Recommended)
34
37
  ```bash
35
38
  uvx ddss
36
39
  ```
37
40
 
38
- This automatically installs all dependencies and starts the DDSS system.
41
+ #### Using pip
42
+ ```bash
43
+ pip install ddss
44
+ ddss
45
+ ```
46
+
47
+ ### TypeScript Version
39
48
 
40
- ### Using pip
49
+ #### Using npx (Recommended)
50
+ ```bash
51
+ npx @hzhangxyz/ddss
52
+ ```
41
53
 
54
+ #### Using npm
42
55
  ```bash
43
- pip install ddss
56
+ npm install -g @hzhangxyz/ddss
44
57
  ddss
45
58
  ```
46
59
 
47
60
  ## Usage
48
61
 
62
+ The usage, command-line options, and interactive syntax are identical regardless of the implementation language used.
63
+
49
64
  ### Basic Usage
50
65
 
51
66
  Run DDSS with a temporary SQLite database:
package/dist/index.js CHANGED
@@ -1,100 +1,74 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import * as path from 'node:path';
2
+ import * as fs from 'node:fs/promises';
4
3
  import * as os from 'node:os';
5
- import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { Command } from 'commander';
6
6
  import { Sequelize, DataTypes, Model, Op } from 'sequelize';
7
7
  import { Search as Search$1, Term, List, Rule } from 'atsds';
8
+ import { stdout, stdin } from 'node:process';
9
+ import { unparse, parse } from 'atsds-bnf';
8
10
  import { EGraph } from 'atsds-egg';
9
11
  import * as readline from 'node:readline/promises';
10
- import { stdout, stdin } from 'node:process';
11
- import { parse, unparse } from 'atsds-bnf';
12
- import * as readline$1 from 'node:readline';
13
12
 
14
- // 定义基础模型类
15
13
  class Fact extends Model {
16
14
  }
17
15
  class Idea extends Model {
18
16
  }
19
17
  async function initializeDatabase(addr) {
20
- // 转换地址:Python sqlite:///path 转为 Sequelize sqlite:path
21
- let sequelizeAddr = addr;
22
- if (addr.startsWith("sqlite:///")) {
23
- sequelizeAddr = `sqlite:${addr.replace("sqlite:///", "")}`;
24
- }
25
- const sequelize = new Sequelize(sequelizeAddr, {
26
- logging: false,
27
- define: {
28
- timestamps: false, // 去掉 createdAt 和 updatedAt
29
- },
30
- });
18
+ const sequelize = new Sequelize(addr, { logging: false });
31
19
  Fact.init({
32
20
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
33
21
  data: { type: DataTypes.TEXT, unique: true, allowNull: false },
34
- }, { sequelize, tableName: "facts" });
22
+ }, { sequelize, tableName: "facts", timestamps: false });
35
23
  Idea.init({
36
24
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
37
25
  data: { type: DataTypes.TEXT, unique: true, allowNull: false },
38
- }, { sequelize, tableName: "ideas" });
26
+ }, { sequelize, tableName: "ideas", timestamps: false });
39
27
  await sequelize.sync();
40
28
  return sequelize;
41
29
  }
42
- /**
43
- * 对应 Python 中的 insert_or_ignore。
44
- * Sequelize 的 bulkCreate 配合 ignoreDuplicates 可以实现跨方言的“重复则跳过”。
45
- */
46
30
  async function insertOrIgnore(model, data) {
47
- try {
48
- await model.bulkCreate([{ data }], {
49
- ignoreDuplicates: true,
50
- });
51
- }
52
- catch (error) {
53
- // 忽略重复键错误
54
- }
31
+ await model.bulkCreate([{ data }], {
32
+ ignoreDuplicates: true,
33
+ });
55
34
  }
56
35
 
57
36
  function strRuleGetStrIdea(data) {
58
37
  if (!data.startsWith("--")) {
59
38
  const lines = data.split("\n");
60
- if (lines.length > 0) {
61
- return `----\n${lines[0]}\n`;
62
- }
39
+ return `----\n${lines[0]}\n`;
63
40
  }
64
41
  return null;
65
42
  }
66
- /**
67
- * Patches process.stdout.write to prevent asynchronous logs from messing up the readline prompt.
68
- */
69
43
  function patchStdout(rl) {
70
- const originalWrite = process.stdout.write;
44
+ const originalWrite = stdout.write;
71
45
  let isReprompting = false;
72
46
  // @ts-ignore
73
- process.stdout.write = (chunk, encoding, callback) => {
47
+ stdout.write = (chunk, encoding, callback) => {
48
+ // (1) Use original write when reprompting to avoid infinite recursion
49
+ // since rl.prompt() internally calls stdout.write.
74
50
  if (isReprompting) {
75
- return originalWrite.call(process.stdout, chunk, encoding, callback);
76
- }
77
- const str = chunk.toString();
78
- // If it's just a newline (like when the user hits Enter), let it pass through.
79
- // This allows the input line to stay in the terminal history.
80
- if (str === "\n" || str === "\r\n") {
81
- return originalWrite.call(process.stdout, chunk, encoding, callback);
82
- }
83
- if (str.includes("\n")) {
84
- // Move to start of line and clear it (wiping the current prompt display)
85
- originalWrite.call(process.stdout, "\r\x1b[K");
86
- const result = originalWrite.call(process.stdout, chunk, encoding, callback);
51
+ return originalWrite.call(stdout, chunk, encoding, callback);
52
+ }
53
+ // (2) Let normal newlines (e.g., from user pressing Enter) pass through
54
+ // without triggering the reprompting logic.
55
+ if (chunk === "\n" || chunk === "\r\n") {
56
+ return originalWrite.call(stdout, chunk, encoding, callback);
57
+ }
58
+ if (chunk.includes("\n")) {
59
+ // Move cursor to the start of the line.
60
+ originalWrite.call(stdout, "\r");
61
+ const result = originalWrite.call(stdout, chunk, encoding, callback);
87
62
  isReprompting = true;
88
- // Redraw the prompt and current input buffer on the NEW line
89
- rl.prompt(true);
63
+ rl.prompt();
90
64
  isReprompting = false;
91
65
  return result;
92
66
  }
93
- return originalWrite.call(process.stdout, chunk, encoding, callback);
67
+ return originalWrite.call(stdout, chunk, encoding, callback);
94
68
  };
95
69
  return () => {
96
70
  // @ts-ignore
97
- process.stdout.write = originalWrite;
71
+ stdout.write = originalWrite;
98
72
  };
99
73
  }
100
74
 
@@ -102,42 +76,50 @@ async function main$5(addr, sequelize) {
102
76
  if (!sequelize) {
103
77
  sequelize = await initializeDatabase(addr);
104
78
  }
105
- try {
106
- const search = new Search$1();
107
- let maxFact = -1;
108
- while (true) {
109
- const begin = Date.now();
110
- let count = 0;
111
- const newFacts = await Fact.findAll({
112
- where: { id: { [Op.gt]: maxFact } },
113
- order: [["id", "ASC"]],
114
- });
115
- for (const fact of newFacts) {
116
- maxFact = Math.max(maxFact, fact.id);
117
- search.add(fact.data);
118
- }
119
- const tasks = [];
120
- const handler = (rule) => {
121
- const dsStr = rule.toString();
122
- tasks.push(insertOrIgnore(Fact, dsStr));
123
- const idea = strRuleGetStrIdea(dsStr);
124
- if (idea) {
125
- tasks.push(insertOrIgnore(Idea, idea));
126
- }
127
- return false;
128
- };
129
- count = search.execute(handler);
130
- await Promise.all(tasks);
131
- const end = Date.now();
132
- const duration = (end - begin) / 1000;
133
- if (count === 0) {
134
- const delay = Math.max(0, 0.1 - duration);
135
- await new Promise((resolve) => setTimeout(resolve, delay * 1000));
79
+ const search = new Search$1();
80
+ let maxFact = -1;
81
+ while (true) {
82
+ const begin = Date.now();
83
+ let count = 0;
84
+ const newFacts = await Fact.findAll({
85
+ where: { id: { [Op.gt]: maxFact } },
86
+ });
87
+ for (const fact of newFacts) {
88
+ maxFact = Math.max(maxFact, fact.id);
89
+ search.add(fact.data);
90
+ }
91
+ const tasks = [];
92
+ const handler = (rule) => {
93
+ const dsStr = rule.toString();
94
+ tasks.push(insertOrIgnore(Fact, dsStr));
95
+ const idea = strRuleGetStrIdea(dsStr);
96
+ if (idea) {
97
+ tasks.push(insertOrIgnore(Idea, idea));
136
98
  }
99
+ return false;
100
+ };
101
+ count = search.execute(handler);
102
+ await Promise.all(tasks);
103
+ const end = Date.now();
104
+ const duration = (end - begin) / 1000;
105
+ if (count === 0) {
106
+ const delay = Math.max(0, 0.1 - duration);
107
+ await new Promise((resolve) => setTimeout(resolve, delay * 1000));
137
108
  }
138
109
  }
139
- catch (err) {
140
- // Handle error
110
+ }
111
+
112
+ async function main$4(addr, sequelize) {
113
+ if (!sequelize) {
114
+ sequelize = await initializeDatabase(addr);
115
+ }
116
+ const ideas = await Idea.findAll();
117
+ for (const idea of ideas) {
118
+ console.log("idea:", unparse(idea.data));
119
+ }
120
+ const facts = await Fact.findAll();
121
+ for (const fact of facts) {
122
+ console.log("fact:", unparse(fact.data));
141
123
  }
142
124
  }
143
125
 
@@ -153,7 +135,6 @@ function extractLhsRhsFromRule(data) {
153
135
  if (!(inner instanceof List)) {
154
136
  return null;
155
137
  }
156
- // (binary == lhs rhs) -> list of 4 elements: "binary", "==", lhs, rhs
157
138
  if (!(inner.length() === 4 && inner.getitem(0).toString() === "binary" && inner.getitem(1).toString() === "==")) {
158
139
  return null;
159
140
  }
@@ -177,8 +158,7 @@ class InternalEGraph {
177
158
  return this.mapping.get(key);
178
159
  }
179
160
  find(data) {
180
- const dataId = this.getOrAdd(data);
181
- return this.core.find(dataId);
161
+ return this.core.find(this.getOrAdd(data));
182
162
  }
183
163
  setEquality(lhs, rhs) {
184
164
  const lhsId = this.getOrAdd(lhs);
@@ -211,22 +191,24 @@ class Search {
211
191
  }
212
192
  rebuild() {
213
193
  this.egraph.rebuild();
194
+ const newlyAddedTermsList = Array.from(this.newlyAddedTerms).map((s) => new Term(s));
214
195
  for (const factStr of this.facts) {
215
196
  const fact = new Term(factStr);
216
197
  if (!this.factMatchingCache.has(factStr)) {
217
198
  this.factMatchingCache.set(factStr, new Set());
218
199
  }
219
- const candidates = this.collectMatchingCandidates(fact, Array.from(this.newlyAddedTerms).map((s) => new Term(s)));
200
+ const candidates = this.collectMatchingCandidates(fact, newlyAddedTermsList);
220
201
  for (const c of candidates) {
221
202
  this.factMatchingCache.get(factStr).add(c.toString());
222
203
  }
223
204
  }
205
+ const termsList = Array.from(this.terms).map((s) => new Term(s));
224
206
  for (const factStr of this.newlyAddedFacts) {
225
207
  const fact = new Term(factStr);
226
208
  if (!this.factMatchingCache.has(factStr)) {
227
209
  this.factMatchingCache.set(factStr, new Set());
228
210
  }
229
- const candidates = this.collectMatchingCandidates(fact, Array.from(this.terms).map((s) => new Term(s)));
211
+ const candidates = this.collectMatchingCandidates(fact, termsList);
230
212
  for (const c of candidates) {
231
213
  this.factMatchingCache.get(factStr).add(c.toString());
232
214
  }
@@ -270,8 +252,9 @@ class Search {
270
252
  if (this.egraph.getEquality(lhs, rhs)) {
271
253
  yield data;
272
254
  }
273
- const lhsPool = this.collectMatchingCandidates(lhs, Array.from(this.terms).map((s) => new Term(s)));
274
- const rhsPool = this.collectMatchingCandidates(rhs, Array.from(this.terms).map((s) => new Term(s)));
255
+ const termsList = Array.from(this.terms).map((s) => new Term(s));
256
+ const lhsPool = this.collectMatchingCandidates(lhs, termsList);
257
+ const rhsPool = this.collectMatchingCandidates(rhs, termsList);
275
258
  if (lhsPool.length === 0 || rhsPool.length === 0)
276
259
  return;
277
260
  const lhsGroups = this.groupByEquivalenceClass(lhsPool);
@@ -311,7 +294,8 @@ class Search {
311
294
  yield data;
312
295
  }
313
296
  }
314
- const ideaPool = this.collectMatchingCandidates(idea, Array.from(this.terms).map((s) => new Term(s)));
297
+ const termsList = Array.from(this.terms).map((s) => new Term(s));
298
+ const ideaPool = this.collectMatchingCandidates(idea, termsList);
315
299
  if (ideaPool.length === 0)
316
300
  return;
317
301
  const ideaGroups = this.groupByEquivalenceClass(ideaPool);
@@ -338,7 +322,7 @@ class Search {
338
322
  const result = target.ground(unification, "1");
339
323
  if (result) {
340
324
  const inner = result.term();
341
- if (inner instanceof List && inner.length() >= 3) {
325
+ if (inner instanceof List) {
342
326
  yield buildTermToRule(inner.getitem(2));
343
327
  }
344
328
  }
@@ -359,7 +343,7 @@ class Search {
359
343
  return [];
360
344
  const eidToTerms = new Map();
361
345
  for (const term of terms) {
362
- const eid = this.egraph.find(term);
346
+ const eid = this.egraph.find(term).toString();
363
347
  if (!eidToTerms.has(eid)) {
364
348
  eidToTerms.set(eid, new Set());
365
349
  }
@@ -369,227 +353,176 @@ class Search {
369
353
  }
370
354
  }
371
355
 
372
- async function main$4(addr, sequelize) {
356
+ async function main$3(addr, sequelize) {
373
357
  if (!sequelize) {
374
358
  sequelize = await initializeDatabase(addr);
375
359
  }
376
- try {
377
- const search = new Search();
378
- let pool = [];
379
- let maxFact = -1;
380
- let maxIdea = -1;
381
- while (true) {
382
- const begin = Date.now();
383
- let count = 0;
384
- const newIdeas = await Idea.findAll({
385
- where: { id: { [Op.gt]: maxIdea } },
386
- order: [["id", "ASC"]],
387
- });
388
- for (const idea of newIdeas) {
389
- maxIdea = Math.max(maxIdea, idea.id);
390
- pool.push(new Rule(idea.data));
391
- }
392
- const newFacts = await Fact.findAll({
393
- where: { id: { [Op.gt]: maxFact } },
394
- order: [["id", "ASC"]],
395
- });
396
- for (const fact of newFacts) {
397
- maxFact = Math.max(maxFact, fact.id);
398
- search.add(new Rule(fact.data));
399
- }
400
- search.rebuild();
401
- const tasks = [];
402
- const nextPool = [];
403
- for (const i of pool) {
404
- let found = false;
405
- for (const o of search.execute(i)) {
406
- tasks.push(insertOrIgnore(Fact, o.toString()));
407
- count++;
408
- if (i.toString() === o.toString()) {
409
- found = true;
410
- break;
411
- }
412
- }
413
- if (!found) {
414
- nextPool.push(i);
360
+ const search = new Search();
361
+ let pool = [];
362
+ let maxFact = -1;
363
+ let maxIdea = -1;
364
+ while (true) {
365
+ const begin = Date.now();
366
+ let count = 0;
367
+ const newIdeas = await Idea.findAll({
368
+ where: { id: { [Op.gt]: maxIdea } },
369
+ });
370
+ for (const idea of newIdeas) {
371
+ maxIdea = Math.max(maxIdea, idea.id);
372
+ pool.push(new Rule(idea.data));
373
+ }
374
+ const newFacts = await Fact.findAll({
375
+ where: { id: { [Op.gt]: maxFact } },
376
+ });
377
+ for (const fact of newFacts) {
378
+ maxFact = Math.max(maxFact, fact.id);
379
+ search.add(new Rule(fact.data));
380
+ }
381
+ search.rebuild();
382
+ const tasks = [];
383
+ const nextPool = [];
384
+ for (const i of pool) {
385
+ let found = false;
386
+ for (const o of search.execute(i)) {
387
+ tasks.push(insertOrIgnore(Fact, o.toString()));
388
+ count++;
389
+ if (i.toString() === o.toString()) {
390
+ found = true;
391
+ break;
415
392
  }
416
393
  }
417
- pool = nextPool;
418
- await Promise.all(tasks);
419
- const end = Date.now();
420
- const duration = (end - begin) / 1000;
421
- if (count === 0) {
422
- const delay = Math.max(0, 0.1 - duration);
423
- await new Promise((resolve) => setTimeout(resolve, delay * 1000));
394
+ if (!found) {
395
+ nextPool.push(i);
424
396
  }
425
397
  }
426
- }
427
- catch (err) {
428
- // Handle error
398
+ pool = nextPool;
399
+ await Promise.all(tasks);
400
+ const end = Date.now();
401
+ const duration = (end - begin) / 1000;
402
+ if (count === 0) {
403
+ const delay = Math.max(0, 0.1 - duration);
404
+ await new Promise((resolve) => setTimeout(resolve, delay * 1000));
405
+ }
429
406
  }
430
407
  }
431
408
 
432
- async function main$3(addr, sequelize) {
409
+ async function main$2(addr, sequelize) {
433
410
  if (!sequelize) {
434
411
  sequelize = await initializeDatabase(addr);
435
412
  }
436
413
  const rl = readline.createInterface({ input: stdin, output: stdout });
437
414
  rl.setPrompt("input: ");
438
415
  const unpatch = patchStdout(rl);
439
- try {
440
- rl.prompt(); // Initial prompt
441
- for await (const line of rl) {
442
- const data = line.trim();
443
- if (data === "" || data.startsWith("//")) {
444
- rl.prompt();
445
- continue;
446
- }
447
- try {
448
- const ds = parse(data);
449
- const dsStr = ds.toString();
450
- await insertOrIgnore(Fact, dsStr);
451
- const idea = strRuleGetStrIdea(dsStr);
452
- if (idea) {
453
- await insertOrIgnore(Idea, idea);
454
- }
455
- }
456
- catch (e) {
457
- console.error(`error: ${e}`);
458
- }
416
+ rl.prompt();
417
+ for await (const line of rl) {
418
+ const data = line.trim();
419
+ if (data === "" || data.startsWith("//")) {
459
420
  rl.prompt();
421
+ continue;
460
422
  }
461
- }
462
- catch (err) {
463
- // Silent catch for unexpected termination
464
- }
465
- finally {
466
- unpatch();
467
- rl.close();
468
- }
469
- }
470
-
471
- async function main$2(addr, sequelize) {
472
- if (!sequelize) {
473
- sequelize = await initializeDatabase(addr);
474
- }
475
- try {
476
- let maxFact = -1;
477
- let maxIdea = -1;
478
- while (true) {
479
- const begin = Date.now();
480
- let count = 0;
481
- const newIdeas = await Idea.findAll({
482
- where: { id: { [Op.gt]: maxIdea } },
483
- order: [["id", "ASC"]],
484
- });
485
- for (const idea of newIdeas) {
486
- maxIdea = Math.max(maxIdea, idea.id);
487
- console.log("idea:", unparse(idea.data));
488
- count++;
489
- }
490
- const newFacts = await Fact.findAll({
491
- where: { id: { [Op.gt]: maxFact } },
492
- order: [["id", "ASC"]],
493
- });
494
- for (const fact of newFacts) {
495
- maxFact = Math.max(maxFact, fact.id);
496
- console.log("fact:", unparse(fact.data));
497
- count++;
498
- }
499
- const end = Date.now();
500
- const duration = (end - begin) / 1000;
501
- if (count === 0) {
502
- const delay = Math.max(0, 0.1 - duration);
503
- await new Promise((resolve) => setTimeout(resolve, delay * 1000));
423
+ try {
424
+ const ds = parse(data);
425
+ const dsStr = ds.toString();
426
+ await insertOrIgnore(Fact, dsStr);
427
+ const idea = strRuleGetStrIdea(dsStr);
428
+ if (idea) {
429
+ await insertOrIgnore(Idea, idea);
504
430
  }
505
431
  }
432
+ catch (e) {
433
+ console.error(`error: ${e}`);
434
+ }
435
+ rl.prompt();
506
436
  }
507
- catch (err) {
508
- // Handle error
509
- }
437
+ unpatch();
438
+ rl.close();
510
439
  }
511
440
 
512
441
  async function main$1(addr, sequelize) {
513
442
  if (!sequelize) {
514
443
  sequelize = await initializeDatabase(addr);
515
444
  }
516
- const rl = readline$1.createInterface({
517
- input: process.stdin,
445
+ const rl = readline.createInterface({
446
+ input: stdin,
518
447
  terminal: false,
519
448
  });
520
- try {
521
- for await (const line of rl) {
522
- const data = line.trim();
523
- if (data === "" || data.startsWith("//")) {
524
- continue;
525
- }
526
- try {
527
- const ds = parse(data);
528
- const dsStr = ds.toString();
529
- await insertOrIgnore(Fact, dsStr);
530
- const idea = strRuleGetStrIdea(dsStr);
531
- if (idea) {
532
- await insertOrIgnore(Idea, idea);
533
- }
534
- }
535
- catch (e) {
536
- console.error(`error: ${e}`);
449
+ for await (const line of rl) {
450
+ const data = line.trim();
451
+ if (data === "" || data.startsWith("//")) {
452
+ continue;
453
+ }
454
+ try {
455
+ const ds = parse(data);
456
+ const dsStr = ds.toString();
457
+ await insertOrIgnore(Fact, dsStr);
458
+ const idea = strRuleGetStrIdea(dsStr);
459
+ if (idea) {
460
+ await insertOrIgnore(Idea, idea);
537
461
  }
538
462
  }
463
+ catch (e) {
464
+ console.error(`error: ${e}`);
465
+ }
539
466
  }
540
- finally {
541
- rl.close();
542
- }
467
+ rl.close();
543
468
  }
544
469
 
545
470
  async function main(addr, sequelize) {
546
471
  if (!sequelize) {
547
472
  sequelize = await initializeDatabase(addr);
548
473
  }
549
- try {
550
- const ideas = await Idea.findAll();
551
- for (const idea of ideas) {
474
+ let maxFact = -1;
475
+ let maxIdea = -1;
476
+ while (true) {
477
+ const begin = Date.now();
478
+ let count = 0;
479
+ const newIdeas = await Idea.findAll({
480
+ where: { id: { [Op.gt]: maxIdea } },
481
+ });
482
+ for (const idea of newIdeas) {
483
+ maxIdea = Math.max(maxIdea, idea.id);
552
484
  console.log("idea:", unparse(idea.data));
485
+ count++;
553
486
  }
554
- const facts = await Fact.findAll();
555
- for (const fact of facts) {
487
+ const newFacts = await Fact.findAll({
488
+ where: { id: { [Op.gt]: maxFact } },
489
+ });
490
+ for (const fact of newFacts) {
491
+ maxFact = Math.max(maxFact, fact.id);
556
492
  console.log("fact:", unparse(fact.data));
493
+ count++;
494
+ }
495
+ const end = Date.now();
496
+ const duration = (end - begin) / 1000;
497
+ if (count === 0) {
498
+ const delay = Math.max(0, 0.1 - duration);
499
+ await new Promise((resolve) => setTimeout(resolve, delay * 1000));
557
500
  }
558
- }
559
- finally {
560
- // Session is handled by main or caller
561
501
  }
562
502
  }
563
503
 
564
504
  const componentMap = {
565
505
  ds: main$5,
566
- egg: main$4,
567
- input: main$3,
568
- output: main$2,
506
+ egg: main$3,
507
+ input: main$2,
508
+ output: main,
569
509
  load: main$1,
570
- dump: main,
510
+ dump: main$4,
571
511
  };
572
512
  async function run(addr, components) {
573
- const sequelize = await initializeDatabase(addr);
574
- try {
575
- const promises = components.map((name) => {
576
- const component = componentMap[name];
577
- if (!component) {
578
- console.error(`error: unsupported component: ${name}`);
579
- process.exit(1);
580
- }
581
- return component(addr, sequelize);
582
- });
583
- // Run all components in parallel.
584
- // Note: Some components like input/output run forever.
585
- await Promise.all(promises);
586
- }
587
- catch (err) {
588
- // Handle error
589
- }
590
- finally {
591
- await sequelize.close();
513
+ for (const name of components) {
514
+ if (!(name in componentMap)) {
515
+ console.error(`error: unsupported component: ${name}`);
516
+ return;
517
+ }
592
518
  }
519
+ const sequelize = await initializeDatabase(addr);
520
+ const promises = components.map((name) => {
521
+ const component = componentMap[name];
522
+ return component(addr, sequelize);
523
+ });
524
+ await Promise.race(promises);
525
+ await sequelize.close();
593
526
  }
594
527
  function cli() {
595
528
  const program = new Command();
@@ -600,13 +533,29 @@ function cli() {
600
533
  .option("-c, --component <names...>", "Components to run.", ["input", "output", "ds", "egg"])
601
534
  .action(async (options) => {
602
535
  let addr = options.addr;
536
+ let tmpDir;
603
537
  if (!addr) {
604
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ddss-"));
538
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "ddss-"));
605
539
  const dbPath = path.join(tmpDir, "ddss.db");
606
540
  addr = `sqlite:///${dbPath}`;
607
541
  }
542
+ if (addr.startsWith("sqlite://")) {
543
+ addr = addr.replace("sqlite:///", "sqlite:");
544
+ }
545
+ else if (addr.startsWith("mysql:///")) ;
546
+ else if (addr.startsWith("mariadb://")) ;
547
+ else if (addr.startsWith("postgresql://")) {
548
+ addr = addr.replace("postgresql://", "postgres://");
549
+ }
550
+ else {
551
+ console.error(`error: unsupported database: ${addr}`);
552
+ return;
553
+ }
608
554
  console.log(`addr: ${addr}`);
609
555
  await run(addr, options.component);
556
+ if (tmpDir) {
557
+ await fs.rm(tmpDir, { recursive: true, force: true });
558
+ }
610
559
  });
611
560
  program.parse();
612
561
  }
package/package.json CHANGED
@@ -22,6 +22,9 @@
22
22
  "atsds-bnf": "^0.0.12",
23
23
  "atsds-egg": "^0.0.12",
24
24
  "commander": "^14.0.2",
25
+ "mariadb": "^3.4.5",
26
+ "mysql2": "^3.16.0",
27
+ "pg": "^8.16.3",
25
28
  "sequelize": "^6.37.7",
26
29
  "sqlite3": "^5.1.7"
27
30
  },
@@ -32,5 +35,5 @@
32
35
  "tslib": "^2.8.1",
33
36
  "typescript": "^5.9.3"
34
37
  },
35
- "version": "0.0.29"
38
+ "version": "0.0.30-alpha1"
36
39
  }