@agiledigital/pingone-aic-script-tester 0.1.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 (169) hide show
  1. package/LICENSE-APACHE +201 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +274 -0
  4. package/dist/bin/fetch-jar.d.ts +2 -0
  5. package/dist/bin/fetch-jar.js +13 -0
  6. package/dist/bin/pull-profile.d.ts +2 -0
  7. package/dist/bin/pull-profile.js +40 -0
  8. package/dist/bin/show-log.d.ts +2 -0
  9. package/dist/bin/show-log.js +31 -0
  10. package/dist/classes/.complete +12 -0
  11. package/dist/classes/AmClassShutter.class +0 -0
  12. package/dist/classes/AmContextFactory$ObservedContext.class +0 -0
  13. package/dist/classes/AmContextFactory.class +0 -0
  14. package/dist/classes/AmScopes.class +0 -0
  15. package/dist/classes/HostOps.class +0 -0
  16. package/dist/classes/JsValues.class +0 -0
  17. package/dist/classes/Json$Parser.class +0 -0
  18. package/dist/classes/Json.class +0 -0
  19. package/dist/classes/LanguageVersions.class +0 -0
  20. package/dist/classes/Runner.class +0 -0
  21. package/dist/classes/ScriptContextScope.class +0 -0
  22. package/dist/src/aic/callbacks.d.ts +35 -0
  23. package/dist/src/aic/callbacks.js +130 -0
  24. package/dist/src/aic/conform.d.ts +90 -0
  25. package/dist/src/aic/conform.js +193 -0
  26. package/dist/src/aic/constants.d.ts +13 -0
  27. package/dist/src/aic/constants.js +13 -0
  28. package/dist/src/aic/diff.d.ts +25 -0
  29. package/dist/src/aic/diff.js +201 -0
  30. package/dist/src/aic/emit-journey.d.ts +64 -0
  31. package/dist/src/aic/emit-journey.js +254 -0
  32. package/dist/src/aic/emit-result.d.ts +10 -0
  33. package/dist/src/aic/emit-result.js +39 -0
  34. package/dist/src/aic/emit-session.d.ts +26 -0
  35. package/dist/src/aic/emit-session.js +132 -0
  36. package/dist/src/aic/emit-subject.d.ts +40 -0
  37. package/dist/src/aic/emit-subject.js +210 -0
  38. package/dist/src/aic/file-lease.d.ts +35 -0
  39. package/dist/src/aic/file-lease.js +543 -0
  40. package/dist/src/aic/http.d.ts +21 -0
  41. package/dist/src/aic/http.js +127 -0
  42. package/dist/src/aic/idm.d.ts +10 -0
  43. package/dist/src/aic/idm.js +112 -0
  44. package/dist/src/aic/index.d.ts +35 -0
  45. package/dist/src/aic/index.js +21 -0
  46. package/dist/src/aic/js.d.ts +8 -0
  47. package/dist/src/aic/js.js +11 -0
  48. package/dist/src/aic/lease-identity.d.ts +38 -0
  49. package/dist/src/aic/lease-identity.js +72 -0
  50. package/dist/src/aic/lease-lock.d.ts +45 -0
  51. package/dist/src/aic/lease-lock.js +173 -0
  52. package/dist/src/aic/managed.d.ts +28 -0
  53. package/dist/src/aic/managed.js +199 -0
  54. package/dist/src/aic/provider.d.ts +156 -0
  55. package/dist/src/aic/provider.js +416 -0
  56. package/dist/src/aic/record.d.ts +28 -0
  57. package/dist/src/aic/record.js +183 -0
  58. package/dist/src/aic/resource-snapshot.d.ts +12 -0
  59. package/dist/src/aic/resource-snapshot.js +116 -0
  60. package/dist/src/aic/run.d.ts +102 -0
  61. package/dist/src/aic/run.js +450 -0
  62. package/dist/src/aic/tenant.d.ts +107 -0
  63. package/dist/src/aic/tenant.js +307 -0
  64. package/dist/src/aic/trace.d.ts +25 -0
  65. package/dist/src/aic/trace.js +56 -0
  66. package/dist/src/aic/txid.d.ts +11 -0
  67. package/dist/src/aic/txid.js +25 -0
  68. package/dist/src/aic/unsupported.d.ts +15 -0
  69. package/dist/src/aic/unsupported.js +60 -0
  70. package/dist/src/bindings/allowlist.d.ts +42 -0
  71. package/dist/src/bindings/allowlist.js +62 -0
  72. package/dist/src/bindings/harvest.d.ts +7 -0
  73. package/dist/src/bindings/harvest.js +137 -0
  74. package/dist/src/bindings/index.d.ts +4 -0
  75. package/dist/src/bindings/index.js +3 -0
  76. package/dist/src/bindings/preamble.d.ts +23 -0
  77. package/dist/src/bindings/preamble.js +36 -0
  78. package/dist/src/bindings/run.d.ts +33 -0
  79. package/dist/src/bindings/run.js +55 -0
  80. package/dist/src/case/equal.d.ts +2 -0
  81. package/dist/src/case/equal.js +40 -0
  82. package/dist/src/case/index.d.ts +5 -0
  83. package/dist/src/case/index.js +4 -0
  84. package/dist/src/case/portable.d.ts +10 -0
  85. package/dist/src/case/portable.js +28 -0
  86. package/dist/src/case/session.d.ts +34 -0
  87. package/dist/src/case/session.js +66 -0
  88. package/dist/src/case/state.d.ts +4 -0
  89. package/dist/src/case/state.js +47 -0
  90. package/dist/src/case/types.d.ts +266 -0
  91. package/dist/src/case/types.js +106 -0
  92. package/dist/src/case/util.d.ts +7 -0
  93. package/dist/src/case/util.js +115 -0
  94. package/dist/src/case/validate.d.ts +8 -0
  95. package/dist/src/case/validate.js +463 -0
  96. package/dist/src/case/verdict.d.ts +6 -0
  97. package/dist/src/case/verdict.js +810 -0
  98. package/dist/src/diagnostics.d.ts +7 -0
  99. package/dist/src/diagnostics.js +5 -0
  100. package/dist/src/emit-dts.d.ts +2 -0
  101. package/dist/src/emit-dts.js +82 -0
  102. package/dist/src/emit-js.d.ts +3 -0
  103. package/dist/src/emit-js.js +149 -0
  104. package/dist/src/harness/failures.d.ts +26 -0
  105. package/dist/src/harness/failures.js +113 -0
  106. package/dist/src/harness/idm.d.ts +12 -0
  107. package/dist/src/harness/idm.js +44 -0
  108. package/dist/src/harness/index.d.ts +11 -0
  109. package/dist/src/harness/index.js +6 -0
  110. package/dist/src/harness/lease.d.ts +136 -0
  111. package/dist/src/harness/lease.js +286 -0
  112. package/dist/src/harness/residue.d.ts +23 -0
  113. package/dist/src/harness/residue.js +73 -0
  114. package/dist/src/harness/show-log.d.ts +43 -0
  115. package/dist/src/harness/show-log.js +253 -0
  116. package/dist/src/harness/spec.d.ts +67 -0
  117. package/dist/src/harness/spec.js +189 -0
  118. package/dist/src/harness/step.d.ts +80 -0
  119. package/dist/src/harness/step.js +97 -0
  120. package/dist/src/harness/types.d.ts +100 -0
  121. package/dist/src/harness/types.js +1 -0
  122. package/dist/src/harness/vitest.d.ts +52 -0
  123. package/dist/src/harness/vitest.js +155 -0
  124. package/dist/src/idents.d.ts +4 -0
  125. package/dist/src/idents.js +42 -0
  126. package/dist/src/jvm.d.ts +124 -0
  127. package/dist/src/jvm.js +432 -0
  128. package/dist/src/load.d.ts +2 -0
  129. package/dist/src/load.js +104 -0
  130. package/dist/src/paths.d.ts +36 -0
  131. package/dist/src/paths.js +50 -0
  132. package/dist/src/profile/index.d.ts +5 -0
  133. package/dist/src/profile/index.js +3 -0
  134. package/dist/src/profile/normalise.d.ts +16 -0
  135. package/dist/src/profile/normalise.js +106 -0
  136. package/dist/src/profile/pull.d.ts +22 -0
  137. package/dist/src/profile/pull.js +36 -0
  138. package/dist/src/profile/seed.d.ts +11 -0
  139. package/dist/src/profile/seed.js +28 -0
  140. package/dist/src/profile/store.d.ts +13 -0
  141. package/dist/src/profile/store.js +60 -0
  142. package/dist/src/profile/types.d.ts +45 -0
  143. package/dist/src/profile/types.js +1 -0
  144. package/dist/src/profile/validate.d.ts +38 -0
  145. package/dist/src/profile/validate.js +129 -0
  146. package/dist/src/project.d.ts +37 -0
  147. package/dist/src/project.js +93 -0
  148. package/dist/src/protocol.d.ts +62 -0
  149. package/dist/src/protocol.js +68 -0
  150. package/dist/src/provider-module.d.ts +11 -0
  151. package/dist/src/provider-module.js +46 -0
  152. package/dist/src/runner.d.ts +39 -0
  153. package/dist/src/runner.js +337 -0
  154. package/dist/src/schema.d.ts +48 -0
  155. package/dist/src/schema.js +93 -0
  156. package/generated/scripted-decision-mocks.cjs +722 -0
  157. package/generated/scripted-decision-mocks.d.ts +626 -0
  158. package/generated/scripted-decision-next.json +3640 -0
  159. package/java/AmClassShutter.java +98 -0
  160. package/java/AmContextFactory.java +69 -0
  161. package/java/AmScopes.java +49 -0
  162. package/java/HostOps.java +180 -0
  163. package/java/JsValues.java +39 -0
  164. package/java/Json.java +441 -0
  165. package/java/LanguageVersions.java +55 -0
  166. package/java/Runner.java +289 -0
  167. package/java/ScriptContextScope.java +147 -0
  168. package/package.json +88 -0
  169. package/src/bindings/rhino/runtime.cjs +3021 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Runner-agnostic scripted-decision case: one definition drives the local
3
+ * Rhino lane and the AIC wrapper-journey lane. `given` is the seed;
4
+ * `expect` is diffs and per-channel effects, never whole-state equality.
5
+ */
6
+ export type JsonPrimitive = string | number | boolean | null;
7
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
8
+ export interface JsonObject {
9
+ [key: string]: JsonValue;
10
+ }
11
+ export type Pattern = string | RegExp;
12
+ export declare const CASE_KEYS: readonly ["name", "script", "outcomes", "given", "expect"];
13
+ export type CaseKey = (typeof CASE_KEYS)[number];
14
+ export declare const ENGINES: readonly ["next-gen", "legacy"];
15
+ export type Engine = (typeof ENGINES)[number];
16
+ export declare const GIVEN_KEYS: readonly ["sharedState", "transientState", "secureState", "realm", "scriptName", "cookieName", "resumedFromSuspend", "requestHeaders", "requestParameters", "requestCookies", "locales", "existingSession", "esv", "secrets", "callbacks", "managed", "http", "engine", "bindings"];
17
+ export type GivenKey = (typeof GIVEN_KEYS)[number];
18
+ /** Bindings that have a dedicated `given` field; do not also put them in `bindings`. */
19
+ export declare const GIVEN_BINDING_SEEDS: readonly ["realm", "scriptName", "cookieName", "resumedFromSuspend", "requestHeaders", "requestParameters", "requestCookies", "locales", "existingSession"];
20
+ export declare const EXPECT_KEYS: readonly ["outcome", "sharedState", "transientState", "secureState", "callbacks", "openidm", "http", "logs", "allowUndeclared"];
21
+ export type ExpectKey = (typeof EXPECT_KEYS)[number];
22
+ export declare const STATE_DIFF_KEYS: readonly ["added", "changed", "removed"];
23
+ export declare const ALLOW_UNDECLARED_CHANNELS: readonly ["openidmWrites", "openidmReads", "http", "logs", "callbacks", "sharedState", "transientState", "secureState"];
24
+ export type AllowUndeclaredChannel = (typeof ALLOW_UNDECLARED_CHANNELS)[number];
25
+ export type AllowUndeclared = {
26
+ [K in AllowUndeclaredChannel]?: boolean;
27
+ };
28
+ /**
29
+ * Per-channel defaults. Fail-closed on undeclared writes, HTTP, callbacks,
30
+ * and state mutations; fail-open on reads and log lines. Loosening a
31
+ * fail-closed channel requires an explicit `allowUndeclared` flag.
32
+ */
33
+ export declare const DEFAULT_ALLOW_UNDECLARED: {
34
+ readonly [K in AllowUndeclaredChannel]: boolean;
35
+ };
36
+ export declare const OPENIDM_WRITE_METHODS: readonly ["create", "update", "patch", "delete", "action"];
37
+ export declare const OPENIDM_READ_METHODS: readonly ["read", "query"];
38
+ export declare const OPENIDM_METHODS: readonly ["create", "update", "patch", "delete", "action", "read", "query"];
39
+ export type OpenidmWriteMethod = (typeof OPENIDM_WRITE_METHODS)[number];
40
+ export type OpenidmReadMethod = (typeof OPENIDM_READ_METHODS)[number];
41
+ export type OpenidmMethod = (typeof OPENIDM_METHODS)[number];
42
+ export declare const LOG_LEVELS: readonly ["error", "warn", "info", "debug", "trace"];
43
+ export type LogLevel = (typeof LOG_LEVELS)[number];
44
+ export declare const CHANNELS: readonly ["outcome", "sharedState", "transientState", "secureState", "callbacks", "openidm", "http", "logs"];
45
+ export type Channel = (typeof CHANNELS)[number];
46
+ export declare const STATE_CHANNELS: readonly ["sharedState", "transientState", "secureState"];
47
+ export type StateChannel = (typeof STATE_CHANNELS)[number];
48
+ export type EvidenceChannel = Channel | "nodeState";
49
+ export declare const ENV_INPUT_KEYS: readonly ["esv", "secrets", "managed", "http"];
50
+ export type EnvInputKey = (typeof ENV_INPUT_KEYS)[number];
51
+ export interface StateDiff {
52
+ added?: JsonObject;
53
+ changed?: JsonObject;
54
+ removed?: string[];
55
+ }
56
+ export interface HttpMatch {
57
+ url: Pattern;
58
+ method?: string;
59
+ }
60
+ export interface HttpReply {
61
+ status: number;
62
+ body?: JsonValue;
63
+ headers?: Record<string, string>;
64
+ }
65
+ export interface HttpStub {
66
+ match: HttpMatch;
67
+ reply: HttpReply;
68
+ }
69
+ export interface HttpExpect {
70
+ url: Pattern;
71
+ method?: string;
72
+ times?: number;
73
+ }
74
+ export interface HttpEffect {
75
+ url: string;
76
+ method: string;
77
+ body?: JsonValue;
78
+ }
79
+ export interface OpenidmExpect {
80
+ method: OpenidmMethod;
81
+ resource: Pattern;
82
+ body?: JsonValue;
83
+ actionName?: Pattern;
84
+ times?: number;
85
+ }
86
+ export interface OpenidmEffect {
87
+ method: OpenidmMethod;
88
+ resource: string;
89
+ body?: JsonValue;
90
+ actionName?: string;
91
+ }
92
+ export interface LogExpect {
93
+ level?: LogLevel;
94
+ message: Pattern;
95
+ times?: number;
96
+ }
97
+ export interface LogEffect {
98
+ level: LogLevel;
99
+ message: string;
100
+ }
101
+ export interface CallbackEffect {
102
+ type: string;
103
+ [key: string]: JsonValue;
104
+ }
105
+ export interface Given {
106
+ sharedState?: JsonObject;
107
+ transientState?: JsonObject;
108
+ secureState?: JsonObject;
109
+ realm?: string;
110
+ scriptName?: string;
111
+ cookieName?: string;
112
+ resumedFromSuspend?: boolean;
113
+ requestHeaders?: Record<string, string[]>;
114
+ requestParameters?: Record<string, string[]>;
115
+ requestCookies?: Record<string, string>;
116
+ locales?: JsonObject;
117
+ /**
118
+ * `existingSession`. Present only when the request carries a session cookie,
119
+ * and then a String->String map: AM's own session properties plus anything a
120
+ * prior journey stored with `putSessionProperty`. Measured 2026-09-14 on both
121
+ * evaluators (docs/api/12-script-bindings-matrix.md).
122
+ */
123
+ existingSession?: Record<string, string>;
124
+ esv?: Record<string, string>;
125
+ secrets?: Record<string, string>;
126
+ /**
127
+ * Submitted callback values a resumed script reads via `callbacks.getXCallbacks()`.
128
+ * Omit to leave the binding unseeded (a read throws naming `given.callbacks`);
129
+ * seed `[]` for a first pass.
130
+ */
131
+ callbacks?: CallbackEffect[];
132
+ managed?: Record<string, JsonObject[]>;
133
+ http?: HttpStub[];
134
+ engine?: Engine;
135
+ /**
136
+ * Extra binding seeds keyed by generated mock binding name. Unknown names
137
+ * fail validation — a typo must not silently seed nothing. The runtime
138
+ * implements one today, `journey` (`name`, `identityResource`); any other
139
+ * name throws when the case runs.
140
+ */
141
+ bindings?: Record<string, JsonValue>;
142
+ }
143
+ export interface Expect {
144
+ /**
145
+ * The outcome the script must reach, or `null` for "must not decide" — a
146
+ * pass that queued callbacks and suspended. A suspended pass is a real
147
+ * expectation, not an absence of one: measured 2026-09-14, a next-gen node
148
+ * that sends callbacks returns no outcome at all, and the only way to say so
149
+ * without `null` is to name an outcome the script never produces.
150
+ */
151
+ outcome: string | null;
152
+ sharedState?: StateDiff;
153
+ transientState?: StateDiff;
154
+ secureState?: StateDiff;
155
+ callbacks?: CallbackEffect[];
156
+ openidm?: OpenidmExpect[];
157
+ http?: HttpExpect[];
158
+ logs?: LogExpect[];
159
+ allowUndeclared?: AllowUndeclared;
160
+ }
161
+ export interface CaseInit {
162
+ name: string;
163
+ script: string;
164
+ /**
165
+ * The outcome vocabulary this script may produce. Optional here, required
166
+ * to run on a tenant: AM answers an undeclared outcome with a bare
167
+ * `401 Login failure` and no callback, byte-identical to a compile error,
168
+ * so the wrapper journey has to declare every outcome up front and the
169
+ * only way to be sure it did is for the case to say what they are.
170
+ *
171
+ * Declaring it also moves the typo from an opaque tenant 401 to a local
172
+ * failure in under a second, which is why the local lane enforces it too.
173
+ */
174
+ outcomes?: readonly string[];
175
+ given?: Given;
176
+ expect: Expect;
177
+ }
178
+ export interface Case {
179
+ name: string;
180
+ script: string;
181
+ outcomes?: readonly string[];
182
+ given: Given;
183
+ expect: Expect;
184
+ }
185
+ export interface StateBucket {
186
+ initial: JsonObject;
187
+ final: JsonObject;
188
+ }
189
+ export type StateMutation = {
190
+ operation: "added";
191
+ key: string;
192
+ after: JsonValue;
193
+ } | {
194
+ operation: "changed";
195
+ key: string;
196
+ before: JsonValue;
197
+ after: JsonValue;
198
+ } | {
199
+ operation: "removed";
200
+ key: string;
201
+ before: JsonValue;
202
+ };
203
+ export type UnbucketedStateMutation = StateMutation & {
204
+ possibleBuckets: StateChannel[];
205
+ };
206
+ /** What a runner could establish in addition to the effect values themselves. */
207
+ export interface RecordingEvidence {
208
+ /** `unified` means per-bucket absence and hidden lower-precedence writes are unknowable. */
209
+ stateBuckets: "exact" | "unified";
210
+ /** Unified state present before the subject but absent from `given`. */
211
+ ambientState: JsonObject;
212
+ /** Unified state mutations whose concrete bucket could not be observed. */
213
+ unbucketedState: UnbucketedStateMutation[];
214
+ /** An empty effect value in one of these channels is not an observation of absence. */
215
+ unobservedChannels: Channel[];
216
+ }
217
+ /**
218
+ * Observed effects of one run. Every channel is required: omitting a
219
+ * fail-closed channel would silently assert nothing, which is the failure
220
+ * mode this harness exists to avoid. The runner fills this in; the verdict
221
+ * engine treats it as data.
222
+ */
223
+ export interface RecordedEffects {
224
+ outcome: string | null;
225
+ sharedState: StateBucket;
226
+ transientState: StateBucket;
227
+ secureState: StateBucket;
228
+ callbacks: CallbackEffect[];
229
+ openidm: OpenidmEffect[];
230
+ http: HttpEffect[];
231
+ logs: LogEffect[];
232
+ /** Omitted by exact recorders; present when a lane has qualified evidence. */
233
+ evidence?: RecordingEvidence;
234
+ /**
235
+ * The mock `openidm` store as it stood when the script finished. Evidence
236
+ * for the post-test residue check, deliberately NOT a judged channel — it
237
+ * answers "did anything survive that the harness did not create", which is
238
+ * a property of the test's housekeeping rather than of the script's
239
+ * behaviour. Only the local lane can produce it; AIC has no way to observe
240
+ * the tenant's whole store, and pretending otherwise would put an empty
241
+ * object where "unknown" belongs.
242
+ */
243
+ managedStore?: Record<string, JsonObject[]>;
244
+ }
245
+ export interface Mismatch {
246
+ channel: EvidenceChannel;
247
+ path: string;
248
+ expected: string;
249
+ actual: string;
250
+ message: string;
251
+ }
252
+ export interface Unverified {
253
+ channel: EvidenceChannel;
254
+ path: string;
255
+ message: string;
256
+ }
257
+ export interface Verdict {
258
+ pass: boolean;
259
+ /** False when no contradiction was seen but one or more assertions were unobservable. */
260
+ conclusive: boolean;
261
+ portable: boolean;
262
+ mismatches: Mismatch[];
263
+ unverified: Unverified[];
264
+ /** Multi-line explanation; empty string when `pass` is true. */
265
+ summary: string;
266
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Runner-agnostic scripted-decision case: one definition drives the local
3
+ * Rhino lane and the AIC wrapper-journey lane. `given` is the seed;
4
+ * `expect` is diffs and per-channel effects, never whole-state equality.
5
+ */
6
+ export const CASE_KEYS = ["name", "script", "outcomes", "given", "expect"];
7
+ export const ENGINES = ["next-gen", "legacy"];
8
+ export const GIVEN_KEYS = [
9
+ "sharedState",
10
+ "transientState",
11
+ "secureState",
12
+ "realm",
13
+ "scriptName",
14
+ "cookieName",
15
+ "resumedFromSuspend",
16
+ "requestHeaders",
17
+ "requestParameters",
18
+ "requestCookies",
19
+ "locales",
20
+ "existingSession",
21
+ "esv",
22
+ "secrets",
23
+ "callbacks",
24
+ "managed",
25
+ "http",
26
+ "engine",
27
+ "bindings",
28
+ ];
29
+ /** Bindings that have a dedicated `given` field; do not also put them in `bindings`. */
30
+ export const GIVEN_BINDING_SEEDS = [
31
+ "realm",
32
+ "scriptName",
33
+ "cookieName",
34
+ "resumedFromSuspend",
35
+ "requestHeaders",
36
+ "requestParameters",
37
+ "requestCookies",
38
+ "locales",
39
+ "existingSession",
40
+ ];
41
+ export const EXPECT_KEYS = [
42
+ "outcome",
43
+ "sharedState",
44
+ "transientState",
45
+ "secureState",
46
+ "callbacks",
47
+ "openidm",
48
+ "http",
49
+ "logs",
50
+ "allowUndeclared",
51
+ ];
52
+ export const STATE_DIFF_KEYS = ["added", "changed", "removed"];
53
+ export const ALLOW_UNDECLARED_CHANNELS = [
54
+ "openidmWrites",
55
+ "openidmReads",
56
+ "http",
57
+ "logs",
58
+ "callbacks",
59
+ "sharedState",
60
+ "transientState",
61
+ "secureState",
62
+ ];
63
+ /**
64
+ * Per-channel defaults. Fail-closed on undeclared writes, HTTP, callbacks,
65
+ * and state mutations; fail-open on reads and log lines. Loosening a
66
+ * fail-closed channel requires an explicit `allowUndeclared` flag.
67
+ */
68
+ export const DEFAULT_ALLOW_UNDECLARED = {
69
+ openidmWrites: false,
70
+ openidmReads: true,
71
+ http: false,
72
+ logs: true,
73
+ callbacks: false,
74
+ sharedState: false,
75
+ transientState: false,
76
+ secureState: false,
77
+ };
78
+ export const OPENIDM_WRITE_METHODS = [
79
+ "create",
80
+ "update",
81
+ "patch",
82
+ "delete",
83
+ "action",
84
+ ];
85
+ export const OPENIDM_READ_METHODS = ["read", "query"];
86
+ export const OPENIDM_METHODS = [
87
+ ...OPENIDM_WRITE_METHODS,
88
+ ...OPENIDM_READ_METHODS,
89
+ ];
90
+ export const LOG_LEVELS = ["error", "warn", "info", "debug", "trace"];
91
+ export const CHANNELS = [
92
+ "outcome",
93
+ "sharedState",
94
+ "transientState",
95
+ "secureState",
96
+ "callbacks",
97
+ "openidm",
98
+ "http",
99
+ "logs",
100
+ ];
101
+ export const STATE_CHANNELS = [
102
+ "sharedState",
103
+ "transientState",
104
+ "secureState",
105
+ ];
106
+ export const ENV_INPUT_KEYS = ["esv", "secrets", "managed", "http"];
@@ -0,0 +1,7 @@
1
+ import type { JsonObject, JsonValue } from "./types.ts";
2
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
3
+ export declare function formatValue(value: unknown): string;
4
+ export declare function suggestKey(unknown: string, allowed: readonly string[]): string | undefined;
5
+ export declare function unknownKeyError(path: string, key: string, allowed: readonly string[]): Error;
6
+ export declare function parseJsonValue(raw: unknown, path: string): JsonValue;
7
+ export declare function parseJsonObject(raw: unknown, path: string): JsonObject;
@@ -0,0 +1,115 @@
1
+ export function isPlainObject(value) {
2
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3
+ return false;
4
+ }
5
+ const proto = Object.getPrototypeOf(value);
6
+ return proto === Object.prototype || proto === null;
7
+ }
8
+ export function formatValue(value) {
9
+ if (value === undefined) {
10
+ return "<absent>";
11
+ }
12
+ if (value instanceof RegExp) {
13
+ return String(value);
14
+ }
15
+ if (typeof value === "string") {
16
+ return JSON.stringify(value);
17
+ }
18
+ if (typeof value === "function") {
19
+ return "<function>";
20
+ }
21
+ try {
22
+ return JSON.stringify(value);
23
+ }
24
+ catch {
25
+ return String(value);
26
+ }
27
+ }
28
+ export function suggestKey(unknown, allowed) {
29
+ const lower = unknown.toLowerCase();
30
+ const contained = allowed.filter((key) => key.toLowerCase().includes(lower) || lower.includes(key.toLowerCase()));
31
+ if (contained.length === 1) {
32
+ return contained[0];
33
+ }
34
+ let best;
35
+ let bestDistance = Infinity;
36
+ for (const key of allowed) {
37
+ const distance = editDistance(lower, key.toLowerCase());
38
+ if (distance < bestDistance) {
39
+ bestDistance = distance;
40
+ best = key;
41
+ }
42
+ }
43
+ if (best !== undefined && bestDistance <= 2) {
44
+ return best;
45
+ }
46
+ return contained[0];
47
+ }
48
+ export function unknownKeyError(path, key, allowed) {
49
+ const suggestion = suggestKey(key, allowed);
50
+ const hint = suggestion === undefined ? "" : ` (did you mean "${suggestion}"?)`;
51
+ return new Error(`rhino-local: ${path} has unknown key "${key}"${hint}. Allowed: ${allowed.join(", ")}`);
52
+ }
53
+ function editDistance(a, b) {
54
+ const rows = a.length;
55
+ const cols = b.length;
56
+ const width = cols + 1;
57
+ const dp = new Int32Array((rows + 1) * width);
58
+ for (let i = 0; i <= rows; i += 1) {
59
+ dp[i * width] = i;
60
+ }
61
+ for (let j = 0; j <= cols; j += 1) {
62
+ dp[j] = j;
63
+ }
64
+ for (let i = 1; i <= rows; i += 1) {
65
+ for (let j = 1; j <= cols; j += 1) {
66
+ const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
67
+ const deletion = (dp[(i - 1) * width + j] ?? 0) + 1;
68
+ const insertion = (dp[i * width + (j - 1)] ?? 0) + 1;
69
+ const substitution = (dp[(i - 1) * width + (j - 1)] ?? 0) + cost;
70
+ dp[i * width + j] = Math.min(deletion, insertion, substitution);
71
+ }
72
+ }
73
+ return dp[rows * width + cols] ?? Math.max(rows, cols);
74
+ }
75
+ export function parseJsonValue(raw, path) {
76
+ if (raw === null || typeof raw === "string" || typeof raw === "boolean") {
77
+ return raw;
78
+ }
79
+ if (typeof raw === "number") {
80
+ if (!Number.isFinite(raw)) {
81
+ throw new Error(`rhino-local: ${path} must be a finite number`);
82
+ }
83
+ return raw;
84
+ }
85
+ if (Array.isArray(raw)) {
86
+ return raw.map((item, index) => parseJsonValue(item, `${path}[${index}]`));
87
+ }
88
+ if (isPlainObject(raw)) {
89
+ const object = {};
90
+ for (const [key, value] of Object.entries(raw)) {
91
+ object[key] = parseJsonValue(value, `${path}.${key}`);
92
+ }
93
+ return object;
94
+ }
95
+ throw new Error(`rhino-local: ${path} is not a JSON value (${describeType(raw)})`);
96
+ }
97
+ export function parseJsonObject(raw, path) {
98
+ const value = parseJsonValue(raw, path);
99
+ if (!isPlainObject(value)) {
100
+ throw new Error(`rhino-local: ${path} must be an object`);
101
+ }
102
+ return value;
103
+ }
104
+ function describeType(value) {
105
+ if (value === null) {
106
+ return "null";
107
+ }
108
+ if (Array.isArray(value)) {
109
+ return "array";
110
+ }
111
+ if (value instanceof RegExp) {
112
+ return "RegExp";
113
+ }
114
+ return typeof value;
115
+ }
@@ -0,0 +1,8 @@
1
+ import type { Case, CaseInit } from "./types.ts";
2
+ /** Typed entry point for case authors. */
3
+ export declare function defineCase(input: CaseInit): Case;
4
+ /**
5
+ * Runtime validation. Dynamic cases (and typos TypeScript never sees) must
6
+ * fail here rather than assert nothing.
7
+ */
8
+ export declare function validateCase(input: unknown): Case;