@fougere/core 0.10.0-alpha.0 → 0.12.0-alpha.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.
Files changed (215) hide show
  1. package/dist/CallIdentity.d.ts.map +1 -1
  2. package/dist/CallIdentity.js +2 -0
  3. package/dist/CallIdentity.js.map +1 -1
  4. package/dist/Constraint.d.ts +1 -1
  5. package/dist/Constraint.d.ts.map +1 -1
  6. package/dist/FougereConfig.d.ts +10 -6
  7. package/dist/FougereConfig.d.ts.map +1 -1
  8. package/dist/FougereConfig.js +19 -4
  9. package/dist/FougereConfig.js.map +1 -1
  10. package/dist/FrondConfig.d.ts +0 -6
  11. package/dist/FrondConfig.d.ts.map +1 -1
  12. package/dist/FrondConfig.js.map +1 -1
  13. package/dist/FrondDeclaration.d.ts +2 -9
  14. package/dist/FrondDeclaration.d.ts.map +1 -1
  15. package/dist/FrondDeclaration.js +0 -1
  16. package/dist/FrondDeclaration.js.map +1 -1
  17. package/dist/FrondsStated.d.ts +54 -0
  18. package/dist/FrondsStated.d.ts.map +1 -0
  19. package/dist/FrondsStated.js +26 -0
  20. package/dist/FrondsStated.js.map +1 -0
  21. package/dist/Source.d.ts.map +1 -1
  22. package/dist/Source.js +4 -3
  23. package/dist/Source.js.map +1 -1
  24. package/dist/StatedFrond.d.ts +21 -0
  25. package/dist/StatedFrond.d.ts.map +1 -0
  26. package/dist/StatedFrond.js +34 -0
  27. package/dist/StatedFrond.js.map +1 -0
  28. package/dist/StatedModules.d.ts +19 -0
  29. package/dist/StatedModules.d.ts.map +1 -0
  30. package/dist/StatedModules.js +37 -0
  31. package/dist/StatedModules.js.map +1 -0
  32. package/dist/boot/CreateAppOptions.d.ts +19 -2
  33. package/dist/boot/CreateAppOptions.d.ts.map +1 -1
  34. package/dist/boot/Emissions.d.ts +9 -0
  35. package/dist/boot/Emissions.d.ts.map +1 -1
  36. package/dist/boot/Emissions.js +11 -5
  37. package/dist/boot/Emissions.js.map +1 -1
  38. package/dist/boot/Hosting.d.ts +38 -0
  39. package/dist/boot/Hosting.d.ts.map +1 -0
  40. package/dist/boot/Hosting.js +2 -0
  41. package/dist/boot/Hosting.js.map +1 -0
  42. package/dist/boot/Peer.d.ts +16 -0
  43. package/dist/boot/Peer.d.ts.map +1 -0
  44. package/dist/boot/Peer.js +2 -0
  45. package/dist/boot/Peer.js.map +1 -0
  46. package/dist/boot/bootstrap.d.ts.map +1 -1
  47. package/dist/boot/bootstrap.js +372 -235
  48. package/dist/boot/bootstrap.js.map +1 -1
  49. package/dist/boot/declared.d.ts.map +1 -1
  50. package/dist/boot/declared.js +38 -3
  51. package/dist/boot/declared.js.map +1 -1
  52. package/dist/boot/frame.d.ts +0 -4
  53. package/dist/boot/frame.d.ts.map +1 -1
  54. package/dist/boot/frame.js +31 -14
  55. package/dist/boot/frame.js.map +1 -1
  56. package/dist/boot/hosted.d.ts.map +1 -1
  57. package/dist/boot/hosted.js +4 -3
  58. package/dist/boot/hosted.js.map +1 -1
  59. package/dist/boot/install.d.ts +19 -0
  60. package/dist/boot/install.d.ts.map +1 -1
  61. package/dist/boot/install.js +273 -178
  62. package/dist/boot/install.js.map +1 -1
  63. package/dist/boot/nesting.d.ts +33 -0
  64. package/dist/boot/nesting.d.ts.map +1 -0
  65. package/dist/boot/nesting.js +164 -0
  66. package/dist/boot/nesting.js.map +1 -0
  67. package/dist/boot/ownership.d.ts +0 -1
  68. package/dist/boot/ownership.d.ts.map +1 -1
  69. package/dist/boot/ownership.js +66 -51
  70. package/dist/boot/ownership.js.map +1 -1
  71. package/dist/boot/peerOver.d.ts +4 -0
  72. package/dist/boot/peerOver.d.ts.map +1 -0
  73. package/dist/boot/peerOver.js +12 -0
  74. package/dist/boot/peerOver.js.map +1 -0
  75. package/dist/boot/ports.js +30 -25
  76. package/dist/boot/ports.js.map +1 -1
  77. package/dist/boot/relations.d.ts +54 -0
  78. package/dist/boot/relations.d.ts.map +1 -0
  79. package/dist/boot/relations.js +150 -0
  80. package/dist/boot/relations.js.map +1 -0
  81. package/dist/boot/remote.d.ts.map +1 -1
  82. package/dist/boot/remote.js +31 -28
  83. package/dist/boot/remote.js.map +1 -1
  84. package/dist/boot/seed.d.ts.map +1 -1
  85. package/dist/boot/seed.js +27 -20
  86. package/dist/boot/seed.js.map +1 -1
  87. package/dist/boot/together.d.ts +3 -0
  88. package/dist/boot/together.d.ts.map +1 -1
  89. package/dist/boot/together.js +72 -56
  90. package/dist/boot/together.js.map +1 -1
  91. package/dist/contract.d.ts +1 -0
  92. package/dist/contract.d.ts.map +1 -1
  93. package/dist/contract.js.map +1 -1
  94. package/dist/descriptor/FrondDescriptor.d.ts +7 -0
  95. package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
  96. package/dist/descriptor/MiddlewareEntry.d.ts +6 -5
  97. package/dist/descriptor/MiddlewareEntry.d.ts.map +1 -1
  98. package/dist/descriptor/ProviderEntry.d.ts +7 -0
  99. package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
  100. package/dist/dispatch/Dependent.d.ts +19 -0
  101. package/dist/dispatch/Dependent.d.ts.map +1 -0
  102. package/dist/dispatch/Dependent.js +2 -0
  103. package/dist/dispatch/Dependent.js.map +1 -0
  104. package/dist/dispatch/Dispatcher.d.ts +14 -1
  105. package/dist/dispatch/Dispatcher.d.ts.map +1 -1
  106. package/dist/dispatch/Dispatcher.js +36 -8
  107. package/dist/dispatch/Dispatcher.js.map +1 -1
  108. package/dist/dispatch/HandlerFacade.d.ts +6 -1
  109. package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
  110. package/dist/dispatch/HandlerFacade.js +23 -20
  111. package/dist/dispatch/HandlerFacade.js.map +1 -1
  112. package/dist/dispatch/Journal.d.ts +25 -0
  113. package/dist/dispatch/Journal.d.ts.map +1 -0
  114. package/dist/dispatch/Journal.js +3 -0
  115. package/dist/dispatch/Journal.js.map +1 -0
  116. package/dist/dispatch/PresenterExecutor.d.ts +6 -0
  117. package/dist/dispatch/PresenterExecutor.d.ts.map +1 -1
  118. package/dist/dispatch/PresenterExecutor.js +30 -22
  119. package/dist/dispatch/PresenterExecutor.js.map +1 -1
  120. package/dist/dispatch/RelationCheck.d.ts +22 -0
  121. package/dist/dispatch/RelationCheck.d.ts.map +1 -0
  122. package/dist/dispatch/RelationCheck.js +2 -0
  123. package/dist/dispatch/RelationCheck.js.map +1 -0
  124. package/dist/dispatch/Release.d.ts +36 -0
  125. package/dist/dispatch/Release.d.ts.map +1 -0
  126. package/dist/dispatch/Release.js +130 -0
  127. package/dist/dispatch/Release.js.map +1 -0
  128. package/dist/dispatch/StorageGuard.d.ts +39 -1
  129. package/dist/dispatch/StorageGuard.d.ts.map +1 -1
  130. package/dist/dispatch/StorageGuard.js +128 -54
  131. package/dist/dispatch/StorageGuard.js.map +1 -1
  132. package/dist/index.d.ts +8 -0
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +6 -0
  135. package/dist/index.js.map +1 -1
  136. package/dist/node.d.ts +2 -1
  137. package/dist/node.d.ts.map +1 -1
  138. package/dist/node.js +2 -1
  139. package/dist/node.js.map +1 -1
  140. package/dist/storage/Storage.js +1 -1
  141. package/dist/storage/Storage.js.map +1 -1
  142. package/dist/storage/Store.d.ts.map +1 -1
  143. package/dist/storage/Store.js +29 -21
  144. package/dist/storage/Store.js.map +1 -1
  145. package/dist/verify.d.ts.map +1 -1
  146. package/dist/verify.js +14 -0
  147. package/dist/verify.js.map +1 -1
  148. package/dist/wire/Emit.d.ts +9 -0
  149. package/dist/wire/Emit.d.ts.map +1 -1
  150. package/dist/wire/Emit.js +16 -0
  151. package/dist/wire/Emit.js.map +1 -1
  152. package/dist/wire/Invocation.d.ts +3 -2
  153. package/dist/wire/Invocation.d.ts.map +1 -1
  154. package/dist/wire/Invocation.js +5 -2
  155. package/dist/wire/Invocation.js.map +1 -1
  156. package/dist/wire/InvocationContext.d.ts +2 -0
  157. package/dist/wire/InvocationContext.d.ts.map +1 -1
  158. package/dist/wire/SignedCall.d.ts +2 -0
  159. package/dist/wire/SignedCall.d.ts.map +1 -1
  160. package/dist/wire/binding.d.ts.map +1 -1
  161. package/dist/wire/binding.js +29 -57
  162. package/dist/wire/binding.js.map +1 -1
  163. package/dist/wire/drift.d.ts.map +1 -1
  164. package/dist/wire/drift.js +22 -19
  165. package/dist/wire/drift.js.map +1 -1
  166. package/package.json +4 -4
  167. package/src/CallIdentity.ts +2 -0
  168. package/src/Constraint.ts +1 -1
  169. package/src/FougereConfig.ts +24 -10
  170. package/src/FrondConfig.ts +0 -6
  171. package/src/FrondDeclaration.ts +2 -7
  172. package/src/FrondsStated.ts +72 -0
  173. package/src/Source.ts +3 -2
  174. package/src/StatedFrond.ts +51 -0
  175. package/src/StatedModules.ts +58 -0
  176. package/src/boot/CreateAppOptions.ts +19 -2
  177. package/src/boot/Emissions.ts +16 -5
  178. package/src/boot/Hosting.ts +38 -0
  179. package/src/boot/Peer.ts +15 -0
  180. package/src/boot/bootstrap.ts +512 -271
  181. package/src/boot/declared.ts +38 -3
  182. package/src/boot/frame.ts +42 -11
  183. package/src/boot/hosted.ts +4 -3
  184. package/src/boot/install.ts +400 -203
  185. package/src/boot/nesting.ts +196 -0
  186. package/src/boot/ownership.ts +79 -51
  187. package/src/boot/peerOver.ts +17 -0
  188. package/src/boot/ports.ts +45 -31
  189. package/src/boot/relations.ts +172 -0
  190. package/src/boot/remote.ts +47 -29
  191. package/src/boot/seed.ts +36 -27
  192. package/src/boot/together.ts +102 -63
  193. package/src/contract.ts +1 -0
  194. package/src/descriptor/FrondDescriptor.ts +7 -0
  195. package/src/descriptor/MiddlewareEntry.ts +6 -5
  196. package/src/descriptor/ProviderEntry.ts +7 -0
  197. package/src/dispatch/Dependent.ts +19 -0
  198. package/src/dispatch/Dispatcher.ts +43 -7
  199. package/src/dispatch/HandlerFacade.ts +27 -17
  200. package/src/dispatch/Journal.ts +26 -0
  201. package/src/dispatch/PresenterExecutor.ts +33 -22
  202. package/src/dispatch/RelationCheck.ts +21 -0
  203. package/src/dispatch/Release.ts +180 -0
  204. package/src/dispatch/StorageGuard.ts +138 -55
  205. package/src/index.ts +8 -0
  206. package/src/node.ts +2 -1
  207. package/src/storage/Storage.ts +2 -2
  208. package/src/storage/Store.ts +39 -18
  209. package/src/verify.ts +15 -0
  210. package/src/wire/Emit.ts +23 -0
  211. package/src/wire/Invocation.ts +5 -2
  212. package/src/wire/InvocationContext.ts +2 -0
  213. package/src/wire/SignedCall.ts +2 -0
  214. package/src/wire/binding.ts +26 -58
  215. package/src/wire/drift.ts +48 -21
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { FrondDescriptor } from '../descriptor/FrondDescriptor.js';
7
7
  import { facadeKeyOf } from '../wire/Facade.js';
8
+ import { factsAnnouncedBy, factsAwaitedBy, factsListenedTo } from '../wire/Emit.js';
8
9
  import { type DeclaredEdge } from '../wire/topology/DeclaredEdge.js';
9
10
  import { type DeclaredFrond } from '../wire/topology/DeclaredFrond.js';
10
11
  import { type DeclaredTopology } from '../wire/topology/DeclaredTopology.js';
@@ -23,6 +24,9 @@ export function declaredTopologyOf({ fronds, remotes }: Declaring): DeclaredTopo
23
24
 
24
25
  function placements(hosted: readonly FrondDescriptor[], remotes: Readonly<Record<string, string>>): DeclaredFrond[] {
25
26
  const here = hosted
27
+ // A frond its family inherits from answers at no address, so it has no placement to
28
+ // report: it stands in every process that holds one of its children.
29
+ .filter((frond) => frond.handlers.length > 0)
26
30
  .filter((frond) => !remotes[frond.name])
27
31
  .map((frond) => ({ frond: frond.name, placement: 'local' as const }));
28
32
 
@@ -65,21 +69,52 @@ export function reachedBy(deps: readonly string[], from: string, index: Map<stri
65
69
  return [...new Set(reached)];
66
70
  }
67
71
 
72
+ /**
73
+ * The two ways a call leaves a frond: a façade its code names, and a fact it announces.
74
+ *
75
+ * The second cannot be read the way the first is. A dependency names what it reaches, where an
76
+ * announcement names a SUBJECT and a subscriber names nothing at all — so the edge is found from
77
+ * both ends, the announcer's deps and the listener's binding plan. Reading the deps alone
78
+ * reported `demos/pipe-split` as four fronds with nothing between them, and three of its four
79
+ * fronds are reached by a fact.
80
+ */
68
81
  function crossings(hosted: readonly FrondDescriptor[]): DeclaredEdge[] {
69
82
  const index = servedBy(hosted);
83
+ const heard = listenedBy(hosted);
70
84
  const found = new Map<string, DeclaredEdge>();
85
+ const cross = (from: string, to: string) => {
86
+ if (from !== to) found.set(`${from} ${to}`, { from, to });
87
+ };
71
88
 
72
89
  for (const frond of hosted) {
73
90
  for (const handler of frond.handlers) {
74
- for (const to of reachedBy(handler.deps, frond.name, index)) {
75
- found.set(`${frond.name} ${to}`, { from: frond.name, to });
76
- }
91
+ for (const to of reachedBy(handler.deps, frond.name, index)) cross(frond.name, to);
92
+ }
93
+ for (const fact of announcedIn(frond)) {
94
+ for (const to of heard.get(fact) ?? []) cross(frond.name, to);
77
95
  }
78
96
  }
79
97
 
80
98
  return [...found.values()];
81
99
  }
82
100
 
101
+ /** Which fronds listen to each fact — every one of them, since an announcement names no recipient. */
102
+ function listenedBy(hosted: readonly FrondDescriptor[]): Map<string, string[]> {
103
+ const index = new Map<string, string[]>();
104
+ for (const frond of hosted) {
105
+ for (const fact of factsListenedTo(frond.handlers)) {
106
+ index.set(fact, [...(index.get(fact) ?? []), frond.name]);
107
+ }
108
+ }
109
+
110
+ return index;
111
+ }
112
+
113
+ /** A fact announced here, whether the announcer waits for an answer or not. */
114
+ function announcedIn(frond: FrondDescriptor): string[] {
115
+ return [...new Set([...factsAnnouncedBy(frond.handlers), ...factsAwaitedBy(frond.handlers)])];
116
+ }
117
+
83
118
  function hostOf(address: string): string {
84
119
  try {
85
120
  const url = new URL(address);
package/src/boot/frame.ts CHANGED
@@ -45,6 +45,15 @@ function refuseAmbiguousUpsert(entity: string, gesture: string): never {
45
45
  * The storage a member is handed inside a compensated frame: the same port, writing the same rows,
46
46
  * leaving an inverse behind each time.
47
47
  */
48
+ /** What one member's writes are recorded against, inside one frame. */
49
+ interface Recording {
50
+ base: Undoable;
51
+ recorded: Undoable;
52
+ entity: string;
53
+ key: string;
54
+ journal: Undo[];
55
+ }
56
+
48
57
  export function recording<T extends object>(storage: T, entity: string, schema: SchemaView, journal: Undo[]): T {
49
58
  const base = storage as unknown as Undoable;
50
59
  if (typeof base.create !== 'function' || typeof base.update !== 'function') return storage;
@@ -58,22 +67,37 @@ export function recording<T extends object>(storage: T, entity: string, schema:
58
67
  }
59
68
 
60
69
  const recorded = Object.create(storage) as T & Undoable;
70
+ const into: Recording = { base, recorded, entity, key, journal };
71
+
72
+ recordCreate(into);
73
+ recordUpdate(into);
74
+ recordDelete(into);
75
+ recordUpserts(into, schema);
76
+
77
+ return recorded;
78
+ }
61
79
 
80
+ /** The inverse of an insertion is a deletion by the key the write handed back. */
81
+ function recordCreate({ base, recorded, entity, key, journal }: Recording): void {
62
82
  recorded.create = async function (...args) {
63
83
  const row = await base.create.apply(this, args);
64
84
  const id = String(row[key]);
65
85
  journal.push({ what: `create ${entity}#${id}`, run: () => base.delete.call(this, id).then(() => undefined) });
66
86
  return row;
67
87
  };
88
+ }
68
89
 
90
+ /**
91
+ * Compared against what the WRITE produced, not against the patch: an `update: 'now'` field is
92
+ * stamped by the storage, so the patch is not what the row now holds.
93
+ */
94
+ function recordUpdate({ base, recorded, entity, journal }: Recording): void {
69
95
  recorded.update = async function (...args) {
70
96
  const [id, patch] = args;
71
97
  const before = await base.findById.call(this, id);
72
98
  const row = await base.update.apply(this, args);
73
99
  if (!before) return row;
74
100
 
75
- // Compare against what the WRITE produced, not against the patch: a `update: 'now'`
76
- // field is stamped by the storage, so the patch is not what the row now holds.
77
101
  const touched = Object.keys(patch);
78
102
  const wrote = pick(row, touched);
79
103
  journal.push({
@@ -97,7 +121,10 @@ export function recording<T extends object>(storage: T, entity: string, schema:
97
121
  });
98
122
  return row;
99
123
  };
124
+ }
100
125
 
126
+ /** Nothing to take back when the row was already gone. */
127
+ function recordDelete({ base, recorded, entity, journal }: Recording): void {
101
128
  recorded.delete = async function (id) {
102
129
  const before = await base.findById.call(this, id);
103
130
  const removed = await base.delete.call(this, id);
@@ -106,10 +133,14 @@ export function recording<T extends object>(storage: T, entity: string, schema:
106
133
  }
107
134
  return removed;
108
135
  };
136
+ }
109
137
 
110
- // An upsert says neither what it wrote over nor what it inserted, so the frame reads the
111
- // keys first: what was there is restored, what was not is deleted. One extra query for
112
- // the page, which is the same bargain `update` already makes for one row.
138
+ /**
139
+ * An upsert says neither what it wrote over nor what it inserted, so the frame reads the keys
140
+ * first: what was there is restored, what was not is deleted. One extra query for the page,
141
+ * the same bargain `update` already makes for one row.
142
+ */
143
+ function recordUpserts({ base, recorded, entity, key, journal }: Recording, schema: SchemaView): void {
113
144
  const undoUpsert = async function (this: unknown, rows: readonly Record<string, unknown>[]): Promise<Undo> {
114
145
  const ids = rows.map((row) => String(row[key]));
115
146
  const before = await base.findByKeys.call(this, ids);
@@ -125,27 +156,27 @@ export function recording<T extends object>(storage: T, entity: string, schema:
125
156
  };
126
157
  };
127
158
 
128
- if (typeof base.upsert === 'function') {
159
+ const upsert = base.upsert;
160
+ if (typeof upsert === 'function') {
129
161
  recorded.upsert = async function (input, ...rest) {
130
162
  if (declares(schema, 'unique')) refuseAmbiguousUpsert(entity, 'upsert');
131
163
  const undo = await undoUpsert.call(this, [input]);
132
- const row = await base.upsert!.call(this, input, ...rest);
164
+ const row = await upsert.call(this, input, ...rest);
133
165
  journal.push(undo);
134
166
  return row;
135
167
  };
136
168
  }
137
169
 
138
- if (typeof base.upsertAll === 'function') {
170
+ const upsertAll = base.upsertAll;
171
+ if (typeof upsertAll === 'function') {
139
172
  recorded.upsertAll = async function (inputs, ...rest) {
140
173
  if (declares(schema, 'unique')) refuseAmbiguousUpsert(entity, 'upsertAll');
141
174
  const undo = await undoUpsert.call(this, inputs);
142
- const written = await base.upsertAll!.call(this, inputs, ...rest);
175
+ const written = await upsertAll.call(this, inputs, ...rest);
143
176
  journal.push(undo);
144
177
  return written;
145
178
  };
146
179
  }
147
-
148
- return recorded;
149
180
  }
150
181
 
151
182
  /** Replay the inverses, most recent first, and report rather than pretend. */
@@ -44,11 +44,12 @@ export async function hostedBy(sources: HostedSources): Promise<ScanResult> {
44
44
  ? await (typeof sources.scan === 'function' ? sources.scan() : sources.scan)
45
45
  : undefined;
46
46
 
47
- if (!sources.fronds) return scanned ?? { fronds: Fronds.hosting([]), diagnostics: [] };
47
+ const stated = sources.fronds;
48
+ if (!stated) return scanned ?? { fronds: Fronds.hosting([]), diagnostics: [] };
48
49
 
49
50
  const found = scanned?.fronds ?? [];
50
- const configured = sources.fronds.map((f) => completed(f, found.find((s) => s.name === f.name)));
51
- const discovered = found.filter((f) => !sources.fronds!.some((s) => s.name === f.name));
51
+ const configured = stated.map((f) => completed(f, found.find((s) => s.name === f.name)));
52
+ const discovered = found.filter((f) => !stated.some((s) => s.name === f.name));
52
53
 
53
54
  return { fronds: Fronds.hosting([...configured, ...discovered]), diagnostics: scanned?.diagnostics ?? [] };
54
55
  }