wiki-plugin-mech 0.1.18 → 0.1.20

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/client/mech.js CHANGED
@@ -519,11 +519,22 @@
519
519
  // http://localhost:3000/plugin/mech/run/testing-mechs-synchronization/5e269010fc81aebe?args=WyJoZWxsbyIsIndvcmxkIl0
520
520
  async function get_emit({elem,command,args,body,state}) {
521
521
  if (!body) return trouble(elem,`GET expects indented commands to run on the server.`)
522
- const site = state.context.site
522
+ let share = {}
523
+ let where = state.context.site
524
+ if (args.length) {
525
+ for(const arg of args) {
526
+ if (arg in state) {
527
+ inspect(elem,arg,state)
528
+ share[arg] = state[arg]}
529
+ else if (arg.match(/\./)) where=arg
530
+ else {return trouble(elem,`GET expected "${arg}" to name state or site.`)}
531
+ }
532
+ }
533
+ // const site = state.context.site
523
534
  const slug = state.context.slug
524
535
  const itemId = state.context.itemId
525
- const query = `args=${btoa(JSON.stringify(body))}`
526
- const url = `//${site}/plugin/mech/run/${slug}/${itemId}?${query}`
536
+ const query = `mech=${btoa(JSON.stringify(body))}&state=${btoa(JSON.stringify(share))}`
537
+ const url = `//${where}/plugin/mech/run/${slug}/${itemId}?${query}`
527
538
  elem.innerHTML = command + ` ⇒ in progress`
528
539
  const start = Date.now()
529
540
  let result
@@ -534,15 +545,45 @@
534
545
  return trouble(elem,`RUN failed with "${err.message}"`)
535
546
  }
536
547
  state.result = result
537
- for(const arg of result.args.flat(9)){
548
+ for(const arg of result.mech.flat(9)){
538
549
  const elem = document.getElementById(arg.key)
539
550
  if('status' in arg) elem.innerHTML = arg.command + ` ⇒ ${arg.status}`
540
551
  if('trouble' in arg) trouble(elem,arg.trouble)
541
552
  }
553
+ if('debug' in result.state) delete result.state.debug
554
+ Object.assign(state,result.state)
542
555
  const elapsed = ((Date.now() - start)/1000).toFixed(3)
543
556
  elem.innerHTML = command + ` ⇒ ${elapsed} seconds`
544
557
  }
545
558
 
559
+ function delta_emit({elem,command,args,body,state}) {
560
+ const copy = obj => JSON.parse(JSON.stringify(obj))
561
+ const size = obj => JSON.stringify(obj).length
562
+ if (args.length < 1) return trouble(elem,`DELTA expects argument, "have" or "apply" on client.`)
563
+ if (body) return trouble(elem,`DELTA doesn't expect indented input.`)
564
+ switch (args[0]) {
565
+ case 'have':
566
+ const edits = state.context.page.journal
567
+ .filter(item => item.type != 'fork')
568
+ state.recent = edits[edits.length-1].date
569
+ elem.innerHTML = command + ` ⇒ ${new Date(state.recent).toLocaleString()}`
570
+ break
571
+ case 'apply':
572
+ if(!('actions' in state)) return trouble(elem,`DELTA apply expect "actions" as input.`)
573
+ inspect(elem,'actions',state)
574
+ const page = copy(state.context.page)
575
+ const before = size(page)
576
+ for (const action of state.actions)
577
+ apply(page,action)
578
+ state.page = page
579
+ const after = size(page)
580
+ elem.innerHTML = command + ` ⇒ ∆ ${((after-before)/before*100).toFixed(1)}%`
581
+ break
582
+ default:
583
+ trouble(elem,`DELTA doesn't know "${args[0]}".`)
584
+ }
585
+ }
586
+
546
587
 
547
588
  // C A T A L O G
548
589
 
@@ -566,7 +607,8 @@
566
607
  RANDOM: {emit:random_emit},
567
608
  SLEEP: {emit:sleep_emit},
568
609
  TOGETHER:{emit:together_emit},
569
- GET: {emit:get_emit}
610
+ GET: {emit:get_emit},
611
+ DELTA: {emit:delta_emit}
570
612
  }
571
613
 
572
614
 
@@ -792,4 +834,83 @@
792
834
  return this.direction}
793
835
  }
794
836
 
837
+ // adapted from wiki-client/lib/revision.coffee
838
+
839
+ // This module interprets journal actions in order to update
840
+ // a story or even regenerate a complete story from some or
841
+ // all of a journal.
842
+
843
+ function apply(page, action) {
844
+ const order = () => {
845
+ return (page.story || []).map(item => item?.id);
846
+ };
847
+
848
+ const add = (after, item) => {
849
+ const index = order().indexOf(after) + 1;
850
+ page.story.splice(index, 0, item);
851
+ };
852
+
853
+ const remove = () => {
854
+ const index = order().indexOf(action.id);
855
+ if (index !== -1) {
856
+ page.story.splice(index, 1);
857
+ }
858
+ };
859
+
860
+ page.story = page.story || [];
861
+
862
+ switch (action.type) {
863
+ case 'create':
864
+ if (action.item) {
865
+ if (action.item.title != null) {
866
+ page.title = action.item.title;
867
+ }
868
+ if (action.item.story != null) {
869
+ page.story = action.item.story.slice();
870
+ }
871
+ }
872
+ break;
873
+ case 'add':
874
+ add(action.after, action.item);
875
+ break;
876
+ case 'edit':
877
+ const index = order().indexOf(action.id);
878
+ if (index !== -1) {
879
+ page.story.splice(index, 1, action.item);
880
+ } else {
881
+ page.story.push(action.item);
882
+ }
883
+ break;
884
+ case 'move':
885
+ // construct relative addresses from absolute order
886
+ const moveIndex = action.order.indexOf(action.id);
887
+ const after = action.order[moveIndex - 1];
888
+ const item = page.story[order().indexOf(action.id)];
889
+ remove();
890
+ add(after, item);
891
+ break;
892
+ case 'remove':
893
+ remove();
894
+ break;
895
+ }
896
+
897
+ page.journal = page.journal || [];
898
+ if (action.fork) {
899
+ // implicit fork
900
+ page.journal.push({ type: 'fork', site: action.fork, date: action.date - 1 });
901
+ }
902
+ page.journal.push(action);
903
+ }
904
+
905
+ function create(revIndex, data) {
906
+ revIndex = +revIndex;
907
+ const revJournal = data.journal.slice(0, revIndex + 1);
908
+ const revPage = { title: data.title, story: [] };
909
+ for (const action of revJournal) {
910
+ apply(revPage, action || {});
911
+ }
912
+ return revPage;
913
+ }
914
+
915
+
795
916
  }).call(this)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-mech",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Federated Wiki - Mechanism Scripting Plugin",
5
5
  "keywords": [
6
6
  "mech",
package/server/server.js CHANGED
@@ -5,28 +5,29 @@
5
5
  (function() {
6
6
 
7
7
  const fs = require('fs')
8
+ const path = require('path')
8
9
  const process = require('process')
9
10
 
11
+ function cors (req, res, next) {
12
+ res.header('Access-Control-Allow-Origin', '*')
13
+ next()
14
+ }
15
+
10
16
  function startServer(params) {
11
17
  var app = params.app,
12
18
  argv = params.argv
13
19
 
14
- return app.get('/plugin/mech/run/:slug([a-z-]+)/:itemId', (req, res, next) => {
20
+ return app.get('/plugin/mech/run/:slug([a-z-]+)/:itemId', cors, (req, res, next) => {
15
21
  console.log(req.params)
16
22
  try {
17
23
  const slug = req.params.slug
18
24
  const itemId = req.params.itemId
19
- const args = JSON.parse(atob(req.query.args || 'W10='))
20
- const path = `${argv.db}/${slug}`
21
- // fs.readFile(path,(err,data) => {
22
- // const page = JSON.parse(data)
23
- // const item = page.story.find(item => item.id == itemId) || null
24
- // return res.json({err,item,args});
25
- // })
26
- const context = {path}
27
- const state = {context,debug:true}
28
- run(args,state)
29
- .then(() => {return res.json({args,state})})
25
+ const mech = JSON.parse(atob(req.query.mech || 'W10='))
26
+ const share = JSON.parse(atob(req.query.state ||'W10='))
27
+ const context = {argv,slug}
28
+ const state = Object.assign(share,{context})
29
+ run(mech,state)
30
+ .then(() => {delete state.context; return res.json({mech,state})})
30
31
  .catch(err => {console.log(err); return res.json({err:err.message+' from promise'})})
31
32
  } catch(err) {
32
33
  return res.json({err:err.message+' from try'})
@@ -98,13 +99,51 @@
98
99
  })
99
100
  }
100
101
 
102
+ async function commons_emit ({elem,args,state}) {
103
+ const readdir = dir => new Promise((res,rej) =>
104
+ fs.readdir(dir,(e,v) => e ? rej(e) : res(v)));
105
+ const stat = file => new Promise((res,rej) =>
106
+ fs.stat(file,(e,v) => e ? rej(e) : res(v)));
107
+ const tally = async dir => {
108
+ const count = {files:0,bytes:0}
109
+ const items = await readdir(dir)
110
+ for(const item of items) {
111
+ const itemPath = path.join(dir, item)
112
+ const stats = await stat(itemPath)
113
+ if (state.debug) console.log({itemPath,stats})
114
+ if (stats.isFile()) {
115
+ count.files++
116
+ count.bytes+=stats.size
117
+ }
118
+ }
119
+ return count
120
+ }
121
+ const all = await tally(state.context.argv.commons)
122
+ const here = await tally(path.join(state.context.argv.data,'assets','plugins','image'))
123
+ state.commons = {all,here}
124
+ status(elem,`${(all.bytes/1000000).toFixed(3)} mb in ${all.files} files`)
125
+ }
126
+
127
+ async function delta_emit ({elem,args,state}) {
128
+ const readFile = path => new Promise((res,rej) =>
129
+ fs.readFile(path,(e,v) => e ? rej(e) : res(v)));
130
+ if(!state.recent) return trouble(elem,`DELTA expects "recent" update time in state.`)
131
+ const file = path.join(state.context.argv.db,state.context.slug)
132
+ const page = JSON.parse(await readFile(file))
133
+ state.actions = page.journal
134
+ .filter(action => action.date > state.recent)
135
+ status(elem,`${state.actions.length} recent actions`)
136
+ }
137
+
101
138
 
102
139
  // C A T A L O G
103
140
 
104
141
  const blocks = {
105
142
  HELLO: {emit:hello_emit},
106
143
  UPTIME: {emit:uptime_emit},
107
- SLEEP: {emit:sleep_emit}
144
+ SLEEP: {emit:sleep_emit},
145
+ COMMONS: {emit:commons_emit},
146
+ DELTA: {emit:delta_emit}
108
147
  }
109
148
 
110
149