wiki-plugin-mech 0.1.19 → 0.1.21

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 = `mech=${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
@@ -539,10 +550,56 @@
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
+
587
+ function roster_emit({elem,command,state}) {
588
+ if(!state.neighborhood) return trouble(elem,`ROSTER expected a neighborhood, like from NEIGHBORS.`)
589
+ inspect(elem,'neighborhood',state)
590
+ const infos = state.neighborhood
591
+ const sites = infos
592
+ .map(info => info.domain)
593
+ .filter(uniq)
594
+ const any = array => array[Math.floor(Math.random()*array.length)]
595
+ console.log(infos)
596
+ const items = [
597
+ {type:'roster', text:"Mech\n"+sites.join("\n")},
598
+ {type:'activity', text:`ROSTER Mech\nSINCE 30 days`}]
599
+ elem.innerHTML = command + ` ⇒ ${sites.length} sites`
600
+ state.items = items
601
+ }
602
+
546
603
 
547
604
  // C A T A L O G
548
605
 
@@ -566,7 +623,9 @@
566
623
  RANDOM: {emit:random_emit},
567
624
  SLEEP: {emit:sleep_emit},
568
625
  TOGETHER:{emit:together_emit},
569
- GET: {emit:get_emit}
626
+ GET: {emit:get_emit},
627
+ DELTA: {emit:delta_emit},
628
+ ROSTER: {emit:roster_emit}
570
629
  }
571
630
 
572
631
 
@@ -792,4 +851,83 @@
792
851
  return this.direction}
793
852
  }
794
853
 
854
+ // adapted from wiki-client/lib/revision.coffee
855
+
856
+ // This module interprets journal actions in order to update
857
+ // a story or even regenerate a complete story from some or
858
+ // all of a journal.
859
+
860
+ function apply(page, action) {
861
+ const order = () => {
862
+ return (page.story || []).map(item => item?.id);
863
+ };
864
+
865
+ const add = (after, item) => {
866
+ const index = order().indexOf(after) + 1;
867
+ page.story.splice(index, 0, item);
868
+ };
869
+
870
+ const remove = () => {
871
+ const index = order().indexOf(action.id);
872
+ if (index !== -1) {
873
+ page.story.splice(index, 1);
874
+ }
875
+ };
876
+
877
+ page.story = page.story || [];
878
+
879
+ switch (action.type) {
880
+ case 'create':
881
+ if (action.item) {
882
+ if (action.item.title != null) {
883
+ page.title = action.item.title;
884
+ }
885
+ if (action.item.story != null) {
886
+ page.story = action.item.story.slice();
887
+ }
888
+ }
889
+ break;
890
+ case 'add':
891
+ add(action.after, action.item);
892
+ break;
893
+ case 'edit':
894
+ const index = order().indexOf(action.id);
895
+ if (index !== -1) {
896
+ page.story.splice(index, 1, action.item);
897
+ } else {
898
+ page.story.push(action.item);
899
+ }
900
+ break;
901
+ case 'move':
902
+ // construct relative addresses from absolute order
903
+ const moveIndex = action.order.indexOf(action.id);
904
+ const after = action.order[moveIndex - 1];
905
+ const item = page.story[order().indexOf(action.id)];
906
+ remove();
907
+ add(after, item);
908
+ break;
909
+ case 'remove':
910
+ remove();
911
+ break;
912
+ }
913
+
914
+ page.journal = page.journal || [];
915
+ if (action.fork) {
916
+ // implicit fork
917
+ page.journal.push({ type: 'fork', site: action.fork, date: action.date - 1 });
918
+ }
919
+ page.journal.push(action);
920
+ }
921
+
922
+ function create(revIndex, data) {
923
+ revIndex = +revIndex;
924
+ const revJournal = data.journal.slice(0, revIndex + 1);
925
+ const revPage = { title: data.title, story: [] };
926
+ for (const action of revJournal) {
927
+ apply(revPage, action || {});
928
+ }
929
+ return revPage;
930
+ }
931
+
932
+
795
933
  }).call(this)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-mech",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Federated Wiki - Mechanism Scripting Plugin",
5
5
  "keywords": [
6
6
  "mech",
package/server/server.js CHANGED
@@ -8,23 +8,24 @@
8
8
  const path = require('path')
9
9
  const process = require('process')
10
10
 
11
+ function cors (req, res, next) {
12
+ res.header('Access-Control-Allow-Origin', '*')
13
+ next()
14
+ }
15
+
11
16
  function startServer(params) {
12
17
  var app = params.app,
13
18
  argv = params.argv
14
19
 
15
- 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) => {
16
21
  console.log(req.params)
17
22
  try {
18
23
  const slug = req.params.slug
19
24
  const itemId = req.params.itemId
20
25
  const mech = JSON.parse(atob(req.query.mech || 'W10='))
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,mech});
25
- // })
26
+ const share = JSON.parse(atob(req.query.state ||'W10='))
26
27
  const context = {argv,slug}
27
- const state = {context}
28
+ const state = Object.assign(share,{context})
28
29
  run(mech,state)
29
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'})})
@@ -123,6 +124,17 @@
123
124
  status(elem,`${(all.bytes/1000000).toFixed(3)} mb in ${all.files} files`)
124
125
  }
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
+
126
138
 
127
139
  // C A T A L O G
128
140
 
@@ -130,7 +142,8 @@
130
142
  HELLO: {emit:hello_emit},
131
143
  UPTIME: {emit:uptime_emit},
132
144
  SLEEP: {emit:sleep_emit},
133
- COMMONS: {emit:commons_emit}
145
+ COMMONS: {emit:commons_emit},
146
+ DELTA: {emit:delta_emit}
134
147
  }
135
148
 
136
149