appsync-scalars 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +122 -2
- package/index.d.ts +21 -0
- package/index.js +98 -0
- package/package.json +27 -4
- package/wasm.generated.js +2 -0
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,123 @@
|
|
|
1
|
-
#
|
|
1
|
+
# appsync-scalars
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Validates the AWS AppSync date and time scalars (`AWSDate`, `AWSTime`, `AWSDateTime` and `AWSTimestamp`) with one Rust implementation, available in Rust, Python, JavaScript and Go.
|
|
4
|
+
|
|
5
|
+
The rules come from results recorded from a live AppSync API, which differ from AWS's written descriptions. See [What AppSync accepts](#what-appsync-accepts).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### Rust
|
|
10
|
+
|
|
11
|
+
```rust
|
|
12
|
+
use appsync_scalars::{is_aws_date, validate_aws_datetime};
|
|
13
|
+
|
|
14
|
+
assert!(is_aws_date("1970-01-01+05:30"));
|
|
15
|
+
|
|
16
|
+
// Err: invalid AWSDateTime: time zone offset is required
|
|
17
|
+
validate_aws_datetime("2021-01-11T12:30:24").unwrap_err();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Python
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from appsync_scalars import is_aws_date, validate_aws_datetime
|
|
24
|
+
|
|
25
|
+
is_aws_date("1970-01-01+05:30") # True
|
|
26
|
+
validate_aws_datetime("2021-01-11T12:30:24") # raises ValueError
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The core package has no dependencies. Pydantic field types are an optional extra, installed with `pip install "appsync-scalars[pydantic]"`:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
from appsync_scalars.pydantic import AWSDate, AWSDateTime, AWSTimestamp
|
|
34
|
+
|
|
35
|
+
class Event(BaseModel):
|
|
36
|
+
day: AWSDate
|
|
37
|
+
starts_at: AWSDateTime
|
|
38
|
+
created: AWSTimestamp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Fields keep the exact `str` or `int` they were given, because Python's `datetime` cannot hold negative years and keeps only microseconds. They are also strict, so `"123"` is not converted to an integer for `AWSTimestamp`.
|
|
42
|
+
|
|
43
|
+
### JavaScript and TypeScript
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { isAWSDate, validateAWSDateTime } from "appsync-scalars";
|
|
47
|
+
|
|
48
|
+
isAWSDate("1970-01-01+05:30"); // true
|
|
49
|
+
validateAWSDateTime("2021-01-11T12:30:24"); // throws AppSyncScalarError
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Go
|
|
53
|
+
|
|
54
|
+
```go
|
|
55
|
+
import appsyncscalars "github.com/wbgoodwin/appsync-scalars/bindings/go"
|
|
56
|
+
|
|
57
|
+
appsyncscalars.IsAWSDate("1970-01-01+05:30") // true
|
|
58
|
+
err := appsyncscalars.ValidateAWSDateTime("2021-01-11T12:30:24") // *appsyncscalars.Error
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`AWSTimestamp` functions take the integer's text in Go and Rust, and an integer in Python and JavaScript.
|
|
62
|
+
|
|
63
|
+
## What AppSync accepts
|
|
64
|
+
|
|
65
|
+
AppSync's descriptions of these scalars are accurate for ordinary values. It also accepts and rejects some values they don't mention, and this library follows AppSync:
|
|
66
|
+
|
|
67
|
+
| Value | Example | AppSync's behavior |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Lowercase `z` and `t` | `2021-01-11t12:30:24z` | Accepted |
|
|
70
|
+
| Empty fraction | `12:00:34.` | Accepted |
|
|
71
|
+
| Signed years of 5 to 10 digits | `+12021-01-11`, `-12021-01-11` | Accepted, up to 999,999,999 either way |
|
|
72
|
+
| `+` with a 4-digit year | `+2021-01-11` | Rejected |
|
|
73
|
+
| Negative year zero | `-0000-01-11` | Rejected |
|
|
74
|
+
| Hours-only offset | `+05` | Accepted by `AWSDateTime`, rejected by `AWSDate` and `AWSTime` |
|
|
75
|
+
| Offset beyond 18 hours | `+18:01` | Rejected by `AWSDateTime`, accepted by `AWSDate` and `AWSTime` up to `+23:59:59` |
|
|
76
|
+
|
|
77
|
+
`AWSTimestamp` follows its documented limits, -31557014167219200 to 31556889864403199.
|
|
78
|
+
|
|
79
|
+
AppSync gave the same verdict for a GraphQL variable and for resolver output on every recorded value. Inline literals differ in one way: AppSync wraps oversized integers, so `18446744073709551616` written in a query reaches the resolver as `0`. This library calls that value invalid, as AppSync does for variables and output.
|
|
80
|
+
|
|
81
|
+
## How it fits together
|
|
82
|
+
|
|
83
|
+
| Path | Purpose |
|
|
84
|
+
|---|---|
|
|
85
|
+
| [`crates/appsync-scalars`](https://github.com/wbgoodwin/appsync-scalars/blob/main/crates/appsync-scalars/src/lib.rs) | The validator: Rust with no dependencies |
|
|
86
|
+
| [`crates/appsync-scalars-wasm`](https://github.com/wbgoodwin/appsync-scalars/blob/main/crates/appsync-scalars-wasm/src/lib.rs) | The WebAssembly module, about 4 KB, shared by JavaScript and Go |
|
|
87
|
+
| [`bindings/python`](https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/python/pyproject.toml) | Native extension built with PyO3 and maturin, plus the Pydantic types |
|
|
88
|
+
| [`bindings/js`](https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/js/index.js) | npm package that embeds the module, so it has no install step or platform-specific builds |
|
|
89
|
+
| [`bindings/go`](https://github.com/wbgoodwin/appsync-scalars/blob/main/bindings/go/appsyncscalars.go) | Go module that runs the embedded module through wazero, with no cgo |
|
|
90
|
+
| [`conformance/smoke.json`](https://github.com/wbgoodwin/appsync-scalars/blob/main/conformance/smoke.json) | Test cases every language runs |
|
|
91
|
+
| [`conformance/`](https://github.com/wbgoodwin/appsync-scalars/blob/main/conformance/README.md) | The live AppSync test suite |
|
|
92
|
+
|
|
93
|
+
Other languages with a WebAssembly runtime can load the same module. Its interface is described in [`crates/appsync-scalars-wasm/src/lib.rs`](https://github.com/wbgoodwin/appsync-scalars/blob/main/crates/appsync-scalars-wasm/src/lib.rs).
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
You need `rustup`, which installs the pinned Rust version, plus `uv`, Node.js 20 or later, and Go 1.25 or later.
|
|
98
|
+
|
|
99
|
+
Run every language's tests:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
scripts/test-all.sh
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
After changing the validator, rebuild the WebAssembly module and commit the Go copy, because `go get` only sees files in the repository:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
scripts/build-wasm.sh
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Releasing
|
|
112
|
+
|
|
113
|
+
GitHub Actions runs the tests, and builds and tests a wheel on Linux, macOS and Windows, for every pull request and push to `main`.
|
|
114
|
+
|
|
115
|
+
To release, set the new version in `crates/appsync-scalars/Cargo.toml`, `bindings/python/pyproject.toml` and `bindings/js/package.json`, merge to `main`, then push a tag:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git tag v1.2.3 && git push origin v1.2.3
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The workflow repeats every check, publishes to crates.io, PyPI and npm, and adds the `bindings/go/v1.2.3` tag Go needs. It skips anything already on a registry, so rerunning a partly failed release is safe.
|
|
122
|
+
|
|
123
|
+
Publishing stores no tokens. Each registry needs a trusted publisher for this repository, with the workflow `ci.yml` and the environment `release`. PyPI can set one up before the first release. crates.io and npm need the first version published by hand, and npm's publisher must allow `npm publish`.
|
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,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appsync-scalars",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Validates AWS AppSync date and time scalar values.",
|
|
5
|
+
"keywords": ["appsync", "aws", "graphql", "validation", "iso8601"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/wbgoodwin/appsync-scalars.git",
|
|
10
|
+
"directory": "bindings/js"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./index.d.ts",
|
|
16
|
+
"default": "./index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"files": ["index.js", "index.d.ts", "wasm.generated.js"],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node --test",
|
|
27
|
+
"prepack": "cp ../../README.md . && ../../scripts/build-wasm.sh"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -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/h1INBUEJIQMgBEGghcCAAGotAABBUGoiBUEJSw0KIAogBa0iC1MgCiALfSIKQgBTcw0KIAEgBEEBaiIERw0ADAQLCyABQX9qIQFBoYXAgAAhBAsCQCABQRBJDQBCACEKA0AgAiAKIApCP4dCCkIAEIyAgIAAIAIpAwggAikDACILQj+HUg0EQQkhAyAELQAAQVBqIgVBCUsNCSAFrSIKQgBTIAsgCnwiCiALU3MNCSAEQQFqIQQgAUF/aiIBDQAMAwsLIAFFDQZCACEKA0BBCSEDIAQtAABBUGoiBUEJSw0IIARBAWohBCAKQgp+IAWtfCEKIAFBf2oiAQ0ADAILCyABQX9qRQ0FQgAhCkEBIQQDQEEJIQMgBEGghcCAAGotAABBUGoiBUEJSw0HIApCCn4gBa19IQogASAEQQFqIgRHDQALCyAKQoDY79z+nYc4fEKAysWxrriO8ABUIgQNBEF/QQkgBBshAwwFC0EJIQMMBAtBACEDDAMLIAItADghAwsgA0H/AXFB/wFHDQELQQAhAwwBC0EAIQQgA0H/AXEhAQJAA0AgBEGAgMCAAGotAAAgAUYNAUEBIQMgBEEBaiIEQQxHDQAMAgsLIARBAWohAwsgAkHAAGokgICAgAAgAwu4BgIIfwR+QQAhAQJAIAAoAgQiAkUNAAJAAkAgACgCACIDLQAAIgRBLUYNACACIQUgAyEGAkACQCAEQStHDQAgACACQX9qIgU2AgQgACADQQFqIgY2AgAgBQ0AQQAhBUEAIQIMAQsgAyACaiEDQQAhAgNAIAYgAmotAABBUGpB/wFxQQlLDQEgBiACQQFqIgJqIANHDQALIAUhAgtBCkEEIARBK0YiAxshB0EFQQQgAxshAwwBCyAAIAJBf2oiBTYCBCAAIANBAWoiBjYCACAFRQ0BQQAhAgNAQQohB0EEIQMgBiACai0AAEFQakH/AXFBCUsNASAFIAJBAWoiAkcNAAsgBSECCyADIAJLDQAgAiAHSw0AIAUgAkkNACAGIAJqIQhCACEJIAIhAwNAIAYtAABBUGoiB0H/AXFBCUsNASAJQgp+IAetQv8Bg3whCSAGQQFqIQYgA0F/aiIDDQALIAAgCDYCACAAIAUgAmsiAjYCBEEAIQFBAA0AIARBLUYgCVBxDQACQCAJQv+T69wDWA0AQQoPCyACRQ0AIAgtAABBLUcNACAAIAJBf2o2AgQgACAIQQFqIgc2AgAgAkEDSQ0AIAhBA2ohBEEAIQFCACEKQQAhBUEAIQYDQCAHIAZqLQAAQVBqIgZB/wFxQQlLDQEgCkIKfiAGrUL/AYN8IQpBASEGIAVBAXEhA0EBIQUgA0UNAAsgACAENgIAIAAgAkF9aiIGNgIEIAZFDQAgBC0AAEEtRw0AIAAgAkF8aiIGNgIEIAAgCEEEaiIHNgIAIAZBAkkNACAIQQZqIQRBACEBQgAhC0EAIQVBACEGA0AgByAGai0AAEFQaiIGQf8BcUEJSw0BIAtCCn4gBq1C/wGDfCELQQEhBiAFQQFxIQNBASEFIANFDQALIAAgBDYCACAAIAJBemo2AgQCQCAKQn98QgtYDQBBAQ8LQh8hDAJAIApCC1YNAAJAQgEgCoZC0BSDQgBSDQAgCkICUg0BQhwhDCAJQgODUEUNAUIdQh1CHCAJQpADglAbIAlC5ACCQgBSGyEMDAELQh4hDAsCQCALUEUNAEECDwtBAkF/IAsgDFYbIQELIAEL9AUEBX8BfgJ/An4CQAJAAkACQAJAAkACQAJAAkACQAJAIAEoAgQiA0UNAAJAAkAgASgCACIELQAAIgVBVWoOAwECAQALAkAgBUH6AEYNACAFQdoARw0CCyAAQgA3AwggAEIBNwMAIAEgA0F/ajYCBCABIARBAWo2AgAPCyABIANBf2o2AgQgASAEQQFqIgY2AgAgA0EDSQ0BIARBA2ohB0EAIQVCACEIQQAhCQNAIAYgBWotAABBUGoiBUH/AXFBCUsNAiAIQgp+IAWtQv8Bg3whCEEBIQUgCUEBcSEKQQEhCSAKRQ0ACyABIAc2AgAgASADQX1qIgk2AgQgCUUNAiAHLQAAQTpGDQMgAg0EDAoLIABCADcDAA8LIABCAjcDACAAQQc6AAgPCyACRQ0HDAILIAEgA0F8aiIFNgIEIAEgBEEEaiIGNgIAIAVBAkkNAyAEQQZqIQJBACEFQgAhC0EAIQkDQCAGIAVqLQAAQVBqIgVB/wFxQQlLDQQgC0IKfiAFrUL/AYN8IQtBASEFIAlBAXEhCkEBIQkgCkUNAAsgASACNgIAIAEgA0F6aiIFNgIEQgAhDCAFRQ0CIAItAABBOkcNAiABIANBeWoiBTYCBCABIARBB2oiBjYCACAFQQJJDQQgBEEJaiEEQQAhBUIAIQxBACEJA0AgBiAFai0AAEFQaiIFQf8BcUEJSw0FIAxCCn4gBa1C/wGDfCEMQQEhBSAJQQFxIQpBASEJIApFDQALIAEgBDYCACABIANBd2o2AgQMAgtBACEFAkADQCAHIAVqLQAAQVBqQf8BcUEJSw0BIAkgBUEBaiIFRg0HDAALCyAFDQULQgAhDEIAIQsLAkAgDEI7Vg0AIAhCF1YNACALQjtYDQMLIABCAjcDACAAQQc6AAgPCyAAQgI3AwAgAEEHOgAIDwsgAEICNwMAIABBBzoACA8LIABCATcDACAAIAwgCEKQHH58IAtCPH58NwMIDwsgAEICNwMAIABBBzoACAucBAQEfwF+BH8BfgJAIAAoAgQiAUECTw0AQQAPCyAAKAIAIgJBAmohA0EAIQRCACEFQQAhBkEAIQcCQANAIAIgB2otAABBUGoiB0H/AXFBCUsNASAFQgp+IAetQv8Bg3whBUEBIQcgBkEBcSEIQQEhBiAIRQ0ACyAAIAM2AgAgACABQX5qIgc2AgQgB0UNACADLQAAQTpHDQAgACABQX1qIgc2AgQgACACQQNqIgM2AgAgB0ECSQ0AIAJBBWohCUEAIQRCACEKQQAhBkEAIQcDQCADIAdqLQAAQVBqIgdB/wFxQQlLDQEgCkIKfiAHrUL/AYN8IQpBASEHIAZBAXEhCEEBIQYgCEUNAAsgACAJNgIAIAAgAUF7aiIHNgIEAkAgBUIXWA0AQQMPCwJAIApCO1gNAEEEDwtB/wEhBCAHRQ0AIAktAABBOkcNACAAIAFBemo2AgQgACACQQZqIgM2AgBBACEEIAdBA0kNACACQQhqIQlBACEEQgAhBUEAIQZBACEHA0AgAyAHai0AAEFQaiIHQf8BcUEJSw0BIAVCCn4gB61C/wGDfCEFQQEhByAGQQFxIQhBASEGIAhFDQALIAAgCTYCACAAIAFBeGoiBzYCBAJAIAVCO1gNAEEFDwtB/wEhBCAHRQ0AIAktAABBLkcNACAAIAFBd2o2AgQgACACQQlqNgIAQQZBfyAAEImAgIAAQQlLGyEECyAEC3IBA38gACgCACEBQQAhAgJAIAAoAgQiA0UNAAJAA0AgASACai0AAEFQakH/AXFBCUsNASADIAJBAWoiAkcNAAsgAyECDAELIAIgA00NACACIAMgAxCKgICAAAALIAAgAyACazYCBCAAIAEgAmo2AgAgAgsJABCLgICAAAALAwAAC24BBn4gACADQv////8PgyIFIAFC/////w+DIgZ+IgcgA0IgiCIIIAZ+IgYgBSABQiCIIgl+fCIFQiCGfCIKNwMAIAAgCCAJfiAFIAZUrUIghiAFQiCIhHwgCiAHVK18IAQgAX4gAyACfnx8NwMICwuqBQEAQYCAwAALoAUAAQIDBAUGBwgJCgtmb3JtYXQgbm90IHJlY29nbml6ZWRtb250aCBpcyBvdXQgb2YgcmFuZ2VkYXkgaXMgb3V0IG9mIHJhbmdlIGZvciB0aGUgbW9udGhob3VyIGlzIG91dCBvZiByYW5nZW1pbnV0ZSBpcyBvdXQgb2YgcmFuZ2VzZWNvbmQgaXMgb3V0IG9mIHJhbmdlZnJhY3Rpb25hbCBzZWNvbmRzIGNhbiBoYXZlIGF0IG1vc3QgOSBkaWdpdHN0aW1lIHpvbmUgb2Zmc2V0IG11c3QgYmUgWiwgK2hoOm1tIG9yICtoaDptbTpzc3RpbWUgem9uZSBvZmZzZXQgaXMgcmVxdWlyZWR0aW1lc3RhbXAgaXMgb3V0IG9mIHJhbmdleWVhciBpcyBvdXQgb2YgcmFuZ2V0aW1lIHpvbmUgb2Zmc2V0IGNhbiBiZSBhdCBtb3N0IDE4IGhvdXJzAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAwMDAwMDAwMDAwMDAwMDBAQEBAQAAAAAAAAAAAAAAAAVFSEUFhYsLxwZFCgMABAAIQAQADYAEABXABAAawAQAIEAEACXABAAwwAQAPIAEAAOARAAJwEQADsBEAA=";
|