metaobjects 0.9.0__py3-none-any.whl
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.
- metaobjects/__init__.py +75 -0
- metaobjects/agent_context/__init__.py +55 -0
- metaobjects/agent_context/_content/README.md +14 -0
- metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
- metaobjects/agent_context/_content/servers/java.meta.json +5 -0
- metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
- metaobjects/agent_context/_content/servers/python.meta.json +5 -0
- metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
- metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
- metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
- metaobjects/agent_context/assemble.py +133 -0
- metaobjects/agent_context/content_root.py +54 -0
- metaobjects/agent_context/scaffold.py +191 -0
- metaobjects/agent_context/types.py +44 -0
- metaobjects/attr_class_map.py +23 -0
- metaobjects/cli.py +696 -0
- metaobjects/codegen/__init__.py +0 -0
- metaobjects/codegen/config.py +11 -0
- metaobjects/codegen/constants.py +13 -0
- metaobjects/codegen/extract_delegate_emitter.py +384 -0
- metaobjects/codegen/extract_schema_emitter.py +139 -0
- metaobjects/codegen/format.py +31 -0
- metaobjects/codegen/fr010_field_mapping.py +220 -0
- metaobjects/codegen/generator.py +62 -0
- metaobjects/codegen/generator_registry.py +163 -0
- metaobjects/codegen/generators/__init__.py +0 -0
- metaobjects/codegen/generators/entity_model.py +263 -0
- metaobjects/codegen/generators/extractor_generator.py +317 -0
- metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
- metaobjects/codegen/generators/m2m_codegen.py +192 -0
- metaobjects/codegen/generators/output_parser_generator.py +272 -0
- metaobjects/codegen/generators/output_prompt_generator.py +192 -0
- metaobjects/codegen/generators/payload_vo_generator.py +672 -0
- metaobjects/codegen/generators/render_helper_generator.py +451 -0
- metaobjects/codegen/generators/router_generator.py +635 -0
- metaobjects/codegen/generators/template_generator.py +70 -0
- metaobjects/codegen/generators/tph_plan.py +120 -0
- metaobjects/codegen/generators/trace_helper_generator.py +336 -0
- metaobjects/codegen/instance_artifacts.py +15 -0
- metaobjects/codegen/output_format_spec_emitter.py +79 -0
- metaobjects/codegen/overwrite_policy.py +27 -0
- metaobjects/codegen/runner.py +110 -0
- metaobjects/codegen/runtime/__init__.py +6 -0
- metaobjects/codegen/runtime/filter_parser.py +193 -0
- metaobjects/codegen/type_map.py +84 -0
- metaobjects/core_types.py +809 -0
- metaobjects/datatype.py +19 -0
- metaobjects/documentation/__init__.py +28 -0
- metaobjects/documentation/doc_constants.py +20 -0
- metaobjects/documentation/doc_provider.py +20 -0
- metaobjects/documentation/doc_schema.py +24 -0
- metaobjects/errors.py +124 -0
- metaobjects/loader/__init__.py +0 -0
- metaobjects/loader/merge.py +287 -0
- metaobjects/loader/meta_data_loader.py +245 -0
- metaobjects/loader/sources/__init__.py +24 -0
- metaobjects/loader/sources/directory_source.py +50 -0
- metaobjects/loader/sources/file_source.py +41 -0
- metaobjects/loader/sources/meta_data_source.py +67 -0
- metaobjects/loader/sources/uri_source.py +56 -0
- metaobjects/loader/validate_discriminator.py +181 -0
- metaobjects/loader/validate_field_readonly.py +146 -0
- metaobjects/loader/validate_source_parameter_ref.py +159 -0
- metaobjects/loader/validate_source_physical_names.py +140 -0
- metaobjects/loader/validation_passes.py +1513 -0
- metaobjects/meta/__init__.py +1 -0
- metaobjects/meta/core/__init__.py +0 -0
- metaobjects/meta/core/attr/__init__.py +0 -0
- metaobjects/meta/core/attr/attr_constants.py +31 -0
- metaobjects/meta/core/attr/meta_attr.py +136 -0
- metaobjects/meta/core/field/__init__.py +0 -0
- metaobjects/meta/core/field/field_constants.py +105 -0
- metaobjects/meta/core/field/meta_field.py +76 -0
- metaobjects/meta/core/identity/__init__.py +0 -0
- metaobjects/meta/core/identity/identity_constants.py +19 -0
- metaobjects/meta/core/identity/meta_identity.py +8 -0
- metaobjects/meta/core/object/__init__.py +0 -0
- metaobjects/meta/core/object/meta_object.py +65 -0
- metaobjects/meta/core/object/meta_object_aware.py +43 -0
- metaobjects/meta/core/object/object_class_registry.py +56 -0
- metaobjects/meta/core/object/object_constants.py +13 -0
- metaobjects/meta/core/object/object_extract.py +400 -0
- metaobjects/meta/core/object/value_object.py +70 -0
- metaobjects/meta/core/relationship/__init__.py +0 -0
- metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
- metaobjects/meta/core/relationship/meta_relationship.py +54 -0
- metaobjects/meta/core/relationship/relationship_constants.py +51 -0
- metaobjects/meta/core/validator/__init__.py +0 -0
- metaobjects/meta/core/validator/validator_constants.py +18 -0
- metaobjects/meta/meta_data.py +206 -0
- metaobjects/meta/meta_root.py +8 -0
- metaobjects/meta/persistence/__init__.py +0 -0
- metaobjects/meta/persistence/db/__init__.py +1 -0
- metaobjects/meta/persistence/db/db_constants.py +41 -0
- metaobjects/meta/persistence/db/db_provider.py +60 -0
- metaobjects/meta/persistence/origin/__init__.py +0 -0
- metaobjects/meta/persistence/origin/meta_origin.py +8 -0
- metaobjects/meta/persistence/origin/origin_constants.py +20 -0
- metaobjects/meta/persistence/source/__init__.py +0 -0
- metaobjects/meta/persistence/source/meta_source.py +137 -0
- metaobjects/meta/persistence/source/source_constants.py +115 -0
- metaobjects/meta/presentation/__init__.py +0 -0
- metaobjects/meta/presentation/layout/__init__.py +0 -0
- metaobjects/meta/presentation/layout/layout_constants.py +13 -0
- metaobjects/meta/presentation/layout/meta_layout.py +8 -0
- metaobjects/meta/presentation/view/__init__.py +0 -0
- metaobjects/meta/presentation/view/meta_view.py +8 -0
- metaobjects/meta/presentation/view/view_constants.py +22 -0
- metaobjects/meta/template/__init__.py +0 -0
- metaobjects/meta/template/meta_template.py +46 -0
- metaobjects/meta/template/template_constants.py +112 -0
- metaobjects/meta/template/template_provider.py +43 -0
- metaobjects/parser.py +380 -0
- metaobjects/parser_yaml.py +82 -0
- metaobjects/provider.py +111 -0
- metaobjects/py.typed +0 -0
- metaobjects/registry.py +210 -0
- metaobjects/registry_manifest.py +223 -0
- metaobjects/render/__init__.py +74 -0
- metaobjects/render/email_document.py +14 -0
- metaobjects/render/escapers.py +109 -0
- metaobjects/render/extract/__init__.py +59 -0
- metaobjects/render/extract/coerce.py +279 -0
- metaobjects/render/extract/extract.py +211 -0
- metaobjects/render/extract/extract_map.py +61 -0
- metaobjects/render/extract/json_forgiving_reader.py +203 -0
- metaobjects/render/extract/locate.py +65 -0
- metaobjects/render/extract/normalize.py +96 -0
- metaobjects/render/extract/strip.py +20 -0
- metaobjects/render/extract/types.py +332 -0
- metaobjects/render/extract/xml_forgiving_reader.py +162 -0
- metaobjects/render/filesystem_provider.py +51 -0
- metaobjects/render/prompt/__init__.py +32 -0
- metaobjects/render/prompt/output_format_renderer.py +340 -0
- metaobjects/render/prompt/output_format_spec.py +28 -0
- metaobjects/render/prompt/prompt_field.py +29 -0
- metaobjects/render/prompt/prompt_overrides.py +29 -0
- metaobjects/render/prompt/prompt_style.py +38 -0
- metaobjects/render/renderer.py +358 -0
- metaobjects/render/verify.py +266 -0
- metaobjects/runtime/__init__.py +39 -0
- metaobjects/runtime/llm_recorder.py +210 -0
- metaobjects/runtime/n2m_resolver.py +155 -0
- metaobjects/runtime/object_manager.py +715 -0
- metaobjects/runtime/tph.py +50 -0
- metaobjects/serializer_json.py +172 -0
- metaobjects/shared/__init__.py +0 -0
- metaobjects/shared/base_types.py +16 -0
- metaobjects/shared/separators.py +4 -0
- metaobjects/shared/structural.py +9 -0
- metaobjects/source/__init__.py +79 -0
- metaobjects/source/error_source.py +266 -0
- metaobjects/source/json_path.py +106 -0
- metaobjects/source/semantic_diff.py +98 -0
- metaobjects/source/yaml_positions.py +174 -0
- metaobjects/super_resolve.py +128 -0
- metaobjects/yaml_desugar.py +481 -0
- metaobjects-0.9.0.dist-info/METADATA +97 -0
- metaobjects-0.9.0.dist-info/RECORD +181 -0
- metaobjects-0.9.0.dist-info/WHEEL +4 -0
- metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
- metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
metaobjects/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""metaobjects — Python implementation of the MetaObjects standard.
|
|
2
|
+
|
|
3
|
+
Top-level exports cover the cross-language loader API (MetaDataLoader +
|
|
4
|
+
LoadResult, the four MetaDataSource impls, and Pythonic module-level
|
|
5
|
+
shortcuts ``load_directory`` / ``load_uris`` / ``load_string`` that
|
|
6
|
+
alias the class factories ala ``requests.get`` over ``Session().get``).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .errors import ErrorCode, MetaError
|
|
11
|
+
from .loader.meta_data_loader import LoadResult, MetaDataLoader
|
|
12
|
+
from .loader.sources import (
|
|
13
|
+
DirectorySource,
|
|
14
|
+
FileSource,
|
|
15
|
+
InMemoryStringSource,
|
|
16
|
+
MetaDataFormat,
|
|
17
|
+
MetaDataSource,
|
|
18
|
+
UriSource,
|
|
19
|
+
)
|
|
20
|
+
from .meta.core.object.meta_object_aware import (
|
|
21
|
+
MetaObjectAware,
|
|
22
|
+
is_meta_object_aware,
|
|
23
|
+
)
|
|
24
|
+
from .meta.core.object.object_class_registry import (
|
|
25
|
+
ObjectClassRegistry,
|
|
26
|
+
ObjectFactory,
|
|
27
|
+
default_object_class_registry,
|
|
28
|
+
)
|
|
29
|
+
from .meta.core.object.object_extract import (
|
|
30
|
+
MAX_NEST_DEPTH,
|
|
31
|
+
ExtractError,
|
|
32
|
+
assemble,
|
|
33
|
+
or_throw,
|
|
34
|
+
extract_object,
|
|
35
|
+
extract_schema_for,
|
|
36
|
+
)
|
|
37
|
+
from .meta.core.object.value_object import ValueObject
|
|
38
|
+
|
|
39
|
+
# Module-level shortcuts: the 99% case for callers who don't need a
|
|
40
|
+
# long-lived loader. Signatures + docstrings come straight from the
|
|
41
|
+
# classmethods — no wrapping layer to drift.
|
|
42
|
+
load_directory = MetaDataLoader.from_directory
|
|
43
|
+
load_uris = MetaDataLoader.from_uris
|
|
44
|
+
load_string = MetaDataLoader.from_string
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"MetaDataLoader",
|
|
49
|
+
"LoadResult",
|
|
50
|
+
"MetaDataSource",
|
|
51
|
+
"MetaDataFormat",
|
|
52
|
+
"FileSource",
|
|
53
|
+
"DirectorySource",
|
|
54
|
+
"UriSource",
|
|
55
|
+
"InMemoryStringSource",
|
|
56
|
+
"ErrorCode",
|
|
57
|
+
"MetaError",
|
|
58
|
+
"load_directory",
|
|
59
|
+
"load_uris",
|
|
60
|
+
"load_string",
|
|
61
|
+
# Runtime object model (Phase A)
|
|
62
|
+
"ValueObject",
|
|
63
|
+
"MetaObjectAware",
|
|
64
|
+
"is_meta_object_aware",
|
|
65
|
+
"ObjectClassRegistry",
|
|
66
|
+
"ObjectFactory",
|
|
67
|
+
"default_object_class_registry",
|
|
68
|
+
# Phase B metadata-driven extract bridge
|
|
69
|
+
"extract_object",
|
|
70
|
+
"extract_schema_for",
|
|
71
|
+
"assemble",
|
|
72
|
+
"or_throw",
|
|
73
|
+
"ExtractError",
|
|
74
|
+
"MAX_NEST_DEPTH",
|
|
75
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Agent-context assembler — scaffold the slim MetaObjects Claude Code context.
|
|
2
|
+
|
|
3
|
+
Reproduces the TypeScript reference assembler (``server/typescript/packages/sdk/
|
|
4
|
+
src/agent-context/``) BYTE-FOR-BYTE: given the repo-root ``agent-context/``
|
|
5
|
+
content tree and a resolved :class:`Stack`, emit the consumer files
|
|
6
|
+
(``.metaobjects/AGENTS.md`` + ``CLAUDE.md`` and the five ``metaobjects-*`` Claude
|
|
7
|
+
Code skills, carrying only the reference fragments for the project's stack).
|
|
8
|
+
|
|
9
|
+
The assembly is pure given the content tree — the only computed content is the
|
|
10
|
+
two always-on template substitutions; every other file is a verbatim UTF-8 copy.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .assemble import (
|
|
16
|
+
AssembledFile,
|
|
17
|
+
assemble,
|
|
18
|
+
make_stack,
|
|
19
|
+
)
|
|
20
|
+
from .content_root import resolve_agent_context_root
|
|
21
|
+
from .scaffold import (
|
|
22
|
+
AGENT_CONTEXT_MANIFEST_PATH,
|
|
23
|
+
Manifest,
|
|
24
|
+
ScaffoldDecision,
|
|
25
|
+
agent_context_staleness,
|
|
26
|
+
hash_contents,
|
|
27
|
+
installed_metaobjects_version,
|
|
28
|
+
plan_scaffold,
|
|
29
|
+
)
|
|
30
|
+
from .types import (
|
|
31
|
+
CLIENT_FRAMEWORKS,
|
|
32
|
+
MIGRATION_TOKEN,
|
|
33
|
+
SERVER_LANGS,
|
|
34
|
+
SKILL_NAMES,
|
|
35
|
+
Stack,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"AGENT_CONTEXT_MANIFEST_PATH",
|
|
40
|
+
"AssembledFile",
|
|
41
|
+
"CLIENT_FRAMEWORKS",
|
|
42
|
+
"MIGRATION_TOKEN",
|
|
43
|
+
"Manifest",
|
|
44
|
+
"SERVER_LANGS",
|
|
45
|
+
"SKILL_NAMES",
|
|
46
|
+
"ScaffoldDecision",
|
|
47
|
+
"Stack",
|
|
48
|
+
"agent_context_staleness",
|
|
49
|
+
"assemble",
|
|
50
|
+
"hash_contents",
|
|
51
|
+
"installed_metaobjects_version",
|
|
52
|
+
"make_stack",
|
|
53
|
+
"plan_scaffold",
|
|
54
|
+
"resolve_agent_context_root",
|
|
55
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# agent-context — source of truth for downstream AI-assistant context
|
|
2
|
+
|
|
3
|
+
This tree is the single source the assembler (`@metaobjectsdev/sdk`,
|
|
4
|
+
`src/agent-context/`) turns into the files scaffolded into a consumer project:
|
|
5
|
+
the slim always-on Markdown (`.metaobjects/AGENTS.md` + `CLAUDE.md`) and the five
|
|
6
|
+
`metaobjects-*` Claude skills (each a universal `SKILL.md` plus the
|
|
7
|
+
`references/<token>.md` fragments matching the project's resolved stack).
|
|
8
|
+
|
|
9
|
+
- `servers/<lang>.meta.json` — per-server install + codegen command (drives the always-on).
|
|
10
|
+
- `templates/always-on.md.mustache` — the slim always-on body (`{{stackLine}}`, `{{codegenCommand}}`).
|
|
11
|
+
- `skills/<skill>/SKILL.md` — universal skill body.
|
|
12
|
+
- `skills/<skill>/references/<token>.md` — language fragment; installed iff `<token>` is in the stack.
|
|
13
|
+
|
|
14
|
+
Design: `docs/superpowers/specs/2026-06-02-downstream-agent-context-design.md`.
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metaobjects-authoring
|
|
3
|
+
description: Use when authoring or modifying MetaObjects metadata — fields, entities, relationships, sources, enums, abstracts/inheritance — in YAML or canonical JSON.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authoring MetaObjects metadata
|
|
7
|
+
|
|
8
|
+
MetaObjects metadata is the durable spine of your app: typed entity declarations
|
|
9
|
+
that drive code generation, runtime behavior, and drift detection. You author it,
|
|
10
|
+
the loader reads it, the codegen emits idiomatic per-language code from it. This
|
|
11
|
+
skill is the procedure for writing it correctly.
|
|
12
|
+
|
|
13
|
+
Metadata lives in files under `metaobjects/` at project root, one file per domain
|
|
14
|
+
concept (`meta.commerce.json`, `meta.users.yaml`, …). Each file declares a
|
|
15
|
+
`package` on its root node. Files in the same `package` with the same object
|
|
16
|
+
`name` are merged by the loader.
|
|
17
|
+
|
|
18
|
+
Two on-disk formats, one shape:
|
|
19
|
+
|
|
20
|
+
- **Canonical JSON** — the on-disk interchange. Every node is a single-key map
|
|
21
|
+
whose key fuses the type and subtype.
|
|
22
|
+
- **YAML** — the sigil-free authoring front-end. Lowered to canonical JSON at load
|
|
23
|
+
time, so it shares the entire downstream pipeline.
|
|
24
|
+
|
|
25
|
+
Author in whichever fits the project. Prefer YAML for new hand-authored metadata
|
|
26
|
+
(it's less noisy); JSON is the format conformance fixtures and tooling pin.
|
|
27
|
+
|
|
28
|
+
## The fused-key encoding (non-negotiable)
|
|
29
|
+
|
|
30
|
+
Every node is `{ "<type>.<subType>": { <body> } }`. The wrapper key fuses type and
|
|
31
|
+
subtype — there is **no** separate `subType` body key.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "object.entity": { "name": "User" } }
|
|
35
|
+
{ "field.string": { "name": "email", "@required": true } }
|
|
36
|
+
{ "field.enum": { "name": "status", "@values": ["OPEN", "CLOSED"] } }
|
|
37
|
+
{ "identity.primary": { "@fields": ["id"] } }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A complete entity in canonical JSON:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"metadata.root": {
|
|
45
|
+
"package": "acme::blog",
|
|
46
|
+
"children": [
|
|
47
|
+
{
|
|
48
|
+
"object.entity": {
|
|
49
|
+
"name": "Author",
|
|
50
|
+
"children": [
|
|
51
|
+
{ "source.rdb": { "@table": "authors" } },
|
|
52
|
+
{ "field.long": { "name": "id" } },
|
|
53
|
+
{ "field.string": { "name": "name", "@required": true, "@maxLength": 200 } },
|
|
54
|
+
{ "field.string": { "name": "bio", "@maxLength": 2000 } },
|
|
55
|
+
{ "identity.primary": { "@fields": ["id"], "@generation": "increment" } }
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The same entity in sigil-free YAML:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
metadata:
|
|
68
|
+
package: acme::blog
|
|
69
|
+
children:
|
|
70
|
+
- object.entity:
|
|
71
|
+
name: Author
|
|
72
|
+
children:
|
|
73
|
+
- source.rdb: { table: authors }
|
|
74
|
+
- field.long: { name: id }
|
|
75
|
+
- field.string: { name: name, required: true, maxLength: 200 }
|
|
76
|
+
- field.string: { name: bio, maxLength: 2000 }
|
|
77
|
+
- identity.primary: { fields: id, generation: increment }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Reserved structural keys vs. attributes
|
|
81
|
+
|
|
82
|
+
There is one closed set of **reserved structural keys**. Everything else is an
|
|
83
|
+
attribute.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
name package extends abstract overlay isArray children value
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- In **canonical JSON**: reserved keys are bare (`"name"`, `"extends"`); every
|
|
90
|
+
other key is `@`-prefixed (`"@required"`, `"@maxLength"`, `"@table"`).
|
|
91
|
+
- In **YAML**: reserved keys are bare AND attributes are bare too — the desugar
|
|
92
|
+
re-adds the `@` when lowering.
|
|
93
|
+
- `@`-prefixing a reserved word (e.g. `"@isArray": true`) is invalid and fails the
|
|
94
|
+
load with `ERR_RESERVED_ATTR`. Use the bare `isArray: true` (YAML) or the `[]`
|
|
95
|
+
key-suffix sugar (`field.long[]: weekIds`).
|
|
96
|
+
|
|
97
|
+
## Two violation rules — internalize these
|
|
98
|
+
|
|
99
|
+
1. **Attribute-name uniqueness within a node.** A node body must not declare the
|
|
100
|
+
same attribute name twice. `{ "field.string": { "name": "x", "@maxLength": 10,
|
|
101
|
+
"@maxLength": 20 } }` is malformed.
|
|
102
|
+
|
|
103
|
+
2. **An inline `@attr` IS an `attr` child — never both.** An inline attribute and
|
|
104
|
+
a child `attr.*` node with the same name are the same slot expressed two ways.
|
|
105
|
+
Declare a given attribute once, in one form. Don't set `@required` inline AND
|
|
106
|
+
also add an `attr.boolean` child named `required` — that's a double-declaration.
|
|
107
|
+
|
|
108
|
+
## Field subtypes (closed vocabulary)
|
|
109
|
+
|
|
110
|
+
| Subtype | Stores | Notes |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `field.string` | text | `@maxLength` drives `varchar(N)` |
|
|
113
|
+
| `field.int` | 32-bit integer | |
|
|
114
|
+
| `field.long` | 64-bit integer | |
|
|
115
|
+
| `field.double` | float | |
|
|
116
|
+
| `field.boolean` | true/false | |
|
|
117
|
+
| `field.date` | calendar date | ISO 8601 `YYYY-MM-DD` on the wire |
|
|
118
|
+
| `field.timestamp` | instant | ISO 8601 with timezone on the wire |
|
|
119
|
+
| `field.decimal` | exact decimal | `@precision` / `@scale`; lossless money/quantity |
|
|
120
|
+
| `field.currency` | integer minor units | see Currency below |
|
|
121
|
+
| `field.enum` | string member | `@values` required; see Enum below |
|
|
122
|
+
| `field.uuid` | UUID | canonical lowercase hex on the wire |
|
|
123
|
+
| `field.object` | embedded value object | `@objectRef` + `@storage`; see below |
|
|
124
|
+
|
|
125
|
+
Common field attributes: `@required`, `@maxLength`, `@column` (physical column
|
|
126
|
+
name), `@default`, `@filterable`, `@sortable`.
|
|
127
|
+
|
|
128
|
+
### Currency
|
|
129
|
+
|
|
130
|
+
`field.currency` stores money as **integer minor units** (cents for USD, yen for
|
|
131
|
+
JPY) — never a float. `@currency` is ISO 4217; `@locale` (on a `view.currency`
|
|
132
|
+
child) is BCP 47. The server never formats currency; formatting is client-side.
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{ "field.currency": {
|
|
136
|
+
"name": "priceCents", "@currency": "USD", "@required": true,
|
|
137
|
+
"children": [ { "view.currency": { "@locale": "en-US" } } ]
|
|
138
|
+
}}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Enum
|
|
142
|
+
|
|
143
|
+
`field.enum` is string-backed. `@values` is **required**: a non-empty set of
|
|
144
|
+
unique members, each matching `^[A-Za-z_][A-Za-z0-9_]*$`. Missing `@values` →
|
|
145
|
+
`ERR_MISSING_REQUIRED_ATTR`; a bad member → `ERR_BAD_ATTR_VALUE`.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{ "field.enum": { "name": "status", "@required": true,
|
|
149
|
+
"@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Reuse a constraint set across entities with an abstract `field.enum` + `extends`.
|
|
153
|
+
|
|
154
|
+
### Embedded value objects — `field.object` + `@storage`
|
|
155
|
+
|
|
156
|
+
`field.object` embeds another `object` declaration. `@objectRef` names it;
|
|
157
|
+
`@storage` controls persistence:
|
|
158
|
+
|
|
159
|
+
- `flattened` — one DB column per sub-field (`address_street`, `address_city`, …).
|
|
160
|
+
Illegal on array fields.
|
|
161
|
+
- `jsonb` — one `jsonb` column.
|
|
162
|
+
- `subdocument` (default, back-compat) — single jsonb column.
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{ "field.object": { "name": "address", "@objectRef": "Address", "@storage": "flattened" } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## YAML sigil-free authoring + the coercion footgun
|
|
169
|
+
|
|
170
|
+
In YAML, write the fused `type.subType` key with a **map body**, bare reserved
|
|
171
|
+
keys, bare attributes. Two house-style rules:
|
|
172
|
+
|
|
173
|
+
1. **Always write the explicit `type.subType`** (`field.string`, not `field`).
|
|
174
|
+
Defaults change; the explicit form survives registry edits.
|
|
175
|
+
|
|
176
|
+
2. **Quote any scalar that looks like a boolean, number, date, or null.** YAML
|
|
177
|
+
silently coerces unquoted `yes` / `no` / `on` / `off` to booleans and bare
|
|
178
|
+
`2026-05-25` to a date. The loader's coercion guard rejects a coerced value in
|
|
179
|
+
a slot that declares a different type (`ERR_YAML_COERCION`) — but quoting is how
|
|
180
|
+
you *prevent* the surprise. Enum members are the classic trap:
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
# Rejected — Y and N coerce to booleans
|
|
184
|
+
field.enum: { name: flag, values: [Y, N] }
|
|
185
|
+
# Correct — quote domain-data members
|
|
186
|
+
field.enum: { name: flag, values: ["Y", "N"] }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The `[]` key-suffix declares an array field: `field.long[]: weekIds` lowers to
|
|
190
|
+
`{ "field.long": { "name": "weekIds", "isArray": true } }`.
|
|
191
|
+
|
|
192
|
+
## Identities
|
|
193
|
+
|
|
194
|
+
| Subtype | Purpose | Key attrs |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `identity.primary` | the PK field(s) | `@fields`, `@generation` |
|
|
197
|
+
| `identity.secondary` | a unique secondary index | `@fields` |
|
|
198
|
+
| `identity.reference` | an inbound FK from this entity to another | `@fields`, `@references`, `@enforce` |
|
|
199
|
+
|
|
200
|
+
`@generation` on a primary controls value generation (e.g. `increment`).
|
|
201
|
+
`@fields` accepts a single string in authoring; it normalizes to an array in
|
|
202
|
+
canonical JSON. `@enforce` on a reference (default `true`) controls whether the
|
|
203
|
+
backend physically enforces it (a SQL FK constraint); set `false` for a logical
|
|
204
|
+
reference for navigation/typing/codegen only. Referential actions
|
|
205
|
+
(`@onDelete`/`@onUpdate`) are NOT on `identity.reference` — they live on the
|
|
206
|
+
`relationship.*` node (see Relationships below).
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{ "identity.primary": { "@fields": ["id"], "@generation": "increment" } }
|
|
210
|
+
{ "identity.secondary": { "@fields": ["email"] } }
|
|
211
|
+
{ "identity.reference": { "name": "fkAuthor", "@fields": ["authorId"], "@references": "Author", "@enforce": true } }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Relationships
|
|
215
|
+
|
|
216
|
+
`relationship.composition` is the "this entity owns / aggregates instances of
|
|
217
|
+
that entity" side; `identity.reference` (above) is the FK-column side. They are
|
|
218
|
+
the two halves of one FK.
|
|
219
|
+
|
|
220
|
+
| Attr | On | Values |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `@objectRef` | composition | target entity name |
|
|
223
|
+
| `@cardinality` | composition | `one` / `many` |
|
|
224
|
+
| `@onDelete` / `@onUpdate` | `relationship.*` only | `cascade` / `set-null` / `restrict` / `no-action` |
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{ "relationship.composition": {
|
|
228
|
+
"name": "posts", "@objectRef": "Post",
|
|
229
|
+
"@cardinality": "many", "@onDelete": "cascade" } }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Sources — `source.rdb` + `@kind`
|
|
233
|
+
|
|
234
|
+
`source.rdb` declares where an entity's data lives. Read-only-ness derives from
|
|
235
|
+
`@kind` (it is NOT a separate subtype):
|
|
236
|
+
|
|
237
|
+
| `@kind` | Read-only | Default? |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| `table` | no | yes (when `@kind` omitted) |
|
|
240
|
+
| `view` | yes | – |
|
|
241
|
+
| `materializedView` | yes | – |
|
|
242
|
+
| `storedProc` | yes | – |
|
|
243
|
+
| `tableFunction` | yes | – |
|
|
244
|
+
|
|
245
|
+
The physical name is `@table` (NOT `@name`). The physical column name on a field
|
|
246
|
+
is `@column`. `@schema` namespaces the DB schema (Postgres default `public`;
|
|
247
|
+
SQLite rejects non-default values). Multi-source: multiple `source.rdb` children,
|
|
248
|
+
each with a `@role`, exactly one `primary`.
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{ "source.rdb": { "@kind": "view", "@table": "v_author", "@schema": "blog" } }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
A `view`-kind entity's fields carry `origin.*` children (`passthrough` /
|
|
255
|
+
`aggregate` / `collection`) declaring where each value comes from.
|
|
256
|
+
|
|
257
|
+
## Abstracts + `extends` (deferred resolution) + `overlay`
|
|
258
|
+
|
|
259
|
+
An **abstract** node (`abstract: true`) describes a shape but is never emitted as
|
|
260
|
+
a concrete entity. A concrete node references it via `extends:` to inherit its
|
|
261
|
+
children + attrs. This is the lightest reuse mechanism — pure data, no codegen
|
|
262
|
+
change.
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
- object.entity:
|
|
266
|
+
name: BaseEntity
|
|
267
|
+
abstract: true
|
|
268
|
+
children:
|
|
269
|
+
- field.long: { name: id }
|
|
270
|
+
- field.timestamp: { name: createdAt, required: true }
|
|
271
|
+
|
|
272
|
+
- object.entity:
|
|
273
|
+
name: Author
|
|
274
|
+
extends: BaseEntity
|
|
275
|
+
children:
|
|
276
|
+
- source.rdb: { table: authors }
|
|
277
|
+
- field.string: { name: name, required: true }
|
|
278
|
+
- identity.primary: { fields: id }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Resolution facts:
|
|
282
|
+
|
|
283
|
+
- **Deferred.** `extends:` resolves *after all files load* — abstracts can live in
|
|
284
|
+
any file, forward references are fine.
|
|
285
|
+
- **Multi-level chains flatten** (`Author extends BaseEntity extends Auditable`).
|
|
286
|
+
- **Cross-package** refs use the fully-qualified name (`extends: "shared::auditable"`);
|
|
287
|
+
same-package refs use the bare name.
|
|
288
|
+
- An unresolved reference fails with `ERR_UNKNOWN_EXTENDS`.
|
|
289
|
+
|
|
290
|
+
`abstract` and `extends` are **structural keys** (bare, no `@`).
|
|
291
|
+
|
|
292
|
+
**`overlay` is a different concept.** `extends:` is an IS-A relationship between
|
|
293
|
+
two distinct nodes. `overlay: true` re-opens the *same* named node to amend it
|
|
294
|
+
across files (same `package` + same `name` → merged; last-writer-wins on attr
|
|
295
|
+
conflicts, structural children accumulate). Use `extends` to share shape between
|
|
296
|
+
distinct entities; use `overlay` to split one entity's declaration across files.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
For non-trivial schema design, use `/superpowers:brainstorming` if installed;
|
|
301
|
+
otherwise proceed.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metaobjects-codegen
|
|
3
|
+
description: Use when configuring or running MetaObjects code generation: generators/targets/dialect config, the gen command, and hand-edit-preserving regeneration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MetaObjects code generation
|
|
7
|
+
|
|
8
|
+
Codegen is the first pillar: MetaObjects reads your typed metadata and emits
|
|
9
|
+
**idiomatic per-language code** — entity types, DB tables/schemas, query helpers,
|
|
10
|
+
REST routes, validators, payload value-objects, output parsers. The metadata is the
|
|
11
|
+
durable spine; the generated code is a disposable artifact. It runs at runtime
|
|
12
|
+
**without any MetaObjects dependency** — if the libraries disappeared tomorrow, you
|
|
13
|
+
keep working code.
|
|
14
|
+
|
|
15
|
+
This skill is the port-agnostic procedure. The exact config file, generator names,
|
|
16
|
+
and command for *this* project's server language live in a reference fragment
|
|
17
|
+
(pointed to at the bottom).
|
|
18
|
+
|
|
19
|
+
## What codegen does
|
|
20
|
+
|
|
21
|
+
You run a `gen` step. The runner:
|
|
22
|
+
|
|
23
|
+
1. Loads all metadata under `metaobjects/` (the same loader the runtime uses).
|
|
24
|
+
2. Resolves output targets and precomputes shared render state.
|
|
25
|
+
3. Runs each configured **generator** — most emit one file per entity; some emit a
|
|
26
|
+
single shared file (a barrel, a DB-context, an app-config).
|
|
27
|
+
4. Refuses to overwrite any file that does NOT carry the `@generated` header;
|
|
28
|
+
overwrites the ones that do.
|
|
29
|
+
|
|
30
|
+
The output is normal idiomatic code in your language — you import it and use it
|
|
31
|
+
like any hand-written module.
|
|
32
|
+
|
|
33
|
+
## The `@generated` header + hand-edit-preserving regen
|
|
34
|
+
|
|
35
|
+
Every emitted file carries a `@generated` header. This is load-bearing:
|
|
36
|
+
|
|
37
|
+
- **Never hand-edit a file with a `@generated` header for a change you want to
|
|
38
|
+
keep.** The next `gen` run overwrites it. If you need different output, change
|
|
39
|
+
the metadata (or the template), not the generated file.
|
|
40
|
+
- **Hand-written regions are preserved by three-way merge.** Where the codegen
|
|
41
|
+
supports designated hand-editable regions, regeneration runs a three-way merge
|
|
42
|
+
(base → yours → newly-generated) so your edits survive a regen. Code review is
|
|
43
|
+
the backstop: a diff on a `@generated` file that wasn't produced by `gen` is a
|
|
44
|
+
smell.
|
|
45
|
+
|
|
46
|
+
Practical rule: **pattern-derivable-from-metadata = regenerate; business logic =
|
|
47
|
+
hand-write in a non-generated file.** FK columns, CRUD, validator chains,
|
|
48
|
+
type-safe finders, `relations()` blocks — all derived, never hand-coded. Custom
|
|
49
|
+
SQL views, regex from outside metadata, and domain logic are what you hand-write.
|
|
50
|
+
|
|
51
|
+
## Selecting generators by stable name
|
|
52
|
+
|
|
53
|
+
Codegen is a set of named generators you opt into. Each generator has a **stable
|
|
54
|
+
name** (kebab-case) that surfaces in diagnostics — reference generators by that
|
|
55
|
+
name, never by inlining what they emit. Typical generators cover: the entity
|
|
56
|
+
type/model, the DB table/schema, query/finder helpers, REST routes, client
|
|
57
|
+
form/grid/hook artifacts, filter + sort allowlists, payload value-objects, and
|
|
58
|
+
`template.output` parsers. You enable the subset your project needs; an abstract
|
|
59
|
+
entity never emits instance/write artifacts regardless.
|
|
60
|
+
|
|
61
|
+
Per-entity opt-outs exist (e.g. skipping client-side artifacts for a given
|
|
62
|
+
entity) and are set as attributes on the entity in metadata, not in code.
|
|
63
|
+
|
|
64
|
+
## Dialects
|
|
65
|
+
|
|
66
|
+
Generated DB schema/DDL targets a SQL **dialect**:
|
|
67
|
+
|
|
68
|
+
- `postgres` — the default, fullest-featured.
|
|
69
|
+
- `sqlite` — supported; rejects non-default DB schemas.
|
|
70
|
+
- `d1` (Cloudflare D1) — **TypeScript-only**. It is SQLite at the SQL level; the
|
|
71
|
+
non-TS server ports have no analogue, so it never appears in their config.
|
|
72
|
+
|
|
73
|
+
Set the dialect once in the project's codegen config. Field subtypes map to the
|
|
74
|
+
dialect's column types deterministically (`field.string` + `@maxLength` →
|
|
75
|
+
`varchar(N)`, `field.currency` → integer, `field.uuid` → native `uuid` on
|
|
76
|
+
Postgres, `field.enum` → `varchar` + `CHECK`, etc.).
|
|
77
|
+
|
|
78
|
+
## Per-target output
|
|
79
|
+
|
|
80
|
+
Generated code can be routed to **multiple output directories/packages** so each
|
|
81
|
+
artifact lands with its runtime concern: the entity model in a database package,
|
|
82
|
+
routes in the API app, client hooks/forms/grids in the web app. Each generator can
|
|
83
|
+
declare which named target it writes to; same-target references stay relative,
|
|
84
|
+
cross-target references go through the target's configured import base. With no
|
|
85
|
+
targets configured, everything lands in a single output directory — output is
|
|
86
|
+
byte-identical to the single-directory case. Use multiple targets only when the
|
|
87
|
+
project's package boundaries justify it.
|
|
88
|
+
|
|
89
|
+
## Running gen
|
|
90
|
+
|
|
91
|
+
The shape is always the same — a `gen` verb that loads metadata, renders, merges,
|
|
92
|
+
and writes — but the binary differs per server language (the Node `meta`, a
|
|
93
|
+
language-native console tool, or a build-plugin goal). A dry-run mode previews
|
|
94
|
+
without writing; a watch mode re-runs on metadata changes where supported. Pass
|
|
95
|
+
specific entity names to scope a run to those entities.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
For this project's server-language codegen specifics, read every `references/*.md` file in this skill's directory (one per server language in this project's stack).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# C# codegen specifics
|
|
2
|
+
|
|
3
|
+
The C# port targets .NET consumers (EF Core + ASP.NET). Codegen runs through the
|
|
4
|
+
**`dotnet meta` .NET tool** — there is no Maven plugin and the Node `meta` binary
|
|
5
|
+
is **schema-migrations only** on the C# side (ADR-0015): `meta migrate` /
|
|
6
|
+
`meta verify --db` are Node-`meta`-owned; everything below is `dotnet meta`.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Per the always-on descriptor:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
dotnet tool install --global MetaObjects.Cli # provides `dotnet meta`
|
|
14
|
+
dotnet add package MetaObjects.Codegen # the codegen generators
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`dotnet meta` is a .NET tool invoked as `dotnet meta <command>` (the underlying
|
|
18
|
+
command is `dotnet-meta`).
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
dotnet meta gen \
|
|
24
|
+
--metadata-dir metaobjects \
|
|
25
|
+
--output-dir Generated \
|
|
26
|
+
--namespace Acme.Generated
|
|
27
|
+
dotnet meta gen --list # list registered generators
|
|
28
|
+
dotnet meta gen --generators entity,db-context,routes # select a subset
|
|
29
|
+
dotnet meta verify --codegen # codegen-drift gate (regenerate + diff vs committed)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`dotnet meta verify` defaults to `--templates` (the FR-004 prompt/template drift
|
|
33
|
+
gate, see the prompts reference); `--codegen` is the codegen-output drift gate.
|
|
34
|
+
**Schema migration + live-DB drift are NOT `dotnet meta`** — they run through the
|
|
35
|
+
Node `meta` tool (see the migration reference).
|
|
36
|
+
|
|
37
|
+
## `MetaObjects.Codegen` generators
|
|
38
|
+
|
|
39
|
+
Wire generators by their stable name (`dotnet meta gen --generators <names>`),
|
|
40
|
+
or run the default set. Output lands under `--namespace` in `--output-dir`.
|
|
41
|
+
|
|
42
|
+
| Stable name | Output |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `entity` | `<Entity>.g.cs` — an EF Core entity class per `object.entity` / projection: PascalCase props mapped via `[Column]`, `[Table]`, `[Key]` (or class-level `[PrimaryKey]` for composites), `[MaxLength]`/validators, nullability from `@required`. Enum fields → a nested (or shared) C# `enum`; object fields → owned-type navigations; value objects → POCOs. A TPH `@discriminator` base is emitted `abstract` with `: Base` subtypes (single-table). |
|
|
45
|
+
| `db-context` | one `AppDbContext` — a `DbSet<T>` per entity + `OnModelCreating`: `.HasConversion<string>()` (enums), `.OwnsOne(...)`/`.ToJson(...)` (owned/jsonb object fields), `.HasPrecision(p,s)` (decimals), `.UsingEntity<>(...)` (M:N), `.HasDiscriminator(e => e.Type).HasValue<Sub>(...)` (TPH). |
|
|
46
|
+
| `routes` | `<Entity>Routes.cs` — ASP.NET **Minimal API** CRUD per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). A TPH base emits polymorphic `GET /<base>(+/{id})` + a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` (create injects the discriminator, cross-subtype get/update/delete → 404). |
|
|
47
|
+
| `filter-allowlist` | per-entity `<Entity>FilterAllowlist` (FR-009 — the server-side field+operator allowlist the routes validate against). |
|
|
48
|
+
| `callable` | `<Entity>.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). |
|
|
49
|
+
| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the `template.output` prompt-pillar artifacts (strict parser, tolerant `extract`, output-format prompt fragment, typed render helper) — see the **prompts** reference. |
|
|
50
|
+
| `template` | the generic Mustache `templateGenerator()` primitive. |
|
|
51
|
+
|
|
52
|
+
Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the
|
|
53
|
+
same canonical JSON every port reads — fused-key form, `source.rdb` + `@table`,
|
|
54
|
+
`@column` for a renamed physical column.
|
|
55
|
+
|
|
56
|
+
## Persistence + routes are the deployed artifact
|
|
57
|
+
|
|
58
|
+
C# generates a *complete* server stack: the entity classes + `AppDbContext` ARE the
|
|
59
|
+
persistence layer (EF Core), and the minimal-API routes mount on your `WebApplication`.
|
|
60
|
+
There is no runtime "ObjectManager" layer to wire — the generated EF Core code is
|
|
61
|
+
what runs. (The other ports leave a repository seam; C# does not.)
|
|
62
|
+
|
|
63
|
+
## Extending the generators (open-for-extension, ADR-0002)
|
|
64
|
+
|
|
65
|
+
The generators are subclassable: the per-class emit methods are `protected virtual`,
|
|
66
|
+
plus finer hooks — `EmitClassHeader` / `EmitClassDeclarationLine` (class declaration:
|
|
67
|
+
`partial`, marker interfaces), `EmitPropertyAttributes` (per-property C# attributes),
|
|
68
|
+
`EmitFileUsings` (extra usings), `EmitClassBodyTrailer` (extra members). Subclass a
|
|
69
|
+
generator and override only the seam you need rather than forking. `@default` on a
|
|
70
|
+
scalar emits a literal initializer; an `object.value`'s default storage is jsonb.
|
|
71
|
+
|
|
72
|
+
**Shared + externally-provided enums (FR-019).** A package-level abstract
|
|
73
|
+
`field.enum` (`abstract: true`, `@values`) extended by concrete entity fields is
|
|
74
|
+
materialized **once** (`Enums.g.cs`) and referenced — no per-entity nested enum.
|
|
75
|
+
Adding `@provided: true` to that declaration suppresses materialization entirely:
|
|
76
|
+
consuming fields reference a hand-written/third-party enum, and the C# namespace
|
|
77
|
+
**binds to the enum's declaring metadata package** via
|
|
78
|
+
`GenConfig.PackageNamespaces["<pkg>"] = "Your.Namespace"` (one entry per namespace;
|
|
79
|
+
`ProvidedEnumNamespace` is the single fallback). The `@values` still drive the DB
|
|
80
|
+
`CHECK` + validation. This replaces the retired C#-only `@csEnumType` FQN attr
|
|
81
|
+
(ADR-0026) — no language FQN ever lives in metadata (ADR-0001).
|
|
82
|
+
|
|
83
|
+
## Re-scaffold this context
|
|
84
|
+
|
|
85
|
+
`dotnet meta agent-docs --server csharp [--out <dir>]` (re)scaffolds the slim
|
|
86
|
+
always-on Markdown + these `metaobjects-*` skills into the project — the C# tool
|
|
87
|
+
bundles the agent-context tree, so a C# consumer needs no Node `meta`.
|