@fougere/core 0.8.3-alpha.0 → 0.9.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 (279) hide show
  1. package/dist/boot/AppLifecycle.d.ts +8 -0
  2. package/dist/boot/AppLifecycle.d.ts.map +1 -1
  3. package/dist/boot/AppLifecycle.js.map +1 -1
  4. package/dist/boot/Emissions.d.ts +26 -0
  5. package/dist/boot/Emissions.d.ts.map +1 -1
  6. package/dist/boot/Emissions.js +144 -9
  7. package/dist/boot/Emissions.js.map +1 -1
  8. package/dist/boot/auth.d.ts +1 -1
  9. package/dist/boot/auth.d.ts.map +1 -1
  10. package/dist/boot/bootstrap.d.ts.map +1 -1
  11. package/dist/boot/bootstrap.js +401 -598
  12. package/dist/boot/bootstrap.js.map +1 -1
  13. package/dist/boot/card.js +1 -1
  14. package/dist/boot/card.js.map +1 -1
  15. package/dist/boot/hosted.d.ts +1 -1
  16. package/dist/boot/hosted.d.ts.map +1 -1
  17. package/dist/boot/hosted.js +1 -0
  18. package/dist/boot/hosted.js.map +1 -1
  19. package/dist/boot/install.d.ts +42 -0
  20. package/dist/boot/install.d.ts.map +1 -0
  21. package/dist/boot/install.js +327 -0
  22. package/dist/boot/install.js.map +1 -0
  23. package/dist/boot/ownership.d.ts +1 -1
  24. package/dist/boot/ownership.d.ts.map +1 -1
  25. package/dist/boot/ownership.js +14 -1
  26. package/dist/boot/ownership.js.map +1 -1
  27. package/dist/boot/ports.d.ts +12 -3
  28. package/dist/boot/ports.d.ts.map +1 -1
  29. package/dist/boot/ports.js +51 -21
  30. package/dist/boot/ports.js.map +1 -1
  31. package/dist/boot/remote.d.ts.map +1 -1
  32. package/dist/boot/remote.js +3 -3
  33. package/dist/boot/remote.js.map +1 -1
  34. package/dist/boot/seed.d.ts +7 -1
  35. package/dist/boot/seed.d.ts.map +1 -1
  36. package/dist/boot/seed.js +7 -2
  37. package/dist/boot/seed.js.map +1 -1
  38. package/dist/boot/statement-drift.d.ts +1 -1
  39. package/dist/boot/statement-drift.d.ts.map +1 -1
  40. package/dist/boot/together.d.ts +1 -1
  41. package/dist/boot/together.d.ts.map +1 -1
  42. package/dist/boot/together.js +1 -1
  43. package/dist/boot/together.js.map +1 -1
  44. package/dist/boot/types.d.ts +18 -3
  45. package/dist/boot/types.d.ts.map +1 -1
  46. package/dist/builtin/LogLine.d.ts +38 -0
  47. package/dist/builtin/LogLine.d.ts.map +1 -0
  48. package/dist/builtin/LogLine.js +37 -0
  49. package/dist/builtin/LogLine.js.map +1 -0
  50. package/dist/builtin/logger.d.ts +57 -0
  51. package/dist/builtin/logger.d.ts.map +1 -1
  52. package/dist/builtin/logger.js +72 -17
  53. package/dist/builtin/logger.js.map +1 -1
  54. package/dist/config-loader.d.ts +3 -2
  55. package/dist/config-loader.d.ts.map +1 -1
  56. package/dist/config-loader.js.map +1 -1
  57. package/dist/contract.d.ts +3 -7
  58. package/dist/contract.d.ts.map +1 -1
  59. package/dist/contract.js +3 -4
  60. package/dist/contract.js.map +1 -1
  61. package/dist/{scan/conventions.d.ts → conventions.d.ts} +6 -0
  62. package/dist/conventions.d.ts.map +1 -0
  63. package/dist/{scan/conventions.js → conventions.js} +3 -2
  64. package/dist/conventions.js.map +1 -0
  65. package/dist/crypto/encoding.d.ts +0 -1
  66. package/dist/crypto/encoding.d.ts.map +1 -1
  67. package/dist/crypto/encoding.js +0 -7
  68. package/dist/crypto/encoding.js.map +1 -1
  69. package/dist/declare.d.ts +11 -1
  70. package/dist/declare.d.ts.map +1 -1
  71. package/dist/declare.js +10 -1
  72. package/dist/declare.js.map +1 -1
  73. package/dist/descriptor/frond.d.ts +34 -0
  74. package/dist/descriptor/frond.d.ts.map +1 -1
  75. package/dist/descriptor/index.d.ts +15 -0
  76. package/dist/descriptor/index.d.ts.map +1 -0
  77. package/dist/descriptor/index.js +14 -0
  78. package/dist/descriptor/index.js.map +1 -0
  79. package/dist/descriptor/surface.d.ts +8 -0
  80. package/dist/descriptor/surface.d.ts.map +1 -0
  81. package/dist/descriptor/surface.js +16 -0
  82. package/dist/descriptor/surface.js.map +1 -0
  83. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
  84. package/dist/dispatch/ArgumentResolver.js +6 -2
  85. package/dist/dispatch/ArgumentResolver.js.map +1 -1
  86. package/dist/dispatch/HandlerFacade.d.ts +2 -0
  87. package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
  88. package/dist/dispatch/HandlerFacade.js +9 -4
  89. package/dist/dispatch/HandlerFacade.js.map +1 -1
  90. package/dist/dispatch/StorageGuard.d.ts.map +1 -1
  91. package/dist/dispatch/StorageGuard.js +9 -23
  92. package/dist/dispatch/StorageGuard.js.map +1 -1
  93. package/dist/dispatch/presenterArguments.d.ts +7 -1
  94. package/dist/dispatch/presenterArguments.d.ts.map +1 -1
  95. package/dist/dispatch/presenterArguments.js +15 -4
  96. package/dist/dispatch/presenterArguments.js.map +1 -1
  97. package/dist/dispatch/validateInput.d.ts.map +1 -1
  98. package/dist/dispatch/validateInput.js +2 -2
  99. package/dist/dispatch/validateInput.js.map +1 -1
  100. package/dist/effective-operation.d.ts +1 -1
  101. package/dist/effective-operation.d.ts.map +1 -1
  102. package/dist/effective-operation.js +13 -19
  103. package/dist/effective-operation.js.map +1 -1
  104. package/dist/frond-config.d.ts +24 -0
  105. package/dist/frond-config.d.ts.map +1 -1
  106. package/dist/frond-config.js.map +1 -1
  107. package/dist/index.d.ts +35 -40
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +17 -14
  110. package/dist/index.js.map +1 -1
  111. package/dist/node.d.ts +2 -12
  112. package/dist/node.d.ts.map +1 -1
  113. package/dist/node.js +2 -12
  114. package/dist/node.js.map +1 -1
  115. package/dist/prefab/crud.d.ts +1 -1
  116. package/dist/prefab/crud.d.ts.map +1 -1
  117. package/dist/prefab/mirror.d.ts +10 -11
  118. package/dist/prefab/mirror.d.ts.map +1 -1
  119. package/dist/prefab/mirror.js +1 -27
  120. package/dist/prefab/mirror.js.map +1 -1
  121. package/dist/prefab/repository.d.ts +1 -1
  122. package/dist/prefab/repository.d.ts.map +1 -1
  123. package/dist/{scan/result.d.ts → scan.d.ts} +2 -2
  124. package/dist/scan.d.ts.map +1 -0
  125. package/dist/scan.js +2 -0
  126. package/dist/scan.js.map +1 -0
  127. package/dist/source.d.ts +1 -1
  128. package/dist/source.d.ts.map +1 -1
  129. package/dist/storage/criterion.d.ts.map +1 -0
  130. package/dist/{criterion.js → storage/criterion.js} +2 -2
  131. package/dist/storage/criterion.js.map +1 -0
  132. package/dist/{storage.d.ts → storage/port.d.ts} +6 -3
  133. package/dist/storage/port.d.ts.map +1 -0
  134. package/dist/{storage.js → storage/port.js} +13 -6
  135. package/dist/storage/port.js.map +1 -0
  136. package/dist/{store.d.ts → storage/store.d.ts} +1 -1
  137. package/dist/storage/store.d.ts.map +1 -0
  138. package/dist/{store.js → storage/store.js} +33 -17
  139. package/dist/storage/store.js.map +1 -0
  140. package/dist/verify.js +1 -1
  141. package/dist/verify.js.map +1 -1
  142. package/dist/wire/Invocation.d.ts +1 -3
  143. package/dist/wire/Invocation.d.ts.map +1 -1
  144. package/dist/wire/Invocation.js +1 -5
  145. package/dist/wire/Invocation.js.map +1 -1
  146. package/dist/wire/binding.d.ts +9 -0
  147. package/dist/wire/binding.d.ts.map +1 -1
  148. package/dist/wire/binding.js +11 -6
  149. package/dist/wire/binding.js.map +1 -1
  150. package/dist/wire/emit.d.ts +45 -0
  151. package/dist/wire/emit.d.ts.map +1 -0
  152. package/dist/wire/emit.js +44 -0
  153. package/dist/wire/emit.js.map +1 -0
  154. package/dist/wire/errors.js +2 -2
  155. package/dist/wire/errors.js.map +1 -1
  156. package/dist/wire/loggerMiddleware.d.ts.map +1 -1
  157. package/dist/wire/loggerMiddleware.js +6 -0
  158. package/dist/wire/loggerMiddleware.js.map +1 -1
  159. package/package.json +10 -4
  160. package/src/boot/AppLifecycle.ts +8 -0
  161. package/src/boot/Emissions.ts +177 -9
  162. package/src/boot/auth.ts +1 -1
  163. package/src/boot/bootstrap.ts +456 -672
  164. package/src/boot/card.ts +1 -1
  165. package/src/boot/hosted.ts +4 -2
  166. package/src/boot/install.ts +421 -0
  167. package/src/boot/ownership.ts +17 -2
  168. package/src/boot/ports.ts +71 -27
  169. package/src/boot/remote.ts +3 -3
  170. package/src/boot/seed.ts +18 -3
  171. package/src/boot/statement-drift.ts +1 -1
  172. package/src/boot/together.ts +1 -1
  173. package/src/boot/types.ts +18 -3
  174. package/src/builtin/LogLine.ts +38 -0
  175. package/src/builtin/logger.ts +109 -17
  176. package/src/config-loader.ts +3 -2
  177. package/src/contract.ts +5 -9
  178. package/src/{scan/conventions.ts → conventions.ts} +9 -2
  179. package/src/crypto/encoding.ts +0 -7
  180. package/src/declare.ts +20 -2
  181. package/src/descriptor/frond.ts +31 -0
  182. package/src/descriptor/index.ts +15 -0
  183. package/src/descriptor/surface.ts +23 -0
  184. package/src/dispatch/ArgumentResolver.ts +6 -2
  185. package/src/dispatch/HandlerFacade.ts +11 -4
  186. package/src/dispatch/StorageGuard.ts +8 -21
  187. package/src/dispatch/presenterArguments.ts +21 -9
  188. package/src/dispatch/validateInput.ts +2 -2
  189. package/src/effective-operation.ts +16 -21
  190. package/src/frond-config.ts +20 -0
  191. package/src/index.ts +44 -57
  192. package/src/node.ts +2 -16
  193. package/src/prefab/crud.ts +1 -1
  194. package/src/prefab/mirror.ts +12 -38
  195. package/src/prefab/repository.ts +1 -1
  196. package/src/{scan/result.ts → scan.ts} +1 -1
  197. package/src/source.ts +1 -1
  198. package/src/{criterion.ts → storage/criterion.ts} +2 -2
  199. package/src/{storage.ts → storage/port.ts} +20 -5
  200. package/src/{store.ts → storage/store.ts} +35 -16
  201. package/src/verify.ts +1 -1
  202. package/src/wire/Invocation.ts +2 -7
  203. package/src/wire/binding.ts +17 -6
  204. package/src/wire/call.ts +1 -1
  205. package/src/wire/emit.ts +84 -0
  206. package/src/wire/errors.ts +2 -2
  207. package/src/wire/loggerMiddleware.ts +6 -0
  208. package/dist/boot/boot.d.ts +0 -45
  209. package/dist/boot/boot.d.ts.map +0 -1
  210. package/dist/boot/boot.js +0 -63
  211. package/dist/boot/boot.js.map +0 -1
  212. package/dist/criterion.d.ts.map +0 -1
  213. package/dist/criterion.js.map +0 -1
  214. package/dist/declarations.d.ts +0 -34
  215. package/dist/declarations.d.ts.map +0 -1
  216. package/dist/declarations.js +0 -75
  217. package/dist/declarations.js.map +0 -1
  218. package/dist/emit.d.ts +0 -13
  219. package/dist/emit.d.ts.map +0 -1
  220. package/dist/emit.js +0 -21
  221. package/dist/emit.js.map +0 -1
  222. package/dist/imports.d.ts +0 -16
  223. package/dist/imports.d.ts.map +0 -1
  224. package/dist/imports.js +0 -78
  225. package/dist/imports.js.map +0 -1
  226. package/dist/placement.d.ts +0 -25
  227. package/dist/placement.d.ts.map +0 -1
  228. package/dist/placement.js +0 -69
  229. package/dist/placement.js.map +0 -1
  230. package/dist/scan/adapters.d.ts +0 -3
  231. package/dist/scan/adapters.d.ts.map +0 -1
  232. package/dist/scan/adapters.js +0 -25
  233. package/dist/scan/adapters.js.map +0 -1
  234. package/dist/scan/bundling.d.ts +0 -3
  235. package/dist/scan/bundling.d.ts.map +0 -1
  236. package/dist/scan/bundling.js +0 -15
  237. package/dist/scan/bundling.js.map +0 -1
  238. package/dist/scan/contract.d.ts +0 -27
  239. package/dist/scan/contract.d.ts.map +0 -1
  240. package/dist/scan/contract.js +0 -46
  241. package/dist/scan/contract.js.map +0 -1
  242. package/dist/scan/conventions.d.ts.map +0 -1
  243. package/dist/scan/conventions.js.map +0 -1
  244. package/dist/scan/emit.d.ts +0 -10
  245. package/dist/scan/emit.d.ts.map +0 -1
  246. package/dist/scan/emit.js +0 -144
  247. package/dist/scan/emit.js.map +0 -1
  248. package/dist/scan/handler-parser.d.ts +0 -26
  249. package/dist/scan/handler-parser.d.ts.map +0 -1
  250. package/dist/scan/handler-parser.js +0 -607
  251. package/dist/scan/handler-parser.js.map +0 -1
  252. package/dist/scan/result.d.ts.map +0 -1
  253. package/dist/scan/result.js +0 -2
  254. package/dist/scan/result.js.map +0 -1
  255. package/dist/scan/scanner.d.ts +0 -12
  256. package/dist/scan/scanner.d.ts.map +0 -1
  257. package/dist/scan/scanner.js +0 -573
  258. package/dist/scan/scanner.js.map +0 -1
  259. package/dist/scan/statement.d.ts +0 -4
  260. package/dist/scan/statement.d.ts.map +0 -1
  261. package/dist/scan/statement.js +0 -135
  262. package/dist/scan/statement.js.map +0 -1
  263. package/dist/storage.d.ts.map +0 -1
  264. package/dist/storage.js.map +0 -1
  265. package/dist/store.d.ts.map +0 -1
  266. package/dist/store.js.map +0 -1
  267. package/src/boot/boot.ts +0 -110
  268. package/src/declarations.ts +0 -110
  269. package/src/emit.ts +0 -32
  270. package/src/imports.ts +0 -94
  271. package/src/placement.ts +0 -86
  272. package/src/scan/adapters.ts +0 -32
  273. package/src/scan/bundling.ts +0 -14
  274. package/src/scan/contract.ts +0 -80
  275. package/src/scan/emit.ts +0 -160
  276. package/src/scan/handler-parser.ts +0 -695
  277. package/src/scan/scanner.ts +0 -698
  278. package/src/scan/statement.ts +0 -159
  279. /package/dist/{criterion.d.ts → storage/criterion.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  /** The thirteen gestures of {@link Storage}, derived from four. */
2
2
  import { applyCreate, applyUpdate, Lifecycle, Role, type SchemaView } from '@fougere/schema';
3
3
  import { comparisonOf, comparisonsIn, type Comparison } from './criterion.js';
4
- import type { Storage, StorageFactory } from './storage.js';
4
+ import type { Storage, StorageFactory } from './port.js';
5
5
 
6
6
  /** One instance, as every realization hands it over. */
7
7
  export type Values = Record<string, unknown>;
@@ -47,11 +47,29 @@ export function storageOver(open: (entity: SchemaView, name: string) => Store):
47
47
  ? Object.fromEntries(Object.entries(values).filter(([key]) => selected.has(key)))
48
48
  : values);
49
49
 
50
+ // Same contract as SQL: the key and the creation stamps survive an overwrite.
51
+ // Named, and not reached through `this`: a caller may have wrapped these gestures,
52
+ // and a derived one that goes back through the front door is judged twice.
53
+ const upsert = async (input: Partial<Record<string, unknown>>): Promise<Values> => {
54
+ const values = applyCreate(fields, applyUpdate(fields, input));
55
+ const id = values[pk] as string | undefined;
56
+ if (id === undefined) throw new Error(`${name}.upsert(): no \`${pk}\` — an upsert needs the key it writes at.`);
57
+ const previous = await store.get(keyOf(id));
58
+ if (previous) {
59
+ for (const [key, field] of Object.entries(fields)) {
60
+ if (key === pk || Lifecycle.of(field).stampedOnce) values[key] = previous[key];
61
+ }
62
+ }
63
+ await store.set(keyOf(id), values);
64
+ return pick(values);
65
+ };
66
+
50
67
  return {
51
68
  client: store.client,
52
69
  async list(options?: any) {
53
70
  let items = await store.all();
54
71
  if (options?.where) items = items.filter((values) => matches(values, options.where));
72
+ if (options?.orderBy) items = sorted(items, options.orderBy, options.order);
55
73
  // Held before the page is cut, and after the filter: `total` answers "how many
56
74
  // match", which is what a paginator divides. Reading `store.size` at the end
57
75
  // answered a different question — everything the store holds, including the ones
@@ -104,22 +122,9 @@ export function storageOver(open: (entity: SchemaView, name: string) => Store):
104
122
  }
105
123
  return grouped;
106
124
  },
107
- // Same contract as SQL: the key and the creation stamps survive an overwrite.
108
- async upsert(input: Partial<Record<string, unknown>>) {
109
- const values = applyCreate(fields, applyUpdate(fields, input));
110
- const id = values[pk] as string | undefined;
111
- if (id === undefined) throw new Error(`${name}.upsert(): no \`${pk}\` — an upsert needs the key it writes at.`);
112
- const previous = await store.get(keyOf(id));
113
- if (previous) {
114
- for (const [key, field] of Object.entries(fields)) {
115
- if (key === pk || Lifecycle.of(field).stampedOnce) values[key] = previous[key];
116
- }
117
- }
118
- await store.set(keyOf(id), values);
119
- return pick(values);
120
- },
125
+ upsert,
121
126
  async upsertAll(inputs: readonly Partial<Record<string, unknown>>[]) {
122
- for (const input of inputs) await this.upsert(input);
127
+ for (const input of inputs) await upsert(input);
123
128
  return inputs.length;
124
129
  },
125
130
  async create(input: Partial<Record<string, unknown>>) {
@@ -189,3 +194,17 @@ const ordered = (held: unknown, asked: unknown, holds: (a: number, b: number) =>
189
194
  /** What a Date and a number have in common, and a string keeps for itself. */
190
195
  const order = (value: unknown): number =>
191
196
  value instanceof Date ? value.getTime() : (value as number);
197
+
198
+ /** The ORDER BY the engine would have compiled, answered over the rows the store held. */
199
+ const sorted = (rows: Values[], field: string, direction: 'asc' | 'desc' | undefined): Values[] =>
200
+ [...rows].sort((left, right) =>
201
+ (direction === 'desc' ? -1 : 1) * rank(left[field], right[field]));
202
+
203
+ /** A row that holds nothing sorts first, and two of them tie. */
204
+ const rank = (left: unknown, right: unknown): number => {
205
+ if (left === null || left === undefined) return right === null || right === undefined ? 0 : -1;
206
+ if (right === null || right === undefined) return 1;
207
+ const [held, against] = [order(left), order(right)];
208
+
209
+ return held === against ? 0 : held < against ? -1 : 1;
210
+ };
package/src/verify.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { FrondDescriptor } from './descriptor/frond.js';
2
2
  import { lowerFirst } from '@fougere/schema';
3
3
  import { repositoryKeyOf } from './prefab/repository.js';
4
- import { storageKeyOf } from './storage.js';
4
+ import { storageKeyOf } from './storage/port.js';
5
5
  import { presenterKeyOf } from './prefab/presenter.js';
6
6
  import { collectorKeyOf } from './prefab/collector.js';
7
7
 
@@ -60,6 +60,8 @@ export class Invocation implements InvocationContext {
60
60
  return context instanceof Invocation ? context : new Invocation(context ?? {});
61
61
  }
62
62
 
63
+ static readonly empty = Invocation.from();
64
+
63
65
  /** Replaces the input — how the façade hands on the value it parsed. */
64
66
  withInput(input: unknown): Invocation {
65
67
  return new Invocation({ ...this, input });
@@ -70,10 +72,3 @@ export class Invocation implements InvocationContext {
70
72
  return new Invocation({ ...this, state });
71
73
  }
72
74
  }
73
-
74
- export const EMPTY_INVOCATION = Invocation.from();
75
-
76
- /** Names the same gesture as `Invocation.from` for callers that read better this way. */
77
- export function canonicalInvocation(context?: PartialInvocation): Invocation {
78
- return Invocation.from(context);
79
- }
@@ -11,6 +11,12 @@ type ParamSource =
11
11
  | { kind: 'collector'; typeName: string }
12
12
  /** `Fact<PostPublished>` — something that happened, not something a caller typed. */
13
13
  | { kind: 'fact'; factName: string }
14
+ /**
15
+ * `Pipe<PostPublished>` — the same thing, BEFORE it is final: what this op answers is
16
+ * the fact every subscriber then receives. The core has one such link already
17
+ * (`Emissions.stamped`, which realizes `created()`); this is the declared form of it.
18
+ */
19
+ | { kind: 'pipe'; factName: string }
14
20
  | { kind: 'param'; name: string; coerce?: 'number' | 'boolean' }
15
21
  | { kind: 'input' }
16
22
  | { kind: 'context' }
@@ -49,14 +55,19 @@ export function computeBindingPlan(
49
55
  // `authoruser` missed `authorUser` and fell through to branch 4, the request input.
50
56
  const typeKey = lowerFirst(typeName);
51
57
 
52
- // 0. Fact — `Fact<X>` names itself, so nothing has to be known in advance. It comes
53
- // FIRST because branch 4 would otherwise hand it the caller's input under the name
54
- // of something that happened.
55
- const factOf = param.type.name === 'Fact' ? param.type.generics?.[0]?.name : undefined;
56
- if (factOf) {
58
+ // 0. Fact, or the same fact before it is final. Both name themselves, so nothing has
59
+ // to be known in advance, and they come FIRST because branch 4 would otherwise hand
60
+ // either the caller's input under the name of something that happened.
61
+ const announced = param.type.name === 'Fact' || param.type.name === 'Pipe'
62
+ ? param.type.generics?.[0]?.name
63
+ : undefined;
64
+ if (announced) {
57
65
  return {
58
66
  name: param.name,
59
- source: { kind: 'fact' as const, factName: lowerFirst(factOf) },
67
+ source: {
68
+ kind: (param.type.name === 'Pipe' ? 'pipe' : 'fact') as 'pipe' | 'fact',
69
+ factName: lowerFirst(announced),
70
+ },
60
71
  optional: param.optional ?? false,
61
72
  };
62
73
  }
package/src/wire/call.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Call contract — a frond call is a value. */
2
2
  import { Card, type SchemaDescriptor } from '@fougere/schema';
3
- import { factsAnnouncedBy } from '../emit.js';
3
+ import { factsAnnouncedBy } from './emit.js';
4
4
  import { Invocation, type InvocationContext, type PartialInvocation } from './Invocation.js';
5
5
  import type { RouteAddress } from './RouteAddress.js';
6
6
  import { FougereError, ErrorCode } from './errors.js';
@@ -0,0 +1,84 @@
1
+ /** Announcing a fact — the half of the topology `remotes:` never covered. */
2
+ import { lowerFirst } from '@fougere/schema';
3
+
4
+ /**
5
+ * What an emitter injects. PARTIAL, because announcing is what REALIZES the fact's
6
+ * `lifecycle.create` — an `at: created()` is stamped by `Emissions`, so requiring it from
7
+ * the announcer made every emitter cast its way past its own type. A missing field is
8
+ * still refused, by the judge that reads the fact at announce time.
9
+ */
10
+ export type Emit<T, A = never> = (fact: Partial<T>) => Promise<A[]>;
11
+
12
+ /**
13
+ * What a subscriber accepts — and what it PROMISES about itself.
14
+ *
15
+ * What it ANSWERS is its own to choose: `Promise<void>` says nothing, any other type is an
16
+ * opinion. It reaches an announcer that asked for one — `Emit<CanBook, Verdict>` — and
17
+ * nobody otherwise, since a plain announcement does not wait.
18
+ */
19
+ export type Fact<T> = T;
20
+
21
+ /**
22
+ * The same fact, BEFORE it is final: an op taking one ANSWERS the value every subscriber
23
+ * then receives, so it is the declared form of what `Emissions.stamped` already does for
24
+ * `created()`.
25
+ *
26
+ * Not a second announcement and not a chain a subscriber joins: the links run once, before
27
+ * anyone is handed anything, which is what keeps a fact the same for every reader.
28
+ */
29
+ export type Pipe<T> = T;
30
+
31
+ /** The container key of an emission — THE one place that spells the format. */
32
+ export function emitKeyOf(fact: string): string {
33
+ return `${lowerFirst(fact)}Emit`;
34
+ }
35
+
36
+ /**
37
+ * The key of an emission that WAITS — `Emit<CanBook, Verdict>`, where the second type is
38
+ * the whole difference. A separate key because they are two functions: one hands the fact
39
+ * over and returns, the other waits for every subscriber and gives back what they said.
40
+ */
41
+ export function awaitKeyOf(fact: string): string {
42
+ return `${lowerFirst(fact)}Await`;
43
+ }
44
+
45
+ const SUFFIX = 'Emit';
46
+ const AWAIT_SUFFIX = 'Await';
47
+
48
+ /** The fact behind an emission key, or `undefined` when the key is not one. */
49
+ export function factOfEmitKey(key: string): string | undefined {
50
+ return behind(key, SUFFIX);
51
+ }
52
+
53
+ /** The fact behind an awaited emission's key — the dual, read the same way. */
54
+ export function factOfAwaitKey(key: string): string | undefined {
55
+ return behind(key, AWAIT_SUFFIX);
56
+ }
57
+
58
+ function behind(key: string, suffix: string): string | undefined {
59
+ return key.length > suffix.length && key.endsWith(suffix)
60
+ ? key.slice(0, -suffix.length)
61
+ : undefined;
62
+ }
63
+
64
+ /** What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies. */
65
+ export function factsAnnouncedBy(handlers: readonly { deps: readonly string[] }[]): string[] {
66
+ return factsIn(handlers, factOfEmitKey);
67
+ }
68
+
69
+ /** What a set of handlers announces AND WAITS FOR — the same reading, one suffix apart. */
70
+ export function factsAwaitedBy(handlers: readonly { deps: readonly string[] }[]): string[] {
71
+ return factsIn(handlers, factOfAwaitKey);
72
+ }
73
+
74
+ function factsIn(
75
+ handlers: readonly { deps: readonly string[] }[],
76
+ behindKey: (key: string) => string | undefined,
77
+ ): string[] {
78
+ return [...new Set(
79
+ handlers
80
+ .flatMap((handler) => handler.deps)
81
+ .map(behindKey)
82
+ .filter((fact): fact is string => fact !== undefined),
83
+ )];
84
+ }
@@ -55,7 +55,7 @@ export class FougereError extends Error {
55
55
 
56
56
  constructor(options: FougereErrorOptions) {
57
57
  super(options.message, { cause: options.cause });
58
- this.name = 'FougereError';
58
+ this.name = new.target.name;
59
59
  this.code = options.code;
60
60
  this.entity = options.entity;
61
61
  this.operation = options.operation;
@@ -97,7 +97,7 @@ export function validationErrorsOf(error: unknown): ValidationError[] | undefine
97
97
  const refusals = details.filter(
98
98
  (entry): entry is ValidationError =>
99
99
  typeof entry === 'object' && entry !== null
100
- && typeof (entry as ValidationError).path === 'string'
100
+ && Array.isArray((entry as ValidationError).path)
101
101
  && typeof (entry as ValidationError).message === 'string',
102
102
  );
103
103
  return refusals.length === details.length ? refusals : undefined;
@@ -3,10 +3,16 @@
3
3
  */
4
4
  import type { AppMiddleware } from './middleware.js';
5
5
  import type { Logger } from '../builtin/logger.js';
6
+ import { CARRIES_LINE } from '../builtin/LogLine.js';
6
7
 
7
8
  /** Create a logger middleware that logs operation entry, exit, and errors. */
8
9
  export function loggerMiddleware(logger: Logger): AppMiddleware {
9
10
  return async (ctx, next) => {
11
+ // An op that CARRIES a line writes none: it is dispatched, so logging it announces a
12
+ // line inside the announcement of one — `Emission cycle: logLine → logLine`, which is
13
+ // the emission catching what `onLog`'s try/catch used to swallow.
14
+ if (CARRIES_LINE.has(ctx.entity)) return next();
15
+
10
16
  const start = performance.now();
11
17
  logger.info(`${ctx.entity}.${ctx.operation}`);
12
18
  try {
@@ -1,45 +0,0 @@
1
- import { type FougereConfig } from '../config-loader.js';
2
- import type { App, CreateAppOptions } from './types.js';
3
- import { type Extension } from './AppLifecycle.js';
4
- import type { Transport } from '../wire/call.js';
5
- import type { Container } from '@fougere/container';
6
- interface BootOptions {
7
- /** Project root. Defaults to process.cwd(). */
8
- root?: string;
9
- /** Override config (merged with fougere.config.ts). */
10
- config?: Partial<FougereConfig>;
11
- /** Container factory. Required. */
12
- createContainer: () => Container;
13
- /** Only boot these fronds (by name). Absent = all. */
14
- fronds?: string[];
15
- /**
16
- * Remote fronds — label → address. A declared remote wins over local
17
- * presence (the frond runs elsewhere). Requires `remoteTransport`.
18
- */
19
- remotes?: Record<string, string>;
20
- /** Builds the transport to reach `remotes`. Supplied by a layer-2 package. */
21
- remoteTransport?: (url: string) => Transport;
22
- /** Carries an announced fact out of this process. */
23
- onEmit?: CreateAppOptions['onEmit'];
24
- /**
25
- * storage setup — returns the storage handle (db), an storageFactory, and its two halves.
26
- * The `db` value is forwarded to the auth provider via AuthContext when `auth` is set.
27
- */
28
- db?: (config: FougereConfig) => {
29
- db?: unknown;
30
- storageFactory: CreateAppOptions['storageFactory'];
31
- /** Bring the schema up to date — an extension's `up`, because it runs after the container. */
32
- migrate?: (app: App) => Promise<void> | void;
33
- /** Closes what the factory opened — `boot()` called it, so `boot()` releases it. */
34
- close?: () => Promise<void>;
35
- };
36
- /**
37
- * What this app takes on beyond its fronds. Appended after the framework's own, so a
38
- * host adds to the ascent — or replaces a member of it by declaring the same name.
39
- */
40
- extensions?: readonly (Extension | undefined)[];
41
- }
42
- /** Boot a Fougere app from fougere.config.ts. */
43
- export declare function boot(options: BootOptions): Promise<App>;
44
- export {};
45
- //# sourceMappingURL=boot.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/boot/boot.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,UAAU,WAAW;IACnB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,mCAAmC;IACnC,eAAe,EAAE,MAAM,SAAS,CAAC;IACjC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,8EAA8E;IAC9E,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;IAC7C,qDAAqD;IACrD,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC;;;OAGG;IACH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK;QAC9B,EAAE,CAAC,EAAE,OAAO,CAAC;QACb,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;QACnD,8FAA8F;QAC9F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAC7C,oFAAoF;QACpF,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KAC7B,CAAC;IACF;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;CACjD;AAED,iDAAiD;AACjD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CA4D7D"}
package/dist/boot/boot.js DELETED
@@ -1,63 +0,0 @@
1
- import { createApp } from './bootstrap.js';
2
- import { seeding } from './seed.js';
3
- import { scanProject } from '../scan/scanner.js';
4
- import { loadConfig } from '../config-loader.js';
5
- import { Logger } from '../builtin/logger.js';
6
- import { applyConfig } from './apply.js';
7
- import { migrating } from './AppLifecycle.js';
8
- /** Boot a Fougere app from fougere.config.ts. */
9
- export async function boot(options) {
10
- const bootStart = performance.now();
11
- const log = new Logger('boot');
12
- const root = options.root ?? process.cwd();
13
- log.info(`root: ${root}`);
14
- log.debug('loading config');
15
- const fileConfig = await loadConfig(root);
16
- const config = { ...fileConfig, ...options.config };
17
- // What this config changes in the process, said the same way at boot and at reload —
18
- // there is one applier, and a host re-reading later calls the very same function.
19
- applyConfig(config);
20
- log.info('config loaded');
21
- let dbSetup;
22
- if (options.db) {
23
- log.debug('initializing database');
24
- dbSetup = options.db(config);
25
- log.info('database initialized');
26
- }
27
- log.debug('creating app (scan + container)');
28
- const app = await createApp({
29
- // boot() lives on the Node entry, so boot() is what reads the disk. `createApp` is
30
- // handed the answer and reaches for nothing.
31
- scan: await scanProject(root, options.fronds, config.conventions),
32
- createContainer: options.createContainer,
33
- storageFactory: dbSetup?.storageFactory,
34
- db: dbSetup?.db,
35
- auth: config.auth,
36
- remotes: options.remotes,
37
- ports: config.ports,
38
- // Read from the config for the same reason `ports` is, one line up: it is a fact the
39
- // project states, not one the caller passes. Absent here, `serveRest` and
40
- // `serveGraphQL` both answered `pass` on an app whose config declared them — the
41
- // hosts got it right through their own boot (`app/shared/src/boot.ts`), and this
42
- // path, the conventional one, served nothing.
43
- adapters: config.adapters,
44
- // boot() called the factory, so boot() owns closing what it opened. Not an extension:
45
- // it was opened before the container existed, so it closes after the container goes.
46
- onDispose: dbSetup?.close,
47
- /**
48
- * The whole ascent, in one ordered list — tables, then rows, then whatever the host takes on.
49
- */
50
- extensions: [
51
- migrating(dbSetup?.migrate, (message) => log.warn(message)),
52
- seeding((message) => log.debug(message)),
53
- ...(options.extensions ?? []),
54
- ],
55
- onEmit: options.onEmit,
56
- remoteTransport: options.remoteTransport,
57
- });
58
- log.info(`ascent: ${app.extensions().join(' → ') || 'nothing declared'}`);
59
- const ms = (performance.now() - bootStart).toFixed(0);
60
- log.info(`ready in ${ms}ms — ${app.fronds.length} frond(s)`);
61
- return app;
62
- }
63
- //# sourceMappingURL=boot.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/boot/boot.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"criterion.d.ts","sourceRoot":"","sources":["../src/criterion.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAE,KAAK,KAAK,EAAU,MAAM,iBAAiB,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,wDAAwD;IACxD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,qFAAsF,CAAC;AAE/G,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAM7F;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAIjF;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE,CAE1D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"criterion.js","sourceRoot":"","sources":["../src/criterion.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAc,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAsBrD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC;AAI/G;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB,EAAE,KAAc;IACnE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1F,uFAAuF;IACvF,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAE/E,OAAO,KAAmB,CAAC;AAC7B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,UAAsB;IAClD,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CACtC,CAAC,KAAK,EAAsC,EAAE,CAAE,WAAiC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACrG,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,UAAsB;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAE,WAAiC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACtG,CAAC"}
@@ -1,34 +0,0 @@
1
- import type { Conventions } from './scan/conventions.js';
2
- import type { HandlerEntry } from './descriptor/frond.js';
3
- export interface HandlerDeclaration {
4
- /** Stable rule name — the same vocabulary `verify()` uses. */
5
- rule: 'handler-declaration';
6
- frond: string;
7
- filePath: string;
8
- /** The handler that hosts it. */
9
- handler: string;
10
- /** What is declared — `AGREED_WITHIN`, `says`, `Assessment`. */
11
- subject: string;
12
- /** What it is in the source: a value, or a shape. */
13
- kind: 'value' | 'shape';
14
- message: string;
15
- }
16
- /**
17
- * Everything a handler file declares that is not the handler itself.
18
- *
19
- * A handler names gestures; what a gesture uses is declared where its nature says, and one
20
- * question settles which: does it arrive INJECTED or IMPORTED? A threshold, a formula, a word
21
- * list arrive imported and belong under `rules/`. Anything holding a client, a connection or
22
- * a cursor arrives injected and belongs under `services/`. Neither ever belongs in the file
23
- * that calls it — nothing there can be reached by a second caller, and the handler grows into
24
- * the domain it was supposed to address.
25
- *
26
- * It names the two addresses, which `outsideConventions` does on the same question one file
27
- * higher. Saying "a module beside its entities" instead left the reader to invent a place,
28
- * and inventing one is the drift both rules report.
29
- */
30
- export declare function handlerDeclarations(fronds: readonly {
31
- name: string;
32
- handlers: readonly Pick<HandlerEntry, 'name' | 'filePath'>[];
33
- }[], conventions: Conventions): Promise<HandlerDeclaration[]>;
34
- //# sourceMappingURL=declarations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"declarations.d.ts","sourceRoot":"","sources":["../src/declarations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQ1D,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC;CAC9D,EAAE,EACH,WAAW,EAAE,WAAW,GACvB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CA0C/B"}
@@ -1,75 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- let _ts;
3
- async function loadTs() {
4
- if (!_ts)
5
- _ts = (await import('@typescript/typescript6')).default;
6
- return _ts;
7
- }
8
- function subjectsOf(typescript, source) {
9
- const found = [];
10
- for (const statement of source.statements) {
11
- if (typescript.isVariableStatement(statement)) {
12
- for (const declared of statement.declarationList.declarations)
13
- if (typescript.isIdentifier(declared.name))
14
- found.push([declared.name.text, 'value']);
15
- }
16
- else if (typescript.isFunctionDeclaration(statement) && statement.name) {
17
- found.push([statement.name.text, 'value']);
18
- }
19
- else if (typescript.isInterfaceDeclaration(statement) ||
20
- typescript.isTypeAliasDeclaration(statement) ||
21
- typescript.isEnumDeclaration(statement)) {
22
- found.push([statement.name.text, 'shape']);
23
- }
24
- }
25
- return found;
26
- }
27
- /**
28
- * Everything a handler file declares that is not the handler itself.
29
- *
30
- * A handler names gestures; what a gesture uses is declared where its nature says, and one
31
- * question settles which: does it arrive INJECTED or IMPORTED? A threshold, a formula, a word
32
- * list arrive imported and belong under `rules/`. Anything holding a client, a connection or
33
- * a cursor arrives injected and belongs under `services/`. Neither ever belongs in the file
34
- * that calls it — nothing there can be reached by a second caller, and the handler grows into
35
- * the domain it was supposed to address.
36
- *
37
- * It names the two addresses, which `outsideConventions` does on the same question one file
38
- * higher. Saying "a module beside its entities" instead left the reader to invent a place,
39
- * and inventing one is the drift both rules report.
40
- */
41
- export async function handlerDeclarations(fronds, conventions) {
42
- const typescript = await loadTs();
43
- const found = [];
44
- for (const frond of fronds) {
45
- for (const handler of frond.handlers) {
46
- const text = await readFile(handler.filePath, 'utf8').catch(() => undefined);
47
- if (text === undefined)
48
- continue;
49
- const source = typescript.createSourceFile(handler.filePath, text, typescript.ScriptTarget.Latest, false);
50
- for (const [subject, kind] of subjectsOf(typescript, source)) {
51
- found.push({
52
- rule: 'handler-declaration',
53
- frond: frond.name,
54
- filePath: handler.filePath,
55
- handler: handler.name,
56
- subject,
57
- kind,
58
- message: kind === 'value'
59
- ? `'${subject}' is declared in ${handler.name}, so only ${handler.name} can reach it. `
60
- + `If it needs nothing but its arguments, it is a word of '${frond.name}' and belongs `
61
- + `in \`${conventions.dirs.rules}/\`; if it holds a dependency, it belongs in `
62
- + `\`${conventions.dirs.services}/\`. Either way it is stated once and imported, and `
63
- + `the handler keeps only the gesture.`
64
- : `'${subject}' is declared in ${handler.name}, which makes the shape of what an `
65
- + `operation answers readable only from the file that answers it. A shape belongs `
66
- + `with the entity it describes in \`${conventions.dirs.entities}/\`, or in `
67
- + `\`${conventions.dirs.rules}/\` beside the words that compute it — where the caller, `
68
- + `the test and the other operations can all name it.`,
69
- });
70
- }
71
- }
72
- }
73
- return found;
74
- }
75
- //# sourceMappingURL=declarations.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"declarations.js","sourceRoot":"","sources":["../src/declarations.ts"],"names":[],"mappings":"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"}
package/dist/emit.d.ts DELETED
@@ -1,13 +0,0 @@
1
- /** What an emitter injects. */
2
- export type Emit<T> = (fact: T) => Promise<void>;
3
- /** What a subscriber accepts — and what it PROMISES about itself. */
4
- export type Fact<T> = T;
5
- /** The container key of an emission — THE one place that spells the format. */
6
- export declare function emitKeyOf(fact: string): string;
7
- /** The fact behind an emission key, or `undefined` when the key is not one. */
8
- export declare function factOfEmitKey(key: string): string | undefined;
9
- /** What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies. */
10
- export declare function factsAnnouncedBy(handlers: readonly {
11
- deps: readonly string[];
12
- }[]): string[];
13
- //# sourceMappingURL=emit.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAGA,+BAA+B;AAC/B,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjD,qEAAqE;AACrE,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAExB,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAID,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7D;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS;IAAE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,GAAG,MAAM,EAAE,CAO3F"}
package/dist/emit.js DELETED
@@ -1,21 +0,0 @@
1
- /** Announcing a fact — the half of the topology `remotes:` never covered. */
2
- import { lowerFirst } from '@fougere/schema';
3
- /** The container key of an emission — THE one place that spells the format. */
4
- export function emitKeyOf(fact) {
5
- return `${lowerFirst(fact)}Emit`;
6
- }
7
- const SUFFIX = 'Emit';
8
- /** The fact behind an emission key, or `undefined` when the key is not one. */
9
- export function factOfEmitKey(key) {
10
- return key.length > SUFFIX.length && key.endsWith(SUFFIX)
11
- ? key.slice(0, -SUFFIX.length)
12
- : undefined;
13
- }
14
- /** What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies. */
15
- export function factsAnnouncedBy(handlers) {
16
- return [...new Set(handlers
17
- .flatMap((handler) => handler.deps)
18
- .map(factOfEmitKey)
19
- .filter((fact) => fact !== undefined))];
20
- }
21
- //# sourceMappingURL=emit.js.map
package/dist/emit.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"emit.js","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQ7C,+EAA+E;AAC/E,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;AACnC,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,CAAC;AAEtB,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;QACvD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;QAC9B,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,QAAgD;IAC/E,OAAO,CAAC,GAAG,IAAI,GAAG,CAChB,QAAQ;aACL,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;aAClC,GAAG,CAAC,aAAa,CAAC;aAClB,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CACxD,CAAC,CAAC;AACL,CAAC"}
package/dist/imports.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import type { FrondDescriptor } from './descriptor/frond.js';
2
- export interface CrossFrondImport {
3
- /** Stable rule name — the same vocabulary `verify()` uses. */
4
- rule: 'cross-frond-import';
5
- /** The frond doing the reaching. */
6
- frond: string;
7
- filePath: string;
8
- /** The specifier as written. */
9
- specifier: string;
10
- /** The frond the path lands in — absent when it lands outside every frond. */
11
- target?: string;
12
- message: string;
13
- }
14
- /** Every relative import that resolves outside its own frond. */
15
- export declare function crossFrondImports(fronds: readonly Pick<FrondDescriptor, 'name' | 'source'>[]): Promise<CrossFrondImport[]>;
16
- //# sourceMappingURL=imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,oBAAoB,CAAC;IAC3B,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA2BD,iEAAiE;AACjE,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,QAAQ,CAAC,EAAE,GAC1D,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAwC7B"}