ccqa-tools 1.37.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 (38) hide show
  1. package/README.md +177 -0
  2. package/dist/coverage/collector.cjs +229 -0
  3. package/dist/coverage/collector.d.cts +183 -0
  4. package/dist/coverage/collector.d.ts +183 -0
  5. package/dist/coverage/collector.js +225 -0
  6. package/dist/coverage/core.cjs +176 -0
  7. package/dist/coverage/core.d.cts +157 -0
  8. package/dist/coverage/core.d.ts +157 -0
  9. package/dist/coverage/core.js +163 -0
  10. package/dist/coverage/middleware.cjs +161 -0
  11. package/dist/coverage/middleware.d.cts +23 -0
  12. package/dist/coverage/middleware.d.ts +23 -0
  13. package/dist/coverage/middleware.js +158 -0
  14. package/dist/coverage/next-loader.cjs +158 -0
  15. package/dist/coverage/next-loader.d.cts +18 -0
  16. package/dist/coverage/next-loader.d.ts +19 -0
  17. package/dist/coverage/next-loader.js +158 -0
  18. package/dist/coverage/next.cjs +101 -0
  19. package/dist/coverage/next.d.cts +33 -0
  20. package/dist/coverage/next.d.ts +33 -0
  21. package/dist/coverage/next.js +100 -0
  22. package/dist/coverage/register.cjs +741 -0
  23. package/dist/coverage/register.d.cts +1 -0
  24. package/dist/coverage/register.d.ts +1 -0
  25. package/dist/coverage/register.js +716 -0
  26. package/dist/coverage/slack.cjs +228 -0
  27. package/dist/coverage/slack.d.cts +47 -0
  28. package/dist/coverage/slack.d.ts +47 -0
  29. package/dist/coverage/slack.js +225 -0
  30. package/dist/coverage/temporal-workflow.cjs +154 -0
  31. package/dist/coverage/temporal-workflow.d.cts +28 -0
  32. package/dist/coverage/temporal-workflow.d.ts +28 -0
  33. package/dist/coverage/temporal-workflow.js +153 -0
  34. package/dist/coverage/temporal.cjs +253 -0
  35. package/dist/coverage/temporal.d.cts +40 -0
  36. package/dist/coverage/temporal.d.ts +40 -0
  37. package/dist/coverage/temporal.js +250 -0
  38. package/package.json +95 -0
@@ -0,0 +1,228 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/coverage/core.ts
3
+ const RUNTIME_KEY = Symbol.for("ccqa.coverage.runtime");
4
+ function globals() {
5
+ return globalThis;
6
+ }
7
+ /**
8
+ * Memoized so the hot path skips the `globalThis` read. `installRuntime`
9
+ * primes it and never replaces an installed runtime, so once set it never
10
+ * goes stale — but it must stay undefined (and keep re-reading `globalThis`)
11
+ * until then, or a module that finished loading before `register` installs
12
+ * the runtime would be stuck uninstrumented for the rest of the process.
13
+ */
14
+ let cachedRuntime;
15
+ function runtime() {
16
+ if (cachedRuntime === void 0) cachedRuntime = globals()[RUNTIME_KEY];
17
+ return cachedRuntime;
18
+ }
19
+ /**
20
+ * Records that `tag` caused this work at `at`, without deciding whose it is.
21
+ *
22
+ * Carrier wins: a request that already named a spec needs no identity, and
23
+ * overwriting it would replace a fact with something the run still has to
24
+ * interpret. Everything else is recorded whoever it came from — the application
25
+ * is never told which identities are being measured, so it cannot filter, and
26
+ * the run discards the ones it did not ask for.
27
+ */
28
+ function runAsActor(tag, at, fn) {
29
+ const rt = runtime();
30
+ if (rt === void 0) return fn();
31
+ if (rt.als.getStore()?.specId !== void 0) return fn();
32
+ return rt.als.run({
33
+ actor: {
34
+ tag,
35
+ at
36
+ },
37
+ files: openActorBucket(rt, tag, at)
38
+ }, fn);
39
+ }
40
+ /**
41
+ * The key both halves of the collector agree on. A space separates them safely:
42
+ * a spec id can never contain one, so identity keys and spec ids stay disjoint
43
+ * where the collector tracks both in one map.
44
+ */
45
+ function actorBucketKey(tag, at) {
46
+ return `${tag} ${at}`;
47
+ }
48
+ /** Returns the identity's file set for that instant, creating it if new. */
49
+ function openActorBucket(runtime, tag, at) {
50
+ const key = actorBucketKey(tag, at);
51
+ let bucket = runtime.actors.get(key);
52
+ if (bucket === void 0) {
53
+ bucket = {
54
+ tag,
55
+ at,
56
+ files: /* @__PURE__ */ new Set()
57
+ };
58
+ runtime.actors.set(key, bucket);
59
+ armGate(runtime);
60
+ }
61
+ return bucket.files;
62
+ }
63
+ function armGate(runtime) {
64
+ runtime.active = runtime.buckets.size + runtime.actors.size;
65
+ }
66
+ //#endregion
67
+ //#region src/coverage/wire.ts
68
+ /**
69
+ * Enables the instrumentation. Unset means the register hook is never loaded
70
+ * and the application pays nothing at all.
71
+ *
72
+ * `1` / `true` turns it on and leaves attribution to the incoming carrier.
73
+ * Any other value is itself a `<runId>.<specId>` and becomes the ambient spec
74
+ * for the process — the only way to attribute an entry point that has no
75
+ * inbound request to read, such as a worker started per spec.
76
+ */
77
+ const ENV_NAME = "CCQA_COVERAGE";
78
+ const SPEC_ID = /^[A-Za-z0-9._\-/]{1,200}$/;
79
+ /**
80
+ * Accepts a carrier value only if it looks like an id we wrote.
81
+ *
82
+ * The cookie is client-controlled, so this is the first of two gates: the
83
+ * second is the hub refusing runs it never started.
84
+ */
85
+ function parseSpecId(raw) {
86
+ if (!raw) return void 0;
87
+ const value = raw.trim();
88
+ if (!SPEC_ID.test(value)) return void 0;
89
+ if (value === "1" || value === "true") return void 0;
90
+ return value;
91
+ }
92
+ //#endregion
93
+ //#region src/coverage/runtime-env.ts
94
+ function readConfig(env = process.env) {
95
+ const raw = env[ENV_NAME];
96
+ const include = (env["CCQA_COVERAGE_INCLUDE"] ?? "src").split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
97
+ return {
98
+ enabled: raw !== void 0 && raw !== "" && raw !== "0" && raw !== "false",
99
+ ambientSpecId: parseSpecId(raw),
100
+ root: env["CCQA_COVERAGE_ROOT"] ?? process.cwd(),
101
+ include,
102
+ debug: env["CCQA_COVERAGE_DEBUG"] === "1" || env["CCQA_COVERAGE_DEBUG"] === "true"
103
+ };
104
+ }
105
+ /**
106
+ * Diagnostics go to stderr and nowhere else. A `--import` preload is inherited
107
+ * by every child node process, and writing to stdout corrupts whatever the host
108
+ * was parsing there — enough to make a framework's own toolchain fail to start.
109
+ */
110
+ function debugLog(config, message) {
111
+ if (!config.debug) return;
112
+ process.stderr.write(`[ccqa-tools] ${message}\n`);
113
+ }
114
+ //#endregion
115
+ //#region src/coverage/presets/slack.ts
116
+ /**
117
+ * Records who caused a Slack request, so the flows a chat platform drives can
118
+ * be attributed at all.
119
+ *
120
+ * Those requests are sent by Slack, not by the browser under test, so none of
121
+ * the carriers the rest of this package relies on is present — no cookie, no
122
+ * baggage header, nothing. What the payload does say is which user acted, and
123
+ * that is the whole of what this records:
124
+ *
125
+ * app.use(slackActor()) // after whatever parses the body
126
+ *
127
+ * No arguments, no configuration, no environment. It does not know which users
128
+ * are being measured, when a spec is running, or that ccqa exists — the run
129
+ * decides all of that from the other end. With `CCQA_COVERAGE` unset the
130
+ * runtime is absent and every call here returns immediately.
131
+ *
132
+ * Extraction is a fixed list of the places Slack puts a user id. A payload
133
+ * shape not on the list records nothing rather than guessing, because a wrong
134
+ * identity is a wrong answer while a missing one is a counted gap.
135
+ */
136
+ /**
137
+ * Read once: the identity of every inbound request would otherwise re-read the
138
+ * environment, and this sits in front of every Slack request the app serves.
139
+ */
140
+ const config = readConfig();
141
+ /** The tag prefix, and therefore the provider key a project writes in its config. */
142
+ const SLACK_PROVIDER = "slack";
143
+ /**
144
+ * Middleware for either shape a Slack app uses.
145
+ *
146
+ * Bolt hands its middleware one object holding the parsed body and its own
147
+ * `next`, and awaits what comes back. A connect-style stack hands
148
+ * `(request, response, next)`, and a context-style one `(ctx, next)`. All are
149
+ * accepted so the application's one line does not have to know which it is —
150
+ * and in a Bolt app `app.use()` is the only insertion point there is.
151
+ *
152
+ * Place it after whatever parses the body: the payload is what carries the
153
+ * identity, and before parsing there is none. A framework whose body is only
154
+ * available by awaiting it is out of reach here for the same reason — there is
155
+ * nothing to read at the moment this runs.
156
+ */
157
+ function slackActor() {
158
+ return async function coverageSlackActor(...args) {
159
+ const first = args[0];
160
+ const bolt = args.length === 1 && isRecord(first) && typeof first.next === "function" ? first : void 0;
161
+ const body = bolt ? bolt.body : requestBody(first);
162
+ const next = bolt ? bolt.next : args.slice(1).find(isFunction);
163
+ if (next === void 0) return;
164
+ const user = slackUserId(body);
165
+ if (user === void 0) {
166
+ await next();
167
+ return;
168
+ }
169
+ const tag = `${SLACK_PROVIDER}:${user}`;
170
+ debugLog(config, `slack actor ${tag}`);
171
+ await runAsActor(tag, Date.now(), next);
172
+ };
173
+ }
174
+ /**
175
+ * The user id in a Slack payload, or undefined if this shape carries none.
176
+ *
177
+ * Exported for its own tests: the list is the entire contract, and a shape
178
+ * silently dropping off it looks exactly like a flow that reached no code.
179
+ */
180
+ function slackUserId(body) {
181
+ if (!isRecord(body)) return void 0;
182
+ const payload = parsePayload(body.payload);
183
+ if (payload !== void 0) return idOf(payload.user);
184
+ const event = body.event;
185
+ if (isRecord(event)) return idOf(event.user);
186
+ if (body.user !== void 0) return idOf(body.user);
187
+ if (typeof body.user_id === "string") return nonEmpty(body.user_id);
188
+ }
189
+ function parsePayload(raw) {
190
+ if (isRecord(raw)) return raw;
191
+ if (typeof raw !== "string") return void 0;
192
+ try {
193
+ const parsed = JSON.parse(raw);
194
+ return isRecord(parsed) ? parsed : void 0;
195
+ } catch {
196
+ return;
197
+ }
198
+ }
199
+ /** Slack writes a user as a bare id in some payloads and as an object in others. */
200
+ function idOf(user) {
201
+ if (typeof user === "string") return nonEmpty(user);
202
+ if (isRecord(user) && typeof user.id === "string") return nonEmpty(user.id);
203
+ }
204
+ function nonEmpty(value) {
205
+ const trimmed = value.trim();
206
+ return trimmed === "" ? void 0 : trimmed;
207
+ }
208
+ function isRecord(value) {
209
+ return typeof value === "object" && value !== null;
210
+ }
211
+ function isFunction(value) {
212
+ return typeof value === "function";
213
+ }
214
+ /**
215
+ * The parsed request body, from the request itself or from a context wrapping
216
+ * it. A context's own `body` is the *response* it is building, so reading that
217
+ * would hand the extractor the wrong object and quietly find no identity.
218
+ */
219
+ function requestBody(first) {
220
+ if (!isRecord(first)) return void 0;
221
+ const request = first.request;
222
+ if (isRecord(request) && isRecord(request.body)) return request.body;
223
+ return isRecord(first.body) ? first.body : void 0;
224
+ }
225
+ //#endregion
226
+ exports.SLACK_PROVIDER = SLACK_PROVIDER;
227
+ exports.slackActor = slackActor;
228
+ exports.slackUserId = slackUserId;
@@ -0,0 +1,47 @@
1
+ //#region src/coverage/presets/slack.d.ts
2
+ /**
3
+ * Records who caused a Slack request, so the flows a chat platform drives can
4
+ * be attributed at all.
5
+ *
6
+ * Those requests are sent by Slack, not by the browser under test, so none of
7
+ * the carriers the rest of this package relies on is present — no cookie, no
8
+ * baggage header, nothing. What the payload does say is which user acted, and
9
+ * that is the whole of what this records:
10
+ *
11
+ * app.use(slackActor()) // after whatever parses the body
12
+ *
13
+ * No arguments, no configuration, no environment. It does not know which users
14
+ * are being measured, when a spec is running, or that ccqa exists — the run
15
+ * decides all of that from the other end. With `CCQA_COVERAGE` unset the
16
+ * runtime is absent and every call here returns immediately.
17
+ *
18
+ * Extraction is a fixed list of the places Slack puts a user id. A payload
19
+ * shape not on the list records nothing rather than guessing, because a wrong
20
+ * identity is a wrong answer while a missing one is a counted gap.
21
+ */
22
+ /** The tag prefix, and therefore the provider key a project writes in its config. */
23
+ declare const SLACK_PROVIDER = "slack";
24
+ /**
25
+ * Middleware for either shape a Slack app uses.
26
+ *
27
+ * Bolt hands its middleware one object holding the parsed body and its own
28
+ * `next`, and awaits what comes back. A connect-style stack hands
29
+ * `(request, response, next)`, and a context-style one `(ctx, next)`. All are
30
+ * accepted so the application's one line does not have to know which it is —
31
+ * and in a Bolt app `app.use()` is the only insertion point there is.
32
+ *
33
+ * Place it after whatever parses the body: the payload is what carries the
34
+ * identity, and before parsing there is none. A framework whose body is only
35
+ * available by awaiting it is out of reach here for the same reason — there is
36
+ * nothing to read at the moment this runs.
37
+ */
38
+ declare function slackActor(): (...args: unknown[]) => Promise<void>;
39
+ /**
40
+ * The user id in a Slack payload, or undefined if this shape carries none.
41
+ *
42
+ * Exported for its own tests: the list is the entire contract, and a shape
43
+ * silently dropping off it looks exactly like a flow that reached no code.
44
+ */
45
+ declare function slackUserId(body: unknown): string | undefined;
46
+ //#endregion
47
+ export { SLACK_PROVIDER, slackActor, slackUserId };
@@ -0,0 +1,47 @@
1
+ //#region src/coverage/presets/slack.d.ts
2
+ /**
3
+ * Records who caused a Slack request, so the flows a chat platform drives can
4
+ * be attributed at all.
5
+ *
6
+ * Those requests are sent by Slack, not by the browser under test, so none of
7
+ * the carriers the rest of this package relies on is present — no cookie, no
8
+ * baggage header, nothing. What the payload does say is which user acted, and
9
+ * that is the whole of what this records:
10
+ *
11
+ * app.use(slackActor()) // after whatever parses the body
12
+ *
13
+ * No arguments, no configuration, no environment. It does not know which users
14
+ * are being measured, when a spec is running, or that ccqa exists — the run
15
+ * decides all of that from the other end. With `CCQA_COVERAGE` unset the
16
+ * runtime is absent and every call here returns immediately.
17
+ *
18
+ * Extraction is a fixed list of the places Slack puts a user id. A payload
19
+ * shape not on the list records nothing rather than guessing, because a wrong
20
+ * identity is a wrong answer while a missing one is a counted gap.
21
+ */
22
+ /** The tag prefix, and therefore the provider key a project writes in its config. */
23
+ declare const SLACK_PROVIDER = "slack";
24
+ /**
25
+ * Middleware for either shape a Slack app uses.
26
+ *
27
+ * Bolt hands its middleware one object holding the parsed body and its own
28
+ * `next`, and awaits what comes back. A connect-style stack hands
29
+ * `(request, response, next)`, and a context-style one `(ctx, next)`. All are
30
+ * accepted so the application's one line does not have to know which it is —
31
+ * and in a Bolt app `app.use()` is the only insertion point there is.
32
+ *
33
+ * Place it after whatever parses the body: the payload is what carries the
34
+ * identity, and before parsing there is none. A framework whose body is only
35
+ * available by awaiting it is out of reach here for the same reason — there is
36
+ * nothing to read at the moment this runs.
37
+ */
38
+ declare function slackActor(): (...args: unknown[]) => Promise<void>;
39
+ /**
40
+ * The user id in a Slack payload, or undefined if this shape carries none.
41
+ *
42
+ * Exported for its own tests: the list is the entire contract, and a shape
43
+ * silently dropping off it looks exactly like a flow that reached no code.
44
+ */
45
+ declare function slackUserId(body: unknown): string | undefined;
46
+ //#endregion
47
+ export { SLACK_PROVIDER, slackActor, slackUserId };
@@ -0,0 +1,225 @@
1
+ //#region src/coverage/core.ts
2
+ const RUNTIME_KEY = Symbol.for("ccqa.coverage.runtime");
3
+ function globals() {
4
+ return globalThis;
5
+ }
6
+ /**
7
+ * Memoized so the hot path skips the `globalThis` read. `installRuntime`
8
+ * primes it and never replaces an installed runtime, so once set it never
9
+ * goes stale — but it must stay undefined (and keep re-reading `globalThis`)
10
+ * until then, or a module that finished loading before `register` installs
11
+ * the runtime would be stuck uninstrumented for the rest of the process.
12
+ */
13
+ let cachedRuntime;
14
+ function runtime() {
15
+ if (cachedRuntime === void 0) cachedRuntime = globals()[RUNTIME_KEY];
16
+ return cachedRuntime;
17
+ }
18
+ /**
19
+ * Records that `tag` caused this work at `at`, without deciding whose it is.
20
+ *
21
+ * Carrier wins: a request that already named a spec needs no identity, and
22
+ * overwriting it would replace a fact with something the run still has to
23
+ * interpret. Everything else is recorded whoever it came from — the application
24
+ * is never told which identities are being measured, so it cannot filter, and
25
+ * the run discards the ones it did not ask for.
26
+ */
27
+ function runAsActor(tag, at, fn) {
28
+ const rt = runtime();
29
+ if (rt === void 0) return fn();
30
+ if (rt.als.getStore()?.specId !== void 0) return fn();
31
+ return rt.als.run({
32
+ actor: {
33
+ tag,
34
+ at
35
+ },
36
+ files: openActorBucket(rt, tag, at)
37
+ }, fn);
38
+ }
39
+ /**
40
+ * The key both halves of the collector agree on. A space separates them safely:
41
+ * a spec id can never contain one, so identity keys and spec ids stay disjoint
42
+ * where the collector tracks both in one map.
43
+ */
44
+ function actorBucketKey(tag, at) {
45
+ return `${tag} ${at}`;
46
+ }
47
+ /** Returns the identity's file set for that instant, creating it if new. */
48
+ function openActorBucket(runtime, tag, at) {
49
+ const key = actorBucketKey(tag, at);
50
+ let bucket = runtime.actors.get(key);
51
+ if (bucket === void 0) {
52
+ bucket = {
53
+ tag,
54
+ at,
55
+ files: /* @__PURE__ */ new Set()
56
+ };
57
+ runtime.actors.set(key, bucket);
58
+ armGate(runtime);
59
+ }
60
+ return bucket.files;
61
+ }
62
+ function armGate(runtime) {
63
+ runtime.active = runtime.buckets.size + runtime.actors.size;
64
+ }
65
+ //#endregion
66
+ //#region src/coverage/wire.ts
67
+ /**
68
+ * Enables the instrumentation. Unset means the register hook is never loaded
69
+ * and the application pays nothing at all.
70
+ *
71
+ * `1` / `true` turns it on and leaves attribution to the incoming carrier.
72
+ * Any other value is itself a `<runId>.<specId>` and becomes the ambient spec
73
+ * for the process — the only way to attribute an entry point that has no
74
+ * inbound request to read, such as a worker started per spec.
75
+ */
76
+ const ENV_NAME = "CCQA_COVERAGE";
77
+ const SPEC_ID = /^[A-Za-z0-9._\-/]{1,200}$/;
78
+ /**
79
+ * Accepts a carrier value only if it looks like an id we wrote.
80
+ *
81
+ * The cookie is client-controlled, so this is the first of two gates: the
82
+ * second is the hub refusing runs it never started.
83
+ */
84
+ function parseSpecId(raw) {
85
+ if (!raw) return void 0;
86
+ const value = raw.trim();
87
+ if (!SPEC_ID.test(value)) return void 0;
88
+ if (value === "1" || value === "true") return void 0;
89
+ return value;
90
+ }
91
+ //#endregion
92
+ //#region src/coverage/runtime-env.ts
93
+ function readConfig(env = process.env) {
94
+ const raw = env[ENV_NAME];
95
+ const include = (env["CCQA_COVERAGE_INCLUDE"] ?? "src").split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
96
+ return {
97
+ enabled: raw !== void 0 && raw !== "" && raw !== "0" && raw !== "false",
98
+ ambientSpecId: parseSpecId(raw),
99
+ root: env["CCQA_COVERAGE_ROOT"] ?? process.cwd(),
100
+ include,
101
+ debug: env["CCQA_COVERAGE_DEBUG"] === "1" || env["CCQA_COVERAGE_DEBUG"] === "true"
102
+ };
103
+ }
104
+ /**
105
+ * Diagnostics go to stderr and nowhere else. A `--import` preload is inherited
106
+ * by every child node process, and writing to stdout corrupts whatever the host
107
+ * was parsing there — enough to make a framework's own toolchain fail to start.
108
+ */
109
+ function debugLog(config, message) {
110
+ if (!config.debug) return;
111
+ process.stderr.write(`[ccqa-tools] ${message}\n`);
112
+ }
113
+ //#endregion
114
+ //#region src/coverage/presets/slack.ts
115
+ /**
116
+ * Records who caused a Slack request, so the flows a chat platform drives can
117
+ * be attributed at all.
118
+ *
119
+ * Those requests are sent by Slack, not by the browser under test, so none of
120
+ * the carriers the rest of this package relies on is present — no cookie, no
121
+ * baggage header, nothing. What the payload does say is which user acted, and
122
+ * that is the whole of what this records:
123
+ *
124
+ * app.use(slackActor()) // after whatever parses the body
125
+ *
126
+ * No arguments, no configuration, no environment. It does not know which users
127
+ * are being measured, when a spec is running, or that ccqa exists — the run
128
+ * decides all of that from the other end. With `CCQA_COVERAGE` unset the
129
+ * runtime is absent and every call here returns immediately.
130
+ *
131
+ * Extraction is a fixed list of the places Slack puts a user id. A payload
132
+ * shape not on the list records nothing rather than guessing, because a wrong
133
+ * identity is a wrong answer while a missing one is a counted gap.
134
+ */
135
+ /**
136
+ * Read once: the identity of every inbound request would otherwise re-read the
137
+ * environment, and this sits in front of every Slack request the app serves.
138
+ */
139
+ const config = readConfig();
140
+ /** The tag prefix, and therefore the provider key a project writes in its config. */
141
+ const SLACK_PROVIDER = "slack";
142
+ /**
143
+ * Middleware for either shape a Slack app uses.
144
+ *
145
+ * Bolt hands its middleware one object holding the parsed body and its own
146
+ * `next`, and awaits what comes back. A connect-style stack hands
147
+ * `(request, response, next)`, and a context-style one `(ctx, next)`. All are
148
+ * accepted so the application's one line does not have to know which it is —
149
+ * and in a Bolt app `app.use()` is the only insertion point there is.
150
+ *
151
+ * Place it after whatever parses the body: the payload is what carries the
152
+ * identity, and before parsing there is none. A framework whose body is only
153
+ * available by awaiting it is out of reach here for the same reason — there is
154
+ * nothing to read at the moment this runs.
155
+ */
156
+ function slackActor() {
157
+ return async function coverageSlackActor(...args) {
158
+ const first = args[0];
159
+ const bolt = args.length === 1 && isRecord(first) && typeof first.next === "function" ? first : void 0;
160
+ const body = bolt ? bolt.body : requestBody(first);
161
+ const next = bolt ? bolt.next : args.slice(1).find(isFunction);
162
+ if (next === void 0) return;
163
+ const user = slackUserId(body);
164
+ if (user === void 0) {
165
+ await next();
166
+ return;
167
+ }
168
+ const tag = `${SLACK_PROVIDER}:${user}`;
169
+ debugLog(config, `slack actor ${tag}`);
170
+ await runAsActor(tag, Date.now(), next);
171
+ };
172
+ }
173
+ /**
174
+ * The user id in a Slack payload, or undefined if this shape carries none.
175
+ *
176
+ * Exported for its own tests: the list is the entire contract, and a shape
177
+ * silently dropping off it looks exactly like a flow that reached no code.
178
+ */
179
+ function slackUserId(body) {
180
+ if (!isRecord(body)) return void 0;
181
+ const payload = parsePayload(body.payload);
182
+ if (payload !== void 0) return idOf(payload.user);
183
+ const event = body.event;
184
+ if (isRecord(event)) return idOf(event.user);
185
+ if (body.user !== void 0) return idOf(body.user);
186
+ if (typeof body.user_id === "string") return nonEmpty(body.user_id);
187
+ }
188
+ function parsePayload(raw) {
189
+ if (isRecord(raw)) return raw;
190
+ if (typeof raw !== "string") return void 0;
191
+ try {
192
+ const parsed = JSON.parse(raw);
193
+ return isRecord(parsed) ? parsed : void 0;
194
+ } catch {
195
+ return;
196
+ }
197
+ }
198
+ /** Slack writes a user as a bare id in some payloads and as an object in others. */
199
+ function idOf(user) {
200
+ if (typeof user === "string") return nonEmpty(user);
201
+ if (isRecord(user) && typeof user.id === "string") return nonEmpty(user.id);
202
+ }
203
+ function nonEmpty(value) {
204
+ const trimmed = value.trim();
205
+ return trimmed === "" ? void 0 : trimmed;
206
+ }
207
+ function isRecord(value) {
208
+ return typeof value === "object" && value !== null;
209
+ }
210
+ function isFunction(value) {
211
+ return typeof value === "function";
212
+ }
213
+ /**
214
+ * The parsed request body, from the request itself or from a context wrapping
215
+ * it. A context's own `body` is the *response* it is building, so reading that
216
+ * would hand the extractor the wrong object and quietly find no identity.
217
+ */
218
+ function requestBody(first) {
219
+ if (!isRecord(first)) return void 0;
220
+ const request = first.request;
221
+ if (isRecord(request) && isRecord(request.body)) return request.body;
222
+ return isRecord(first.body) ? first.body : void 0;
223
+ }
224
+ //#endregion
225
+ export { SLACK_PROVIDER, slackActor, slackUserId };