@comms-id/asx 0.2.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 +15 -0
- package/README.md +30 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.js +6 -0
- package/dist/descriptor.d.ts +15 -0
- package/dist/descriptor.js +20 -0
- package/dist/fixtures.d.ts +5 -0
- package/dist/fixtures.js +10 -0
- package/dist/generated/client.d.ts +10 -0
- package/dist/generated/client.js +7 -0
- package/dist/generated/fixtures.d.ts +2 -0
- package/dist/generated/fixtures.js +16 -0
- package/dist/generated/operations.d.ts +6 -0
- package/dist/generated/operations.js +894 -0
- package/dist/generated/types.d.ts +135 -0
- package/dist/generated/types.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/runtime/browser-core.d.ts +23 -0
- package/dist/runtime/browser-core.js +41 -0
- package/dist/runtime/environment.d.ts +5 -0
- package/dist/runtime/environment.js +30 -0
- package/dist/runtime/errors.d.ts +29 -0
- package/dist/runtime/errors.js +40 -0
- package/dist/runtime/fixture-core.d.ts +7 -0
- package/dist/runtime/fixture-core.js +26 -0
- package/dist/runtime/http.d.ts +34 -0
- package/dist/runtime/http.js +165 -0
- package/dist/runtime/server-core.d.ts +26 -0
- package/dist/runtime/server-core.js +39 -0
- package/dist/runtime/sign.d.ts +22 -0
- package/dist/runtime/sign.js +41 -0
- package/dist/runtime/validate.d.ts +57 -0
- package/dist/runtime/validate.js +175 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +8 -0
- package/package.json +62 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Runtime template: copied into every generated client package as src/runtime/sign.ts.
|
|
2
|
+
// Server entry only: the browser entry never imports this file. Web Crypto, no dependencies.
|
|
3
|
+
const DEFAULT_LIFETIME_SECONDS = 120;
|
|
4
|
+
const MAX_LIFETIME_SECONDS = 600;
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const base64url = (bytes) => {
|
|
7
|
+
let binary = "";
|
|
8
|
+
for (const byte of bytes) {
|
|
9
|
+
binary += String.fromCharCode(byte);
|
|
10
|
+
}
|
|
11
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
12
|
+
};
|
|
13
|
+
const encodeJson = (value) => base64url(encoder.encode(JSON.stringify(value)));
|
|
14
|
+
/**
|
|
15
|
+
* Returns a function that signs one short-lived token for each call. The key is imported once.
|
|
16
|
+
* The token is a JWT with alg EdDSA and the claims iss, aud, sub, jti, iat, exp and scope.
|
|
17
|
+
*/
|
|
18
|
+
export const createTokenSigner = (privateJwk) => {
|
|
19
|
+
let key;
|
|
20
|
+
const importKey = () => {
|
|
21
|
+
key ??= crypto.subtle.importKey("jwk", privateJwk, { name: "Ed25519" }, false, ["sign"]);
|
|
22
|
+
return key;
|
|
23
|
+
};
|
|
24
|
+
return async (input) => {
|
|
25
|
+
const lifetime = Math.min(Math.max(input.lifetimeSeconds ?? DEFAULT_LIFETIME_SECONDS, 1), MAX_LIFETIME_SECONDS);
|
|
26
|
+
const issuedAt = Math.floor((input.now?.() ?? Date.now()) / 1000);
|
|
27
|
+
const header = encodeJson({ alg: "EdDSA" });
|
|
28
|
+
const payload = encodeJson({
|
|
29
|
+
scope: input.scope,
|
|
30
|
+
iss: input.clientId,
|
|
31
|
+
aud: input.audience,
|
|
32
|
+
sub: crypto.randomUUID(),
|
|
33
|
+
jti: crypto.randomUUID(),
|
|
34
|
+
iat: issuedAt,
|
|
35
|
+
exp: issuedAt + lifetime,
|
|
36
|
+
});
|
|
37
|
+
const signingInput = `${header}.${payload}`;
|
|
38
|
+
const signature = await crypto.subtle.sign({ name: "Ed25519" }, await importKey(), encoder.encode(signingInput));
|
|
39
|
+
return `${signingInput}.${base64url(new Uint8Array(signature))}`;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** The JSON Schema subset that generated clients check, in a compact form the generator writes. */
|
|
2
|
+
export type Schema = {
|
|
3
|
+
readonly t: "str";
|
|
4
|
+
readonly min?: number;
|
|
5
|
+
readonly max?: number;
|
|
6
|
+
readonly values?: readonly string[];
|
|
7
|
+
/** A JSON Schema pattern: checked in a request only. */
|
|
8
|
+
readonly pattern?: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly t: "int" | "num";
|
|
11
|
+
readonly min?: number;
|
|
12
|
+
readonly max?: number;
|
|
13
|
+
/** exclusiveMinimum and exclusiveMaximum, checked in a request only. */
|
|
14
|
+
readonly exMin?: number;
|
|
15
|
+
readonly exMax?: number;
|
|
16
|
+
readonly values?: readonly number[];
|
|
17
|
+
} | {
|
|
18
|
+
readonly t: "bool";
|
|
19
|
+
} | {
|
|
20
|
+
readonly t: "null";
|
|
21
|
+
} | {
|
|
22
|
+
readonly t: "any";
|
|
23
|
+
} | {
|
|
24
|
+
readonly t: "const";
|
|
25
|
+
readonly value: string | number | boolean | null;
|
|
26
|
+
} | {
|
|
27
|
+
readonly t: "arr";
|
|
28
|
+
readonly of: Schema;
|
|
29
|
+
readonly min?: number;
|
|
30
|
+
readonly max?: number;
|
|
31
|
+
} | {
|
|
32
|
+
readonly t: "tup";
|
|
33
|
+
readonly of: readonly Schema[];
|
|
34
|
+
} | {
|
|
35
|
+
readonly t: "map";
|
|
36
|
+
readonly of: Schema;
|
|
37
|
+
} | {
|
|
38
|
+
readonly t: "obj";
|
|
39
|
+
readonly props: Readonly<Record<string, Schema>>;
|
|
40
|
+
readonly req: readonly string[];
|
|
41
|
+
readonly extra: boolean;
|
|
42
|
+
} | {
|
|
43
|
+
readonly t: "any_of";
|
|
44
|
+
readonly of: readonly Schema[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A request is checked against every constraint before it leaves the client. A response is
|
|
48
|
+
* checked for shape only: unknown properties, new enum values and size limits are accepted, so
|
|
49
|
+
* a service can add to a reply in a minor version without breaking an installed client.
|
|
50
|
+
*/
|
|
51
|
+
export type Mode = "request" | "response";
|
|
52
|
+
export interface Issue {
|
|
53
|
+
readonly message: string;
|
|
54
|
+
readonly path: string;
|
|
55
|
+
}
|
|
56
|
+
/** Returns the problems found, at most eight, or an empty array when the value is acceptable. */
|
|
57
|
+
export declare const validate: (schema: Schema, value: unknown, mode: Mode) => readonly Issue[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Runtime template: copied into every generated client package as src/runtime/validate.ts.
|
|
2
|
+
// It has no imports, so the copy is identical to this file.
|
|
3
|
+
const MAX_ISSUES = 8;
|
|
4
|
+
const describe = (value) => {
|
|
5
|
+
if (value === null) {
|
|
6
|
+
return "null";
|
|
7
|
+
}
|
|
8
|
+
return Array.isArray(value) ? "array" : typeof value;
|
|
9
|
+
};
|
|
10
|
+
const add = (issues, path, message) => {
|
|
11
|
+
if (issues.length < MAX_ISSUES) {
|
|
12
|
+
issues.push({ path, message });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const strictSize = (mode, size, min, max) => {
|
|
16
|
+
if (mode !== "request") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (min !== undefined && size < min) {
|
|
20
|
+
return `must be at least ${min}`;
|
|
21
|
+
}
|
|
22
|
+
if (max !== undefined && size > max) {
|
|
23
|
+
return `must be at most ${max}`;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
};
|
|
27
|
+
/** A pattern the runtime cannot compile is not checked here: the service checks it. */
|
|
28
|
+
const matches = (pattern, value) => {
|
|
29
|
+
try {
|
|
30
|
+
return new RegExp(pattern).test(value);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const checkString = (schema, value, path, mode, issues) => {
|
|
37
|
+
if (typeof value !== "string") {
|
|
38
|
+
add(issues, path, `expected a string, got ${describe(value)}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const size = strictSize(mode, value.length, schema.min, schema.max);
|
|
42
|
+
if (size !== undefined) {
|
|
43
|
+
add(issues, path, `length ${size}`);
|
|
44
|
+
}
|
|
45
|
+
if (mode === "request" && schema.values !== undefined && !schema.values.includes(value)) {
|
|
46
|
+
add(issues, path, `must be one of ${schema.values.join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
if (mode === "request" && schema.pattern !== undefined && !matches(schema.pattern, value)) {
|
|
49
|
+
add(issues, path, `must match the pattern ${schema.pattern}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const checkNumber = (schema, value, path, mode, issues) => {
|
|
53
|
+
const ok = typeof value === "number" && Number.isFinite(value) && (schema.t === "num" || Number.isInteger(value));
|
|
54
|
+
if (!ok) {
|
|
55
|
+
add(issues, path, `expected ${schema.t === "int" ? "an integer" : "a number"}, got ${describe(value)}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const size = strictSize(mode, value, schema.min, schema.max);
|
|
59
|
+
if (size !== undefined) {
|
|
60
|
+
add(issues, path, `value ${size}`);
|
|
61
|
+
}
|
|
62
|
+
if (mode === "request" && schema.exMin !== undefined && value <= schema.exMin) {
|
|
63
|
+
add(issues, path, `value must be greater than ${schema.exMin}`);
|
|
64
|
+
}
|
|
65
|
+
if (mode === "request" && schema.exMax !== undefined && value >= schema.exMax) {
|
|
66
|
+
add(issues, path, `value must be less than ${schema.exMax}`);
|
|
67
|
+
}
|
|
68
|
+
if (mode === "request" && schema.values !== undefined && !schema.values.includes(value)) {
|
|
69
|
+
add(issues, path, `must be one of ${schema.values.join(", ")}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const checkArray = (schema, value, path, mode, issues) => {
|
|
73
|
+
if (!Array.isArray(value)) {
|
|
74
|
+
add(issues, path, `expected an array, got ${describe(value)}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const size = strictSize(mode, value.length, schema.min, schema.max);
|
|
78
|
+
if (size !== undefined) {
|
|
79
|
+
add(issues, path, `item count ${size}`);
|
|
80
|
+
}
|
|
81
|
+
value.forEach((item, index) => {
|
|
82
|
+
check(schema.of, item, `${path}[${index}]`, mode, issues);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const checkTuple = (schema, value, path, mode, issues) => {
|
|
86
|
+
if (!Array.isArray(value) || value.length !== schema.of.length) {
|
|
87
|
+
add(issues, path, `expected an array of ${schema.of.length} items`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
schema.of.forEach((member, index) => {
|
|
91
|
+
check(member, value[index], `${path}[${index}]`, mode, issues);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const checkObject = (schema, value, path, mode, issues) => {
|
|
95
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
96
|
+
add(issues, path, `expected an object, got ${describe(value)}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const record = value;
|
|
100
|
+
for (const key of schema.req) {
|
|
101
|
+
if (!(key in record) || record[key] === undefined) {
|
|
102
|
+
add(issues, `${path}.${key}`, "is required");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const [key, member] of Object.entries(record)) {
|
|
106
|
+
const known = schema.props[key];
|
|
107
|
+
if (known !== undefined) {
|
|
108
|
+
if (member !== undefined) {
|
|
109
|
+
check(known, member, `${path}.${key}`, mode, issues);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (mode === "request" && !schema.extra) {
|
|
113
|
+
add(issues, `${path}.${key}`, "is not a known property");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const checkMap = (schema, value, path, mode, issues) => {
|
|
118
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
119
|
+
add(issues, path, `expected an object, got ${describe(value)}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const [key, member] of Object.entries(value)) {
|
|
123
|
+
if (member !== undefined) {
|
|
124
|
+
check(schema.of, member, `${path}.${key}`, mode, issues);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const checkUnion = (schema, value, path, mode, issues) => {
|
|
129
|
+
const matches = schema.of.some((member) => {
|
|
130
|
+
const nested = [];
|
|
131
|
+
check(member, value, path, mode, nested);
|
|
132
|
+
return nested.length === 0;
|
|
133
|
+
});
|
|
134
|
+
if (!matches) {
|
|
135
|
+
add(issues, path, "does not match any allowed shape");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const checkers = {
|
|
139
|
+
str: checkString,
|
|
140
|
+
int: checkNumber,
|
|
141
|
+
num: checkNumber,
|
|
142
|
+
arr: checkArray,
|
|
143
|
+
tup: checkTuple,
|
|
144
|
+
map: checkMap,
|
|
145
|
+
obj: checkObject,
|
|
146
|
+
any_of: checkUnion,
|
|
147
|
+
};
|
|
148
|
+
const checkSimple = (schema, value, path, issues) => {
|
|
149
|
+
if (schema.t === "bool" && typeof value !== "boolean") {
|
|
150
|
+
add(issues, path, `expected a boolean, got ${describe(value)}`);
|
|
151
|
+
}
|
|
152
|
+
else if (schema.t === "null" && value !== null) {
|
|
153
|
+
add(issues, path, `expected null, got ${describe(value)}`);
|
|
154
|
+
}
|
|
155
|
+
else if (schema.t === "const" && value !== schema.value) {
|
|
156
|
+
add(issues, path, `must be ${JSON.stringify(schema.value)}`);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const check = (schema, value, path, mode, issues) => {
|
|
160
|
+
if (issues.length >= MAX_ISSUES || schema.t === "any") {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const checker = checkers[schema.t];
|
|
164
|
+
if (checker === undefined) {
|
|
165
|
+
checkSimple(schema, value, path, issues);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
checker(schema, value, path, mode, issues);
|
|
169
|
+
};
|
|
170
|
+
/** Returns the problems found, at most eight, or an empty array when the value is acceptable. */
|
|
171
|
+
export const validate = (schema, value, mode) => {
|
|
172
|
+
const issues = [];
|
|
173
|
+
check(schema, value, "$", mode, issues);
|
|
174
|
+
return issues;
|
|
175
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AsxClient } from "./generated/client.js";
|
|
2
|
+
import { type ServerOptions } from "./runtime/server-core.js";
|
|
3
|
+
export type { Environment } from "./runtime/environment.js";
|
|
4
|
+
export type { PrivateJwk } from "./runtime/sign.js";
|
|
5
|
+
export type { ServerOptions } from "./runtime/server-core.js";
|
|
6
|
+
/** What the service verifies in the token. */
|
|
7
|
+
export declare const AUDIENCE = "asx-lookup";
|
|
8
|
+
export declare const SCOPE = "asx";
|
|
9
|
+
export declare const createAsxClient: (options: ServerOptions) => AsxClient;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
|
|
2
|
+
// Server entry: signs a short-lived token for each call with the app's private key.
|
|
3
|
+
import { buildClient } from "./generated/client.js";
|
|
4
|
+
import { createServerTransport } from "./runtime/server-core.js";
|
|
5
|
+
/** What the service verifies in the token. */
|
|
6
|
+
export const AUDIENCE = "asx-lookup";
|
|
7
|
+
export const SCOPE = "asx";
|
|
8
|
+
export const createAsxClient = (options) => buildClient(createServerTransport(options, { audience: AUDIENCE, scope: SCOPE }));
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@comms-id/asx",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Typed client for the Comms.ID ASX API: ASX-listed companies: suggest as a user types, and look one up",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server.d.ts",
|
|
16
|
+
"default": "./dist/server.js"
|
|
17
|
+
},
|
|
18
|
+
"./browser": {
|
|
19
|
+
"types": "./dist/browser.d.ts",
|
|
20
|
+
"default": "./dist/browser.js"
|
|
21
|
+
},
|
|
22
|
+
"./fixtures": {
|
|
23
|
+
"types": "./dist/fixtures.d.ts",
|
|
24
|
+
"default": "./dist/fixtures.js"
|
|
25
|
+
},
|
|
26
|
+
"./descriptor": {
|
|
27
|
+
"types": "./dist/descriptor.d.ts",
|
|
28
|
+
"default": "./dist/descriptor.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://comms.id",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/comms-id/platform/issues"
|
|
34
|
+
},
|
|
35
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
36
|
+
"author": "COMMS.ID PTY LTD",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/comms-id/platform.git",
|
|
40
|
+
"directory": "packages/asx"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "24.19.0",
|
|
49
|
+
"typescript": "7.0.2",
|
|
50
|
+
"vitest": "5.0.2"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"registry": "https://registry.npmjs.org"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
58
|
+
"lint": "biome check .",
|
|
59
|
+
"check-types": "tsc -p tsconfig.json --noEmit",
|
|
60
|
+
"test": "vitest run"
|
|
61
|
+
}
|
|
62
|
+
}
|