@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,463 @@
1
+ import { loadContext } from "../load.js";
2
+ import { bindingsJsonPath } from "../paths.js";
3
+ import { ALLOW_UNDECLARED_CHANNELS, CASE_KEYS, ENGINES, EXPECT_KEYS, GIVEN_BINDING_SEEDS, GIVEN_KEYS, LOG_LEVELS, OPENIDM_METHODS, STATE_DIFF_KEYS, } from "./types.js";
4
+ import { isPlainObject, parseJsonObject, parseJsonValue, unknownKeyError } from "./util.js";
5
+ const GIVEN_BINDING_SEED_SET = new Set(GIVEN_BINDING_SEEDS);
6
+ const OPENIDM_METHOD_SET = new Set(OPENIDM_METHODS);
7
+ const LOG_LEVEL_SET = new Set(LOG_LEVELS);
8
+ const ENGINE_SET = new Set(ENGINES);
9
+ const ALLOW_UNDECLARED_SET = new Set(ALLOW_UNDECLARED_CHANNELS);
10
+ let cachedBindingNames;
11
+ function knownBindingNames() {
12
+ if (cachedBindingNames === undefined) {
13
+ cachedBindingNames = new Set(loadContext(bindingsJsonPath).bindings.map((binding) => binding.name));
14
+ }
15
+ return cachedBindingNames;
16
+ }
17
+ /** Typed entry point for case authors. */
18
+ export function defineCase(input) {
19
+ return validateCase(input);
20
+ }
21
+ /**
22
+ * Runtime validation. Dynamic cases (and typos TypeScript never sees) must
23
+ * fail here rather than assert nothing.
24
+ */
25
+ export function validateCase(input) {
26
+ if (!isPlainObject(input)) {
27
+ throw new Error("rhino-local: case is not an object");
28
+ }
29
+ rejectUnknownKeys("case", input, CASE_KEYS);
30
+ const name = parseNonEmptyString(input.name, "case.name");
31
+ const script = parseNonEmptyString(input.script, "case.script");
32
+ const given = input.given === undefined ? {} : parseGiven(input.given, "case.given");
33
+ if (!("expect" in input)) {
34
+ throw new Error("rhino-local: case.expect is required");
35
+ }
36
+ const expect = parseExpect(input.expect, "case.expect");
37
+ const kase = { name, script, given, expect };
38
+ if (input.outcomes !== undefined) {
39
+ kase.outcomes = parseOutcomes(input.outcomes, "case.outcomes", expect.outcome);
40
+ }
41
+ return kase;
42
+ }
43
+ /**
44
+ * The declared outcome vocabulary. Empty is rejected rather than treated as
45
+ * "no opinion" — `outcomes: []` reads like a deliberate statement and would
46
+ * otherwise silently disable the check it was written to request.
47
+ */
48
+ function parseOutcomes(raw, path, expected) {
49
+ if (!Array.isArray(raw)) {
50
+ throw new Error(`rhino-local: ${path} must be an array of strings`);
51
+ }
52
+ if (raw.length === 0) {
53
+ throw new Error(`rhino-local: ${path} must not be empty; omit the key entirely to decline to declare`);
54
+ }
55
+ const seen = new Set();
56
+ for (const [index, value] of raw.entries()) {
57
+ if (typeof value !== "string" || value.trim() === "") {
58
+ throw new Error(`rhino-local: ${path}[${index}] must be a non-empty string`);
59
+ }
60
+ if (seen.has(value)) {
61
+ throw new Error(`rhino-local: ${path} lists ${JSON.stringify(value)} twice`);
62
+ }
63
+ seen.add(value);
64
+ }
65
+ // A suspended pass reaches no outcome, so it cannot be checked against the
66
+ // vocabulary — and the vocabulary still matters, because the wrapper journey
67
+ // has to declare every outcome the LATER passes may produce.
68
+ if (expected !== null && !seen.has(expected)) {
69
+ throw new Error(`rhino-local: case.expect.outcome ${JSON.stringify(expected)} is not in ${path} [${raw.join(", ")}] — the case expects an outcome the script is not declared to produce`);
70
+ }
71
+ return raw.slice();
72
+ }
73
+ function parseGiven(raw, path) {
74
+ if (!isPlainObject(raw)) {
75
+ throw new Error(`rhino-local: ${path} is not an object`);
76
+ }
77
+ rejectUnknownKeys(path, raw, GIVEN_KEYS);
78
+ const given = {};
79
+ assignJsonObject(given, "sharedState", raw.sharedState, `${path}.sharedState`);
80
+ assignJsonObject(given, "transientState", raw.transientState, `${path}.transientState`);
81
+ assignJsonObject(given, "secureState", raw.secureState, `${path}.secureState`);
82
+ assignString(given, "realm", raw.realm, `${path}.realm`);
83
+ assignString(given, "scriptName", raw.scriptName, `${path}.scriptName`);
84
+ assignString(given, "cookieName", raw.cookieName, `${path}.cookieName`);
85
+ if (raw.resumedFromSuspend !== undefined) {
86
+ if (typeof raw.resumedFromSuspend !== "boolean") {
87
+ throw new Error(`rhino-local: ${path}.resumedFromSuspend must be a boolean`);
88
+ }
89
+ given.resumedFromSuspend = raw.resumedFromSuspend;
90
+ }
91
+ assignStringArrayMap(given, "requestHeaders", raw.requestHeaders, `${path}.requestHeaders`);
92
+ assignStringArrayMap(given, "requestParameters", raw.requestParameters, `${path}.requestParameters`);
93
+ assignStringMap(given, "requestCookies", raw.requestCookies, `${path}.requestCookies`);
94
+ assignJsonObject(given, "locales", raw.locales, `${path}.locales`);
95
+ // String->String, because that is what AM stores: every value in the measured
96
+ // 23-key session was a string, `AuthLevel: "0"` included.
97
+ assignStringMap(given, "existingSession", raw.existingSession, `${path}.existingSession`);
98
+ assignStringMap(given, "esv", raw.esv, `${path}.esv`);
99
+ assignStringMap(given, "secrets", raw.secrets, `${path}.secrets`);
100
+ if (raw.callbacks !== undefined) {
101
+ given.callbacks = parseArray(raw.callbacks, `${path}.callbacks`, parseCallback);
102
+ }
103
+ if (raw.managed !== undefined) {
104
+ given.managed = parseManaged(raw.managed, `${path}.managed`);
105
+ }
106
+ if (raw.http !== undefined) {
107
+ given.http = parseArray(raw.http, `${path}.http`, parseHttpStub);
108
+ }
109
+ if (raw.engine !== undefined) {
110
+ given.engine = parseEngine(raw.engine, `${path}.engine`);
111
+ }
112
+ if (raw.bindings !== undefined) {
113
+ given.bindings = parseBindings(raw.bindings, `${path}.bindings`);
114
+ }
115
+ return given;
116
+ }
117
+ function parseExpect(raw, path) {
118
+ if (!isPlainObject(raw)) {
119
+ throw new Error(`rhino-local: ${path} is not an object`);
120
+ }
121
+ rejectUnknownKeys(path, raw, EXPECT_KEYS);
122
+ if (!("outcome" in raw)) {
123
+ throw new Error(`rhino-local: ${path}.outcome is required (engine-neutral; use "true" whether the script returns via action.goTo or a legacy outcome variable)`);
124
+ }
125
+ if (raw.outcome !== null && typeof raw.outcome !== "string") {
126
+ throw new Error(`rhino-local: ${path}.outcome must be a string, or null for a pass that suspends with callbacks`);
127
+ }
128
+ const expect = { outcome: raw.outcome };
129
+ if (raw.sharedState !== undefined) {
130
+ expect.sharedState = parseStateDiff(raw.sharedState, `${path}.sharedState`);
131
+ }
132
+ if (raw.transientState !== undefined) {
133
+ expect.transientState = parseStateDiff(raw.transientState, `${path}.transientState`);
134
+ }
135
+ if (raw.secureState !== undefined) {
136
+ expect.secureState = parseStateDiff(raw.secureState, `${path}.secureState`);
137
+ }
138
+ if (raw.callbacks !== undefined) {
139
+ expect.callbacks = parseArray(raw.callbacks, `${path}.callbacks`, parseCallback);
140
+ }
141
+ if (raw.openidm !== undefined) {
142
+ expect.openidm = parseArray(raw.openidm, `${path}.openidm`, parseOpenidmExpect);
143
+ }
144
+ if (raw.http !== undefined) {
145
+ expect.http = parseArray(raw.http, `${path}.http`, parseHttpExpect);
146
+ }
147
+ if (raw.logs !== undefined) {
148
+ expect.logs = parseArray(raw.logs, `${path}.logs`, parseLogExpect);
149
+ }
150
+ if (raw.allowUndeclared !== undefined) {
151
+ expect.allowUndeclared = parseAllowUndeclared(raw.allowUndeclared, `${path}.allowUndeclared`);
152
+ }
153
+ return expect;
154
+ }
155
+ function parseStateDiff(raw, path) {
156
+ if (!isPlainObject(raw)) {
157
+ throw new Error(`rhino-local: ${path} is not an object`);
158
+ }
159
+ rejectUnknownKeys(path, raw, STATE_DIFF_KEYS);
160
+ const diff = {};
161
+ assignJsonObject(diff, "added", raw.added, `${path}.added`);
162
+ assignJsonObject(diff, "changed", raw.changed, `${path}.changed`);
163
+ if (raw.removed !== undefined) {
164
+ if (!Array.isArray(raw.removed) || raw.removed.some((key) => typeof key !== "string")) {
165
+ throw new Error(`rhino-local: ${path}.removed must be an array of strings`);
166
+ }
167
+ diff.removed = raw.removed.slice();
168
+ }
169
+ return diff;
170
+ }
171
+ function parseHttpStub(raw, path) {
172
+ if (!isPlainObject(raw)) {
173
+ throw new Error(`rhino-local: ${path} is not an object`);
174
+ }
175
+ rejectUnknownKeys(path, raw, ["match", "reply"]);
176
+ if (raw.match === undefined) {
177
+ throw new Error(`rhino-local: ${path}.match is required`);
178
+ }
179
+ if (raw.reply === undefined) {
180
+ throw new Error(`rhino-local: ${path}.reply is required`);
181
+ }
182
+ return {
183
+ match: parseHttpMatch(raw.match, `${path}.match`),
184
+ reply: parseHttpReply(raw.reply, `${path}.reply`),
185
+ };
186
+ }
187
+ function parseHttpMatch(raw, path) {
188
+ if (!isPlainObject(raw)) {
189
+ throw new Error(`rhino-local: ${path} is not an object`);
190
+ }
191
+ rejectUnknownKeys(path, raw, ["url", "method"]);
192
+ if (raw.url === undefined) {
193
+ throw new Error(`rhino-local: ${path}.url is required`);
194
+ }
195
+ const match = { url: parsePattern(raw.url, `${path}.url`) };
196
+ assignOptionalString(match, "method", raw.method, `${path}.method`);
197
+ return match;
198
+ }
199
+ function parseHttpReply(raw, path) {
200
+ if (!isPlainObject(raw)) {
201
+ throw new Error(`rhino-local: ${path} is not an object`);
202
+ }
203
+ rejectUnknownKeys(path, raw, ["status", "body", "headers"]);
204
+ if (typeof raw.status !== "number" || !Number.isInteger(raw.status)) {
205
+ throw new Error(`rhino-local: ${path}.status must be an integer`);
206
+ }
207
+ const reply = { status: raw.status };
208
+ if (raw.body !== undefined) {
209
+ reply.body = parseJsonValue(raw.body, `${path}.body`);
210
+ }
211
+ assignStringMap(reply, "headers", raw.headers, `${path}.headers`);
212
+ return reply;
213
+ }
214
+ function parseHttpExpect(raw, path) {
215
+ if (!isPlainObject(raw)) {
216
+ throw new Error(`rhino-local: ${path} is not an object`);
217
+ }
218
+ rejectUnknownKeys(path, raw, ["url", "method", "times"]);
219
+ if (raw.url === undefined) {
220
+ throw new Error(`rhino-local: ${path}.url is required`);
221
+ }
222
+ const expect = { url: parsePattern(raw.url, `${path}.url`) };
223
+ assignOptionalString(expect, "method", raw.method, `${path}.method`);
224
+ assignTimes(expect, raw.times, `${path}.times`);
225
+ return expect;
226
+ }
227
+ function parseOpenidmExpect(raw, path) {
228
+ if (!isPlainObject(raw)) {
229
+ throw new Error(`rhino-local: ${path} is not an object`);
230
+ }
231
+ rejectUnknownKeys(path, raw, ["method", "resource", "body", "actionName", "times"]);
232
+ if (raw.method === undefined) {
233
+ throw new Error(`rhino-local: ${path}.method is required`);
234
+ }
235
+ if (raw.resource === undefined) {
236
+ throw new Error(`rhino-local: ${path}.resource is required`);
237
+ }
238
+ const method = parseOpenidmMethod(raw.method, `${path}.method`);
239
+ if (raw.actionName !== undefined && method !== "action") {
240
+ throw new Error(`rhino-local: ${path}.actionName is only valid when method is "action"`);
241
+ }
242
+ const expect = {
243
+ method,
244
+ resource: parsePattern(raw.resource, `${path}.resource`),
245
+ };
246
+ if (raw.body !== undefined) {
247
+ expect.body = parseJsonValue(raw.body, `${path}.body`);
248
+ }
249
+ if (raw.actionName !== undefined) {
250
+ expect.actionName = parsePattern(raw.actionName, `${path}.actionName`);
251
+ }
252
+ assignTimes(expect, raw.times, `${path}.times`);
253
+ return expect;
254
+ }
255
+ function parseLogExpect(raw, path) {
256
+ if (!isPlainObject(raw)) {
257
+ throw new Error(`rhino-local: ${path} is not an object`);
258
+ }
259
+ rejectUnknownKeys(path, raw, ["level", "message", "times"]);
260
+ if (raw.message === undefined) {
261
+ throw new Error(`rhino-local: ${path}.message is required`);
262
+ }
263
+ const expect = {
264
+ message: parsePattern(raw.message, `${path}.message`),
265
+ };
266
+ if (raw.level !== undefined) {
267
+ expect.level = parseLogLevel(raw.level, `${path}.level`);
268
+ }
269
+ assignTimes(expect, raw.times, `${path}.times`);
270
+ return expect;
271
+ }
272
+ function parseCallback(raw, path) {
273
+ if (!isPlainObject(raw)) {
274
+ throw new Error(`rhino-local: ${path} is not an object`);
275
+ }
276
+ if (typeof raw.type !== "string" || raw.type.trim() === "") {
277
+ throw new Error(`rhino-local: ${path}.type must be a non-empty string`);
278
+ }
279
+ const callback = { type: raw.type };
280
+ for (const [key, value] of Object.entries(raw)) {
281
+ if (key === "type") {
282
+ continue;
283
+ }
284
+ callback[key] = parseJsonValue(value, `${path}.${key}`);
285
+ }
286
+ return callback;
287
+ }
288
+ function parseAllowUndeclared(raw, path) {
289
+ if (!isPlainObject(raw)) {
290
+ throw new Error(`rhino-local: ${path} is not an object`);
291
+ }
292
+ const flags = {};
293
+ for (const key of Object.keys(raw)) {
294
+ if (!ALLOW_UNDECLARED_SET.has(key)) {
295
+ throw unknownKeyError(path, key, ALLOW_UNDECLARED_CHANNELS);
296
+ }
297
+ const value = raw[key];
298
+ if (typeof value !== "boolean") {
299
+ throw new Error(`rhino-local: ${path}.${key} must be a boolean`);
300
+ }
301
+ flags[key] = value;
302
+ }
303
+ return flags;
304
+ }
305
+ function parseManaged(raw, path) {
306
+ if (!isPlainObject(raw)) {
307
+ throw new Error(`rhino-local: ${path} is not an object`);
308
+ }
309
+ const managed = {};
310
+ for (const [type, records] of Object.entries(raw)) {
311
+ if (!Array.isArray(records)) {
312
+ throw new Error(`rhino-local: ${path}.${type} must be an array of managed records`);
313
+ }
314
+ managed[type] = records.map((record, index) => {
315
+ const value = parseJsonValue(record, `${path}.${type}[${index}]`);
316
+ if (!isPlainObject(value)) {
317
+ throw new Error(`rhino-local: ${path}.${type}[${index}] must be an object`);
318
+ }
319
+ return value;
320
+ });
321
+ }
322
+ return managed;
323
+ }
324
+ function parseBindings(raw, path) {
325
+ if (!isPlainObject(raw)) {
326
+ throw new Error(`rhino-local: ${path} is not an object`);
327
+ }
328
+ const known = knownBindingNames();
329
+ const bindings = {};
330
+ for (const key of Object.keys(raw)) {
331
+ if (key === "nodeState") {
332
+ throw new Error(`rhino-local: ${path}.nodeState is not a seed; use given.sharedState / transientState / secureState`);
333
+ }
334
+ if (GIVEN_BINDING_SEED_SET.has(key)) {
335
+ throw new Error(`rhino-local: ${path}.${key} collides with given.${key}; seed it there, not under bindings`);
336
+ }
337
+ if (!known.has(key)) {
338
+ throw unknownKeyError(path, key, [...known].filter((name) => !GIVEN_BINDING_SEED_SET.has(name) && name !== "nodeState"));
339
+ }
340
+ const value = raw[key];
341
+ bindings[key] = parseJsonValue(value, `${path}.${key}`);
342
+ }
343
+ return bindings;
344
+ }
345
+ function parseEngine(raw, path) {
346
+ if (typeof raw !== "string" || !ENGINE_SET.has(raw)) {
347
+ throw new Error(`rhino-local: ${path} must be ${ENGINES.map((engine) => JSON.stringify(engine)).join(" or ")}`);
348
+ }
349
+ return raw;
350
+ }
351
+ function parseOpenidmMethod(raw, path) {
352
+ if (typeof raw !== "string" || !OPENIDM_METHOD_SET.has(raw)) {
353
+ throw new Error(`rhino-local: ${path} must be one of ${OPENIDM_METHODS.join(", ")}`);
354
+ }
355
+ return raw;
356
+ }
357
+ function parseLogLevel(raw, path) {
358
+ if (typeof raw !== "string" || !LOG_LEVEL_SET.has(raw)) {
359
+ throw new Error(`rhino-local: ${path} must be one of ${LOG_LEVELS.join(", ")}`);
360
+ }
361
+ return raw;
362
+ }
363
+ function parsePattern(raw, path) {
364
+ if (typeof raw === "string") {
365
+ return raw;
366
+ }
367
+ if (raw instanceof RegExp) {
368
+ return raw;
369
+ }
370
+ throw new Error(`rhino-local: ${path} must be a string or RegExp`);
371
+ }
372
+ function parseNonEmptyString(raw, path) {
373
+ if (typeof raw !== "string" || raw.trim() === "") {
374
+ throw new Error(`rhino-local: ${path} must be a non-empty string`);
375
+ }
376
+ return raw;
377
+ }
378
+ function parseArray(raw, path, item) {
379
+ if (!Array.isArray(raw)) {
380
+ throw new Error(`rhino-local: ${path} is not an array`);
381
+ }
382
+ return raw.map((value, index) => item(value, `${path}[${index}]`));
383
+ }
384
+ function parseStringMap(raw, path) {
385
+ if (!isPlainObject(raw)) {
386
+ throw new Error(`rhino-local: ${path} is not an object`);
387
+ }
388
+ const map = {};
389
+ for (const [key, value] of Object.entries(raw)) {
390
+ if (typeof value !== "string") {
391
+ throw new Error(`rhino-local: ${path}.${key} must be a string`);
392
+ }
393
+ map[key] = value;
394
+ }
395
+ return map;
396
+ }
397
+ function parseStringArrayMap(raw, path) {
398
+ if (!isPlainObject(raw)) {
399
+ throw new Error(`rhino-local: ${path} is not an object`);
400
+ }
401
+ const map = {};
402
+ for (const [key, value] of Object.entries(raw)) {
403
+ if (!Array.isArray(value) ||
404
+ value.some((entry) => typeof entry !== "string")) {
405
+ throw new Error(`rhino-local: ${path}.${key} must be an array of strings`);
406
+ }
407
+ map[key] = value.slice();
408
+ }
409
+ return map;
410
+ }
411
+ function assignJsonObject(target, key, raw, path) {
412
+ if (raw === undefined) {
413
+ return;
414
+ }
415
+ target[key] = parseJsonObject(raw, path);
416
+ }
417
+ function assignString(target, key, raw, path) {
418
+ if (raw === undefined) {
419
+ return;
420
+ }
421
+ if (typeof raw !== "string") {
422
+ throw new Error(`rhino-local: ${path} must be a string`);
423
+ }
424
+ target[key] = raw;
425
+ }
426
+ function assignOptionalString(target, key, raw, path) {
427
+ if (raw === undefined) {
428
+ return;
429
+ }
430
+ if (typeof raw !== "string" || raw.trim() === "") {
431
+ throw new Error(`rhino-local: ${path} must be a non-empty string`);
432
+ }
433
+ target[key] = raw;
434
+ }
435
+ function assignStringMap(target, key, raw, path) {
436
+ if (raw === undefined) {
437
+ return;
438
+ }
439
+ target[key] = parseStringMap(raw, path);
440
+ }
441
+ function assignStringArrayMap(target, key, raw, path) {
442
+ if (raw === undefined) {
443
+ return;
444
+ }
445
+ target[key] = parseStringArrayMap(raw, path);
446
+ }
447
+ function assignTimes(target, raw, path) {
448
+ if (raw === undefined) {
449
+ return;
450
+ }
451
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
452
+ throw new Error(`rhino-local: ${path} must be a non-negative integer`);
453
+ }
454
+ target.times = raw;
455
+ }
456
+ function rejectUnknownKeys(path, raw, allowed) {
457
+ const allowedSet = new Set(allowed);
458
+ for (const key of Object.keys(raw)) {
459
+ if (!allowedSet.has(key)) {
460
+ throw unknownKeyError(path, key, allowed);
461
+ }
462
+ }
463
+ }
@@ -0,0 +1,6 @@
1
+ import type { Verdict } from "./types.ts";
2
+ /**
3
+ * Pure function: (case, recorded effects) → pass/fail with a readable
4
+ * explanation of each difference. Does not run a script.
5
+ */
6
+ export declare function judge(input: unknown, effects: unknown): Verdict;