appsync-scalars 0.0.0-stage → 0.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Goodwin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,78 @@
1
- # Temporary Holding Version
1
+ # appsync-scalars for JavaScript and TypeScript
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Checks whether a value is a valid AWS AppSync `AWSDate`, `AWSTime`, `AWSDateTime` or `AWSTimestamp`, using rules recorded from a live AppSync API. Use it to catch a bad value before AppSync rejects the whole request.
4
+
5
+ Also available for [Python](https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/python/README.md), [Rust](https://github.com/wbgoodwin/appsync-scalars/blob/main/crates/appsync-scalars/README.md) and [Go](https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/go/README.md).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install appsync-scalars
11
+ ```
12
+
13
+ It's an ES module with no dependencies, TypeScript types included. The validator is WebAssembly embedded in the package, so there's no install script or platform-specific build. It's tested on Node.js 20, 24 and 26, but not in browsers or edge runtimes.
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import { AppSyncScalarError, isAWSDate, validateAWSDateTime } from "appsync-scalars";
19
+
20
+ isAWSDate("1970-01-01+05:30"); // true
21
+ isAWSDate("2021-02-29"); // false
22
+
23
+ try {
24
+ validateAWSDateTime("2021-01-11T12:30:24");
25
+ } catch (error) {
26
+ if (error instanceof AppSyncScalarError) {
27
+ console.log(error.message); // invalid AWSDateTime: time zone offset is required
28
+ console.log(error.scalar); // AWSDateTime
29
+ console.log(error.reason); // time zone offset is required
30
+ }
31
+ }
32
+ ```
33
+
34
+ Each scalar has two functions. The `is` function returns a boolean, and the `validate` function returns nothing or throws an `AppSyncScalarError`.
35
+
36
+ | Scalar | Takes | Returns a boolean | Throws |
37
+ |---|---|---|---|
38
+ | `AWSDate` | `string` | `isAWSDate` | `validateAWSDate` |
39
+ | `AWSTime` | `string` | `isAWSTime` | `validateAWSTime` |
40
+ | `AWSDateTime` | `string` | `isAWSDateTime` | `validateAWSDateTime` |
41
+ | `AWSTimestamp` | `number` or `bigint` | `isAWSTimestamp` | `validateAWSTimestamp` |
42
+
43
+ Any other type counts as invalid. Timestamps must be whole numbers, so `1.5` and `"123"` are invalid too.
44
+
45
+ ## TypeScript
46
+
47
+ The functions accept `unknown` and narrow the type, so they work directly on untrusted input:
48
+
49
+ ```ts
50
+ import { isAWSDate, validateAWSTimestamp } from "appsync-scalars";
51
+
52
+ function check(day: unknown, created: unknown) {
53
+ if (isAWSDate(day)) {
54
+ day.toUpperCase(); // day is a string here
55
+ }
56
+ validateAWSTimestamp(created);
57
+ return created; // number | bigint
58
+ }
59
+ ```
60
+
61
+ ## Large timestamps
62
+
63
+ Use a `bigint` for a timestamp beyond `Number.MAX_SAFE_INTEGER`. A `number` that large has already been rounded before this library sees it, so the largest valid value only passes as a `bigint`: `31556889864403199n`.
64
+
65
+ ## What counts as valid
66
+
67
+ | Scalar | Valid examples |
68
+ |---|---|
69
+ | `AWSDate` | `1970-01-01`, `1970-01-01Z`, `1970-01-01+05:30`, `-2017-05-01` |
70
+ | `AWSTime` | `12:30`, `12:30:24.500`, `12:30:24-07:00` |
71
+ | `AWSDateTime` | `2021-01-11T12:30Z`, `1970-01-01T12:00:00.123456789+05:30` (the offset is required) |
72
+ | `AWSTimestamp` | `1610368224`, `-123123` (whole seconds since 1970) |
73
+
74
+ AppSync accepts and rejects some values its descriptions don't mention, and this library follows AppSync. See [what AppSync accepts](https://github.com/wbgoodwin/appsync-scalars#what-appsync-accepts).
75
+
76
+ ## License
77
+
78
+ [MIT](https://github.com/wbgoodwin/appsync-scalars/blob/main/LICENSE)
package/index.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type AppSyncScalar = "AWSDate" | "AWSTime" | "AWSDateTime" | "AWSTimestamp";
2
+
3
+ export class AppSyncScalarError extends Error {
4
+ readonly scalar: AppSyncScalar;
5
+ readonly reason: string;
6
+ constructor(scalar: AppSyncScalar, reason: string);
7
+ }
8
+
9
+ export function isAWSDate(value: unknown): value is string;
10
+ export function isAWSTime(value: unknown): value is string;
11
+ export function isAWSDateTime(value: unknown): value is string;
12
+ export function isAWSTimestamp(value: unknown): value is number | bigint;
13
+
14
+ /** @throws {AppSyncScalarError} if the value is invalid. */
15
+ export function validateAWSDate(value: unknown): asserts value is string;
16
+ /** @throws {AppSyncScalarError} if the value is invalid. */
17
+ export function validateAWSTime(value: unknown): asserts value is string;
18
+ /** @throws {AppSyncScalarError} if the value is invalid. */
19
+ export function validateAWSDateTime(value: unknown): asserts value is string;
20
+ /** @throws {AppSyncScalarError} if the value is invalid. */
21
+ export function validateAWSTimestamp(value: unknown): asserts value is number | bigint;
package/index.js ADDED
@@ -0,0 +1,98 @@
1
+ import { WASM_BASE64 } from "./wasm.generated.js";
2
+
3
+ const SCALAR_CODES = { AWSDate: 0, AWSTime: 1, AWSDateTime: 2, AWSTimestamp: 3 };
4
+
5
+ export class AppSyncScalarError extends Error {
6
+ constructor(scalar, reason) {
7
+ super(`invalid ${scalar}: ${reason}`);
8
+ this.name = "AppSyncScalarError";
9
+ this.scalar = scalar;
10
+ this.reason = reason;
11
+ }
12
+ }
13
+
14
+ const encoder = new TextEncoder();
15
+ const decoder = new TextDecoder();
16
+ let module;
17
+
18
+ // Loads on first use, so importing the package costs nothing until a value is checked.
19
+ function load() {
20
+ if (!module) {
21
+ const bytes = Uint8Array.from(atob(WASM_BASE64), (char) => char.charCodeAt(0));
22
+ const { exports } = new WebAssembly.Instance(new WebAssembly.Module(bytes));
23
+ module = {
24
+ exports,
25
+ inputPtr: exports.appsync_input_ptr(),
26
+ inputCapacity: exports.appsync_input_capacity(),
27
+ };
28
+ }
29
+ return module;
30
+ }
31
+
32
+ /** Returns null if `text` is valid, or the reason it is not. */
33
+ function reasonFor(scalar, text) {
34
+ const { exports, inputPtr, inputCapacity } = load();
35
+ const input = new Uint8Array(exports.memory.buffer, inputPtr, inputCapacity);
36
+ const { read, written } = encoder.encodeInto(text, input);
37
+ // Text that did not fit is reported as too long, which the module always rejects.
38
+ const length = read < text.length ? inputCapacity + 1 : written;
39
+ const code = exports.appsync_validate(SCALAR_CODES[scalar], length);
40
+ if (code === 0) {
41
+ return null;
42
+ }
43
+ const message = new Uint8Array(
44
+ exports.memory.buffer,
45
+ exports.appsync_reason_ptr(code),
46
+ exports.appsync_reason_len(code),
47
+ );
48
+ return decoder.decode(message);
49
+ }
50
+
51
+ function checkText(scalar, value) {
52
+ return typeof value === "string" ? reasonFor(scalar, value) : "expected a string";
53
+ }
54
+
55
+ function checkTimestamp(value) {
56
+ if (typeof value === "bigint" || Number.isInteger(value)) {
57
+ return reasonFor("AWSTimestamp", String(value));
58
+ }
59
+ return "expected an integer";
60
+ }
61
+
62
+ function throwIfInvalid(scalar, reason) {
63
+ if (reason !== null) {
64
+ throw new AppSyncScalarError(scalar, reason);
65
+ }
66
+ }
67
+
68
+ export function isAWSDate(value) {
69
+ return checkText("AWSDate", value) === null;
70
+ }
71
+
72
+ export function isAWSTime(value) {
73
+ return checkText("AWSTime", value) === null;
74
+ }
75
+
76
+ export function isAWSDateTime(value) {
77
+ return checkText("AWSDateTime", value) === null;
78
+ }
79
+
80
+ export function isAWSTimestamp(value) {
81
+ return checkTimestamp(value) === null;
82
+ }
83
+
84
+ export function validateAWSDate(value) {
85
+ throwIfInvalid("AWSDate", checkText("AWSDate", value));
86
+ }
87
+
88
+ export function validateAWSTime(value) {
89
+ throwIfInvalid("AWSTime", checkText("AWSTime", value));
90
+ }
91
+
92
+ export function validateAWSDateTime(value) {
93
+ throwIfInvalid("AWSDateTime", checkText("AWSDateTime", value));
94
+ }
95
+
96
+ export function validateAWSTimestamp(value) {
97
+ throwIfInvalid("AWSTimestamp", checkTimestamp(value));
98
+ }
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "appsync-scalars",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Validates AWS AppSync date and time scalar values.",
5
+ "keywords": ["appsync", "aws", "graphql", "validation", "iso8601"],
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/js/README.md",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/wbgoodwin/appsync-scalars.git",
11
+ "directory": "bindings/js"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./index.d.ts",
17
+ "default": "./index.js"
18
+ }
19
+ },
20
+ "types": "./index.d.ts",
21
+ "files": ["index.js", "index.d.ts", "wasm.generated.js"],
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "scripts": {
27
+ "test": "node --test",
28
+ "prepack": "../../scripts/build-wasm.sh"
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/build-wasm.sh. Do not edit.
2
+ export const WASM_BASE64 = "AGFzbQEAAAABJgdgAn9/AGAAAX9gAX8Bf2ACf38Bf2ADf39/AGAAAGAFf35+fn4AAw4NAAEBAgIDAgQCAgQFBgUDAQARBgkBfwFBgIDAAAsHdAYGbWVtb3J5AgAWYXBwc3luY19pbnB1dF9jYXBhY2l0eQABEWFwcHN5bmNfaW5wdXRfcHRyAAISYXBwc3luY19yZWFzb25fbGVuAAMSYXBwc3luY19yZWFzb25fcHRyAAQQYXBwc3luY192YWxpZGF0ZQAFCosiDVYBAn9BACECAkACQCABRQ0AIAFBDEsNACABQf//v4AAaiIBRQ0BIAEtAAAiAUECdCgC8ITAgAAhAiABLQDkhMCAACEDDAELCyAAIAM2AgQgACACNgIACwUAQYAICwgAQaCFwIAAC0IBAn8jgICAgABBEGsiASSAgICAACABQQhqIAAQgICAgAAgASgCCCEAIAEoAgwhAiABQRBqJICAgIAAIAJBACAAGws2AQF/I4CAgIAAQRBrIgEkgICAgAAgAUEIaiAAEICAgIAAIAEoAgghACABQRBqJICAgIAAIAAL6g0CCH8CfiOAgICAAEHAAGsiAiSAgICAAEEBIQMCQCAAQQNLDQAgAUGACEsNAAJAIAFFDQBBACABQXlqIgQgBCABSxshBUGjhcCAAEF8cUGghcCAAGshBkEAIQQDQAJAAkACQAJAIAQtAKCFwIAAIgfAIghBAEgNACAGIARrQQNxDQEgBCAFTw0CA0AgBEGkhcCAAGooAgAgBEGghcCAAGooAgByQYCBgoR4cQ0DIARBCGoiBCAFSQ0ADAMLC0EBIQMCQAJAAkACQAJAAkACQAJAAkAgBy0A44LAgABBfmoOAwACAQ4LQQEhAyAEQQFqIgQgAU8NDSAELACghcCAAEG/f0wNBwwNC0EBIQMgBEEBaiIJIAFPDQwgCSwAoIXAgAAhCSAHQZB+ag4FAgEBAQMBC0EBIQMgBEEBaiIJIAFPDQsgCSwAoIXAgAAhCQJAAkACQCAHQeABRg0AIAdB7QFGDQEgCEEfakH/AXFBDEkNAiAIQX5xQW5HDQ4gCUFASA0HDA4LIAlBYHFBoH9GDQYMDQsgCUGff0oNDAwFCyAJQUBIDQQMCwsgCEEPakH/AXFBAksNCiAJQUBIDQIMCgsgCUHwAGpB/wFxQTBJDQEMCQsgCUGPf0oNCAsgBEECaiIHIAFPDQcgBywAoIXAgABBv39KDQcgBEEDaiIEIAFPDQcgBCwAoIXAgABBv39KDQcMAQsgBEECaiIEIAFPDQYgBCwAoIXAgABBv39KDQYLIARBAWohBAwCCyAEQQFqIQQMAQsgBCABTw0AA0AgBEGghcCAAGosAABBAEgNASABIARBAWoiBEcNAAwDCwsgBCABSQ0ACwsCQAJAAkACQAJAAkACQAJAIAAOBAABAgMACyACIAE2AiwgAkGghcCAADYCKCACQShqEIaAgIAAIgNB/wFxQf8BRw0EIAJBMGogAkEoakEAEIeAgIAAIAIpAzBCAlENA0EAQX8gAigCLBshAwwECyACIAE2AiwgAkGghcCAADYCKCACQShqEIiAgIAAIgNB/wFxQf8BRw0DIAJBMGogAkEoakEAEIeAgIAAIAIpAzBCAlENAkEAQX8gAigCLBshAwwDCyACIAE2AiwgAkGghcCAADYCKCACQShqEIaAgIAAIgNB/wFxQf8BRw0CQQAhAyACKAIsIgRFDQIgAigCKCIBLQAAQSByQfQARw0CIAIgBEF/ajYCLCACIAFBAWo2AiggAkEoahCIgICAACIDQf8BcUH/AUcNAiACQTBqIAJBKGpBARCHgICAACACKQMwIgpCAlENAUEAIQMgAigCLA0CQQtBfyACKQM4QqD6A1YbQQggCqdBAXEbIQMMAgtBACEDIAEgAUEAR0EALQCghcCAAEEtRnEiB2siBUUNA0GhhcCAAEGghcCAACAHGyEAQQAhBAJAAkADQCAFIARGDQEgACAEaiEDIARBAWohBCADLQAAQUZqQf8BcUH2AU8NAAwCCwsCQCAFQQFGDQAgB0GghcCAAGotAABB/wFxQTBGDQELQQkhAwJAAkACQCABDgIHAAELQQkhA0EALQCghcCAACIFQVVqDgMGAQYBC0EALQCghcCAACEFC0GghcCAACEEAkACQAJAAkACQAJAIAVB/wFxQVVqDgMBAgACCyABQRFJDQJBASEEQgAhCgNAIAJBEGogCiAKQj+HQgpCABCMgICAACACKQMYIAIpAxAiCkI/h1INBUEJIQMgBEGghcCAAGotAABBUGoiBUEJSw0KIAogBa0iC1MgCiALfSIKQgBTcw0KIAEgBEEBaiIERw0ADAQLCyABQX9qIQFBoYXAgAAhBAsC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";