Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ dist
# IntelliJ Project Files
.idea
coverage/*

# Keria API types
src/types
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ Typescript source files needs to be transpiled before running scripts or integra
npm run build
```

To build and generate TypeScript types from KERIA OpenAPI docs dynamically

```
SPEC_URL=http://localhost:3902/spec.yaml npm run build
```

- ready() must be called before library is useable. Example minimum viable client code.

```javascript
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
],
"scripts": {
"start": "npm run build -- --watch",
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.json --noEmit",
"generate:types": "node scripts/generate-types.js",
"build": "npm run generate:types && tsc -p tsconfig.build.json && tsc -p tsconfig.json --noEmit",
"test": "vitest",
"prepare": "tsc -p tsconfig.build.json",
"test:integration": "vitest -c vitest.integration.ts",
Expand Down
32 changes: 32 additions & 0 deletions scripts/generate-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { execSync } from "child_process";
import fs from "fs";
import path from "path";

const specUrl = process.env.SPEC_URL;
const outputFile = path.resolve("src/types/keria-api-schema.ts");

if (!specUrl) {
console.log("⚠️ Skipping OpenAPI type generation: SPEC_URL is not set.");
process.exit(0);
}

console.log(`📦 Generating types from ${specUrl}`);
execSync(`npx openapi-typescript "${specUrl}" --output ${outputFile}`, {
stdio: "inherit",
});

// Read the full file
const fullContent = fs.readFileSync(outputFile, "utf8");

// Extract only the `export interface components { ... }` block
const match = fullContent.match(/export interface components \{[\s\S]+?\n\}/);

if (!match) {
console.error("❌ Could not find 'export interface components' block.");
process.exit(1);
}

// Add comment header
const cleaned = `// AUTO-GENERATED: Only components retained from OpenAPI schema\n\n${match[0]}\n`;

fs.writeFileSync(outputFile, cleaned, "utf8");
125 changes: 125 additions & 0 deletions test-integration/openapi-type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { assert, describe, test } from 'vitest';
import { EventResult, SignifyClient } from 'signify-ts';
import {
getOrCreateClients,
waitOperation,
} from './utils/test-util.ts';
import { resolveEnvironment } from './utils/resolve-env.ts';
import { components } from '../src/types/keria-api-schema.ts';

Check failure on line 8 in test-integration/openapi-type.test.ts

View workflow job for this annotation

GitHub Actions / Run integration test using keria:0.2.0-dev6

Cannot find module '../src/types/keria-api-schema.ts' or its corresponding type declarations.


type IdentifierCreateResponse = components["schemas"]["IdentifierCreateResponse"];
type ResponseUnion = components["schemas"]["ResponseUnion"];
type OpMetadata = components["schemas"]["OpMetadata"];
type OpResponseKel = components["schemas"]["OpResponseKel"];


function isOpResponseKel(obj: any): obj is OpResponseKel {
return (
typeof obj === "object" &&
obj !== null &&
(obj.a === undefined || Array.isArray(obj.a)) &&
Array.isArray(obj.b) &&
typeof obj.bt === "string" &&
(obj.c === undefined || Array.isArray(obj.c)) &&
typeof obj.d === "string" &&
typeof obj.i === "string" &&
Array.isArray(obj.k) &&
typeof obj.kt === "string" &&
Array.isArray(obj.n) &&
typeof obj.nt === "string" &&
typeof obj.s === "string" &&
typeof obj.t === "string" &&
typeof obj.v === "string"
);
}

function isResponseUnion(obj: any): obj is ResponseUnion {
if (obj === null) return true;
if (typeof obj === "object") {
if (isOpResponseKel(obj)) return true;
if (Object.keys(obj).length === 0) return true; // empty object
}
return false;
}

function isIdentifierCreateResponse(obj: any): obj is IdentifierCreateResponse {
if (
typeof obj !== "object" ||
obj === null ||
typeof obj.done !== "boolean" ||
typeof obj.name !== "string"
) {
return false;
}
// Check optional metadata
if ("metadata" in obj && obj.metadata !== undefined && typeof obj.metadata !== "object") {
return false;
}
// Check optional response
if ("response" in obj && obj.response !== undefined && !isResponseUnion(obj.response)) {
return false;
}
// Only allow expected keys
const allowedKeys = ["done", "metadata", "name", "response"];
for (const key of Object.keys(obj)) {
if (!allowedKeys.includes(key)) {
return false;
}
}
return true;
}

let client: SignifyClient;

describe('test-setup-clients', async () => {
[client] = await getOrCreateClients(1);

const env = resolveEnvironment();
const kargs = {
toad: env.witnessIds.length,
wits: env.witnessIds,
};

test('should return IdentifierCreateResponse from waitOperation', async () => {
const result: EventResult = await client
.identifiers()
.create("name", kargs);
let op = await result.op();
op = await waitOperation(client, op);

console.log('Operation result:', op);

if (!isIdentifierCreateResponse(op)) {
let errors: string[] = [];
if (typeof op !== "object" || op === null) {
errors.push(`op is not an object: ${JSON.stringify(op)}`);
} else {
if (typeof op.done !== "boolean") {
errors.push(`op.done is not boolean: ${JSON.stringify(op.done)}`);
}
if (typeof op.name !== "string") {
errors.push(`op.name is not string: ${JSON.stringify(op.name)}`);
}
if ("metadata" in op && op.metadata !== undefined && typeof op.metadata !== "object") {
errors.push(`op.metadata is not object or undefined: ${JSON.stringify(op.metadata)}`);
}
if ("response" in op && op.response !== undefined && !isResponseUnion(op.response)) {
errors.push(`op.response is not ResponseUnion: ${JSON.stringify(op.response)}`);
}
const allowedKeys = ["done", "metadata", "name", "response"];
for (const key of Object.keys(op)) {
if (!allowedKeys.includes(key)) {
errors.push(`Unexpected key in op: ${key}`);
}
}
}
throw new Error("IdentifierCreateResponse check failed:\n" + errors.join("\n"));
}

assert.equal(isIdentifierCreateResponse(op), true);
assert.typeOf(op.done, 'boolean');
assert.typeOf(op.name, 'string');

});
});
Loading