Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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 @@ -66,3 +66,6 @@ __pycache__/
env/
venv/
ENV/

# Polytest (temporary files until polytest branch merged to main)
.polytest_algokit-polytest/
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Vitest Test",
"runtimeExecutable": "npm",
"runtimeArgs": ["test", "--", "--run", "--no-coverage", "${file}"],
"cwd": "${workspaceFolder}/packages/algod_client",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
45 changes: 37 additions & 8 deletions algokit-configs/openapi-converter/specs/algod.oas3.json
Original file line number Diff line number Diff line change
Expand Up @@ -4054,9 +4054,7 @@
"description": "base64 encoded program bytes"
},
"sourcemap": {
"type": "object",
"properties": {},
"description": "JSON of the source map"
"$ref": "#/components/schemas/SourceMap"
}
}
}
Expand Down Expand Up @@ -4695,8 +4693,7 @@
"id",
"network",
"proto",
"rwd",
"timestamp"
"rwd"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -4994,6 +4991,7 @@
"type": "integer",
"description": "unique asset identifier",
"x-go-type": "basics.AssetIndex",
"x-algokit-field-rename": "id",
"x-algokit-bigint": true
},
"params": {
Expand Down Expand Up @@ -6551,6 +6549,39 @@
}
},
"description": "Proof of transaction in a block."
},
"SourceMap": {
"type": "object",
"required": [
"version",
"sources",
"names",
"mappings"
],
"properties": {
"version": {
"type": "integer"
},
"sources": {
"description": "A list of original sources used by the \"mappings\" entry.",
"type": "array",
"items": {
"type": "string"
}
},
"names": {
"description": "A list of symbol names used by the \"mappings\" entry.",
"type": "array",
"items": {
"type": "string"
}
},
"mappings": {
"description": "A string with the encoded mapping data.",
"type": "string"
}
},
"description": "Source map for the program"
}
},
"responses": {
Expand Down Expand Up @@ -7333,9 +7364,7 @@
"description": "base64 encoded program bytes"
},
"sourcemap": {
"type": "object",
"properties": {},
"description": "JSON of the source map"
"$ref": "#/components/schemas/SourceMap"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions algokit-configs/openapi-converter/specs/indexer.oas3.json
Original file line number Diff line number Diff line change
Expand Up @@ -3587,6 +3587,7 @@
"index": {
"type": "integer",
"description": "unique asset identifier",
"x-algokit-field-rename": "id",
"x-algokit-bigint": true
},
"deleted": {
Expand Down
2 changes: 2 additions & 0 deletions oas-generator/src/oas_generator/generator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class FieldDescriptor:
is_signed_txn: bool
is_optional: bool
is_nullable: bool
inline_object_schema: dict | None = None
inline_meta_name: str | None = None


@dataclass
Expand Down
88 changes: 32 additions & 56 deletions oas-generator/src/oas_generator/generator/template_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
signed_txn = False
bytes_flag = False
bigint_flag = False
inline_object_schema = None

if is_array and isinstance(items, dict):
if "$ref" in items:
ref_model = ts_pascal_case(items["$ref"].split("/")[-1])
Expand All @@ -209,15 +211,31 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
else:
if "$ref" in (prop_schema or {}):
ref_model = ts_pascal_case(prop_schema["$ref"].split("/")[-1])
fmt = prop_schema.get(constants.SchemaKey.FORMAT)
bytes_flag = fmt == "byte" or prop_schema.get(constants.X_ALGOKIT_BYTES_BASE64) is True
bigint_flag = bool(prop_schema.get(constants.X_ALGOKIT_BIGINT) is True)
signed_txn = bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True)
# Check for special codec flags first
elif bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True):
signed_txn = True
# For inline nested objects, store the schema for inline metadata generation
elif (prop_schema.get(constants.SchemaKey.TYPE) == "object" and
"properties" in prop_schema and
"$ref" not in prop_schema and
prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is not True):
# Store the inline object schema for metadata generation
inline_object_schema = prop_schema
else:
fmt = prop_schema.get(constants.SchemaKey.FORMAT)
bytes_flag = fmt == "byte" or prop_schema.get(constants.X_ALGOKIT_BYTES_BASE64) is True
bigint_flag = bool(prop_schema.get(constants.X_ALGOKIT_BIGINT) is True)
signed_txn = bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True)

is_optional = prop_name not in required_fields
# Nullable per OpenAPI
is_nullable = bool(prop_schema.get(constants.SchemaKey.NULLABLE) is True)

# Generate inline metadata name for nested objects
inline_meta_name = None
if inline_object_schema:
inline_meta_name = f"{model_name}{ts_pascal_case(canonical)}Meta"

fields.append(
FieldDescriptor(
name=name_camel,
Expand All @@ -230,6 +248,8 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
is_signed_txn=signed_txn,
is_optional=is_optional,
is_nullable=is_nullable,
inline_object_schema=inline_object_schema,
inline_meta_name=inline_meta_name,
)
)

Expand Down Expand Up @@ -855,56 +875,27 @@ def generate(
if ts_pascal_case(name) in all_used_types}



# Generate components (only used schemas)
files.update(self.schema_processor.generate_models(output_dir, used_schemas))

if service_class == "AlgodApi":
models_dir = output_dir / constants.DirectoryName.SRC / constants.DirectoryName.MODELS

# Add SuggestedParams custom model
# Generate the custom typed models
files[models_dir / "suggested-params.ts"] = self.renderer.render(
"models/transaction-params/suggested-params.ts.j2",
{"spec": spec},
)

# Custom typed block models
# Block-specific models (prefixed to avoid shape collisions)
files[models_dir / "block-eval-delta.ts"] = self.renderer.render(
"models/block/block-eval-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block-state-delta.ts"] = self.renderer.render(
"models/block/block-state-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block-account-state-delta.ts"] = self.renderer.render(
"models/block/block-account-state-delta.ts.j2",
{"spec": spec},
)
# BlockAppEvalDelta is implemented by repurposing application-eval-delta.ts.j2 to new name
files[models_dir / "block-app-eval-delta.ts"] = self.renderer.render(
"models/block/application-eval-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block_state_proof_tracking_data.ts"] = self.renderer.render(
"models/block/block-state-proof-tracking-data.ts.j2",
{"spec": spec},
)
files[models_dir / "block_state_proof_tracking.ts"] = self.renderer.render(
"models/block/block-state-proof-tracking.ts.j2",
{"spec": spec},
)
files[models_dir / "signed-txn-in-block.ts"] = self.renderer.render(
"models/block/signed-txn-in-block.ts.j2",
"models/custom/suggested-params.ts.j2",
{"spec": spec},
)
files[models_dir / "block.ts"] = self.renderer.render(
"models/block/block.ts.j2",
"models/custom/block.ts.j2",
{"spec": spec},
)
files[models_dir / "get-block.ts"] = self.renderer.render(
"models/block/get-block.ts.j2",
"models/custom/get-block.ts.j2",
{"spec": spec},
)
files[models_dir / "ledger-state-delta.ts"] = self.renderer.render(
"models/custom/ledger-state-delta.ts.j2",
{"spec": spec},
)

Expand All @@ -914,22 +905,8 @@ def generate(
extras = (
"\n"
"export type { SuggestedParams, SuggestedParamsMeta } from './suggested-params';\n"
"export type { BlockEvalDelta } from './block-eval-delta';\n"
"export { BlockEvalDeltaMeta } from './block-eval-delta';\n"
"export type { BlockStateDelta } from './block-state-delta';\n"
"export { BlockStateDeltaMeta } from './block-state-delta';\n"
"export type { BlockAccountStateDelta } from './block-account-state-delta';\n"
"export { BlockAccountStateDeltaMeta } from './block-account-state-delta';\n"
"export type { BlockAppEvalDelta } from './block-app-eval-delta';\n"
"export { BlockAppEvalDeltaMeta } from './block-app-eval-delta';\n"
"export type { BlockStateProofTrackingData } from './block_state_proof_tracking_data';\n"
"export { BlockStateProofTrackingDataMeta } from './block_state_proof_tracking_data';\n"
"export type { BlockStateProofTracking } from './block_state_proof_tracking';\n"
"export { BlockStateProofTrackingMeta } from './block_state_proof_tracking';\n"
"export type { Block } from './block';\n"
"export { BlockMeta } from './block';\n"
"export type { SignedTxnInBlock } from './signed-txn-in-block';\n"
"export { SignedTxnInBlockMeta } from './signed-txn-in-block';\n"
)
files[index_path] = base_index + extras
files.update(self._generate_client_files(output_dir, client_class, service_class))
Expand Down Expand Up @@ -962,7 +939,6 @@ def _generate_runtime(
core_dir / "fetch-http-request.ts": ("base/src/core/fetch-http-request.ts.j2", context),
core_dir / "api-error.ts": ("base/src/core/api-error.ts.j2", context),
core_dir / "request.ts": ("base/src/core/request.ts.j2", context),
core_dir / "serialization.ts": ("base/src/core/serialization.ts.j2", context),
core_dir / "codecs.ts": ("base/src/core/codecs.ts.j2", context),
core_dir / "model-runtime.ts": ("base/src/core/model-runtime.ts.j2", context),
# Project files
Expand Down
32 changes: 20 additions & 12 deletions oas-generator/src/oas_generator/templates/apis/service.ts.j2
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { {% for t in sorted %}{{ t }}Meta{% if not loop.last %}, {% endif %}{% e

{% macro field_type_meta(type_name) -%}
{%- if type_name in import_types -%}
({ kind: 'model', meta: () => {{ type_name }}Meta } as const)
({ kind: 'model', meta: {{ type_name }}Meta } as const)
{%- elif type_name == 'SignedTransaction' -%}
({ kind: 'codec', codecKey: 'SignedTransaction' } as const)
{%- elif type_name == 'Uint8Array' -%}
Expand Down Expand Up @@ -105,8 +105,8 @@ export class {{ service_class_name }} {
{%- endif %}
): Promise<{{ op.responseTsType }}> {
const headers: Record<string, string> = {};
{% set supports_msgpack = op.returnsMsgpack or (op.requestBody and op.requestBody.supportsMsgpack) %}
const responseFormat: BodyFormat = {% if op.forceMsgpackQuery %}'msgpack'{% elif supports_msgpack %}'json'{% else %}'json'{% endif %};
{% set body_format = 'msgpack' if op.forceMsgpackQuery else 'json' %}
const responseFormat: BodyFormat = '{{ body_format }}'
headers['Accept'] = {{ service_class_name }}.acceptFor(responseFormat);

{% if op.requestBody and op.method.upper() not in ['GET', 'HEAD'] %}
Expand All @@ -118,9 +118,11 @@ export class {{ service_class_name }} {
const bodyMeta = {{ meta_expr(op.requestBody.tsType) }};
const mediaType = bodyMeta ? {{ service_class_name }}.mediaFor(responseFormat) : undefined;
if (mediaType) headers['Content-Type'] = mediaType;
const serializedBody = bodyMeta && body !== undefined
? AlgorandSerializer.encode(body, bodyMeta, responseFormat)
: body;
{% if op.requestBody and not meta_expr(op.requestBody.tsType) == 'undefined' %}
const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined;
{% else %}
const serializedBody = body;
{% endif %}
{% endif %}
{% endif %}

Expand All @@ -132,7 +134,11 @@ export class {{ service_class_name }} {
}
{% endif %}

const payload = await this.httpRequest.request<unknown>({
{% if op.responseTsType == 'void' %}
await this.httpRequest.request<void>({
{% else %}
const payload = await this.httpRequest.request<{{'Uint8Array' if body_format == 'msgpack' else 'string'}}>({
{% endif %}
method: '{{ op.method }}',
url: '{{ op.path }}',
path: {
Expand All @@ -158,11 +164,13 @@ export class {{ service_class_name }} {
{% endif %}
});

const responseMeta = {{ meta_expr(op.responseTsType) }};
if (responseMeta) {
return AlgorandSerializer.decode(payload, responseMeta, responseFormat);
}
return payload as {{ op.responseTsType }};
{% if op.responseTsType != 'void' %}
{% if meta_expr(op.responseTsType) == 'undefined' %}
return payload;
{% else %}
return AlgorandSerializer.decode(payload, {{ meta_expr(op.responseTsType) }}, responseFormat);
{% endif %}
{% endif %}
}

{% endfor %}
Expand Down
Loading