Driving rules
- Ask for the domain idea (one paragraph). If vague: one sharp clarifying question, not an interrogation.
- Tables = plural snake_case. Implicit collections → own tables.
- Every table: PK (
int autoincrement default; UUID only if domain dictates). Business identifier → unique index. Lookup/filter column → non-unique index.
- FKs optional — only include when the relation is unambiguous. Otherwise
foreignKeys: {} and let the Designer model it via relates/depend.
- Output = one complete
Schema.yaml block + explicit assumption list.
Import instruction: Schema → Import → File in the Designer.
1. Schema.yaml structure
tables:
<table_name_snake_case>:
name: <same as the key>
columns:
-
name: <column_name>
type: <SQL type, see catalogue>
phpType: <int | string | datetime | float | bool>
length: <optional integer, for varchar/char>
nullable: <true | false>
primary: <true | false> # PK column only
autoincrement: <true | false> # PK int only
indexes:
-
name: <index_name | PRIMARY>
columns: [<col>, <col>, …]
type: <primary | unique | index>
foreignKeys: {} # empty map OR list of:
-
column: <local_column>
referencedTable: <other_table>
referencedColumn: <other_column>
Column required: name, type, phpType, nullable. length required for varchar. primary/autoincrement only on PK.
Index required: name, columns, type. PK index = PRIMARY.
FKs empty: use {} (matches real DB exports) or [].
2. Types
type |
phpType |
Use for |
Notes |
int |
int |
numeric IDs, counts |
autoincrement: true on PKs |
bigint |
int |
large IDs, counts |
Row count > 2^31 |
varchar |
string |
identifiers, names, short text |
length mandatory; common 36/50/100/255 |
text |
string |
long-form text |
No length |
date |
datetime |
dates, timestamps |
Builder maps both to datetime in PHP |
decimal |
string |
money, precise numerics |
length as precision,scale if supported |
float |
float |
imprecise numerics |
Avoid for currency |
bool |
bool |
flags |
|
json |
string |
semi-structured payloads |
Use sparingly |
3. Modelling heuristics
- Identifier columns: every business object typically has an internal
int PK (id) and a public business identifier (identifier, often varchar(36) UUID7). Unique index on the business identifier.
- Active period: "active period" in the idea →
activeFrom (date, not nullable) + activeUntil (date, nullable = open period).
- Lookup tables: categories/types/statuses get their own table even when described as enums.
- Junctions: many-to-many → explicit junction table with PK + two FK columns.
- No relations in Schema.yaml unless obvious. Relations go into
Aggregate.yaml (relates/depend/adopt) via tools-definition.
4. Example
Idea: "Track meter readings. A counter has a number and an active period, lives at a meter location, can link to multiple registers via a gateway."
tables:
counters:
name: counters
columns:
- { name: id, type: int, phpType: int, nullable: false, primary: true, autoincrement: true }
- { name: identifier, type: varchar, phpType: string, length: 36, nullable: false }
- { name: meterLocationIdentifier, type: varchar, phpType: string, length: 50, nullable: false }
- { name: counterNumber, type: varchar, phpType: string, length: 50, nullable: false }
- { name: activeFrom, type: date, phpType: datetime, nullable: false }
- { name: activeUntil, type: date, phpType: datetime, nullable: true }
indexes:
- { name: PRIMARY, columns: [id], type: primary }
- { name: identifier, columns: [identifier], type: unique }
- { name: meterLocationIdentifier, columns: [meterLocationIdentifier], type: index }
foreignKeys: {}
registers:
name: registers
columns:
- { name: id, type: int, phpType: int, nullable: false, primary: true, autoincrement: true }
- { name: identifier, type: varchar, phpType: string, length: 36, nullable: false }
indexes:
- { name: PRIMARY, columns: [id], type: primary }
- { name: identifier, columns: [identifier], type: unique }
foreignKeys: {}
Assumptions to state:
- Business identifier =
identifier (UUID). If actual key is counterNumber, move unique index there.
- FKs empty — counter ↔ register link goes into the Designer via
relates/depend.
5. Reference
- Full working example with FKs:
examples/Schema.yaml (MeterDevice — counters, registers, gateways, meter locations).
- Post-import interpretation:
tools-definition
1---2name: schema-authoring3description: Author a Schema.yaml from scratch for the Jardis Designer — from a plain-text domain idea, draft tables (snake_case plural), columns with realistic types, primary keys, indexes (primary/unique/index), optional foreign keys. Output matches the DB-export format the Designer's importer parses.4---5
6## Driving rules
7
81. Ask for the domain idea (one paragraph). If vague: **one** sharp clarifying question, not an interrogation.
92. Tables = plural snake_case. Implicit collections → own tables.
103. Every table: PK (`int` autoincrement default; UUID only if domain dictates). Business identifier → `unique` index. Lookup/filter column → non-unique `index`.
114. FKs **optional** — only include when the relation is unambiguous. Otherwise `foreignKeys: {}` and let the Designer model it via `relates`/`depend`.
125. Output = one complete `Schema.yaml` block + explicit assumption list.
13
14Import instruction: `Schema → Import → File` in the Designer.
15
16### 1. Schema.yaml structure
17
18```yaml
19tables:
20 <table_name_snake_case>:
21 name: <same as the key>
22 columns:
23 -
24 name: <column_name>
25 type: <SQL type, see catalogue>
26 phpType: <int | string | datetime | float | bool>
27 length: <optional integer, for varchar/char>
28 nullable: <true | false>
29 primary: <true | false> # PK column only
30 autoincrement: <true | false> # PK int only
31 indexes:
32 -
33 name: <index_name | PRIMARY>
34 columns: [<col>, <col>, …]
35 type: <primary | unique | index>
36 foreignKeys: {} # empty map OR list of:
37 -
38 column: <local_column>
39 referencedTable: <other_table>
40 referencedColumn: <other_column>
41```
42
43**Column required:** `name`, `type`, `phpType`, `nullable`. `length` required for `varchar`. `primary`/`autoincrement` only on PK.
44**Index required:** `name`, `columns`, `type`. PK index = `PRIMARY`.
45**FKs empty:** use `{}` (matches real DB exports) or `[]`.
46
47### 2. Types
48
49| `type` | `phpType` | Use for | Notes |
50|---|---|---|---|
51| `int` | `int` | numeric IDs, counts | `autoincrement: true` on PKs |
52| `bigint` | `int` | large IDs, counts | Row count > 2^31 |
53| `varchar` | `string` | identifiers, names, short text | `length` mandatory; common 36/50/100/255 |
54| `text` | `string` | long-form text | No `length` |
55| `date` | `datetime` | dates, timestamps | Builder maps both to `datetime` in PHP |
56| `decimal` | `string` | money, precise numerics | `length` as `precision,scale` if supported |
57| `float` | `float` | imprecise numerics | Avoid for currency |
58| `bool` | `bool` | flags | |
59| `json` | `string` | semi-structured payloads | Use sparingly |
60
61### 3. Modelling heuristics
62
63- **Identifier columns:** every business object typically has an internal `int` PK (`id`) **and** a public business identifier (`identifier`, often `varchar(36)` UUID7). Unique index on the business identifier.
64- **Active period:** "active period" in the idea → `activeFrom` (`date`, not nullable) + `activeUntil` (`date`, nullable = open period).
65- **Lookup tables:** categories/types/statuses get their own table even when described as enums.
66- **Junctions:** many-to-many → explicit junction table with PK + two FK columns.
67- **No relations in Schema.yaml unless obvious.** Relations go into `Aggregate.yaml` (`relates`/`depend`/`adopt`) via `tools-definition`.
68
69### 4. Example
70
71Idea: *"Track meter readings. A counter has a number and an active period, lives at a meter location, can link to multiple registers via a gateway."*
72
73```yaml
74tables:
75 counters:
76 name: counters
77 columns:
78 - { name: id, type: int, phpType: int, nullable: false, primary: true, autoincrement: true }
79 - { name: identifier, type: varchar, phpType: string, length: 36, nullable: false }
80 - { name: meterLocationIdentifier, type: varchar, phpType: string, length: 50, nullable: false }
81 - { name: counterNumber, type: varchar, phpType: string, length: 50, nullable: false }
82 - { name: activeFrom, type: date, phpType: datetime, nullable: false }
83 - { name: activeUntil, type: date, phpType: datetime, nullable: true }
84 indexes:
85 - { name: PRIMARY, columns: [id], type: primary }
86 - { name: identifier, columns: [identifier], type: unique }
87 - { name: meterLocationIdentifier, columns: [meterLocationIdentifier], type: index }
88 foreignKeys: {}
89
90 registers:
91 name: registers
92 columns:
93 - { name: id, type: int, phpType: int, nullable: false, primary: true, autoincrement: true }
94 - { name: identifier, type: varchar, phpType: string, length: 36, nullable: false }
95 indexes:
96 - { name: PRIMARY, columns: [id], type: primary }
97 - { name: identifier, columns: [identifier], type: unique }
98 foreignKeys: {}
99```
100
101Assumptions to state:
102
103- Business identifier = `identifier` (UUID). If actual key is `counterNumber`, move unique index there.
104- FKs empty — counter ↔ register link goes into the Designer via `relates`/`depend`.
105
106### 5. Reference
107
108- Full working example with FKs: `examples/Schema.yaml` (MeterDevice — counters, registers, gateways, meter locations).
109- Post-import interpretation: `tools-definition`