@uncefact/untp-utils 0.2.0 → 0.3.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 (238) hide show
  1. package/README.md +40 -12
  2. package/artefacts/context/untp/0.6.0/dcc.json +1241 -0
  3. package/artefacts/context/untp/0.6.0/dfr.json +619 -0
  4. package/artefacts/context/untp/0.6.0/dia.json +162 -0
  5. package/artefacts/context/untp/0.6.0/dpp.json +1078 -0
  6. package/artefacts/context/untp/0.6.0/dte.json +1285 -0
  7. package/artefacts/context/untp/0.6.1/dcc.json +1388 -0
  8. package/artefacts/context/untp/0.6.1/dfr.json +690 -0
  9. package/artefacts/context/untp/0.6.1/dia.json +196 -0
  10. package/artefacts/context/untp/0.6.1/dpp.json +1164 -0
  11. package/artefacts/context/untp/0.6.1/dte.json +1340 -0
  12. package/artefacts/context/untp/0.7.0/untp.json +3493 -0
  13. package/artefacts/context/vcdm/2/credentials.json +301 -0
  14. package/artefacts/manifest.json +214 -0
  15. package/artefacts/schema/untp/0.6.0/dcc.json +1131 -0
  16. package/artefacts/schema/untp/0.6.0/dfr.json +1028 -0
  17. package/artefacts/schema/untp/0.6.0/dia.json +236 -0
  18. package/artefacts/schema/untp/0.6.0/dpp.json +1219 -0
  19. package/artefacts/schema/untp/0.6.0/dte.json +845 -0
  20. package/artefacts/schema/untp/0.6.1/dcc.json +1196 -0
  21. package/artefacts/schema/untp/0.6.1/dfr.json +1037 -0
  22. package/artefacts/schema/untp/0.6.1/dia.json +236 -0
  23. package/artefacts/schema/untp/0.6.1/dpp.json +1229 -0
  24. package/artefacts/schema/untp/0.6.1/dte.json +845 -0
  25. package/artefacts/schema/untp/0.7.0/cvc.json +701 -0
  26. package/artefacts/schema/untp/0.7.0/dcc.json +1133 -0
  27. package/artefacts/schema/untp/0.7.0/dfr.json +1245 -0
  28. package/artefacts/schema/untp/0.7.0/dia.json +443 -0
  29. package/artefacts/schema/untp/0.7.0/dpp.json +1300 -0
  30. package/artefacts/schema/untp/0.7.0/dte.json +1226 -0
  31. package/artefacts/schema/untp/0.7.0/linkset.json +119 -0
  32. package/artefacts/schema/vcdm/2/verifiable-credential.json +318 -0
  33. package/build/artefacts/urls.d.ts +6 -0
  34. package/build/artefacts/urls.js +8 -0
  35. package/build/artefacts/urls.js.map +1 -1
  36. package/build/artefacts/urls.test.js +9 -1
  37. package/build/artefacts/urls.test.js.map +1 -1
  38. package/build/bundle/artefacts/context/untp/0.6.0/dcc.d.ts +2 -0
  39. package/build/bundle/artefacts/context/untp/0.6.0/dcc.js +1244 -0
  40. package/build/bundle/artefacts/context/untp/0.6.0/dcc.js.map +1 -0
  41. package/build/bundle/artefacts/context/untp/0.6.0/dfr.d.ts +2 -0
  42. package/build/bundle/artefacts/context/untp/0.6.0/dfr.js +622 -0
  43. package/build/bundle/artefacts/context/untp/0.6.0/dfr.js.map +1 -0
  44. package/build/bundle/artefacts/context/untp/0.6.0/dia.d.ts +2 -0
  45. package/build/bundle/artefacts/context/untp/0.6.0/dia.js +165 -0
  46. package/build/bundle/artefacts/context/untp/0.6.0/dia.js.map +1 -0
  47. package/build/bundle/artefacts/context/untp/0.6.0/dpp.d.ts +2 -0
  48. package/build/bundle/artefacts/context/untp/0.6.0/dpp.js +1081 -0
  49. package/build/bundle/artefacts/context/untp/0.6.0/dpp.js.map +1 -0
  50. package/build/bundle/artefacts/context/untp/0.6.0/dte.d.ts +2 -0
  51. package/build/bundle/artefacts/context/untp/0.6.0/dte.js +1288 -0
  52. package/build/bundle/artefacts/context/untp/0.6.0/dte.js.map +1 -0
  53. package/build/bundle/artefacts/context/untp/0.6.1/dcc.d.ts +2 -0
  54. package/build/bundle/artefacts/context/untp/0.6.1/dcc.js +1391 -0
  55. package/build/bundle/artefacts/context/untp/0.6.1/dcc.js.map +1 -0
  56. package/build/bundle/artefacts/context/untp/0.6.1/dfr.d.ts +2 -0
  57. package/build/bundle/artefacts/context/untp/0.6.1/dfr.js +693 -0
  58. package/build/bundle/artefacts/context/untp/0.6.1/dfr.js.map +1 -0
  59. package/build/bundle/artefacts/context/untp/0.6.1/dia.d.ts +2 -0
  60. package/build/bundle/artefacts/context/untp/0.6.1/dia.js +199 -0
  61. package/build/bundle/artefacts/context/untp/0.6.1/dia.js.map +1 -0
  62. package/build/bundle/artefacts/context/untp/0.6.1/dpp.d.ts +2 -0
  63. package/build/bundle/artefacts/context/untp/0.6.1/dpp.js +1167 -0
  64. package/build/bundle/artefacts/context/untp/0.6.1/dpp.js.map +1 -0
  65. package/build/bundle/artefacts/context/untp/0.6.1/dte.d.ts +2 -0
  66. package/build/bundle/artefacts/context/untp/0.6.1/dte.js +1343 -0
  67. package/build/bundle/artefacts/context/untp/0.6.1/dte.js.map +1 -0
  68. package/build/bundle/artefacts/context/untp/0.7.0/untp.d.ts +2 -0
  69. package/build/bundle/artefacts/context/untp/0.7.0/untp.js +3496 -0
  70. package/build/bundle/artefacts/context/untp/0.7.0/untp.js.map +1 -0
  71. package/build/bundle/artefacts/context/vcdm/2/credentials.d.ts +2 -0
  72. package/build/bundle/artefacts/context/vcdm/2/credentials.js +304 -0
  73. package/build/bundle/artefacts/context/vcdm/2/credentials.js.map +1 -0
  74. package/build/bundle/artefacts/schema/untp/0.6.0/dcc.d.ts +2 -0
  75. package/build/bundle/artefacts/schema/untp/0.6.0/dcc.js +1134 -0
  76. package/build/bundle/artefacts/schema/untp/0.6.0/dcc.js.map +1 -0
  77. package/build/bundle/artefacts/schema/untp/0.6.0/dfr.d.ts +2 -0
  78. package/build/bundle/artefacts/schema/untp/0.6.0/dfr.js +1031 -0
  79. package/build/bundle/artefacts/schema/untp/0.6.0/dfr.js.map +1 -0
  80. package/build/bundle/artefacts/schema/untp/0.6.0/dia.d.ts +2 -0
  81. package/build/bundle/artefacts/schema/untp/0.6.0/dia.js +239 -0
  82. package/build/bundle/artefacts/schema/untp/0.6.0/dia.js.map +1 -0
  83. package/build/bundle/artefacts/schema/untp/0.6.0/dpp.d.ts +2 -0
  84. package/build/bundle/artefacts/schema/untp/0.6.0/dpp.js +1222 -0
  85. package/build/bundle/artefacts/schema/untp/0.6.0/dpp.js.map +1 -0
  86. package/build/bundle/artefacts/schema/untp/0.6.0/dte.d.ts +2 -0
  87. package/build/bundle/artefacts/schema/untp/0.6.0/dte.js +848 -0
  88. package/build/bundle/artefacts/schema/untp/0.6.0/dte.js.map +1 -0
  89. package/build/bundle/artefacts/schema/untp/0.6.1/dcc.d.ts +2 -0
  90. package/build/bundle/artefacts/schema/untp/0.6.1/dcc.js +1199 -0
  91. package/build/bundle/artefacts/schema/untp/0.6.1/dcc.js.map +1 -0
  92. package/build/bundle/artefacts/schema/untp/0.6.1/dfr.d.ts +2 -0
  93. package/build/bundle/artefacts/schema/untp/0.6.1/dfr.js +1040 -0
  94. package/build/bundle/artefacts/schema/untp/0.6.1/dfr.js.map +1 -0
  95. package/build/bundle/artefacts/schema/untp/0.6.1/dia.d.ts +2 -0
  96. package/build/bundle/artefacts/schema/untp/0.6.1/dia.js +239 -0
  97. package/build/bundle/artefacts/schema/untp/0.6.1/dia.js.map +1 -0
  98. package/build/bundle/artefacts/schema/untp/0.6.1/dpp.d.ts +2 -0
  99. package/build/bundle/artefacts/schema/untp/0.6.1/dpp.js +1232 -0
  100. package/build/bundle/artefacts/schema/untp/0.6.1/dpp.js.map +1 -0
  101. package/build/bundle/artefacts/schema/untp/0.6.1/dte.d.ts +2 -0
  102. package/build/bundle/artefacts/schema/untp/0.6.1/dte.js +848 -0
  103. package/build/bundle/artefacts/schema/untp/0.6.1/dte.js.map +1 -0
  104. package/build/bundle/artefacts/schema/untp/0.7.0/cvc.d.ts +2 -0
  105. package/build/bundle/artefacts/schema/untp/0.7.0/cvc.js +704 -0
  106. package/build/bundle/artefacts/schema/untp/0.7.0/cvc.js.map +1 -0
  107. package/build/bundle/artefacts/schema/untp/0.7.0/dcc.d.ts +2 -0
  108. package/build/bundle/artefacts/schema/untp/0.7.0/dcc.js +1136 -0
  109. package/build/bundle/artefacts/schema/untp/0.7.0/dcc.js.map +1 -0
  110. package/build/bundle/artefacts/schema/untp/0.7.0/dfr.d.ts +2 -0
  111. package/build/bundle/artefacts/schema/untp/0.7.0/dfr.js +1248 -0
  112. package/build/bundle/artefacts/schema/untp/0.7.0/dfr.js.map +1 -0
  113. package/build/bundle/artefacts/schema/untp/0.7.0/dia.d.ts +2 -0
  114. package/build/bundle/artefacts/schema/untp/0.7.0/dia.js +446 -0
  115. package/build/bundle/artefacts/schema/untp/0.7.0/dia.js.map +1 -0
  116. package/build/bundle/artefacts/schema/untp/0.7.0/dpp.d.ts +2 -0
  117. package/build/bundle/artefacts/schema/untp/0.7.0/dpp.js +1303 -0
  118. package/build/bundle/artefacts/schema/untp/0.7.0/dpp.js.map +1 -0
  119. package/build/bundle/artefacts/schema/untp/0.7.0/dte.d.ts +2 -0
  120. package/build/bundle/artefacts/schema/untp/0.7.0/dte.js +1229 -0
  121. package/build/bundle/artefacts/schema/untp/0.7.0/dte.js.map +1 -0
  122. package/build/bundle/artefacts/schema/untp/0.7.0/linkset.d.ts +2 -0
  123. package/build/bundle/artefacts/schema/untp/0.7.0/linkset.js +122 -0
  124. package/build/bundle/artefacts/schema/untp/0.7.0/linkset.js.map +1 -0
  125. package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.d.ts +2 -0
  126. package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.js +321 -0
  127. package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.js.map +1 -0
  128. package/build/bundle/fallback.d.ts +41 -0
  129. package/build/bundle/fallback.js +62 -0
  130. package/build/bundle/fallback.js.map +1 -0
  131. package/build/bundle/fallback.test.d.ts +1 -0
  132. package/build/bundle/fallback.test.js +124 -0
  133. package/build/bundle/fallback.test.js.map +1 -0
  134. package/build/bundle/index.d.ts +4 -0
  135. package/build/bundle/index.js +77 -0
  136. package/build/bundle/index.js.map +1 -0
  137. package/build/bundle/lookup.d.ts +14 -0
  138. package/build/bundle/lookup.js +40 -0
  139. package/build/bundle/lookup.js.map +1 -0
  140. package/build/bundle/lookup.test.d.ts +1 -0
  141. package/build/bundle/lookup.test.js +70 -0
  142. package/build/bundle/lookup.test.js.map +1 -0
  143. package/build/bundle/public.d.ts +25 -0
  144. package/build/bundle/public.js +56 -0
  145. package/build/bundle/public.js.map +1 -0
  146. package/build/bundle/public.test.d.ts +1 -0
  147. package/build/bundle/public.test.js +35 -0
  148. package/build/bundle/public.test.js.map +1 -0
  149. package/build/bundle/refresh-classify.test.d.ts +1 -0
  150. package/build/bundle/refresh-classify.test.js +20 -0
  151. package/build/bundle/refresh-classify.test.js.map +1 -0
  152. package/build/bundle/refresh-main.test.d.ts +1 -0
  153. package/build/bundle/refresh-main.test.js +98 -0
  154. package/build/bundle/refresh-main.test.js.map +1 -0
  155. package/build/common/as-date-time.d.ts +13 -0
  156. package/build/common/as-date-time.js +44 -0
  157. package/build/common/as-date-time.js.map +1 -0
  158. package/build/common/as-date-time.test.d.ts +1 -0
  159. package/build/common/as-date-time.test.js +42 -0
  160. package/build/common/as-date-time.test.js.map +1 -0
  161. package/build/common/as-non-empty-string.d.ts +11 -4
  162. package/build/common/as-non-empty-string.js +15 -5
  163. package/build/common/as-non-empty-string.js.map +1 -1
  164. package/build/common/as-non-empty-string.test.js +7 -2
  165. package/build/common/as-non-empty-string.test.js.map +1 -1
  166. package/build/common/index.d.ts +4 -0
  167. package/build/common/index.js +5 -0
  168. package/build/common/index.js.map +1 -0
  169. package/build/common/validity-window.d.ts +45 -0
  170. package/build/common/validity-window.js +68 -0
  171. package/build/common/validity-window.js.map +1 -0
  172. package/build/common/validity-window.test.d.ts +1 -0
  173. package/build/common/validity-window.test.js +67 -0
  174. package/build/common/validity-window.test.js.map +1 -0
  175. package/build/loaders/index.d.ts +2 -0
  176. package/build/loaders/index.js +1 -0
  177. package/build/loaders/index.js.map +1 -1
  178. package/build/loaders/jsonld-document-loader.d.ts +7 -5
  179. package/build/loaders/jsonld-document-loader.js +11 -2
  180. package/build/loaders/jsonld-document-loader.js.map +1 -1
  181. package/build/loaders/jsonld-document-loader.test.js +41 -1
  182. package/build/loaders/jsonld-document-loader.test.js.map +1 -1
  183. package/build/loaders/schema-loader.d.ts +13 -3
  184. package/build/loaders/schema-loader.js +24 -8
  185. package/build/loaders/schema-loader.js.map +1 -1
  186. package/build/loaders/schema-loader.test.js +56 -1
  187. package/build/loaders/schema-loader.test.js.map +1 -1
  188. package/build/node/errors.d.ts +9 -3
  189. package/build/node/errors.js +9 -3
  190. package/build/node/errors.js.map +1 -1
  191. package/build/node/index.d.ts +1 -1
  192. package/build/node/index.js +1 -1
  193. package/build/node/index.js.map +1 -1
  194. package/build/node/is-private-ip.d.ts +8 -4
  195. package/build/node/is-private-ip.fail-closed.test.d.ts +1 -0
  196. package/build/node/is-private-ip.fail-closed.test.js +36 -0
  197. package/build/node/is-private-ip.fail-closed.test.js.map +1 -0
  198. package/build/node/is-private-ip.js +77 -12
  199. package/build/node/is-private-ip.js.map +1 -1
  200. package/build/node/is-private-ip.test.js +28 -0
  201. package/build/node/is-private-ip.test.js.map +1 -1
  202. package/build/node/validate-public-url.d.ts +43 -11
  203. package/build/node/validate-public-url.js +68 -34
  204. package/build/node/validate-public-url.js.map +1 -1
  205. package/build/node/validate-public-url.test.js +161 -14
  206. package/build/node/validate-public-url.test.js.map +1 -1
  207. package/build/resolvers/errors.d.ts +36 -6
  208. package/build/resolvers/errors.js +41 -7
  209. package/build/resolvers/errors.js.map +1 -1
  210. package/build/resolvers/resolve-document-if-changed.test.js +7 -0
  211. package/build/resolvers/resolve-document-if-changed.test.js.map +1 -1
  212. package/build/resolvers/resolve-document.d.ts +29 -9
  213. package/build/resolvers/resolve-document.integration.test.d.ts +1 -0
  214. package/build/resolvers/resolve-document.integration.test.js +108 -0
  215. package/build/resolvers/resolve-document.integration.test.js.map +1 -0
  216. package/build/resolvers/resolve-document.js +129 -49
  217. package/build/resolvers/resolve-document.js.map +1 -1
  218. package/build/resolvers/resolve-document.test.js +462 -12
  219. package/build/resolvers/resolve-document.test.js.map +1 -1
  220. package/build/resolvers/resolve-json-document.d.ts +11 -8
  221. package/build/resolvers/resolve-json-document.js +7 -6
  222. package/build/resolvers/resolve-json-document.js.map +1 -1
  223. package/build/resolvers/resolve-json-document.test.js +21 -4
  224. package/build/resolvers/resolve-json-document.test.js.map +1 -1
  225. package/build/validation/describe-jsonld-failure.d.ts +48 -8
  226. package/build/validation/describe-jsonld-failure.js +94 -27
  227. package/build/validation/describe-jsonld-failure.js.map +1 -1
  228. package/build/validation/describe-jsonld-failure.test.js +98 -13
  229. package/build/validation/describe-jsonld-failure.test.js.map +1 -1
  230. package/build/validation/index.d.ts +3 -2
  231. package/build/validation/index.js +2 -2
  232. package/build/validation/index.js.map +1 -1
  233. package/build/validation/validate-jsonld.d.ts +29 -3
  234. package/build/validation/validate-jsonld.js +49 -22
  235. package/build/validation/validate-jsonld.js.map +1 -1
  236. package/build/validation/validate-jsonld.test.js +62 -5
  237. package/build/validation/validate-jsonld.test.js.map +1 -1
  238. package/package.json +23 -3
@@ -0,0 +1,98 @@
1
+ // Drives the refresh script's main() against a temporary copy of the bundle
2
+ // with fetch stubbed, so the refusal, repair and dry-run paths are proven
3
+ // without the network. The script is loaded by runtime URL (tsc's rootDir is
4
+ // `src`); UNTP_ARTEFACTS_ROOT points it at the temporary tree.
5
+ import { jest } from '@jest/globals';
6
+ import { cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ const scriptUrl = new URL('../../scripts/refresh-artefacts.mjs', import.meta.url).href;
10
+ const packageRoot = new URL('../../', import.meta.url).pathname;
11
+ describe('refresh-artefacts main', () => {
12
+ let root;
13
+ let script;
14
+ let exit;
15
+ let stubbed;
16
+ let fetchImpl;
17
+ const run = (argv) => script.main(argv, { fetchImpl });
18
+ beforeAll(async () => {
19
+ // The script resolves its root when it loads, so the temporary root is
20
+ // fixed before the import and refilled from the real bundle per test.
21
+ root = await mkdtemp(join(tmpdir(), 'untp-artefacts-'));
22
+ process.env.UNTP_ARTEFACTS_ROOT = root;
23
+ script = (await import(scriptUrl));
24
+ });
25
+ afterAll(async () => {
26
+ delete process.env.UNTP_ARTEFACTS_ROOT;
27
+ await rm(root, { recursive: true, force: true });
28
+ });
29
+ beforeEach(async () => {
30
+ await rm(join(root, 'artefacts'), { recursive: true, force: true });
31
+ await rm(join(root, 'src'), { recursive: true, force: true });
32
+ await cp(join(packageRoot, 'artefacts'), join(root, 'artefacts'), { recursive: true });
33
+ await cp(join(packageRoot, 'src', 'bundle'), join(root, 'src', 'bundle'), { recursive: true });
34
+ // Serve every source from the bundled copy on disk, so "published" equals "bundled".
35
+ stubbed = new Map();
36
+ for (const { file, source } of script.ARTEFACTS) {
37
+ stubbed.set(source, await readFile(join(packageRoot, 'artefacts', file), 'utf8'));
38
+ }
39
+ fetchImpl = (async (url) => {
40
+ const body = stubbed.get(String(url));
41
+ return body === undefined
42
+ ? new Response('missing', { status: 404 })
43
+ : new Response(body, { status: 200, headers: { 'content-type': 'application/json' } });
44
+ });
45
+ exit = jest.spyOn(process, 'exit').mockImplementation(((code) => {
46
+ throw new Error(`exit ${code}`);
47
+ }));
48
+ jest.spyOn(console, 'log').mockImplementation(() => { });
49
+ jest.spyOn(console, 'error').mockImplementation(() => { });
50
+ });
51
+ afterEach(() => {
52
+ jest.restoreAllMocks();
53
+ });
54
+ const corruptFile = 'context/untp/0.6.0/dia.json';
55
+ it('--check passes on a matching tree and writes nothing', async () => {
56
+ const before = await readFile(join(root, 'artefacts', 'manifest.json'), 'utf8');
57
+ await run(['--check']);
58
+ expect(exit).not.toHaveBeenCalled();
59
+ expect(await readFile(join(root, 'artefacts', 'manifest.json'), 'utf8')).toBe(before);
60
+ });
61
+ it('--check refuses a corrupt bundled copy, naming it, and writes nothing', async () => {
62
+ await writeFile(join(root, 'artefacts', corruptFile), '{not json');
63
+ await expect(run(['--check'])).rejects.toThrow('exit 1');
64
+ expect(await readFile(join(root, 'artefacts', corruptFile), 'utf8')).toBe('{not json');
65
+ });
66
+ it('a plain refresh also refuses a corrupt copy rather than hashing it', async () => {
67
+ await writeFile(join(root, 'artefacts', corruptFile), '{not json');
68
+ await expect(run([])).rejects.toThrow('exit 1');
69
+ expect(await readFile(join(root, 'artefacts', corruptFile), 'utf8')).toBe('{not json');
70
+ });
71
+ it('--force repairs a corrupt copy from the published source', async () => {
72
+ await writeFile(join(root, 'artefacts', corruptFile), '{not json');
73
+ await run(['--force']);
74
+ expect(exit).not.toHaveBeenCalled();
75
+ expect(JSON.parse(await readFile(join(root, 'artefacts', corruptFile), 'utf8'))).toHaveProperty('@context');
76
+ });
77
+ it('refuses a published copy that differs unless forced', async () => {
78
+ const entry = script.ARTEFACTS.find((a) => a.file === corruptFile);
79
+ stubbed.set(entry.source, JSON.stringify({ '@context': { changed: true } }));
80
+ await expect(run(['--check'])).rejects.toThrow('exit 1');
81
+ await expect(run([])).rejects.toThrow('exit 1');
82
+ await run(['--force']);
83
+ expect(JSON.parse(await readFile(join(root, 'artefacts', corruptFile), 'utf8'))).toEqual({
84
+ '@context': { changed: true },
85
+ });
86
+ });
87
+ it('writes nothing when a later fetch fails', async () => {
88
+ const entry = script.ARTEFACTS[script.ARTEFACTS.length - 1];
89
+ stubbed.delete(entry.source);
90
+ await writeFile(join(root, 'artefacts', corruptFile), '{not json');
91
+ await expect(run(['--force'])).rejects.toThrow(/answered 404/);
92
+ expect(await readFile(join(root, 'artefacts', corruptFile), 'utf8')).toBe('{not json');
93
+ });
94
+ it('rejects --check together with --force', async () => {
95
+ await expect(run(['--check', '--force'])).rejects.toThrow('exit 1');
96
+ });
97
+ });
98
+ //# sourceMappingURL=refresh-main.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-main.test.js","sourceRoot":"","sources":["../../src/bundle/refresh-main.test.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,6EAA6E;AAC7E,+DAA+D;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMjC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,qCAAqC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACvF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;AAEhE,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,IAAI,IAAY,CAAC;IACjB,IAAI,MAAc,CAAC;IACnB,IAAI,IAA6C,CAAC;IAClD,IAAI,OAA4B,CAAC;IACjC,IAAI,SAAuB,CAAC;IAC5B,MAAM,GAAG,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAEjE,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,uEAAuE;QACvE,sEAAsE;QACtE,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,IAAI,CAAC;QACvC,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAW,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACvC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/F,qFAAqF;QACrF,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,CAAC;QACD,SAAS,GAAG,CAAC,KAAK,EAAE,GAA2B,EAAE,EAAE;YACjD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACtC,OAAO,IAAI,KAAK,SAAS;gBACvB,CAAC,CAAC,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;gBAC1C,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;QAC3F,CAAC,CAA4B,CAAC;QAC9B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAa,EAAE,EAAE;YACvE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QAClC,CAAC,CAAU,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,6BAA6B,CAAC;IAElD,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;QAChF,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAE,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC7E,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACvF,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACnE,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns the date and time `value` refers to, when `value` is a well-formed
3
+ * RFC 3339 date-time for a day that exists. Otherwise returns `undefined`.
4
+ *
5
+ * `new Date()` alone is not enough. It accepts loose forms such as `2024-01-15`
6
+ * or `January 15, 2024`, and it quietly normalises an impossible date, turning
7
+ * `2024-02-30` into 1 March, which would report a date the document never
8
+ * asserted.
9
+ *
10
+ * `undefined` rather than `null` because this reports what a document does not
11
+ * usably contain. How to record that absence is the caller's decision.
12
+ */
13
+ export declare function asDateTime(value: unknown): Date | undefined;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Matches the JSON Schema `date-time` format (RFC 3339 section 5.6).
3
+ *
4
+ * As of UNTP 0.7.0, every data model declares a credential's `validFrom` and
5
+ * `validUntil` as `"type": "string", "format": "date-time"`, and that is what
6
+ * this system validates a payload against before signing it.
7
+ *
8
+ * The W3C VC Data Model 2.0 is looser: it requires only an XML Schema 1.1
9
+ * `dateTime`, which permits an offsetless local time, years outside four
10
+ * digits, and `24:00:00` (https://www.w3.org/TR/vc-data-model-2.0/#validity-period).
11
+ * A credential issued elsewhere may therefore carry a value this rejects, and
12
+ * it is reported as absent rather than guessed: with no timezone there is no
13
+ * way to tell which moment a local time such as `09:00` refers to.
14
+ */
15
+ const RFC3339_DATE_TIME = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
16
+ /**
17
+ * Returns the date and time `value` refers to, when `value` is a well-formed
18
+ * RFC 3339 date-time for a day that exists. Otherwise returns `undefined`.
19
+ *
20
+ * `new Date()` alone is not enough. It accepts loose forms such as `2024-01-15`
21
+ * or `January 15, 2024`, and it quietly normalises an impossible date, turning
22
+ * `2024-02-30` into 1 March, which would report a date the document never
23
+ * asserted.
24
+ *
25
+ * `undefined` rather than `null` because this reports what a document does not
26
+ * usably contain. How to record that absence is the caller's decision.
27
+ */
28
+ export function asDateTime(value) {
29
+ if (typeof value !== 'string' || !RFC3339_DATE_TIME.test(value))
30
+ return undefined;
31
+ const parsed = new Date(value);
32
+ if (Number.isNaN(parsed.getTime()))
33
+ return undefined;
34
+ // Round-trip the calendar day: a normalised day (30 February) lands on a
35
+ // different day-of-month than the string names.
36
+ const day = Number(value.slice(8, 10));
37
+ const offsetMatch = /([+-])(\d{2}):(\d{2})$/.exec(value);
38
+ const offsetMinutes = offsetMatch
39
+ ? (offsetMatch[1] === '-' ? -1 : 1) * (Number(offsetMatch[2]) * 60 + Number(offsetMatch[3]))
40
+ : 0;
41
+ const local = new Date(parsed.getTime() + offsetMinutes * 60_000);
42
+ return local.getUTCDate() === day ? parsed : undefined;
43
+ }
44
+ //# sourceMappingURL=as-date-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-date-time.js","sourceRoot":"","sources":["../../src/common/as-date-time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,iBAAiB,GAAG,wEAAwE,CAAC;AAEnG;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAClF,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,yEAAyE;IACzE,gDAAgD;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,WAAW;QAC/B,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5F,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,aAAa,GAAG,MAAM,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC,UAAU,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { asDateTime } from './as-date-time.js';
2
+ describe('asDateTime', () => {
3
+ it('returns the date and time a well-formed UTC value refers to', () => {
4
+ expect(asDateTime('2024-01-15T00:00:00Z')).toEqual(new Date('2024-01-15T00:00:00.000Z'));
5
+ expect(asDateTime('2024-01-15T10:30:00.250Z')).toEqual(new Date('2024-01-15T10:30:00.250Z'));
6
+ });
7
+ it('reads an offset value as the same moment in UTC', () => {
8
+ expect(asDateTime('2024-01-15T23:30:00+10:00')).toEqual(new Date('2024-01-15T13:30:00.000Z'));
9
+ expect(asDateTime('2024-01-15T00:30:00-05:00')).toEqual(new Date('2024-01-15T05:30:00.000Z'));
10
+ });
11
+ it('returns undefined for a day the calendar does not have, rather than normalising it', () => {
12
+ expect(asDateTime('2024-02-30T00:00:00Z')).toBeUndefined();
13
+ expect(asDateTime('2023-02-29T00:00:00Z')).toBeUndefined();
14
+ });
15
+ it('accepts a real leap day', () => {
16
+ expect(asDateTime('2024-02-29T00:00:00Z')).toEqual(new Date('2024-02-29T00:00:00.000Z'));
17
+ });
18
+ it('returns undefined for a leap second', () => {
19
+ // Well-formed RFC 3339, but JavaScript has no value for it.
20
+ expect(asDateTime('1990-12-31T23:59:60Z')).toBeUndefined();
21
+ });
22
+ it.each([
23
+ ['a date with no time', '2024-01-15'],
24
+ ['a human-readable date', 'January 15, 2024'],
25
+ ['a bare number', '0'],
26
+ ['nonsense', 'not-a-date'],
27
+ ['the empty string', ''],
28
+ ['a time with no zone', '2024-01-15T00:00:00'],
29
+ ])('returns undefined for %s', (_label, value) => {
30
+ expect(asDateTime(value)).toBeUndefined();
31
+ });
32
+ it.each([
33
+ ['number', 1705276800000],
34
+ ['null', null],
35
+ ['undefined', undefined],
36
+ ['Date', new Date()],
37
+ ['object', {}],
38
+ ])('returns undefined for a non-string %s', (_label, value) => {
39
+ expect(asDateTime(value)).toBeUndefined();
40
+ });
41
+ });
42
+ //# sourceMappingURL=as-date-time.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-date-time.test.js","sourceRoot":"","sources":["../../src/common/as-date-time.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;QACzF,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;QAC9F,MAAM,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC5F,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC3D,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,4DAA4D;QAC5D,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,qBAAqB,EAAE,YAAY,CAAC;QACrC,CAAC,uBAAuB,EAAE,kBAAkB,CAAC;QAC7C,CAAC,eAAe,EAAE,GAAG,CAAC;QACtB,CAAC,UAAU,EAAE,YAAY,CAAC;QAC1B,CAAC,kBAAkB,EAAE,EAAE,CAAC;QACxB,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KAC/C,CAAC,CAAC,0BAA0B,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,QAAQ,EAAE,aAAa,CAAC;QACzB,CAAC,MAAM,EAAE,IAAI,CAAC;QACd,CAAC,WAAW,EAAE,SAAS,CAAC;QACxB,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACpB,CAAC,QAAQ,EAAE,EAAE,CAAC;KACf,CAAC,CAAC,uCAAuC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAC5D,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,7 +1,14 @@
1
1
  /**
2
- * Returns `value` if it is a non-empty string, otherwise `undefined`.
3
- * Useful inside parsers that emit a structured failure separately when a
4
- * required field is missing or malformed, so the type check and the
5
- * failure-emission policy stay decoupled.
2
+ * Returns `value` trimmed if it is a string with non-whitespace content,
3
+ * otherwise `undefined`.
4
+ *
5
+ * Surrounding whitespace is never meaningful in the values this reads (names,
6
+ * identifiers, labels), and a whitespace-only string carries no more
7
+ * information than an absent one, so both collapse to `undefined`.
8
+ *
9
+ * `undefined` rather than `null` because this reports what a document does
10
+ * not contain. Recording that absence as a stored or transmitted value is a
11
+ * separate decision, made by the caller at the boundary where a read becomes
12
+ * a row or a response.
6
13
  */
7
14
  export declare function asNonEmptyString(value: unknown): string | undefined;
@@ -1,10 +1,20 @@
1
1
  /**
2
- * Returns `value` if it is a non-empty string, otherwise `undefined`.
3
- * Useful inside parsers that emit a structured failure separately when a
4
- * required field is missing or malformed, so the type check and the
5
- * failure-emission policy stay decoupled.
2
+ * Returns `value` trimmed if it is a string with non-whitespace content,
3
+ * otherwise `undefined`.
4
+ *
5
+ * Surrounding whitespace is never meaningful in the values this reads (names,
6
+ * identifiers, labels), and a whitespace-only string carries no more
7
+ * information than an absent one, so both collapse to `undefined`.
8
+ *
9
+ * `undefined` rather than `null` because this reports what a document does
10
+ * not contain. Recording that absence as a stored or transmitted value is a
11
+ * separate decision, made by the caller at the boundary where a read becomes
12
+ * a row or a response.
6
13
  */
7
14
  export function asNonEmptyString(value) {
8
- return typeof value === 'string' && value.length > 0 ? value : undefined;
15
+ if (typeof value !== 'string')
16
+ return undefined;
17
+ const trimmed = value.trim();
18
+ return trimmed.length > 0 ? trimmed : undefined;
9
19
  }
10
20
  //# sourceMappingURL=as-non-empty-string.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"as-non-empty-string.js","sourceRoot":"","sources":["../../src/common/as-non-empty-string.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"as-non-empty-string.js","sourceRoot":"","sources":["../../src/common/as-non-empty-string.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC"}
@@ -2,11 +2,16 @@ import { asNonEmptyString } from './as-non-empty-string.js';
2
2
  describe('asNonEmptyString', () => {
3
3
  it('returns the value for a non-empty string', () => {
4
4
  expect(asNonEmptyString('foo')).toBe('foo');
5
- expect(asNonEmptyString(' ')).toBe(' ');
6
5
  expect(asNonEmptyString('0')).toBe('0');
7
6
  });
8
- it('returns undefined for the empty string', () => {
7
+ it('trims surrounding whitespace from the value it returns', () => {
8
+ expect(asNonEmptyString(' foo ')).toBe('foo');
9
+ expect(asNonEmptyString('\tfoo\n')).toBe('foo');
10
+ });
11
+ it('returns undefined for the empty string and for whitespace only', () => {
9
12
  expect(asNonEmptyString('')).toBeUndefined();
13
+ expect(asNonEmptyString(' ')).toBeUndefined();
14
+ expect(asNonEmptyString('\n\t')).toBeUndefined();
10
15
  });
11
16
  it.each([
12
17
  ['number', 0],
@@ -1 +1 @@
1
- {"version":3,"file":"as-non-empty-string.test.js","sourceRoot":"","sources":["../../src/common/as-non-empty-string.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,QAAQ,EAAE,CAAC,CAAC;QACb,CAAC,mBAAmB,EAAE,EAAE,CAAC;QACzB,CAAC,MAAM,EAAE,IAAI,CAAC;QACd,CAAC,WAAW,EAAE,SAAS,CAAC;QACxB,CAAC,cAAc,EAAE,IAAI,CAAC;QACtB,CAAC,eAAe,EAAE,KAAK,CAAC;QACxB,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,KAAK,EAAE,GAAG,CAAC;KACb,CAAC,CAAC,0BAA0B,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"as-non-empty-string.test.js","sourceRoot":"","sources":["../../src/common/as-non-empty-string.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAChD,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,QAAQ,EAAE,CAAC,CAAC;QACb,CAAC,mBAAmB,EAAE,EAAE,CAAC;QACzB,CAAC,MAAM,EAAE,IAAI,CAAC;QACd,CAAC,WAAW,EAAE,SAAS,CAAC;QACxB,CAAC,cAAc,EAAE,IAAI,CAAC;QACtB,CAAC,eAAe,EAAE,KAAK,CAAC;QACxB,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,KAAK,EAAE,GAAG,CAAC;KACb,CAAC,CAAC,0BAA0B,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './as-date-time.js';
2
+ export * from './as-non-empty-string.js';
3
+ export * from './require-string.js';
4
+ export * from './validity-window.js';
@@ -0,0 +1,5 @@
1
+ export * from './as-date-time.js';
2
+ export * from './as-non-empty-string.js';
3
+ export * from './require-string.js';
4
+ export * from './validity-window.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The outcome of judging a credential's validity window at a point in time.
3
+ *
4
+ * - `pass`: the credential is within its window now. A credential with
5
+ * neither bound is "valid indefinitely" (VCDM 2.0, Validity Period) and
6
+ * passes.
7
+ * - `fail`: the credential is outside its window (`expired`,
8
+ * `not_yet_valid`), or a bound is present but is malformed or in a form
9
+ * this evaluator does not support (`unreadable_bound`). A present bound is
10
+ * never dropped: a credential whose bound cannot be read is not the same
11
+ * as a credential without one.
12
+ */
13
+ export type ValidityWindowOutcome = {
14
+ result: 'pass';
15
+ } | {
16
+ result: 'fail';
17
+ reason: 'expired' | 'not_yet_valid' | 'unreadable_bound';
18
+ message: string;
19
+ };
20
+ /**
21
+ * Judges the VCDM 2.0 `validFrom` and `validUntil` claims against `now`,
22
+ * comparing them as points on a timeline as the data model requires.
23
+ *
24
+ * Supported form: an XML Schema `dateTimeStamp` written as
25
+ * `YYYY-MM-DDTHH:MM:SS[.fraction]` followed by `Z` or an offset within
26
+ * `-14:00`..`+14:00`, with uppercase `T` and `Z`, naming a calendar day that
27
+ * exists. XML Schema's `24:00:00` end-of-day form, a lowercase separator, an
28
+ * offset outside that range, an impossible day and any non-string value are
29
+ * unreadable and fail as `unreadable_bound`, naming the bound, rather than
30
+ * being normalised to a date the credential never asserted.
31
+ *
32
+ * Comparison is at millisecond precision: a fraction finer than that in a
33
+ * bound is truncated when the bound is parsed, so a bound within the same
34
+ * millisecond as `now` is treated as reached. `now` must be a valid Date;
35
+ * an invalid one is a caller error and throws rather than judging anything.
36
+ *
37
+ * Verification providers may not enforce these claims for every securing
38
+ * mechanism (the pinned VCKit reads only the JOSE `exp` and `nbf` claims for
39
+ * a `vc+jwt` envelope), so every verification path applies this judgement
40
+ * to the credential's own claims.
41
+ */
42
+ export declare function evaluateValidityWindow(claims: {
43
+ validFrom?: unknown;
44
+ validUntil?: unknown;
45
+ }, now?: Date): ValidityWindowOutcome;
@@ -0,0 +1,68 @@
1
+ import { asDateTime } from './as-date-time.js';
2
+ /**
3
+ * Judges the VCDM 2.0 `validFrom` and `validUntil` claims against `now`,
4
+ * comparing them as points on a timeline as the data model requires.
5
+ *
6
+ * Supported form: an XML Schema `dateTimeStamp` written as
7
+ * `YYYY-MM-DDTHH:MM:SS[.fraction]` followed by `Z` or an offset within
8
+ * `-14:00`..`+14:00`, with uppercase `T` and `Z`, naming a calendar day that
9
+ * exists. XML Schema's `24:00:00` end-of-day form, a lowercase separator, an
10
+ * offset outside that range, an impossible day and any non-string value are
11
+ * unreadable and fail as `unreadable_bound`, naming the bound, rather than
12
+ * being normalised to a date the credential never asserted.
13
+ *
14
+ * Comparison is at millisecond precision: a fraction finer than that in a
15
+ * bound is truncated when the bound is parsed, so a bound within the same
16
+ * millisecond as `now` is treated as reached. `now` must be a valid Date;
17
+ * an invalid one is a caller error and throws rather than judging anything.
18
+ *
19
+ * Verification providers may not enforce these claims for every securing
20
+ * mechanism (the pinned VCKit reads only the JOSE `exp` and `nbf` claims for
21
+ * a `vc+jwt` envelope), so every verification path applies this judgement
22
+ * to the credential's own claims.
23
+ */
24
+ export function evaluateValidityWindow(claims, now = new Date()) {
25
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
26
+ throw new TypeError('evaluateValidityWindow requires a valid Date for now');
27
+ }
28
+ const from = readBound(claims.validFrom);
29
+ const until = readBound(claims.validUntil);
30
+ if (from.state === 'readable' && now < from.date) {
31
+ return {
32
+ result: 'fail',
33
+ reason: 'not_yet_valid',
34
+ message: `Credential is not yet valid: validFrom ${from.date.toISOString()} is after ${now.toISOString()}`,
35
+ };
36
+ }
37
+ if (until.state === 'readable' && now > until.date) {
38
+ return {
39
+ result: 'fail',
40
+ reason: 'expired',
41
+ message: `Credential has expired: validUntil ${until.date.toISOString()} is before ${now.toISOString()}`,
42
+ };
43
+ }
44
+ const unreadable = [
45
+ from.state === 'unreadable' ? 'validFrom' : null,
46
+ until.state === 'unreadable' ? 'validUntil' : null,
47
+ ].filter((name) => name !== null);
48
+ if (unreadable.length > 0) {
49
+ return {
50
+ result: 'fail',
51
+ reason: 'unreadable_bound',
52
+ message: `${unreadable.join(' and ')} could not be read as a date-time`,
53
+ };
54
+ }
55
+ return { result: 'pass' };
56
+ }
57
+ const DATE_TIME_STAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-](?:0\d|1[0-3]):[0-5]\d|[+-]14:00)$/;
58
+ function readBound(value) {
59
+ // Only an omitted property is an absent bound; an explicit null is a
60
+ // present value the data model does not allow.
61
+ if (value === undefined)
62
+ return { state: 'absent' };
63
+ if (typeof value !== 'string' || !DATE_TIME_STAMP.test(value))
64
+ return { state: 'unreadable' };
65
+ const date = asDateTime(value);
66
+ return date === undefined ? { state: 'unreadable' } : { state: 'readable', date };
67
+ }
68
+ //# sourceMappingURL=validity-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validity-window.js","sourceRoot":"","sources":["../../src/common/validity-window.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAkB/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAqD,EACrD,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,CAAC,CAAC,GAAG,YAAY,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,sDAAsD,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO;YACL,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,eAAe;YACvB,OAAO,EAAE,0CAA0C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,GAAG,CAAC,WAAW,EAAE,EAAE;SAC3G,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,UAAU,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO;YACL,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,sCAAsC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,GAAG,CAAC,WAAW,EAAE,EAAE;SACzG,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;QAChD,KAAK,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;KACnD,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAClD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,mCAAmC;SACxE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAID,MAAM,eAAe,GAAG,2FAA2F,CAAC;AAEpH,SAAS,SAAS,CAAC,KAAc;IAC/B,qEAAqE;IACrE,+CAA+C;IAC/C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAC9F,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { evaluateValidityWindow } from './validity-window.js';
2
+ const NOW = new Date('2026-09-11T00:00:00.000Z');
3
+ describe('evaluateValidityWindow', () => {
4
+ it('fails an expired credential, naming the bound', () => {
5
+ expect(evaluateValidityWindow({ validFrom: '2020-01-01T00:00:00Z', validUntil: '2021-01-01T00:00:00Z' }, NOW)).toEqual({
6
+ result: 'fail',
7
+ reason: 'expired',
8
+ message: expect.stringContaining('validUntil 2021-01-01T00:00:00.000Z'),
9
+ });
10
+ });
11
+ it('fails a credential that is not yet valid', () => {
12
+ expect(evaluateValidityWindow({ validFrom: '2035-01-01T00:00:00Z' }, NOW)).toMatchObject({
13
+ result: 'fail',
14
+ reason: 'not_yet_valid',
15
+ });
16
+ });
17
+ it('passes inside the window, exactly on a bound, and with no bound at all', () => {
18
+ expect(evaluateValidityWindow({ validFrom: '2026-01-01T00:00:00Z', validUntil: '2027-01-01T00:00:00Z' }, NOW)).toEqual({ result: 'pass' });
19
+ expect(evaluateValidityWindow({ validFrom: NOW.toISOString() }, NOW)).toEqual({ result: 'pass' });
20
+ expect(evaluateValidityWindow({ validUntil: NOW.toISOString() }, NOW)).toEqual({ result: 'pass' });
21
+ // VCDM 2.0: with neither bound the credential is valid indefinitely.
22
+ expect(evaluateValidityWindow({}, NOW)).toEqual({ result: 'pass' });
23
+ expect(evaluateValidityWindow({ validFrom: undefined, validUntil: undefined }, NOW)).toEqual({ result: 'pass' });
24
+ expect(evaluateValidityWindow({ validUntil: '2035-01-01T00:00:00+14:00' }, NOW)).toEqual({ result: 'pass' });
25
+ expect(evaluateValidityWindow({ validUntil: '2035-01-01T00:00:00.250-13:59' }, NOW)).toEqual({ result: 'pass' });
26
+ });
27
+ it('fails a bound that is present but not a date-time, and never normalises an impossible date', () => {
28
+ // A violated readable bound wins over an unreadable one; otherwise an
29
+ // unreadable bound fails the credential rather than being dropped.
30
+ expect(evaluateValidityWindow({ validFrom: 'yesterday', validUntil: '2021-01-01T00:00:00Z' }, NOW)).toMatchObject({
31
+ result: 'fail',
32
+ reason: 'expired',
33
+ });
34
+ expect(evaluateValidityWindow({ validFrom: '2020-01-01T00:00:00Z', validUntil: 'never' }, NOW)).toEqual({
35
+ result: 'fail',
36
+ reason: 'unreadable_bound',
37
+ message: 'validUntil could not be read as a date-time',
38
+ });
39
+ expect(evaluateValidityWindow({ validFrom: 12345, validUntil: {} }, NOW)).toMatchObject({
40
+ message: 'validFrom and validUntil could not be read as a date-time',
41
+ });
42
+ // 30 February is not a date; it must not become 2 March and then expire.
43
+ expect(evaluateValidityWindow({ validUntil: '2021-02-30T00:00:00Z' }, NOW)).toMatchObject({
44
+ reason: 'unreadable_bound',
45
+ });
46
+ // XML Schema's end-of-day form is outside the supported profile.
47
+ expect(evaluateValidityWindow({ validUntil: '2030-12-31T24:00:00Z' }, NOW)).toMatchObject({
48
+ reason: 'unreadable_bound',
49
+ });
50
+ // An explicit null is a present value the data model does not allow.
51
+ expect(evaluateValidityWindow({ validUntil: null }, NOW)).toMatchObject({ reason: 'unreadable_bound' });
52
+ // Only the dateTimeStamp form: uppercase separators and offsets within 14 hours.
53
+ expect(evaluateValidityWindow({ validUntil: '2035-01-01t00:00:00z' }, NOW)).toMatchObject({
54
+ reason: 'unreadable_bound',
55
+ });
56
+ expect(evaluateValidityWindow({ validUntil: '2035-01-01T00:00:00+23:59' }, NOW)).toMatchObject({
57
+ reason: 'unreadable_bound',
58
+ });
59
+ expect(evaluateValidityWindow({ validUntil: '2035-01-01T00:00:00+14:01' }, NOW)).toMatchObject({
60
+ reason: 'unreadable_bound',
61
+ });
62
+ });
63
+ it('refuses an invalid clock rather than judging against it', () => {
64
+ expect(() => evaluateValidityWindow({ validUntil: '2021-01-01T00:00:00Z' }, new Date('not a date'))).toThrow(TypeError);
65
+ });
66
+ });
67
+ //# sourceMappingURL=validity-window.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validity-window.test.js","sourceRoot":"","sources":["../../src/common/validity-window.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
1
  export { SchemaLoaderError, SchemaLoaderNetworkError, SchemaLoaderHttpError, SchemaLoaderInvalidJsonError, } from './errors.js';
2
2
  export { createSchemaLoader, type SchemaLoader } from './schema-loader.js';
3
3
  export { createJsonLdDocumentLoader, type JsonLdDocumentLoaderOptions, type LoadedRemoteDocument, } from './jsonld-document-loader.js';
4
+ export { isHostDeliveryFailure } from '../bundle/fallback.js';
5
+ export type { BundledFallbackEvent, BundledFallbackOptions } from '../bundle/fallback.js';
@@ -1,4 +1,5 @@
1
1
  export { SchemaLoaderError, SchemaLoaderNetworkError, SchemaLoaderHttpError, SchemaLoaderInvalidJsonError, } from './errors.js';
2
2
  export { createSchemaLoader } from './schema-loader.js';
3
3
  export { createJsonLdDocumentLoader, } from './jsonld-document-loader.js';
4
+ export { isHostDeliveryFailure } from '../bundle/fallback.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/loaders/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACL,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/loaders/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACL,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { ResolveDocumentOptions } from '../resolvers/index.js';
2
+ import type { BundledFallbackOptions } from '../bundle/fallback.js';
2
3
  import type { TtlCache } from '../cache/ttl-cache.js';
3
4
  /**
4
5
  * The subset of jsonld's `RemoteDocument` shape this loader returns. Carries
@@ -13,17 +14,18 @@ export interface LoadedRemoteDocument {
13
14
  document: unknown;
14
15
  }
15
16
  /** Options for {@link createJsonLdDocumentLoader}. */
16
- export interface JsonLdDocumentLoaderOptions extends ResolveDocumentOptions {
17
+ export interface JsonLdDocumentLoaderOptions extends ResolveDocumentOptions, BundledFallbackOptions {
17
18
  /** `Accept` header for `@context` fetches. Defaults to JSON-LD then JSON. */
18
19
  accept?: string;
19
20
  /**
20
21
  * Optional cache, keyed by URL, for resolved `@context` documents. When
21
22
  * supplied, a document fetched once is reused for the cache's TTL rather
22
- * than re-fetched on every expansion. Only successful fetches are cached
23
+ * than re-fetched on every expansion. Only successful loads are cached
23
24
  * (the {@link TtlCache} contract does not cache rejected fetches), so a
24
- * URL the guard rejects is re-checked every time. A successfully fetched
25
- * and parsed document is cached even if downstream JSON-LD processing
26
- * later rejects it.
25
+ * URL the guard rejects is re-checked every time; a bundled copy served
26
+ * after a host failure counts as a successful load and is cached like a
27
+ * fetched one. A successfully loaded document is cached even if
28
+ * downstream JSON-LD processing later rejects it.
27
29
  */
28
30
  cache?: TtlCache<LoadedRemoteDocument>;
29
31
  }
@@ -1,3 +1,4 @@
1
+ import { withBundledFallback } from '../bundle/fallback.js';
1
2
  /** Default `Accept` for `@context` documents: JSON-LD, falling back to JSON. */
2
3
  const CONTEXT_ACCEPT_HEADER = 'application/ld+json, application/json;q=0.9';
3
4
  /**
@@ -17,8 +18,8 @@ const CONTEXT_ACCEPT_HEADER = 'application/ld+json, application/json;q=0.9';
17
18
  * @see https://github.com/uncefact/tests-untp/issues/707
18
19
  */
19
20
  export function createJsonLdDocumentLoader(options) {
20
- const { cache, accept, ...resolverOptions } = options ?? {};
21
- const load = async (url) => {
21
+ const { cache, accept, bundledFallback, onBundledFallback, ...resolverOptions } = options ?? {};
22
+ const fetchContext = async (url) => {
22
23
  // Lazy import: the resolver stack pulls in undici, which jsdom test
23
24
  // environments cannot evaluate, so it loads at fetch time to keep this
24
25
  // module importable there.
@@ -29,6 +30,14 @@ export function createJsonLdDocumentLoader(options) {
29
30
  });
30
31
  return { documentUrl: finalUrl, document: json };
31
32
  };
33
+ // A bundled context is served under the URL that was requested, since no
34
+ // redirect was followed to reach it.
35
+ const load = async (url) => {
36
+ const fetched = await withBundledFallback(url, { bundledFallback, onBundledFallback }, () => fetchContext(url));
37
+ return 'documentUrl' in fetched && 'document' in fetched
38
+ ? fetched
39
+ : { documentUrl: url, document: fetched };
40
+ };
32
41
  return cache ? (url) => cache.get(url, () => load(url)) : load;
33
42
  }
34
43
  //# sourceMappingURL=jsonld-document-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonld-document-loader.js","sourceRoot":"","sources":["../../src/loaders/jsonld-document-loader.ts"],"names":[],"mappings":"AAgCA,gFAAgF;AAChF,MAAM,qBAAqB,GAAG,6CAA6C,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAqC;IAErC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAE5D,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiC,EAAE;QAChE,oEAAoE;QACpE,uEAAuE;QACvE,2BAA2B;QAC3B,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACtE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE;YACxD,GAAG,eAAe;YAClB,MAAM,EAAE,MAAM,IAAI,qBAAqB;SACxC,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACnD,CAAC,CAAC;IAEF,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC"}
1
+ {"version":3,"file":"jsonld-document-loader.js","sourceRoot":"","sources":["../../src/loaders/jsonld-document-loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAiC5D,gFAAgF;AAChF,MAAM,qBAAqB,GAAG,6CAA6C,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAqC;IAErC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAEhG,MAAM,YAAY,GAAG,KAAK,EAAE,GAAW,EAAiC,EAAE;QACxE,oEAAoE;QACpE,uEAAuE;QACvE,2BAA2B;QAC3B,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACtE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE;YACxD,GAAG,eAAe;YAClB,MAAM,EAAE,MAAM,IAAI,qBAAqB;SACxC,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACnD,CAAC,CAAC;IAEF,yEAAyE;IACzE,qCAAqC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiC,EAAE;QAChE,MAAM,OAAO,GAAG,MAAM,mBAAmB,CACvC,GAAG,EACH,EAAE,eAAe,EAAE,iBAAiB,EAAE,EACtC,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CACxB,CAAC;QACF,OAAO,aAAa,IAAI,OAAO,IAAI,UAAU,IAAI,OAAO;YACtD,CAAC,CAAE,OAAgC;YACnC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC9C,CAAC,CAAC;IAEF,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC"}