Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4d389f7
spike: Feasibility of generating Signify types from Python type hints…
Sotatek-Patrick-Vu Jun 27, 2025
56a35f5
add and use generated TS types
Sotatek-Patrick-Vu Jul 7, 2025
c003c1b
demo generated types for credentials().get()
Sotatek-Patrick-Vu Jul 8, 2025
8016535
update types for CredentialState
Sotatek-Patrick-Vu Jul 9, 2025
1679dc6
resolve review comments
Sotatek-Patrick-Vu Jul 10, 2025
98b637f
fix credentials types + add registries's types
Sotatek-Patrick-Vu Jul 11, 2025
508b1eb
Add more types for credentialing
Sotatek-Patrick-Vu Jul 14, 2025
0663bbe
resolve review comments
Sotatek-Patrick-Vu Jul 16, 2025
025ed27
fix errors/warning for swagger validator
Sotatek-Patrick-Vu Jul 16, 2025
63ed799
re-generate types
Sotatek-Patrick-Vu Jul 17, 2025
8b4c712
re-generate types
Sotatek-Patrick-Vu Jul 18, 2025
911318d
remove debug logs and add multiple overload signatures
Sotatek-Patrick-Vu Jul 18, 2025
84d5c3d
aiding.py type hints and auto-generated OpenAPI specs
Sotatek-Patrick-Vu Aug 7, 2025
82d077e
Generate types for Group Member
Sotatek-Patrick-Vu Aug 8, 2025
7c9bcd7
resolve review comments
Sotatek-Patrick-Vu Aug 14, 2025
9acdffe
fix mock data for aiding test
Sotatek-Patrick-Vu Aug 19, 2025
2756a67
feat: agenting.py type hints and OpenAPI specs #377
Sotatek-Patrick-Vu Aug 14, 2025
7a0e185
chore: remove any type for decrypter, encrypter and utils
Aug 6, 2025
cfd9227
feat(ui): replace any type
Aug 8, 2025
d74ba14
chore: remove cast string
Aug 8, 2025
50384d4
chore: update type
Aug 11, 2025
7656c87
chore: update review comments
Sotatek-DucVu Aug 26, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist
# IntelliJ Project Files
.idea
coverage/*

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
36 changes: 36 additions & 0 deletions scripts/generate-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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} --enum`, {
stdio: 'inherit',
});

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

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

// Extract all `export enum ... { ... }` blocks
const enumMatches = [...fullContent.matchAll(/export enum [\w\d_]+ \{[\s\S]+?\n\}/g)];

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

// Combine the interface and enums
const enumsText = enumMatches.map(m => m[0]).join('\n\n');
const cleaned = `// AUTO-GENERATED: Only components and enums retained from OpenAPI schema\n\n${enumsText}\n\n${componentsMatch[0]}\n`;

fs.writeFileSync(outputFile, cleaned, 'utf8');
21 changes: 12 additions & 9 deletions src/keri/app/aiding.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Tier } from '../core/salter.ts';
import { Algos } from '../core/manager.ts';
import { incept, interact, reply, rotate } from '../core/eventing.ts';
import { components } from '../../types/keria-api-schema.ts';
import { b, Ilks, Serials, Vrsn_1_0 } from '../core/core.ts';
import { Tholder } from '../core/tholder.ts';
import { MtrDex } from '../core/matter.ts';
import { Serder } from '../core/serder.ts';
import { incept, interact, reply, EndRoleAddAttributes, rotate } from '../core/eventing.ts';
import { parseRangeHeaders } from '../core/httping.ts';
import { IdentifierManagerFactory } from '../core/keeping.ts';
import { HabState } from '../core/keyState.ts';
import { Algos } from '../core/manager.ts';
import { MtrDex } from '../core/matter.ts';
import { Tier } from '../core/salter.ts';
import { Serder } from '../core/serder.ts';
import { Tholder } from '../core/tholder.ts';

/** Arguments required to create an identfier */
export interface CreateIdentiferArgs {
Expand Down Expand Up @@ -73,6 +74,8 @@ export interface IdentifierInfo {
name: string;
}

export type GroupMember = components['schemas']['GroupMember'];

/** Identifier */
export class Identifier {
public client: IdentifierDeps;
Expand Down Expand Up @@ -472,7 +475,7 @@ export class Identifier {
eid?: string,
stamp?: string
): Serder {
const data: any = {
const data: EndRoleAddAttributes = {
cid: pre,
role: role,
};
Expand All @@ -487,9 +490,9 @@ export class Identifier {
* Get the members of a group identifier
* @async
* @param {string} name - Name or alias of the identifier
* @returns {Promise<any>} - A promise to the list of members
* @returns {Promise<GroupMember>} - A promise to the list of members
*/
async members(name: string): Promise<any> {
async members(name: string): Promise<GroupMember> {
const res = await this.client.fetch(
'/identifiers/' + name + '/members',
'GET',
Expand Down
12 changes: 8 additions & 4 deletions src/keri/app/clienting.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { components } from '../../types/keria-api-schema.ts';
import { Authenticater } from '../core/authing.ts';
import { HEADER_SIG_TIME } from '../core/httping.ts';
import { ExternalModule, IdentifierManagerFactory } from '../core/keeping.ts';
import { Tier } from '../core/salter.ts';

import { Identifier } from './aiding.ts';
import { Contacts, Challenges } from './contacting.ts';
import { Challenges, Contacts } from './contacting.ts';
import { Agent, Controller } from './controller.ts';
import { Oobis, Operations, KeyEvents, KeyStates, Config } from './coring.ts';
import { Config, KeyEvents, KeyStates, Oobis, Operations } from './coring.ts';
import { Credentials, Ipex, Registries, Schemas } from './credentialing.ts';
import { Delegations } from './delegating.ts';
import { Escrows } from './escrowing.ts';
Expand All @@ -16,6 +17,9 @@ import { Notifications } from './notifying.ts';

const DEFAULT_BOOT_URL = 'http://localhost:3903';

// Export type outside the class
export type AgentResourceResult = components["schemas"]["AgentResourceResult"];

class State {
agent: any | null;
controller: any | null;
Expand Down Expand Up @@ -117,7 +121,7 @@ export class SignifyClient {
throw new Error(`agent does not exist for controller ${caid}`);
}

const data = await res.json();
const data = await res.json() as AgentResourceResult;
const state = new State();
state.agent = data.agent ?? {};
state.controller = data.controller ?? {};
Expand Down Expand Up @@ -172,7 +176,7 @@ export class SignifyClient {
async fetch(
path: string,
method: string,
data: any,
data: unknown | null | undefined,
extraHeaders?: Headers
): Promise<Response> {
const headers = new Headers();
Expand Down
19 changes: 7 additions & 12 deletions src/keri/app/contacting.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { SignifyClient } from './clienting.ts';
import { Operation } from './coring.ts';
import { components } from '../../types/keria-api-schema.ts';

export interface Contact {
alias: string;
oobi: string;
id: string;
[key: string]: unknown;
}

export type Contact = components['schemas']['Contact'];

export interface ContactInfo {
[key: string]: unknown;
Expand Down Expand Up @@ -111,9 +108,7 @@ export class Contacts {
}
}

export interface Challenge {
words: string[];
}
export type Challenge = components['schemas']['Challenge'];

/**
* Challenges
Expand Down Expand Up @@ -189,15 +184,15 @@ export class Challenges {
* Mark challenge response as signed and accepted
* @param source Prefix of the identifier that was challenged
* @param said qb64 AID of exn message representing the signed response
* @returns {Promise<Response>} A promise to the result
* @returns {Promise<Operation<any>>} A promise to the result
*/
async responded(source: string, said: string): Promise<Response> {
async responded(source: string, said: string): Promise<Operation<any>> {
const path = `/challenges_verify/${source}`;
const method = 'PUT';
const data = {
said: said,
};
const res = await this.client.fetch(path, method, data);
return res;
return await res.json() as Operation<any>;
}
}
35 changes: 20 additions & 15 deletions src/keri/app/controller.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Cipher } from '../core/cipher.ts';
import { b, Ilks, Serials, Vrsn_1_0 } from '../core/core.ts';
import { Decrypter } from '../core/decrypter.ts';
import { Diger } from '../core/diger.ts';
import { Encrypter } from '../core/encrypter.ts';
import { incept, InceptEventSAD, interact, InteractEventSAD, rotate } from '../core/eventing.ts';
import { SaltyCreator } from '../core/manager.ts';
import { Salter, Tier } from '../core/salter.ts';
import { MtrDex } from '../core/matter.ts';
import { Diger } from '../core/diger.ts';
import { incept, rotate, interact } from '../core/eventing.ts';
import { CesrNumber } from '../core/number.ts';
import { Salter, Tier } from '../core/salter.ts';
import { Seqner } from '../core/seqner.ts';
import { Serder } from '../core/serder.ts';
import { Signer } from '../core/signer.ts';
import { Tholder } from '../core/tholder.ts';
import { Ilks, b, Serials, Vrsn_1_0 } from '../core/core.ts';
import { Verfer } from '../core/verfer.ts';
import { Encrypter } from '../core/encrypter.ts';
import { Decrypter } from '../core/decrypter.ts';
import { Cipher } from '../core/cipher.ts';
import { Seqner } from '../core/seqner.ts';
import { CesrNumber } from '../core/number.ts';

/**
* Agent is a custodial entity that can be used in conjuntion with a local Client to establish the
Expand Down Expand Up @@ -128,7 +129,7 @@ export class Controller {
/**
* Either the current establishment event, inception or rotation, or the interaction event used for delegation approval.
*/
public serder: Serder;
public serder: Serder<InteractEventSAD | InceptEventSAD>;
/**
* Current public keys formatted in fully-qualified Base64.
* @private
Expand Down Expand Up @@ -227,11 +228,12 @@ export class Controller {

approveDelegation(_agent: Agent) {
const seqner = new Seqner({ sn: _agent.sn });
const anchor = { i: _agent.pre, s: seqner.snh, d: _agent.said };
const sn = new CesrNumber({}, undefined, this.serder.sad['s']).num + 1;
const anchor = { i: _agent.pre, s: seqner.snh, d: _agent.said! };
const sn =
new CesrNumber({}, undefined, String(this.serder.sad['s'])).num + 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sotatek-Patrick-Vu is currently working on the typing for ee Serders (establishment events). I think we could narrow the typing here and we'd know exactly the format of sad so we'd know sad.s is a string. That'd be better but requires some coordination.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the status here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

this.serder = interact({
pre: this.serder.pre,
dig: this.serder.sad['d'],
dig: this.serder.sad['d']!,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for these ! values

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

sn: sn,
data: [anchor],
version: Vrsn_1_0,
Expand Down Expand Up @@ -265,7 +267,10 @@ export class Controller {
wits: [],
});
} else {
return new Serder({ sad: state.controller['ee'] });
return new Serder({
sad: state.controller['ee'],
d: '',
});
}
}

Expand Down Expand Up @@ -386,7 +391,7 @@ export class Controller {
const signers = [];
for (const prx of prxs) {
const cipher = new Cipher({ qb64: prx });
const dsigner = decrypter.decrypt(null, cipher, true);
const dsigner = decrypter.decrypt(null, cipher, true) as Signer;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to myself: the initiation of CESR instances is very hard to work with in TypeScript. We should be able to know this decrypter returns Signer based on cipher. We probably need to handle this in a follow up ticket

signers.push(dsigner);
nprxs.push(encrypter.encrypt(b(dsigner.qb64)).qb64);
}
Expand Down
40 changes: 20 additions & 20 deletions src/keri/app/coring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { SignifyClient } from './clienting.ts';
import libsodium from 'libsodium-wrappers-sumo';
import { Salter } from '../core/salter.ts';
import { Matter, MtrDex } from '../core/matter.ts';
import { components } from '../../types/keria-api-schema.ts';

type OperationBase = components['schemas']['OperationBase'];
type OOBI = components['schemas']['OOBI'];
type KeyState = components['schemas']['KeyStateRecord'];
type KeyEventRecord = components['schemas']['KeyEventRecord'];
type AgentConfig = components['schemas']['AgentConfig'];

export function randomPasscode(): string {
const raw = libsodium.randombytes_buf(16);
Expand Down Expand Up @@ -33,7 +40,7 @@ export class Oobis {
* @param {string} role Authorized role
* @returns {Promise<any>} A promise to the OOBI(s)
*/
async get(name: string, role: string = 'agent'): Promise<any> {
async get(name: string, role: string = 'agent'): Promise<OOBI> {
const path = `/identifiers/${name}/oobis?role=${role}`;
const method = 'GET';
const res = await this.client.fetch(path, method, null);
Expand All @@ -45,9 +52,9 @@ export class Oobis {
* @async
* @param {string} oobi The OOBI to be resolver
* @param {string} [alias] Optional name or alias to link the OOBI resolution to a contact
* @returns {Promise<any>} A promise to the long-running operation
* @returns {Promise<Operation<any>>} A promise to the long-running operation
*/
async resolve(oobi: string, alias?: string): Promise<any> {
async resolve(oobi: string, alias?: string): Promise<Operation<any>> {
const path = `/oobis`;
const data: any = {
url: oobi,
Expand All @@ -61,16 +68,13 @@ export class Oobis {
}
}

export interface Operation<T = unknown> {
name: string;
export type Operation<T = unknown> = OperationBase & {
response?: T;
metadata?: {
depends?: Operation;
[property: string]: any;
};
done?: boolean;
error?: any;
response?: T;
}
};

export interface OperationsDeps {
fetch(
Expand All @@ -81,10 +85,6 @@ export interface OperationsDeps {
): Promise<Response>;
}

export interface AgentConfig {
iurls?: string[];
}

/**
* Operations
* @remarks
Expand Down Expand Up @@ -204,9 +204,9 @@ export class KeyEvents {
* Retrieve key events for an identifier
* @async
* @param {string} pre Identifier prefix
* @returns {Promise<any>} A promise to the key events
* @returns {Promise<KeyEventRecord[]>} A promise to the key events
*/
async get(pre: string): Promise<any> {
async get(pre: string): Promise<KeyEventRecord[]> {
const path = `/events?pre=${pre}`;
const data = null;
const method = 'GET';
Expand All @@ -232,9 +232,9 @@ export class KeyStates {
* Retriene the key state for an identifier
* @async
* @param {string} pre Identifier prefix
* @returns {Promise<any>} A promise to the key states
* @returns {Promise<KeyState[]>} A promise to the key states
*/
async get(pre: string): Promise<any> {
async get(pre: string): Promise<KeyState[]> {
const path = `/states?pre=${pre}`;
const data = null;
const method = 'GET';
Expand All @@ -248,7 +248,7 @@ export class KeyStates {
* @param {Array<string>} pres List of identifier prefixes
* @returns {Promise<any>} A promise to the key states
*/
async list(pres: string[]): Promise<any> {
async list(pres: string[]): Promise<KeyState[]> {
const path = `/states?${pres.map((pre) => `pre=${pre}`).join('&')}`;
const data = null;
const method = 'GET';
Expand All @@ -262,9 +262,9 @@ export class KeyStates {
* @param {string} pre Identifier prefix
* @param {number} [sn] Optional sequence number
* @param {any} [anchor] Optional anchor
* @returns {Promise<any>} A promise to the long-running operation
* @returns {Promise<Operation<any>>} A promise to the long-running operation
*/
async query(pre: string, sn?: string, anchor?: any): Promise<any> {
async query(pre: string, sn?: string, anchor?: any): Promise<Operation<any>> {
const path = `/queries`;
const data: any = {
pre: pre,
Expand Down
Loading