@miso.ai/doggoganger 0.9.0-beta.9 → 0.9.1-beta.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/src/data/lorem.js CHANGED
@@ -1,11 +1,5 @@
1
- import { readFileSync } from 'fs';
2
- import { fileURLToPath } from 'url';
3
- import { dirname, resolve } from 'path';
4
- import yaml from 'js-yaml';
5
- import { randomInt } from './utils.js';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const DEFAULT_WORDS = yaml.load(readFileSync(resolve(__dirname, './words.yaml'), 'utf8'));
1
+ import { randomInt, gaussRandom } from './utils.js';
2
+ import DEFAULT_WORDS from './words.js';
9
3
 
10
4
  export function lorem({ decorates = [], output = 'string', size, min, max, ...options } = {}) {
11
5
  let iterator = limit(size || [min, max])(base(options));
@@ -24,7 +18,18 @@ const FNS = {
24
18
  }
25
19
 
26
20
  function lookup(fn) {
27
- return typeof fn === 'string' ? FNS[fn]() : fn;
21
+ switch (typeof fn) {
22
+ case 'string':
23
+ return FNS[fn]();
24
+ case 'function':
25
+ return fn;
26
+ case 'object':
27
+ if (Array.isArray(fn)) {
28
+ const [name, options = {}] = fn;
29
+ return FNS[name](options);
30
+ }
31
+ }
32
+ throw new Error(`Unrecognized decorator/output form: ${fn}`);
28
33
  }
29
34
 
30
35
  // base //
@@ -97,6 +102,7 @@ export function description({
97
102
  std: 5,
98
103
  min: 1,
99
104
  },
105
+ punctuation = '.',
100
106
  } = {}) {
101
107
  return function *(iterator) {
102
108
  let word;
@@ -111,12 +117,12 @@ export function description({
111
117
  slen = gaussMS(wordsPerSentence);
112
118
  }
113
119
  if (--slen === 0) {
114
- word += '.';
120
+ word += punctuation;
115
121
  }
116
122
  }
117
123
  if (word) {
118
- if (!word.endsWith('.')) {
119
- word += '.';
124
+ if (!word.endsWith(punctuation)) {
125
+ word += punctuation;
120
126
  }
121
127
  yield word;
122
128
  }
@@ -146,11 +152,3 @@ function gaussMS(args) {
146
152
  }
147
153
  return Math.round(n);
148
154
  }
149
-
150
- function gaussRandom() {
151
- return uniformRandom() + uniformRandom() + uniformRandom();
152
- }
153
-
154
- function uniformRandom() {
155
- return Math.random() * 2 - 1;
156
- }
@@ -1,24 +1,55 @@
1
1
  import { randomInt, imageUrl, shuffle } from '../utils.js';
2
2
  import * as lorem from '../lorem.js';
3
+ import * as languages from './languages.js';
3
4
 
4
5
  // TODO: wild mode that generates edge cases
5
6
 
6
- export function markdown({ features, blocks = [8, 12] } = {}) {
7
+ function extractLangFeatures(features = []) {
8
+ const languages = new Set();
9
+ const rest = [];
10
+ for (const feature of features) {
11
+ if (feature.startsWith('lang-')) {
12
+ let lang = feature.slice(5);
13
+ if (lang === 'javascript') {
14
+ lang = 'js';
15
+ }
16
+ languages.add(lang);
17
+ } else {
18
+ rest.push(feature);
19
+ }
20
+ }
21
+ return [[...languages], rest];
22
+ }
23
+
24
+ export function markdown({ features, blocks = [8, 12], sampling = 1 } = {}) {
25
+ let languages = [];
26
+ [languages, features] = extractLangFeatures(features);
7
27
  // TODO: block features
8
- return [
9
- atxHeading({ features }),
10
- paragraph({ features }),
11
- fencedCodeBlock({ features }),
12
- paragraph({ features }),
13
- table({ features }),
14
- image(),
15
- paragraph({ features }),
16
- hr(),
17
- atxHeading({ features }),
18
- paragraph({ features }),
19
- list({ features }),
20
- paragraph({ features }),
21
- ].join('\n\n');
28
+ return sample([
29
+ () => atxHeading({ features }),
30
+ () => paragraph({ features }),
31
+ ...languages.map(lang => () => fencedCodeBlock({ lang, features })),
32
+ ...(languages.length ? [] : [() => fencedCodeBlock({ features })]),
33
+ () => paragraph({ features }),
34
+ () => table({ features }),
35
+ () => image(),
36
+ () => paragraph({ features }),
37
+ () => hr(),
38
+ () => atxHeading({ features }),
39
+ () => paragraph({ features }),
40
+ () => list({ features }),
41
+ () => paragraph({ features }),
42
+ ], sampling).join('\n\n');
43
+ }
44
+
45
+ function sample(fns, sampling) {
46
+ const arr = [];
47
+ for (const fn of fns) {
48
+ if (sampling >= 1 || Math.random() < sampling) {
49
+ arr.push(fn());
50
+ }
51
+ }
52
+ return arr;
22
53
  }
23
54
 
24
55
  // leaf blocks //
@@ -57,7 +88,8 @@ export function fencedCodeBlock({ lang, content, size, fenceChar = '`' }) {
57
88
  }
58
89
 
59
90
  export function paragraph({ features, size = [20, 50] }) {
60
- return lorem.lorem({ size, decorates: ['description', decorate({ features })] });
91
+ // force all inline features
92
+ return lorem.lorem({ size, decorates: ['description', decorate()] });
61
93
  }
62
94
 
63
95
  export function table({ features, columns = [2, 4], rows = [2, 8] }) {
@@ -214,11 +246,7 @@ function listItemPrefix(type, checked = 'random') {
214
246
  }
215
247
 
216
248
  function codeContent({ lang, size = [10, 30] }) {
217
- // TODO
218
- switch (lang) {
219
- default:
220
- return lorem.lorem({ output: 'multiline', size });
221
- }
249
+ return lang && languages[lang] ? languages[lang]() : lorem.lorem({ output: 'multiline', size });
222
250
  }
223
251
 
224
252
  function tableRow(cells) {
@@ -0,0 +1,101 @@
1
+ export function javascript() {
2
+ return JAVASCRIPT;
3
+ }
4
+
5
+ export function js() {
6
+ return JAVASCRIPT;
7
+ }
8
+
9
+ const JAVASCRIPT = `
10
+ // module
11
+ import { a, b } from './module';
12
+ import * as module from './module';
13
+ import module from './module';
14
+ export default module;
15
+ export const a = 0;
16
+ export * from './module';
17
+ export * as module from './module';
18
+
19
+ // variables
20
+ let a = 10;
21
+ const b = 20;
22
+
23
+ // function declaration
24
+ function sum(x, y) {
25
+ return x + y;
26
+ }
27
+
28
+ // generator function
29
+ function* iterator() {
30
+ yield 0;
31
+ yield 1;
32
+ }
33
+
34
+ // arrow function
35
+ const multiply = (x, y) => x * y;
36
+
37
+ // class
38
+ class Person {
39
+ constructor(name, age) {
40
+ this.name = name;
41
+ this.age = age;
42
+ }
43
+
44
+ greet() {
45
+ console.log('Hello, my name is Miso.');
46
+ }
47
+ }
48
+
49
+ // primitive
50
+ const str = 'Hello, world!';
51
+ const num = 10.99;
52
+
53
+ // object
54
+ const person = new Person('John', 30);
55
+ person.greet();
56
+
57
+ // object literal
58
+ const object = {
59
+ name: 'John',
60
+ [x]: 10,
61
+ ...props,
62
+ };
63
+
64
+ // array literal
65
+ const arr = [1, 2, 3, 4, 5, ...props];
66
+
67
+ // regexp literal
68
+ const regexp = /\\w+/g;
69
+
70
+ // operators
71
+ const sum = a + b;
72
+ const product = a * b;
73
+ const negation = -a;
74
+ const max = a > b ? a : b;
75
+
76
+ // flow control
77
+ let i = 9;
78
+ for (const n of arr) {
79
+ if (n > i) {
80
+ console.log(n);
81
+ }
82
+ i++;
83
+ }
84
+
85
+ // async/await
86
+ (async () => {
87
+ const result = await asyncFunction();
88
+ })();
89
+
90
+ // try/catch
91
+ try {
92
+ } catch (e) {
93
+ }
94
+
95
+ // destructuring
96
+ const { name, age, ...rest } = person;
97
+ const [ x, y, ...rest ] = arr;
98
+
99
+ // template literals
100
+ ${'console.log(`The sum of ${a} and ${b} is ${sum(a, b)}.`);'}
101
+ `.trim();
@@ -0,0 +1,11 @@
1
+ import * as fields from './fields.js';
2
+
3
+ export function *questions({ rows, ...options } = {}) {
4
+ for (let i = 0; i < rows; i++) {
5
+ yield question({ ...options, index: i });
6
+ }
7
+ }
8
+
9
+ function question({} = {}) {
10
+ return fields.description({ size: [4, 8], punctuation: '?' });
11
+ }
package/src/data/utils.js CHANGED
@@ -1,3 +1,7 @@
1
+ export function uuid() {
2
+ return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, a => (a ^ Math.random() * 16 >> a / 4).toString(16));
3
+ }
4
+
1
5
  export function randomInt(min, max) {
2
6
  return max == null || (max <= min) ? min : (min + Math.floor(Math.random() * (max - min)));
3
7
  }
@@ -29,3 +33,20 @@ export function imageUrl(size) {
29
33
  const sizePath = Array.isArray(size) ? size.length > 1 ? `${size[0]}/${size[1]}` : `${size[0]}` : `${size}`;
30
34
  return `https://picsum.photos/seed/${seed}/${sizePath}`;
31
35
  }
36
+
37
+ export function formatDatetime(timestamp) {
38
+ const str = new Date(timestamp).toISOString();
39
+ return str.endsWith('Z') ? str.slice(0, -1) : str;
40
+ }
41
+
42
+ export function sample(size, sampling) {
43
+ return sampling !== undefined ? Math.ceil(size * sampling) : size;
44
+ }
45
+
46
+ export function gaussRandom() {
47
+ return uniformRandom() + uniformRandom() + uniformRandom();
48
+ }
49
+
50
+ function uniformRandom() {
51
+ return Math.random() * 2 - 1;
52
+ }
@@ -0,0 +1,182 @@
1
+
2
+ // genereated by bin/data.js
3
+ export default [
4
+ "lorem",
5
+ "ipsum",
6
+ "dolor",
7
+ "sit",
8
+ "amet",
9
+ "consectetur",
10
+ "adipiscing",
11
+ "elit",
12
+ "curabitur",
13
+ "vel",
14
+ "hendrerit",
15
+ "libero",
16
+ "eleifend",
17
+ "blandit",
18
+ "nunc",
19
+ "ornare",
20
+ "odio",
21
+ "ut",
22
+ "orci",
23
+ "gravida",
24
+ "imperdiet",
25
+ "nullam",
26
+ "purus",
27
+ "lacinia",
28
+ "a",
29
+ "pretium",
30
+ "quis",
31
+ "congue",
32
+ "praesent",
33
+ "sagittis",
34
+ "laoreet",
35
+ "auctor",
36
+ "mauris",
37
+ "non",
38
+ "velit",
39
+ "eros",
40
+ "dictum",
41
+ "proin",
42
+ "accumsan",
43
+ "sapien",
44
+ "nec",
45
+ "massa",
46
+ "volutpat",
47
+ "venenatis",
48
+ "sed",
49
+ "eu",
50
+ "molestie",
51
+ "lacus",
52
+ "quisque",
53
+ "porttitor",
54
+ "ligula",
55
+ "dui",
56
+ "mollis",
57
+ "tempus",
58
+ "at",
59
+ "magna",
60
+ "vestibulum",
61
+ "turpis",
62
+ "ac",
63
+ "diam",
64
+ "tincidunt",
65
+ "id",
66
+ "condimentum",
67
+ "enim",
68
+ "sodales",
69
+ "in",
70
+ "hac",
71
+ "habitasse",
72
+ "platea",
73
+ "dictumst",
74
+ "aenean",
75
+ "neque",
76
+ "fusce",
77
+ "augue",
78
+ "leo",
79
+ "eget",
80
+ "semper",
81
+ "mattis",
82
+ "tortor",
83
+ "scelerisque",
84
+ "nulla",
85
+ "interdum",
86
+ "tellus",
87
+ "malesuada",
88
+ "rhoncus",
89
+ "porta",
90
+ "sem",
91
+ "aliquet",
92
+ "et",
93
+ "nam",
94
+ "suspendisse",
95
+ "potenti",
96
+ "vivamus",
97
+ "luctus",
98
+ "fringilla",
99
+ "erat",
100
+ "donec",
101
+ "justo",
102
+ "vehicula",
103
+ "ultricies",
104
+ "varius",
105
+ "ante",
106
+ "primis",
107
+ "faucibus",
108
+ "ultrices",
109
+ "posuere",
110
+ "cubilia",
111
+ "curae",
112
+ "etiam",
113
+ "cursus",
114
+ "aliquam",
115
+ "quam",
116
+ "dapibus",
117
+ "nisl",
118
+ "feugiat",
119
+ "egestas",
120
+ "class",
121
+ "aptent",
122
+ "taciti",
123
+ "sociosqu",
124
+ "ad",
125
+ "litora",
126
+ "torquent",
127
+ "per",
128
+ "conubia",
129
+ "nostra",
130
+ "inceptos",
131
+ "himenaeos",
132
+ "phasellus",
133
+ "nibh",
134
+ "pulvinar",
135
+ "vitae",
136
+ "urna",
137
+ "iaculis",
138
+ "lobortis",
139
+ "nisi",
140
+ "viverra",
141
+ "arcu",
142
+ "morbi",
143
+ "pellentesque",
144
+ "metus",
145
+ "commodo",
146
+ "ut",
147
+ "facilisis",
148
+ "felis",
149
+ "tristique",
150
+ "ullamcorper",
151
+ "placerat",
152
+ "aenean",
153
+ "convallis",
154
+ "sollicitudin",
155
+ "integer",
156
+ "rutrum",
157
+ "duis",
158
+ "est",
159
+ "etiam",
160
+ "bibendum",
161
+ "donec",
162
+ "pharetra",
163
+ "vulputate",
164
+ "maecenas",
165
+ "mi",
166
+ "fermentum",
167
+ "consequat",
168
+ "suscipit",
169
+ "aliquam",
170
+ "habitant",
171
+ "senectus",
172
+ "netus",
173
+ "fames",
174
+ "quisque",
175
+ "euismod",
176
+ "curabitur",
177
+ "lectus",
178
+ "elementum",
179
+ "tempor",
180
+ "risus",
181
+ "cras"
182
+ ];
@@ -0,0 +1,54 @@
1
+ import Koa from 'koa';
2
+ import Router from '@koa/router';
3
+ import cors from '@koa/cors';
4
+ import serveStatic from 'koa-static';
5
+ import { koaBody } from 'koa-body';
6
+ import _route from './route/index.js';
7
+ import Api from './api/index.js';
8
+ import { exclusion } from './utils.js';
9
+
10
+ export default function doggoganger({ port = 9901, serve = false, ...options } = {}) {
11
+ const app = new Koa();
12
+ const router = new Router();
13
+ const api = _route(new Api(options), options);
14
+
15
+ router.use('/api', api.routes(), api.allowedMethods());
16
+ router.use('/v1', api.routes(), api.allowedMethods());
17
+
18
+ if (serve) {
19
+ app
20
+ .use(exclusion)
21
+ .use(serveStatic('.'));
22
+ }
23
+
24
+ app
25
+ .use(cors())
26
+ .use(koaBody({
27
+ formLimit: '100mb',
28
+ textLimit: '100mb',
29
+ jsonLimit: '100mb',
30
+ onerror: function (err, ctx) {
31
+ console.error(err);
32
+ ctx.throw('body parse error', 422);
33
+ },
34
+ }))
35
+ .use(handleAllPath(options))
36
+ .use(router.routes())
37
+ .use(router.allowedMethods())
38
+ .use(handleUnrecognizedPath(options))
39
+ .listen(port);
40
+ }
41
+
42
+ function handleAllPath({ verbose } = {}) {
43
+ return async (ctx, next) => {
44
+ verbose && console.log(`${ctx.method} ${ctx.url}`);
45
+ await next();
46
+ };
47
+ }
48
+
49
+ function handleUnrecognizedPath({ verbose } = {}) {
50
+ return async (ctx, next) => {
51
+ verbose && console.log(`Unrecognized path: ${ctx.method} ${ctx.url}`);
52
+ await next();
53
+ };
54
+ }
package/src/index.js CHANGED
@@ -1,28 +1,3 @@
1
- import Koa from 'koa';
2
- import Router from '@koa/router';
3
- import cors from '@koa/cors';
4
- import serveStatic from 'koa-static';
5
- import { koaBody } from 'koa-body';
6
- import _api from './api/index.js';
7
- import { exclusion } from './utils.js';
8
-
9
- export default function doggoganger({ port = 9901, serve = false, ...options } = {}) {
10
- const app = new Koa();
11
- const router = new Router();
12
- const api = _api(options);
13
-
14
- router.use('/api', api.routes(), api.allowedMethods());
15
-
16
- if (serve) {
17
- app
18
- .use(exclusion)
19
- .use(serveStatic('.'));
20
- }
21
-
22
- app
23
- .use(cors())
24
- .use(koaBody())
25
- .use(router.routes())
26
- .use(router.allowedMethods())
27
- .listen(port);
28
- }
1
+ export { default as doggoganger } from './doggoganger.js';
2
+ export { default as buildApi } from './browser.js';
3
+ export { default as buildRouter } from './route/index.js';
@@ -0,0 +1,29 @@
1
+ import { trimObj } from '../utils.js';
2
+
3
+ const DEFAULT_OPTIONS = {
4
+ rate: 0,
5
+ verbose: false,
6
+ };
7
+
8
+ export default function error(options) {
9
+ const globalOptions = {
10
+ ...DEFAULT_OPTIONS,
11
+ ...trimObj(options),
12
+ };
13
+ return async (ctx, next) => {
14
+ const { rate, verbose } = {
15
+ ...globalOptions,
16
+ ...getOptionsFromCtx(ctx),
17
+ };
18
+ if (rate && Math.random() < rate) {
19
+ verbose && console.log(`Simulate error`);
20
+ throw new Error();
21
+ }
22
+ await next();
23
+ };
24
+ }
25
+
26
+ function getOptionsFromCtx(ctx) {
27
+ const rate = Number(ctx.get('x-error-rate')) || undefined;
28
+ return trimObj({ rate });
29
+ }
@@ -0,0 +1,2 @@
1
+ export { default as latency } from './latency.js';
2
+ export { default as error } from './error.js';
@@ -0,0 +1,39 @@
1
+ import { delay, trimObj } from '../utils.js';
2
+ import { gaussRandom } from '../data/utils.js';
3
+
4
+ const DEFAULT_OPTIONS = {
5
+ enabled: true,
6
+ verbose: false,
7
+ min: 200,
8
+ max: 2000,
9
+ };
10
+
11
+ export default function latency(options) {
12
+ const globalOptions = {
13
+ ...DEFAULT_OPTIONS,
14
+ ...trimObj(options),
15
+ };
16
+ return async (ctx, next) => {
17
+ const { enabled, min, max, verbose } = {
18
+ ...globalOptions,
19
+ ...getOptionsFromCtx(ctx),
20
+ };
21
+ if (enabled) {
22
+ const time = (min + max) / 2 + gaussRandom() * (max - min) / 6;
23
+ verbose && console.log(`Add latency: ${time}ms`);
24
+ await delay(time);
25
+ }
26
+ await next();
27
+ };
28
+ }
29
+
30
+ function getOptionsFromCtx(ctx) {
31
+ const latencyStr = ctx.get('x-latency') || undefined;
32
+ if (latencyStr === '0' || latencyStr === 'false') {
33
+ return { enabled: false };
34
+ }
35
+ const value = Number(latencyStr) || undefined;
36
+ const min = value || Number(ctx.get('x-latency-min')) || undefined;
37
+ const max = value || Number(ctx.get('x-latency-max')) || undefined;
38
+ return trimObj({ min, max });
39
+ }
@@ -0,0 +1,37 @@
1
+ import Router from '@koa/router';
2
+ import { parseBodyIfNecessary } from './utils.js';
3
+
4
+ function getOptionsFromCtx(ctx) {
5
+ const speedRate = Number(ctx.get('x-speed-rate')) || undefined;
6
+ const answerFormat = ctx.get('x-answer-format') || undefined;
7
+ const answerSampling = Number(ctx.get('x-answer-sampling')) || undefined;
8
+ const answerLanguagesStr = ctx.get('x-answer-languages') || undefined;
9
+ const answerLanguages = answerLanguagesStr ? answerLanguagesStr.split(',') : undefined;
10
+ return { answerFormat, answerSampling, answerLanguages, speedRate };
11
+ }
12
+
13
+ export default function(api) {
14
+ const answers = new Map();
15
+ const router = new Router();
16
+
17
+ router.post('/questions', (ctx) => {
18
+ const { question, parent_question_id } = parseBodyIfNecessary(ctx.request.body);
19
+ const answer = api.ask.questions({ question, parent_question_id }, getOptionsFromCtx(ctx));
20
+ const { question_id } = answer;
21
+ answers.set(question_id, answer);
22
+ ctx.body = JSON.stringify({ data: { question_id } });
23
+ });
24
+
25
+ router.get('/questions/:id/answer', (ctx) => {
26
+ const { id } = ctx.params;
27
+ const answer = answers.get(id);
28
+ if (!answer) {
29
+ ctx.status = 404;
30
+ } else {
31
+ const data = answer.get();
32
+ ctx.body = JSON.stringify({ data });
33
+ }
34
+ });
35
+
36
+ return router;
37
+ }