@choiceapps/communityservices-sdk 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 (294) hide show
  1. package/AGENTS.md +43 -0
  2. package/API.md +86 -0
  3. package/CHANGELOG.md +12 -0
  4. package/LICENSE +201 -0
  5. package/NOTICE +11 -0
  6. package/README.md +100 -0
  7. package/SSVF.md +131 -0
  8. package/browser/attachment-conflict.mjs +6 -0
  9. package/browser/case-plans-ui.mjs +23 -0
  10. package/browser/case-plans.d.mts +20 -0
  11. package/browser/case-plans.mjs +89 -0
  12. package/browser/client-creation.d.mts +16 -0
  13. package/browser/client-creation.mjs +60 -0
  14. package/browser/client-profile-ui.mjs +59 -0
  15. package/browser/client-profile.d.mts +24 -0
  16. package/browser/client-profile.mjs +94 -0
  17. package/browser/client-workflows.mjs +135 -0
  18. package/browser/connection.d.mts +4 -0
  19. package/browser/connection.mjs +41 -0
  20. package/browser/current-living-situation.d.mts +44 -0
  21. package/browser/current-living-situation.mjs +161 -0
  22. package/browser/date-of-birth-ui.mjs +68 -0
  23. package/browser/date-of-birth.d.mts +28 -0
  24. package/browser/date-of-birth.mjs +112 -0
  25. package/browser/enrollment-workflow.d.mts +174 -0
  26. package/browser/enrollment-workflow.mjs +518 -0
  27. package/browser/exit-details.mjs +21 -0
  28. package/browser/file-store.mjs +32 -0
  29. package/browser/gwt-mapped.mjs +46 -0
  30. package/browser/household-exit.d.mts +34 -0
  31. package/browser/household-exit.mjs +102 -0
  32. package/browser/households-ui.mjs +20 -0
  33. package/browser/households.d.mts +22 -0
  34. package/browser/households.mjs +115 -0
  35. package/browser/index.d.mts +78 -0
  36. package/browser/index.mjs +46 -0
  37. package/browser/member-entry-date.d.mts +30 -0
  38. package/browser/member-entry-date.mjs +71 -0
  39. package/browser/need-recording.d.mts +37 -0
  40. package/browser/need-recording.mjs +68 -0
  41. package/browser/need-ui.mjs +138 -0
  42. package/browser/page-lock.mjs +11 -0
  43. package/browser/password-recovery.mjs +49 -0
  44. package/browser/playwright-session.mjs +196 -0
  45. package/browser/program-discovery.d.mts +22 -0
  46. package/browser/program-discovery.mjs +80 -0
  47. package/browser/recovery-diagnostics.mjs +9 -0
  48. package/browser/service-recording.d.mts +45 -0
  49. package/browser/service-recording.mjs +148 -0
  50. package/browser/service-ui.mjs +160 -0
  51. package/browser/session-authentication.mjs +40 -0
  52. package/browser/shelter-inventory.d.mts +103 -0
  53. package/browser/shelter-inventory.mjs +273 -0
  54. package/browser/shelter-ui.mjs +382 -0
  55. package/browser/shelters.d.mts +139 -0
  56. package/browser/shelters.mjs +425 -0
  57. package/browser/ssvf-rules.d.mts +21 -0
  58. package/browser/ssvf-rules.mjs +145 -0
  59. package/browser/ui-login.mjs +135 -0
  60. package/dist/cjs/bulk.d.ts +33 -0
  61. package/dist/cjs/bulk.d.ts.map +1 -0
  62. package/dist/cjs/bulk.js +42 -0
  63. package/dist/cjs/bulk.js.map +1 -0
  64. package/dist/cjs/control.d.ts +35 -0
  65. package/dist/cjs/control.d.ts.map +1 -0
  66. package/dist/cjs/control.js +141 -0
  67. package/dist/cjs/control.js.map +1 -0
  68. package/dist/cjs/effective-date.d.ts +3 -0
  69. package/dist/cjs/effective-date.d.ts.map +1 -0
  70. package/dist/cjs/effective-date.js +27 -0
  71. package/dist/cjs/effective-date.js.map +1 -0
  72. package/dist/cjs/encoding.d.ts +32 -0
  73. package/dist/cjs/encoding.d.ts.map +1 -0
  74. package/dist/cjs/encoding.js +121 -0
  75. package/dist/cjs/encoding.js.map +1 -0
  76. package/dist/cjs/enrollment-verifications.d.ts +39 -0
  77. package/dist/cjs/enrollment-verifications.d.ts.map +1 -0
  78. package/dist/cjs/enrollment-verifications.js +96 -0
  79. package/dist/cjs/enrollment-verifications.js.map +1 -0
  80. package/dist/cjs/errors.d.ts +33 -0
  81. package/dist/cjs/errors.d.ts.map +1 -0
  82. package/dist/cjs/errors.js +45 -0
  83. package/dist/cjs/errors.js.map +1 -0
  84. package/dist/cjs/generated/v2-models.d.ts +1482 -0
  85. package/dist/cjs/generated/v2-models.d.ts.map +1 -0
  86. package/dist/cjs/generated/v2-models.js +3 -0
  87. package/dist/cjs/generated/v2-models.js.map +1 -0
  88. package/dist/cjs/generated/v2.d.ts +1149 -0
  89. package/dist/cjs/generated/v2.d.ts.map +1 -0
  90. package/dist/cjs/generated/v2.js +252 -0
  91. package/dist/cjs/generated/v2.js.map +1 -0
  92. package/dist/cjs/generated/v3-models.d.ts +1420 -0
  93. package/dist/cjs/generated/v3-models.d.ts.map +1 -0
  94. package/dist/cjs/generated/v3-models.js +3 -0
  95. package/dist/cjs/generated/v3-models.js.map +1 -0
  96. package/dist/cjs/generated/v3.d.ts +1149 -0
  97. package/dist/cjs/generated/v3.d.ts.map +1 -0
  98. package/dist/cjs/generated/v3.js +252 -0
  99. package/dist/cjs/generated/v3.js.map +1 -0
  100. package/dist/cjs/gwt-enrollments.d.ts +131 -0
  101. package/dist/cjs/gwt-enrollments.d.ts.map +1 -0
  102. package/dist/cjs/gwt-enrollments.js +280 -0
  103. package/dist/cjs/gwt-enrollments.js.map +1 -0
  104. package/dist/cjs/index.d.ts +3 -0
  105. package/dist/cjs/index.d.ts.map +1 -0
  106. package/dist/cjs/index.js +19 -0
  107. package/dist/cjs/index.js.map +1 -0
  108. package/dist/cjs/metadata.d.ts +100 -0
  109. package/dist/cjs/metadata.d.ts.map +1 -0
  110. package/dist/cjs/metadata.js +165 -0
  111. package/dist/cjs/metadata.js.map +1 -0
  112. package/dist/cjs/package.json +1 -0
  113. package/dist/cjs/program-discovery.d.ts +28 -0
  114. package/dist/cjs/program-discovery.d.ts.map +1 -0
  115. package/dist/cjs/program-discovery.js +58 -0
  116. package/dist/cjs/program-discovery.js.map +1 -0
  117. package/dist/cjs/race.d.ts +4 -0
  118. package/dist/cjs/race.d.ts.map +1 -0
  119. package/dist/cjs/race.js +40 -0
  120. package/dist/cjs/race.js.map +1 -0
  121. package/dist/cjs/runtime.d.ts +108 -0
  122. package/dist/cjs/runtime.d.ts.map +1 -0
  123. package/dist/cjs/runtime.js +38 -0
  124. package/dist/cjs/runtime.js.map +1 -0
  125. package/dist/cjs/subassessments.d.ts +29 -0
  126. package/dist/cjs/subassessments.d.ts.map +1 -0
  127. package/dist/cjs/subassessments.js +107 -0
  128. package/dist/cjs/subassessments.js.map +1 -0
  129. package/dist/cjs/testing.d.ts +45 -0
  130. package/dist/cjs/testing.d.ts.map +1 -0
  131. package/dist/cjs/testing.js +66 -0
  132. package/dist/cjs/testing.js.map +1 -0
  133. package/dist/cjs/transport.d.ts +19 -0
  134. package/dist/cjs/transport.d.ts.map +1 -0
  135. package/dist/cjs/transport.js +373 -0
  136. package/dist/cjs/transport.js.map +1 -0
  137. package/dist/cjs/v2.d.ts +13 -0
  138. package/dist/cjs/v2.d.ts.map +1 -0
  139. package/dist/cjs/v2.js +29 -0
  140. package/dist/cjs/v2.js.map +1 -0
  141. package/dist/cjs/v3.d.ts +13 -0
  142. package/dist/cjs/v3.d.ts.map +1 -0
  143. package/dist/cjs/v3.js +29 -0
  144. package/dist/cjs/v3.js.map +1 -0
  145. package/dist/cjs/validation.d.ts +33 -0
  146. package/dist/cjs/validation.d.ts.map +1 -0
  147. package/dist/cjs/validation.js +77 -0
  148. package/dist/cjs/validation.js.map +1 -0
  149. package/dist/cjs/verification.d.ts +82 -0
  150. package/dist/cjs/verification.d.ts.map +1 -0
  151. package/dist/cjs/verification.js +122 -0
  152. package/dist/cjs/verification.js.map +1 -0
  153. package/dist/esm/bulk.d.ts +33 -0
  154. package/dist/esm/bulk.d.ts.map +1 -0
  155. package/dist/esm/bulk.js +39 -0
  156. package/dist/esm/bulk.js.map +1 -0
  157. package/dist/esm/control.d.ts +35 -0
  158. package/dist/esm/control.d.ts.map +1 -0
  159. package/dist/esm/control.js +132 -0
  160. package/dist/esm/control.js.map +1 -0
  161. package/dist/esm/effective-date.d.ts +3 -0
  162. package/dist/esm/effective-date.d.ts.map +1 -0
  163. package/dist/esm/effective-date.js +24 -0
  164. package/dist/esm/effective-date.js.map +1 -0
  165. package/dist/esm/encoding.d.ts +32 -0
  166. package/dist/esm/encoding.d.ts.map +1 -0
  167. package/dist/esm/encoding.js +117 -0
  168. package/dist/esm/encoding.js.map +1 -0
  169. package/dist/esm/enrollment-verifications.d.ts +39 -0
  170. package/dist/esm/enrollment-verifications.d.ts.map +1 -0
  171. package/dist/esm/enrollment-verifications.js +93 -0
  172. package/dist/esm/enrollment-verifications.js.map +1 -0
  173. package/dist/esm/errors.d.ts +33 -0
  174. package/dist/esm/errors.d.ts.map +1 -0
  175. package/dist/esm/errors.js +33 -0
  176. package/dist/esm/errors.js.map +1 -0
  177. package/dist/esm/generated/v2-models.d.ts +1482 -0
  178. package/dist/esm/generated/v2-models.d.ts.map +1 -0
  179. package/dist/esm/generated/v2-models.js +2 -0
  180. package/dist/esm/generated/v2-models.js.map +1 -0
  181. package/dist/esm/generated/v2.d.ts +1149 -0
  182. package/dist/esm/generated/v2.d.ts.map +1 -0
  183. package/dist/esm/generated/v2.js +247 -0
  184. package/dist/esm/generated/v2.js.map +1 -0
  185. package/dist/esm/generated/v3-models.d.ts +1420 -0
  186. package/dist/esm/generated/v3-models.d.ts.map +1 -0
  187. package/dist/esm/generated/v3-models.js +2 -0
  188. package/dist/esm/generated/v3-models.js.map +1 -0
  189. package/dist/esm/generated/v3.d.ts +1149 -0
  190. package/dist/esm/generated/v3.d.ts.map +1 -0
  191. package/dist/esm/generated/v3.js +247 -0
  192. package/dist/esm/generated/v3.js.map +1 -0
  193. package/dist/esm/gwt-enrollments.d.ts +131 -0
  194. package/dist/esm/gwt-enrollments.d.ts.map +1 -0
  195. package/dist/esm/gwt-enrollments.js +274 -0
  196. package/dist/esm/gwt-enrollments.js.map +1 -0
  197. package/dist/esm/index.d.ts +3 -0
  198. package/dist/esm/index.d.ts.map +1 -0
  199. package/dist/esm/index.js +3 -0
  200. package/dist/esm/index.js.map +1 -0
  201. package/dist/esm/metadata.d.ts +100 -0
  202. package/dist/esm/metadata.d.ts.map +1 -0
  203. package/dist/esm/metadata.js +157 -0
  204. package/dist/esm/metadata.js.map +1 -0
  205. package/dist/esm/program-discovery.d.ts +28 -0
  206. package/dist/esm/program-discovery.d.ts.map +1 -0
  207. package/dist/esm/program-discovery.js +55 -0
  208. package/dist/esm/program-discovery.js.map +1 -0
  209. package/dist/esm/race.d.ts +4 -0
  210. package/dist/esm/race.d.ts.map +1 -0
  211. package/dist/esm/race.js +37 -0
  212. package/dist/esm/race.js.map +1 -0
  213. package/dist/esm/runtime.d.ts +108 -0
  214. package/dist/esm/runtime.d.ts.map +1 -0
  215. package/dist/esm/runtime.js +33 -0
  216. package/dist/esm/runtime.js.map +1 -0
  217. package/dist/esm/subassessments.d.ts +29 -0
  218. package/dist/esm/subassessments.d.ts.map +1 -0
  219. package/dist/esm/subassessments.js +103 -0
  220. package/dist/esm/subassessments.js.map +1 -0
  221. package/dist/esm/testing.d.ts +45 -0
  222. package/dist/esm/testing.d.ts.map +1 -0
  223. package/dist/esm/testing.js +60 -0
  224. package/dist/esm/testing.js.map +1 -0
  225. package/dist/esm/transport.d.ts +19 -0
  226. package/dist/esm/transport.d.ts.map +1 -0
  227. package/dist/esm/transport.js +369 -0
  228. package/dist/esm/transport.js.map +1 -0
  229. package/dist/esm/v2.d.ts +13 -0
  230. package/dist/esm/v2.d.ts.map +1 -0
  231. package/dist/esm/v2.js +13 -0
  232. package/dist/esm/v2.js.map +1 -0
  233. package/dist/esm/v3.d.ts +13 -0
  234. package/dist/esm/v3.d.ts.map +1 -0
  235. package/dist/esm/v3.js +13 -0
  236. package/dist/esm/v3.js.map +1 -0
  237. package/dist/esm/validation.d.ts +33 -0
  238. package/dist/esm/validation.d.ts.map +1 -0
  239. package/dist/esm/validation.js +72 -0
  240. package/dist/esm/validation.js.map +1 -0
  241. package/dist/esm/verification.d.ts +82 -0
  242. package/dist/esm/verification.d.ts.map +1 -0
  243. package/dist/esm/verification.js +117 -0
  244. package/dist/esm/verification.js.map +1 -0
  245. package/docs/README.md +56 -0
  246. package/docs/assessments.md +103 -0
  247. package/docs/browser-session.md +129 -0
  248. package/docs/capabilities.md +75 -0
  249. package/docs/configuration.md +81 -0
  250. package/docs/current-living-situation.md +56 -0
  251. package/docs/dates-and-answers.md +40 -0
  252. package/docs/development.md +81 -0
  253. package/docs/durable-records.md +156 -0
  254. package/docs/enrollments.md +217 -0
  255. package/docs/files-and-notes.md +39 -0
  256. package/docs/getting-started.md +56 -0
  257. package/docs/independent-needs.md +77 -0
  258. package/docs/integration-methods.md +60 -0
  259. package/docs/profiles.md +68 -0
  260. package/docs/reference.md +64 -0
  261. package/docs/rest-client.md +97 -0
  262. package/docs/services.md +66 -0
  263. package/docs/shelter-inventory.md +64 -0
  264. package/docs/shelters.md +75 -0
  265. package/docs/troubleshooting.md +61 -0
  266. package/docs/using-the-sdk.md +203 -0
  267. package/docs/vendor-issues/README.md +46 -0
  268. package/docs/vendor-issues/api-coverage.md +27 -0
  269. package/docs/vendor-issues/attachment-authentication.md +40 -0
  270. package/docs/vendor-issues/date-semantics.md +51 -0
  271. package/docs/vendor-issues/empty-service-list-404.md +51 -0
  272. package/docs/vendor-issues/enrollment-complete-target-error.md +178 -0
  273. package/docs/vendor-issues/enrollment-effective-date-column.md +112 -0
  274. package/docs/vendor-issues/household-head-validation.md +26 -0
  275. package/docs/vendor-issues/write-contracts.md +51 -0
  276. package/examples/README.md +41 -0
  277. package/examples/current-living-situation.mts +27 -0
  278. package/examples/durable-records.mts +78 -0
  279. package/examples/enrollment-workflow.mts +46 -0
  280. package/examples/gwt-enrollment-session.ts +17 -0
  281. package/examples/gwt-file-store.mjs +2 -0
  282. package/examples/household-enrollment-workflow.mts +43 -0
  283. package/examples/household-exit-workflow.mts +35 -0
  284. package/examples/household-lifecycle-workflow.mts +34 -0
  285. package/examples/independent-needs.mts +30 -0
  286. package/examples/program-discovery.ts +19 -0
  287. package/examples/quickstart.mjs +13 -0
  288. package/examples/shelter-inventory.mts +30 -0
  289. package/examples/shelters.mts +33 -0
  290. package/examples/usage.ts +23 -0
  291. package/examples/verification-snapshot.ts +9 -0
  292. package/llms.txt +26 -0
  293. package/package.json +103 -0
  294. package/specs/provenance.json +17 -0
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRecord = isRecord;
4
+ exports.matches = matches;
5
+ exports.decodeResponse = decodeResponse;
6
+ const runtime_js_1 = require("./runtime.js");
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ /** Validates the structural subset used by the pinned specifications. */
11
+ function matches(value, schema, registry, mode, depth = 0) {
12
+ if (depth > 100)
13
+ return false;
14
+ if (value === null && schema.nullable)
15
+ return true;
16
+ if (schema.$ref) {
17
+ const target = registry[schema.$ref.split("/").at(-1) ?? ""];
18
+ return target !== undefined && matches(value, target, registry, mode, depth + 1);
19
+ }
20
+ if (schema.enum && !schema.enum.includes(value))
21
+ return false;
22
+ switch (schema.type?.toLowerCase()) {
23
+ case "string":
24
+ if (schema.format === "binary")
25
+ return value instanceof Blob;
26
+ if (typeof value !== "string")
27
+ return false;
28
+ // Format strings and .NET regexes are not portable business validation.
29
+ return mode === "response" || ((schema.minLength === undefined || value.length >= schema.minLength) && (schema.maxLength === undefined || value.length <= schema.maxLength));
30
+ case "integer": return typeof value === "number" && Number.isSafeInteger(value);
31
+ case "number": return typeof value === "number" && Number.isFinite(value);
32
+ case "boolean": return typeof value === "boolean";
33
+ case "array": {
34
+ const itemSchema = schema.items;
35
+ return Array.isArray(value) && itemSchema !== undefined && value.every(item => matches(item, itemSchema, registry, mode, depth + 1));
36
+ }
37
+ case "object": {
38
+ if (!isRecord(value))
39
+ return false;
40
+ if (schema.required?.some(key => !Object.hasOwn(value, key) || value[key] === undefined))
41
+ return false;
42
+ for (const [key, child] of Object.entries(schema.properties ?? {})) {
43
+ if (Object.hasOwn(value, key) && value[key] !== undefined && !matches(value[key], child, registry, mode, depth + 1))
44
+ return false;
45
+ }
46
+ // Response fields added by WellSky are allowed. Request typos are rejected.
47
+ if (mode === "request" && schema.additionalProperties === false && Object.keys(value).some(key => !Object.hasOwn(schema.properties ?? {}, key)))
48
+ return false;
49
+ return true;
50
+ }
51
+ default: return false;
52
+ }
53
+ }
54
+ function decodeResponse(value, schema, registry, operation) {
55
+ // No schema means the public return type is unknown; empty responses remain null.
56
+ if (!schema || value === null)
57
+ return value;
58
+ let valid;
59
+ if (Array.isArray(value))
60
+ valid = value.every(item => matches(item, schema, registry, "response"));
61
+ else if (isRecord(value) && Object.hasOwn(value, "value")) {
62
+ valid = Array.isArray(value.value) && value.value.every(item => matches(item, schema, registry, "response")) &&
63
+ (value["@odata.count"] === undefined || typeof value["@odata.count"] === "number" && Number.isSafeInteger(value["@odata.count"]) && value["@odata.count"] >= 0) &&
64
+ (value["@odata.nextLink"] === undefined || typeof value["@odata.nextLink"] === "string");
65
+ }
66
+ else if (isRecord(value) && (Object.hasOwn(value, "count") || Object.hasOwn(value, "values"))) {
67
+ valid = typeof value.count === "number" && Number.isSafeInteger(value.count) && value.count >= 0 &&
68
+ Array.isArray(value.values) && value.values.every(item => matches(item, schema, registry, "response"));
69
+ }
70
+ else
71
+ valid = matches(value, schema, registry, "response");
72
+ if (!valid)
73
+ throw new runtime_js_1.WellSkyError("protocol", operation);
74
+ // Generated return types and runtime schemas come from the same specification.
75
+ return value;
76
+ }
77
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { WellSkyError } from \"./runtime.js\";\n\nexport interface Schema {\n $ref?: string;\n type?: string;\n format?: string;\n nullable?: boolean;\n properties?: Record<string, Schema>;\n required?: string[];\n items?: Schema;\n additionalProperties?: boolean | Schema;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n default?: unknown;\n enum?: unknown[];\n}\n\nexport type SchemaRegistry = Record<string, Schema>;\nexport type Method = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"HEAD\" | \"OPTIONS\";\nexport interface OperationDefinition {\n method: Method;\n path: string;\n authenticated: boolean;\n pathSchema: Schema;\n querySchema: Schema;\n bodySchema?: Schema;\n responseSchema?: Schema;\n multipart: boolean;\n pageable: boolean;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Validates the structural subset used by the pinned specifications. */\nexport function matches(value: unknown, schema: Schema, registry: SchemaRegistry, mode: \"request\" | \"response\", depth = 0): boolean {\n if (depth > 100) return false;\n if (value === null && schema.nullable) return true;\n if (schema.$ref) {\n const target = registry[schema.$ref.split(\"/\").at(-1) ?? \"\"];\n return target !== undefined && matches(value, target, registry, mode, depth + 1);\n }\n if (schema.enum && !schema.enum.includes(value)) return false;\n switch (schema.type?.toLowerCase()) {\n case \"string\":\n if (schema.format === \"binary\") return value instanceof Blob;\n if (typeof value !== \"string\") return false;\n // Format strings and .NET regexes are not portable business validation.\n return mode === \"response\" || ((schema.minLength === undefined || value.length >= schema.minLength) && (schema.maxLength === undefined || value.length <= schema.maxLength));\n case \"integer\": return typeof value === \"number\" && Number.isSafeInteger(value);\n case \"number\": return typeof value === \"number\" && Number.isFinite(value);\n case \"boolean\": return typeof value === \"boolean\";\n case \"array\": {\n const itemSchema = schema.items;\n return Array.isArray(value) && itemSchema !== undefined && value.every(item => matches(item, itemSchema, registry, mode, depth + 1));\n }\n case \"object\": {\n if (!isRecord(value)) return false;\n if (schema.required?.some(key => !Object.hasOwn(value, key) || value[key] === undefined)) return false;\n for (const [key, child] of Object.entries(schema.properties ?? {})) {\n if (Object.hasOwn(value, key) && value[key] !== undefined && !matches(value[key], child, registry, mode, depth + 1)) return false;\n }\n // Response fields added by WellSky are allowed. Request typos are rejected.\n if (mode === \"request\" && schema.additionalProperties === false && Object.keys(value).some(key => !Object.hasOwn(schema.properties ?? {}, key))) return false;\n return true;\n }\n default: return false;\n }\n}\n\nexport function decodeResponse<T>(value: unknown, schema: Schema | undefined, registry: SchemaRegistry, operation: string): T {\n // No schema means the public return type is unknown; empty responses remain null.\n if (!schema || value === null) return value as T;\n let valid: boolean;\n if (Array.isArray(value)) valid = value.every(item => matches(item, schema, registry, \"response\"));\n else if (isRecord(value) && Object.hasOwn(value, \"value\")) {\n valid = Array.isArray(value.value) && value.value.every(item => matches(item, schema, registry, \"response\")) &&\n (value[\"@odata.count\"] === undefined || typeof value[\"@odata.count\"] === \"number\" && Number.isSafeInteger(value[\"@odata.count\"]) && value[\"@odata.count\"] >= 0) &&\n (value[\"@odata.nextLink\"] === undefined || typeof value[\"@odata.nextLink\"] === \"string\");\n } else if (isRecord(value) && (Object.hasOwn(value, \"count\") || Object.hasOwn(value, \"values\"))) {\n valid = typeof value.count === \"number\" && Number.isSafeInteger(value.count) && value.count >= 0 &&\n Array.isArray(value.values) && value.values.every(item => matches(item, schema, registry, \"response\"));\n } else valid = matches(value, schema, registry, \"response\");\n if (!valid) throw new WellSkyError(\"protocol\", operation);\n // Generated return types and runtime schemas come from the same specification.\n return value as T;\n}\n"]}
@@ -0,0 +1,82 @@
1
+ import { WellSkyError } from "./runtime.js";
2
+ import type { RequestOptions } from "./runtime.js";
3
+ export type ReadOutcome<T> = {
4
+ kind: "found";
5
+ value: T;
6
+ } | {
7
+ kind: "absent";
8
+ } | {
9
+ kind: "unknown";
10
+ error: WellSkyError;
11
+ };
12
+ export interface ReadBackEvidence {
13
+ endpoint: string;
14
+ readAt: string;
15
+ ids: Record<string, number>;
16
+ fields: Record<string, "match" | "absent" | "different">;
17
+ }
18
+ export interface FieldMismatch {
19
+ field: string;
20
+ expected: string;
21
+ actual: string | null;
22
+ }
23
+ export interface AttemptInfo {
24
+ correlationId: string;
25
+ startedAt: string;
26
+ durationMs: number;
27
+ httpStatus?: number;
28
+ }
29
+ interface AttemptResult {
30
+ requestHash: string;
31
+ attempt: AttemptInfo;
32
+ }
33
+ export type WriteResult<T> = (AttemptResult & {
34
+ status: "verified";
35
+ committed: true;
36
+ data: T;
37
+ evidence: ReadBackEvidence;
38
+ }) | (AttemptResult & {
39
+ status: "unverified";
40
+ committed: true | "unknown";
41
+ data?: Partial<T>;
42
+ mismatches?: FieldMismatch[];
43
+ evidence?: ReadBackEvidence;
44
+ error?: WellSkyError;
45
+ }) | (AttemptResult & {
46
+ status: "failed";
47
+ committed: false;
48
+ reason: "preflight" | "rejected" | "proven_absent";
49
+ error: WellSkyError;
50
+ });
51
+ /** Include tenant, target IDs, operation, effective date, and encoded answers in the input. */
52
+ export declare function semanticHash(value: unknown, options?: RequestOptions): Promise<string>;
53
+ /** Absence semantics are endpoint-specific. No HTTP status means absent by default. */
54
+ export declare function readOutcome<T>(reader: () => Promise<T>, options: {
55
+ operation: string;
56
+ isAbsent?: (value: T) => boolean;
57
+ absentStatuses?: readonly number[];
58
+ signal?: AbortSignal;
59
+ }): Promise<ReadOutcome<T>>;
60
+ export interface ReadBackRecord<T> {
61
+ data: T;
62
+ ids: Record<string, number>;
63
+ fields: Record<string, unknown>;
64
+ /** Set only when the read proves this attempt committed, not just that an older record exists. */
65
+ commitConfirmed?: boolean;
66
+ }
67
+ export interface VerificationPlan<T> extends RequestOptions {
68
+ operation: string;
69
+ readEndpoint: string;
70
+ semanticPayload: unknown;
71
+ expectedFields: Record<string, unknown>;
72
+ preflight?: (options: RequestOptions) => Promise<void>;
73
+ submit: (options: RequestOptions) => Promise<unknown>;
74
+ readBack: (options: RequestOptions) => Promise<ReadOutcome<ReadBackRecord<T>>>;
75
+ /** An authoritative proof supplied by an endpoint adapter, not merely an empty list or a 404. */
76
+ proveNotCommitted?: (options: RequestOptions) => Promise<boolean>;
77
+ mode?: "write" | "verify-only";
78
+ }
79
+ /** Sends at most once, always reads back, and never infers success or rollback from an HTTP status. */
80
+ export declare function verifyWrite<T>(plan: VerificationPlan<T>): Promise<WriteResult<T>>;
81
+ export {};
82
+ //# sourceMappingURL=verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../src/verification.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.semanticHash = semanticHash;
4
+ exports.readOutcome = readOutcome;
5
+ exports.verifyWrite = verifyWrite;
6
+ const runtime_js_1 = require("./runtime.js");
7
+ const errors_js_1 = require("./errors.js");
8
+ function canonical(value, seen = new Set()) {
9
+ if (value === null)
10
+ return "null";
11
+ if (typeof value === "string" || typeof value === "boolean")
12
+ return JSON.stringify(value);
13
+ if (typeof value === "number" && Number.isFinite(value))
14
+ return JSON.stringify(value);
15
+ if (typeof value !== "object" || seen.has(value))
16
+ throw new errors_js_1.ValidationError("semantic payload hash");
17
+ seen.add(value);
18
+ let result;
19
+ // Array.from visits holes as undefined, which canonical rejects. map skips them
20
+ // and would otherwise hash a one-hole array exactly like an empty array.
21
+ if (Array.isArray(value))
22
+ result = `[${Array.from(value, item => canonical(item, seen)).join(",")}]`;
23
+ else {
24
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
25
+ throw new errors_js_1.ValidationError("semantic payload hash");
26
+ result = `{${Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item, seen)}`).join(",")}}`;
27
+ }
28
+ seen.delete(value);
29
+ return result;
30
+ }
31
+ /** Include tenant, target IDs, operation, effective date, and encoded answers in the input. */
32
+ async function semanticHash(value, options = {}) {
33
+ options.signal?.throwIfAborted();
34
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical(value)));
35
+ options.signal?.throwIfAborted();
36
+ return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join("");
37
+ }
38
+ function safeError(error, operation) {
39
+ return error instanceof runtime_js_1.WellSkyError ? error : new runtime_js_1.WellSkyError("protocol", operation);
40
+ }
41
+ /** Absence semantics are endpoint-specific. No HTTP status means absent by default. */
42
+ async function readOutcome(reader, options) {
43
+ try {
44
+ options.signal?.throwIfAborted();
45
+ const value = await reader();
46
+ return options.isAbsent?.(value) ? { kind: "absent" } : { kind: "found", value };
47
+ }
48
+ catch (error) {
49
+ const safe = safeError(error, options.operation);
50
+ if (safe.status !== undefined && options.absentStatuses?.includes(safe.status))
51
+ return { kind: "absent" };
52
+ return { kind: "unknown", error: safe };
53
+ }
54
+ }
55
+ /** Sends at most once, always reads back, and never infers success or rollback from an HTTP status. */
56
+ async function verifyWrite(plan) {
57
+ const started = Date.now();
58
+ const correlationId = plan.correlationId ?? crypto.randomUUID();
59
+ const context = { correlationId, ...(plan.signal ? { signal: plan.signal } : {}), ...(plan.timeoutMs === undefined ? {} : { timeoutMs: plan.timeoutMs }) };
60
+ // Hashing invalid semantic input throws before any network operation.
61
+ const requestHash = await semanticHash(plan.semanticPayload);
62
+ let httpStatus;
63
+ const base = () => ({ requestHash, attempt: { correlationId, startedAt: new Date(started).toISOString(), durationMs: Date.now() - started, ...(httpStatus === undefined ? {} : { httpStatus }) } });
64
+ try {
65
+ plan.signal?.throwIfAborted();
66
+ await plan.preflight?.(context);
67
+ if (!Object.keys(plan.expectedFields).length)
68
+ throw new errors_js_1.ValidationError(plan.operation, ["expectedFields"]);
69
+ for (const value of Object.values(plan.expectedFields))
70
+ canonical(value);
71
+ plan.signal?.throwIfAborted();
72
+ }
73
+ catch (error) {
74
+ return { ...base(), status: "failed", committed: false, reason: "preflight", error: safeError(error, plan.operation) };
75
+ }
76
+ let writeError;
77
+ if (plan.mode !== "verify-only") {
78
+ try {
79
+ await plan.submit(context);
80
+ }
81
+ catch (error) {
82
+ writeError = safeError(error, plan.operation);
83
+ httpStatus = writeError.status;
84
+ }
85
+ }
86
+ let read;
87
+ try {
88
+ read = await plan.readBack(context);
89
+ }
90
+ catch (error) {
91
+ read = { kind: "unknown", error: safeError(error, plan.readEndpoint) };
92
+ }
93
+ if (read.kind === "unknown")
94
+ return { ...base(), status: "unverified", committed: "unknown", error: read.error };
95
+ if (read.kind === "absent") {
96
+ let proven = false;
97
+ try {
98
+ proven = await plan.proveNotCommitted?.(context) === true;
99
+ }
100
+ catch { /* An unavailable proof is not absence. */ }
101
+ const error = writeError ?? new errors_js_1.AmbiguousCommitError(plan.operation);
102
+ return proven ? { ...base(), status: "failed", committed: false, reason: writeError?.status && writeError.status >= 400 && writeError.status < 500 ? "rejected" : "proven_absent", error } : { ...base(), status: "unverified", committed: "unknown", error };
103
+ }
104
+ const fields = {};
105
+ const mismatches = [];
106
+ for (const [field, expected] of Object.entries(plan.expectedFields)) {
107
+ const present = Object.hasOwn(read.value.fields, field);
108
+ let equal = false;
109
+ try {
110
+ equal = present && canonical(expected) === canonical(read.value.fields[field]);
111
+ }
112
+ catch { /* Malformed read values cannot verify a write. */ }
113
+ fields[field] = equal ? "match" : present ? "different" : "absent";
114
+ if (!equal)
115
+ mismatches.push({ field, expected: "[REDACTED]", actual: present ? "[REDACTED]" : null });
116
+ }
117
+ const evidence = { endpoint: plan.readEndpoint, readAt: new Date().toISOString(), ids: { ...read.value.ids }, fields };
118
+ if (!mismatches.length)
119
+ return { ...base(), status: "verified", committed: true, data: read.value.data, evidence };
120
+ return { ...base(), status: "unverified", committed: read.value.commitConfirmed === true ? true : "unknown", data: read.value.data, mismatches, evidence, ...(writeError ? { error: writeError } : {}) };
121
+ }
122
+ //# sourceMappingURL=verification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.js","sourceRoot":"","sources":["../../src/verification.ts"],"names":[],"mappings":"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","sourcesContent":["import { WellSkyError } from \"./runtime.js\";\nimport type { RequestOptions } from \"./runtime.js\";\nimport { AmbiguousCommitError, ValidationError } from \"./errors.js\";\n\nexport type ReadOutcome<T> = { kind: \"found\"; value: T } | { kind: \"absent\" } | { kind: \"unknown\"; error: WellSkyError };\nexport interface ReadBackEvidence {\n endpoint: string;\n readAt: string;\n ids: Record<string, number>;\n fields: Record<string, \"match\" | \"absent\" | \"different\">;\n}\nexport interface FieldMismatch { field: string; expected: string; actual: string | null }\nexport interface AttemptInfo { correlationId: string; startedAt: string; durationMs: number; httpStatus?: number }\ninterface AttemptResult { requestHash: string; attempt: AttemptInfo }\nexport type WriteResult<T> =\n | (AttemptResult & { status: \"verified\"; committed: true; data: T; evidence: ReadBackEvidence })\n | (AttemptResult & { status: \"unverified\"; committed: true | \"unknown\"; data?: Partial<T>; mismatches?: FieldMismatch[]; evidence?: ReadBackEvidence; error?: WellSkyError })\n | (AttemptResult & { status: \"failed\"; committed: false; reason: \"preflight\" | \"rejected\" | \"proven_absent\"; error: WellSkyError });\n\nfunction canonical(value: unknown, seen = new Set<object>()): string {\n if (value === null) return \"null\";\n if (typeof value === \"string\" || typeof value === \"boolean\") return JSON.stringify(value);\n if (typeof value === \"number\" && Number.isFinite(value)) return JSON.stringify(value);\n if (typeof value !== \"object\" || seen.has(value)) throw new ValidationError(\"semantic payload hash\");\n seen.add(value);\n let result: string;\n // Array.from visits holes as undefined, which canonical rejects. map skips them\n // and would otherwise hash a one-hole array exactly like an empty array.\n if (Array.isArray(value)) result = `[${Array.from(value, item => canonical(item, seen)).join(\",\")}]`;\n else {\n if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new ValidationError(\"semantic payload hash\");\n result = `{${Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item, seen)}`).join(\",\")}}`;\n }\n seen.delete(value);\n return result;\n}\n\n/** Include tenant, target IDs, operation, effective date, and encoded answers in the input. */\nexport async function semanticHash(value: unknown, options: RequestOptions = {}): Promise<string> {\n options.signal?.throwIfAborted();\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(canonical(value)));\n options.signal?.throwIfAborted();\n return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction safeError(error: unknown, operation: string): WellSkyError {\n return error instanceof WellSkyError ? error : new WellSkyError(\"protocol\", operation);\n}\n\n/** Absence semantics are endpoint-specific. No HTTP status means absent by default. */\nexport async function readOutcome<T>(reader: () => Promise<T>, options: { operation: string; isAbsent?: (value: T) => boolean; absentStatuses?: readonly number[]; signal?: AbortSignal }): Promise<ReadOutcome<T>> {\n try {\n options.signal?.throwIfAborted();\n const value = await reader();\n return options.isAbsent?.(value) ? { kind: \"absent\" } : { kind: \"found\", value };\n } catch (error) {\n const safe = safeError(error, options.operation);\n if (safe.status !== undefined && options.absentStatuses?.includes(safe.status)) return { kind: \"absent\" };\n return { kind: \"unknown\", error: safe };\n }\n}\n\nexport interface ReadBackRecord<T> {\n data: T;\n ids: Record<string, number>;\n fields: Record<string, unknown>;\n /** Set only when the read proves this attempt committed, not just that an older record exists. */\n commitConfirmed?: boolean;\n}\nexport interface VerificationPlan<T> extends RequestOptions {\n operation: string;\n readEndpoint: string;\n semanticPayload: unknown;\n expectedFields: Record<string, unknown>;\n preflight?: (options: RequestOptions) => Promise<void>;\n submit: (options: RequestOptions) => Promise<unknown>;\n readBack: (options: RequestOptions) => Promise<ReadOutcome<ReadBackRecord<T>>>;\n /** An authoritative proof supplied by an endpoint adapter, not merely an empty list or a 404. */\n proveNotCommitted?: (options: RequestOptions) => Promise<boolean>;\n mode?: \"write\" | \"verify-only\";\n}\n\n/** Sends at most once, always reads back, and never infers success or rollback from an HTTP status. */\nexport async function verifyWrite<T>(plan: VerificationPlan<T>): Promise<WriteResult<T>> {\n const started = Date.now();\n const correlationId = plan.correlationId ?? crypto.randomUUID();\n const context: RequestOptions = { correlationId, ...(plan.signal ? { signal: plan.signal } : {}), ...(plan.timeoutMs === undefined ? {} : { timeoutMs: plan.timeoutMs }) };\n // Hashing invalid semantic input throws before any network operation.\n const requestHash = await semanticHash(plan.semanticPayload);\n let httpStatus: number | undefined;\n const base = (): AttemptResult => ({ requestHash, attempt: { correlationId, startedAt: new Date(started).toISOString(), durationMs: Date.now() - started, ...(httpStatus === undefined ? {} : { httpStatus }) } });\n try {\n plan.signal?.throwIfAborted();\n await plan.preflight?.(context);\n if (!Object.keys(plan.expectedFields).length) throw new ValidationError(plan.operation, [\"expectedFields\"]);\n for (const value of Object.values(plan.expectedFields)) canonical(value);\n plan.signal?.throwIfAborted();\n } catch (error) { return { ...base(), status: \"failed\", committed: false, reason: \"preflight\", error: safeError(error, plan.operation) }; }\n let writeError: WellSkyError | undefined;\n if (plan.mode !== \"verify-only\") {\n try { await plan.submit(context); }\n catch (error) { writeError = safeError(error, plan.operation); httpStatus = writeError.status; }\n }\n let read: ReadOutcome<ReadBackRecord<T>>;\n try { read = await plan.readBack(context); }\n catch (error) { read = { kind: \"unknown\", error: safeError(error, plan.readEndpoint) }; }\n if (read.kind === \"unknown\") return { ...base(), status: \"unverified\", committed: \"unknown\", error: read.error };\n if (read.kind === \"absent\") {\n let proven = false;\n try { proven = await plan.proveNotCommitted?.(context) === true; } catch { /* An unavailable proof is not absence. */ }\n const error = writeError ?? new AmbiguousCommitError(plan.operation);\n return proven ? { ...base(), status: \"failed\", committed: false, reason: writeError?.status && writeError.status >= 400 && writeError.status < 500 ? \"rejected\" : \"proven_absent\", error } : { ...base(), status: \"unverified\", committed: \"unknown\", error };\n }\n const fields: ReadBackEvidence[\"fields\"] = {};\n const mismatches: FieldMismatch[] = [];\n for (const [field, expected] of Object.entries(plan.expectedFields)) {\n const present = Object.hasOwn(read.value.fields, field);\n let equal = false;\n try { equal = present && canonical(expected) === canonical(read.value.fields[field]); } catch { /* Malformed read values cannot verify a write. */ }\n fields[field] = equal ? \"match\" : present ? \"different\" : \"absent\";\n if (!equal) mismatches.push({ field, expected: \"[REDACTED]\", actual: present ? \"[REDACTED]\" : null });\n }\n const evidence: ReadBackEvidence = { endpoint: plan.readEndpoint, readAt: new Date().toISOString(), ids: { ...read.value.ids }, fields };\n if (!mismatches.length) return { ...base(), status: \"verified\", committed: true, data: read.value.data, evidence };\n return { ...base(), status: \"unverified\", committed: read.value.commitConfirmed === true ? true : \"unknown\", data: read.value.data, mismatches, evidence, ...(writeError ? { error: writeError } : {}) };\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { WellSkyError } from "./runtime.js";
2
+ export type BulkItemResult<T> = {
3
+ index: number;
4
+ outcome: "fulfilled";
5
+ value: T;
6
+ } | {
7
+ index: number;
8
+ outcome: "rejected";
9
+ error: WellSkyError;
10
+ } | {
11
+ index: number;
12
+ outcome: "skipped";
13
+ reason: "cancelled" | "callback_failed";
14
+ };
15
+ export interface BulkReport<T> {
16
+ results: BulkItemResult<T>[];
17
+ callbackErrors: {
18
+ index: number;
19
+ error: WellSkyError;
20
+ }[];
21
+ }
22
+ export interface BulkOptions<T> {
23
+ concurrency?: number;
24
+ signal?: AbortSignal;
25
+ /** Called as each item completes. Awaited before scheduling the next item on that worker. */
26
+ onResult?: (result: BulkItemResult<T>) => void | Promise<void>;
27
+ }
28
+ /** No item is retried here. A rejected write promise does not prove the write was rolled back. */
29
+ export declare function mapBulk<I, O>(items: readonly I[], worker: (item: I, context: {
30
+ index: number;
31
+ signal?: AbortSignal;
32
+ }) => Promise<O>, options?: BulkOptions<O>): Promise<BulkReport<O>>;
33
+ //# sourceMappingURL=bulk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk.d.ts","sourceRoot":"","sources":["../../src/bulk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACjD;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAC3D;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAAA;CAAE,CAAC;AACnF,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,EAAE,CAAC;CAC1D;AACD,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,kGAAkG;AAClG,wBAAsB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,WAAW,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CA0BhM"}
@@ -0,0 +1,39 @@
1
+ import { WellSkyError } from "./runtime.js";
2
+ import { ValidationError } from "./errors.js";
3
+ /** No item is retried here. A rejected write promise does not prove the write was rolled back. */
4
+ export async function mapBulk(items, worker, options = {}) {
5
+ const concurrency = options.concurrency ?? 8;
6
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1)
7
+ throw new ValidationError("bulk configuration", ["concurrency"]);
8
+ const results = [];
9
+ const callbackErrors = [];
10
+ let next = 0;
11
+ let callbackFailed = false;
12
+ const jobs = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
13
+ while (next < items.length && !options.signal?.aborted && !callbackFailed) {
14
+ const index = next++;
15
+ // The index is reserved synchronously and is bounded by items.length.
16
+ const item = items[index];
17
+ let result;
18
+ try {
19
+ result = { index, outcome: "fulfilled", value: await worker(item, { index, ...(options.signal ? { signal: options.signal } : {}) }) };
20
+ }
21
+ catch (error) {
22
+ result = { index, outcome: "rejected", error: error instanceof WellSkyError ? error : new WellSkyError("protocol", "bulk item") };
23
+ }
24
+ results[index] = result;
25
+ try {
26
+ await options.onResult?.(result);
27
+ }
28
+ catch {
29
+ callbackFailed = true;
30
+ callbackErrors.push({ index, error: new WellSkyError("protocol", "bulk result callback") });
31
+ }
32
+ }
33
+ });
34
+ await Promise.all(jobs);
35
+ for (let index = next; index < items.length; index++)
36
+ results[index] = { index, outcome: "skipped", reason: callbackFailed ? "callback_failed" : "cancelled" };
37
+ return { results, callbackErrors };
38
+ }
39
+ //# sourceMappingURL=bulk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk.js","sourceRoot":"","sources":["../../src/bulk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAiB9C,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,OAAO,CAAO,KAAmB,EAAE,MAAiF,EAAE,UAA0B,EAAE;IACtK,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC;QAAE,MAAM,IAAI,eAAe,CAAC,oBAAoB,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5H,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,MAAM,cAAc,GAAoC,EAAE,CAAC;IAC3D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QAClF,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1E,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;YACrB,sEAAsE;YACtE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAM,CAAC;YAC/B,IAAI,MAAyB,CAAC;YAC9B,IAAI,CAAC;gBAAC,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YAAC,CAAC;YAC9I,OAAO,KAAK,EAAE,CAAC;gBAAC,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,YAAY,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC;YAAC,CAAC;YACpJ,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;YACxB,IAAI,CAAC;gBAAC,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC;YAAC,CAAC;YACzC,MAAM,CAAC;gBACL,cAAc,GAAG,IAAI,CAAC;gBACtB,cAAc,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,YAAY,CAAC,UAAU,EAAE,sBAAsB,CAAC,EAAE,CAAC,CAAC;YAC9F,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE;QAAE,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/J,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AACrC,CAAC","sourcesContent":["import { WellSkyError } from \"./runtime.js\";\nimport { ValidationError } from \"./errors.js\";\n\nexport type BulkItemResult<T> =\n | { index: number; outcome: \"fulfilled\"; value: T }\n | { index: number; outcome: \"rejected\"; error: WellSkyError }\n | { index: number; outcome: \"skipped\"; reason: \"cancelled\" | \"callback_failed\" };\nexport interface BulkReport<T> {\n results: BulkItemResult<T>[];\n callbackErrors: { index: number; error: WellSkyError }[];\n}\nexport interface BulkOptions<T> {\n concurrency?: number;\n signal?: AbortSignal;\n /** Called as each item completes. Awaited before scheduling the next item on that worker. */\n onResult?: (result: BulkItemResult<T>) => void | Promise<void>;\n}\n\n/** No item is retried here. A rejected write promise does not prove the write was rolled back. */\nexport async function mapBulk<I, O>(items: readonly I[], worker: (item: I, context: { index: number; signal?: AbortSignal }) => Promise<O>, options: BulkOptions<O> = {}): Promise<BulkReport<O>> {\n const concurrency = options.concurrency ?? 8;\n if (!Number.isSafeInteger(concurrency) || concurrency < 1) throw new ValidationError(\"bulk configuration\", [\"concurrency\"]);\n const results: BulkItemResult<O>[] = [];\n const callbackErrors: BulkReport<O>[\"callbackErrors\"] = [];\n let next = 0;\n let callbackFailed = false;\n const jobs = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (next < items.length && !options.signal?.aborted && !callbackFailed) {\n const index = next++;\n // The index is reserved synchronously and is bounded by items.length.\n const item = items[index] as I;\n let result: BulkItemResult<O>;\n try { result = { index, outcome: \"fulfilled\", value: await worker(item, { index, ...(options.signal ? { signal: options.signal } : {}) }) }; }\n catch (error) { result = { index, outcome: \"rejected\", error: error instanceof WellSkyError ? error : new WellSkyError(\"protocol\", \"bulk item\") }; }\n results[index] = result;\n try { await options.onResult?.(result); }\n catch {\n callbackFailed = true;\n callbackErrors.push({ index, error: new WellSkyError(\"protocol\", \"bulk result callback\") });\n }\n }\n });\n await Promise.all(jobs);\n for (let index = next; index < items.length; index++) results[index] = { index, outcome: \"skipped\", reason: callbackFailed ? \"callback_failed\" : \"cancelled\" };\n return { results, callbackErrors };\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import type { ClientEvent, ClientHealth, ClientOptions } from "./runtime.js";
2
+ export declare class Semaphore {
3
+ #private;
4
+ active: number;
5
+ constructor(capacity: number);
6
+ get queued(): number;
7
+ acquire(signal: AbortSignal, operation: string): Promise<() => void>;
8
+ }
9
+ export interface SharedAuthentication {
10
+ cached?: {
11
+ token: string;
12
+ expiresAt: number;
13
+ } | undefined;
14
+ inFlight?: Promise<string> | undefined;
15
+ }
16
+ export declare function identity(value: object): number;
17
+ /** Credentials are hashed for cache identity and never exposed in keys or diagnostics. */
18
+ export declare function sharedAuthentication(scope: string, auth: ClientOptions["auth"]): Promise<SharedAuthentication>;
19
+ export declare const tokenMintQueue: Semaphore;
20
+ export declare class RequestControl {
21
+ #private;
22
+ readonly semaphore: Semaphore;
23
+ tokenMintCount: number;
24
+ constructor(concurrency: number, settings: {
25
+ threshold: number;
26
+ windowMs: number;
27
+ durationMs: number;
28
+ });
29
+ check(operation: string): void;
30
+ failed(status: number): number | undefined;
31
+ health(): ClientHealth;
32
+ }
33
+ export declare function requestControl(scope: string, options: ClientOptions): RequestControl;
34
+ export declare function emit(options: Pick<ClientOptions, "hooks" | "logger">, event: ClientEvent): void;
35
+ //# sourceMappingURL=control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/control.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7E,qBAAa,SAAS;;IACpB,MAAM,SAAK;gBAGC,QAAQ,EAAE,MAAM;IAC5B,IAAI,MAAM,IAAI,MAAM,CAA+B;IACnD,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;CAuBrE;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;CACxC;AAGD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,0FAA0F;AAC1F,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CASpH;AACD,eAAO,MAAM,cAAc,WAAmB,CAAC;AAE/C,qBAAa,cAAc;;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAI9B,cAAc,SAAK;gBACP,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAItG,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI9B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ1C,MAAM,IAAI,YAAY;CAGvB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,cAAc,CASpF;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAO/F"}
@@ -0,0 +1,132 @@
1
+ import { WellSkyError } from "./runtime.js";
2
+ import { CooldownError } from "./errors.js";
3
+ export class Semaphore {
4
+ active = 0;
5
+ #capacity;
6
+ #queue = [];
7
+ constructor(capacity) { this.#capacity = capacity; }
8
+ get queued() { return this.#queue.length; }
9
+ acquire(signal, operation) {
10
+ return new Promise((resolve, reject) => {
11
+ let released = false;
12
+ const grant = () => {
13
+ signal.removeEventListener("abort", abort);
14
+ this.active++;
15
+ resolve(() => {
16
+ if (released)
17
+ return;
18
+ released = true;
19
+ this.active--;
20
+ this.#queue.shift()?.grant();
21
+ });
22
+ };
23
+ const abort = () => {
24
+ const index = this.#queue.indexOf(waiter);
25
+ if (index >= 0)
26
+ this.#queue.splice(index, 1);
27
+ reject(new WellSkyError(signal.reason instanceof DOMException && signal.reason.name === "TimeoutError" ? "timeout" : "aborted", operation));
28
+ };
29
+ const waiter = { grant, signal, abort };
30
+ if (signal.aborted) {
31
+ abort();
32
+ return;
33
+ }
34
+ if (this.active < this.#capacity)
35
+ grant();
36
+ else {
37
+ this.#queue.push(waiter);
38
+ signal.addEventListener("abort", abort, { once: true });
39
+ }
40
+ });
41
+ }
42
+ }
43
+ const identities = new WeakMap();
44
+ let nextIdentity = 1;
45
+ export function identity(value) {
46
+ const prior = identities.get(value);
47
+ if (prior !== undefined)
48
+ return prior;
49
+ const id = nextIdentity++;
50
+ identities.set(value, id);
51
+ return id;
52
+ }
53
+ const authentication = new Map();
54
+ /** Credentials are hashed for cache identity and never exposed in keys or diagnostics. */
55
+ export async function sharedAuthentication(scope, auth) {
56
+ const descriptor = auth.kind === "credentials" ? [scope, auth.kind, auth.username, auth.password] : auth.kind === "provider" ? [scope, auth.kind, identity(auth.getToken)] : [scope, auth.kind];
57
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(descriptor)));
58
+ const key = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join("");
59
+ const prior = authentication.get(key);
60
+ if (prior)
61
+ return prior;
62
+ const state = {};
63
+ authentication.set(key, state);
64
+ return state;
65
+ }
66
+ export const tokenMintQueue = new Semaphore(1);
67
+ export class RequestControl {
68
+ semaphore;
69
+ #settings;
70
+ #failures = [];
71
+ #until = 0;
72
+ tokenMintCount = 0;
73
+ constructor(concurrency, settings) {
74
+ for (const value of [concurrency, settings.threshold, settings.windowMs, settings.durationMs])
75
+ if (!Number.isSafeInteger(value) || value < 1)
76
+ throw new TypeError("Concurrency and cooldown settings must be positive integers");
77
+ this.semaphore = new Semaphore(concurrency);
78
+ this.#settings = { ...settings };
79
+ }
80
+ check(operation) {
81
+ if (this.#until > Date.now())
82
+ throw new CooldownError(operation, this.#until);
83
+ if (this.#until !== 0) {
84
+ this.#until = 0;
85
+ this.#failures = [];
86
+ }
87
+ }
88
+ failed(status) {
89
+ if (status !== 401 && status !== 403 && status !== 429 && status < 500)
90
+ return undefined;
91
+ const now = Date.now();
92
+ this.#failures = this.#failures.filter(time => time > now - this.#settings.windowMs);
93
+ this.#failures.push(now);
94
+ if (this.#failures.length >= this.#settings.threshold && this.#until <= now) {
95
+ this.#until = now + this.#settings.durationMs;
96
+ return this.#until;
97
+ }
98
+ return undefined;
99
+ }
100
+ health() {
101
+ return { state: this.#until > Date.now() ? "open" : "closed", until: this.#until > Date.now() ? new Date(this.#until).toISOString() : null, activeRequests: this.semaphore.active, queuedRequests: this.semaphore.queued, tokenMintCount: this.tokenMintCount };
102
+ }
103
+ }
104
+ const controls = new Map();
105
+ export function requestControl(scope, options) {
106
+ const concurrency = options.maxConcurrency ?? 8;
107
+ const cooldown = options.cooldown ?? { threshold: 5, windowMs: 10_000, durationMs: 30_000 };
108
+ const signature = JSON.stringify([concurrency, cooldown.threshold, cooldown.windowMs, cooldown.durationMs]);
109
+ const existing = controls.get(scope);
110
+ if (existing && existing.signature !== signature)
111
+ throw new TypeError("Clients sharing a tenant and transport must use the same concurrency/cooldown settings");
112
+ if (existing)
113
+ return existing.value;
114
+ const value = new RequestControl(concurrency, cooldown);
115
+ controls.set(scope, { signature, value });
116
+ return value;
117
+ }
118
+ export function emit(options, event) {
119
+ const hooks = options.hooks;
120
+ const handler = event.type === "request" ? hooks?.onRequest : event.type === "response" ? hooks?.onResponse : event.type === "tokenMint" ? hooks?.onTokenMint : event.type === "throttle" ? hooks?.onThrottle : hooks?.onCooldown;
121
+ const safe = Object.freeze({ ...event });
122
+ // Observers must not turn a completed write into a thrown error or a replay.
123
+ try {
124
+ void Promise.resolve(handler?.(safe)).catch(() => { });
125
+ }
126
+ catch { /* Observer failures cannot change request outcomes. */ }
127
+ try {
128
+ void Promise.resolve(options.logger?.(safe)).catch(() => { });
129
+ }
130
+ catch { /* Same boundary for user-supplied loggers. */ }
131
+ }
132
+ //# sourceMappingURL=control.js.map