@elaraai/east-claude-plugin 1.0.99 → 1.0.100
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.
- package/.build/mcp/server.js +3 -2
- package/.claude-plugin/plugin.json +1 -1
- package/index.json +12207 -216
- package/package.json +1 -1
- package/skills/e3/SKILL.md +29 -802
- package/skills/e3/reference/browser.md +93 -0
- package/skills/e3/reference/cli.md +120 -0
- package/skills/e3/reference/from-code.md +48 -0
- package/skills/e3/reference/rack.md +139 -0
- package/skills/e3/reference/record-indexes.md +326 -0
- package/skills/e3/reference/record-migrations.md +63 -0
- package/skills/e3/reference/repository.md +25 -0
- package/skills/east/SKILL.md +30 -459
- package/skills/east/reference/beast2.md +71 -0
- package/skills/east/reference/cross-language-and-json-schema.md +87 -0
- package/skills/east/reference/queries.md +46 -0
- package/skills/east/reference/runners-and-ir.md +148 -0
- package/skills/east/reference/trees-and-recursion.md +56 -0
- package/skills/east/reference/values-in-typescript.md +62 -0
- package/skills/east-py/SKILL.md +28 -1647
- package/skills/east-py/reference/beast2.md +108 -0
- package/skills/east-py/reference/cli.md +51 -0
- package/skills/east-py/reference/columns-and-platform-functions.md +87 -0
- package/skills/east-py/reference/control-flow-and-queries.md +147 -0
- package/skills/east-py/reference/cross-language.md +70 -0
- package/skills/east-py/reference/ir-and-diagnostics.md +150 -0
- package/skills/east-py/reference/patterns.md +369 -0
- package/skills/east-py/reference/performance.md +70 -0
- package/skills/east-py/reference/value-methods.md +306 -0
- package/skills/east-py/reference/values-construction-and-json.md +88 -0
- package/skills/east-py/reference/writing-east-functions.md +275 -0
- package/skills/east-py-datascience/SKILL.md +11 -28
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# Record indexes
|
|
2
|
+
|
|
3
|
+
Secondary, interval and area, series and summary, and nested series indexes: what each files, how it is read, and what it costs. Part of the `e3` skill.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Secondary indexes — `index.on([...fields])`, `index.key(fn)`, `index.keys(fn)`
|
|
8
|
+
- Interval and area indexes — `index.interval`, `index.area`
|
|
9
|
+
- Series and summary indexes — `index.series`, `index.summary`
|
|
10
|
+
- Nested series indexes — `index.nested`
|
|
11
|
+
|
|
12
|
+
## Secondary indexes — `index.on([...fields])`, `index.key(fn)`, `index.keys(fn)`
|
|
13
|
+
|
|
14
|
+
A second collection over a Dict record, in another sort order, maintained in the
|
|
15
|
+
same commit, so a view by an attribute is a page rather than a scan.
|
|
16
|
+
`index.on([...])` names row fields in index order: one field uses its value as
|
|
17
|
+
the key, several use a Struct in that order, and a Set field gives an entry per
|
|
18
|
+
element. It also carries field metadata for `Data.bindPaged(record, { indexes })`
|
|
19
|
+
and `Slice.bindRecord` (see **e3-ui**). `index.key(fn)` gives one computed entry
|
|
20
|
+
per row; `index.keys(fn)` returns a Set, one entry per element (a row naming five
|
|
21
|
+
resources appears five times, an empty set not at all); the `value` option of
|
|
22
|
+
each is a projection a view renders without touching the record. The functions
|
|
23
|
+
are pure and synchronous, since an index maintained commit by commit must equal
|
|
24
|
+
its rebuild to the byte. Its name — its key in `indexes` — is an identifier,
|
|
25
|
+
never `primary` (the record's own order). Read through one with `index=<name>` on a page or key search; `e3
|
|
26
|
+
reindex` rebuilds. A deploy builds an index the package declares and the state
|
|
27
|
+
lacks, drops one it no longer declares, and runs nothing for the rest; a build
|
|
28
|
+
is a split task over the record.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const plans = e3.record('plans', PlansType, {
|
|
32
|
+
initial: new Map(),
|
|
33
|
+
indexes: (index) => ({
|
|
34
|
+
by_due: index.on(['due']),
|
|
35
|
+
by_owner_due: index.on(['owner', 'due']),
|
|
36
|
+
by_owner: index.key(East.function([StringType, PlanType], StringType, ($, _id, plan) => plan.owner), {
|
|
37
|
+
value: East.function([StringType, PlanType], StringType, ($, _id, plan) => plan.title),
|
|
38
|
+
}),
|
|
39
|
+
by_resource: index.keys(East.function([StringType, PlanType], SetType(StringType), ($, _id, plan) => plan.resources)),
|
|
40
|
+
}),
|
|
41
|
+
mutations: (mutation) => ({ /* add_plan, reschedule, patch — above */ }),
|
|
42
|
+
});
|
|
43
|
+
const pkg = e3.package('planning', '1.0.0', plans); // the record whole: its indexes and mutations with it
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Interval and area indexes — `index.interval`, `index.area`
|
|
47
|
+
|
|
48
|
+
These indexes answer more than an order — as do the series and summary indexes
|
|
49
|
+
below — each with a builder method of its own whose keys the SDK generates.
|
|
50
|
+
Underneath each is an ordinary index — the same build,
|
|
51
|
+
mutation, storage, paging and `e3 reindex` — and the record Slice reads a
|
|
52
|
+
window through either (see **e3-ui**). Their generated keys are query
|
|
53
|
+
machinery: edits and sessions always target the record's primary key.
|
|
54
|
+
|
|
55
|
+
A generated key is a struct whose own fields are e3's: `on` holds the
|
|
56
|
+
declaration's leading fields, by their names, at their key types (a Set field's
|
|
57
|
+
element type) — a struct with no fields when it lists none — and the kind's own
|
|
58
|
+
parts sit beside it. So no name is reserved: a row may name a field anything,
|
|
59
|
+
`area`, `tile` and `__area` among them, and an index may be led by it.
|
|
60
|
+
|
|
61
|
+
| Index | Key | e3's parts |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| interval | `{ on, node, at }` | the tree node an entry is filed at; the instant it is ordered by, at the `from` field's type |
|
|
64
|
+
| area | `{ on, area }` | the cell's Morton code times 32 plus its level; −1 for a row not yet placed |
|
|
65
|
+
| series | `{ on, key, level, tile }` | the value of the field `key` names; the level an event is filed at, and the start of that level's tile (#1433) |
|
|
66
|
+
| summary | `{ on, key, tile }` | the value of the field `key` names; the tile's start |
|
|
67
|
+
|
|
68
|
+
A definition's `on` is the leading fields alone; its kind's options say the
|
|
69
|
+
rest (`events.indexes.by_time.interval`, `racks.indexes.racks_by_area.area`,
|
|
70
|
+
`jobs.indexes.jobs_by_press_week.series`).
|
|
71
|
+
|
|
72
|
+
**`index.interval({ on?, from, to })`** keeps events visible while they
|
|
73
|
+
overlap a selected time window. `from` and `to` are the row's DateTime or
|
|
74
|
+
Option<DateTime> fields; optional `on` fields are exact leading choices:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const events = e3.record('events', EventsType, {
|
|
78
|
+
initial: new Map(),
|
|
79
|
+
indexes: (index) => ({
|
|
80
|
+
by_time: index.interval({ from: 'start', to: 'end' }),
|
|
81
|
+
by_status_time: index.interval({ on: ['status'], from: 'start', to: 'end' }),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Its key is `{ on, node, at }`. It stores at most three entries per event per
|
|
87
|
+
leading-field combination, independent of duration. The record Slice adapter resolves a half-open
|
|
88
|
+
`[from, to)` window with at most 55 disjoint bounded queries per exact prefix;
|
|
89
|
+
each matching original record occurs once, including events that started
|
|
90
|
+
earlier or span the whole window. It never scans the record or guesses a
|
|
91
|
+
maximum duration. An event ending at `from` is outside; an instant at `from`
|
|
92
|
+
is inside and one at `to` is outside. A missing or non-positive end is an
|
|
93
|
+
instant. A missing start remains selectable as Unset/backlog.
|
|
94
|
+
|
|
95
|
+
Use `on: ['start']` only when the intended query is **starts within**, since it
|
|
96
|
+
cannot preserve carry-in events. When both declarations are available, a Slice
|
|
97
|
+
range on the start field uses the interval declaration. The generated interval
|
|
98
|
+
keys are query machinery; record edits and sessions always target the original
|
|
99
|
+
primary key. Calendar's `Calendar.events(binding, { slice, entries, … })`
|
|
100
|
+
requires a matching interval declaration and the primary `entries` binding to
|
|
101
|
+
confirm saves even after an edit leaves the selected window.
|
|
102
|
+
|
|
103
|
+
**`index.area({ on?, x, y, width?, height?, at?, cell })`** reads the rows
|
|
104
|
+
whose box meets a 2D window — what a floor plan's viewport shows — the interval
|
|
105
|
+
index's twin:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// RacksType: Dict<String, { hall: String, x: Option<Float>, y: Option<Float>, w: Float, h: Float }>
|
|
109
|
+
const racks = e3.record('racks', RacksType, {
|
|
110
|
+
initial: new Map(),
|
|
111
|
+
indexes: (index) => ({
|
|
112
|
+
racks_by_area: index.area({ x: 'x', y: 'y', width: 'w', height: 'h', at: 'centre', cell: 0.01 }),
|
|
113
|
+
racks_by_hall_area: index.area({ on: ['hall'], x: 'x', y: 'y', width: 'w', height: 'h', at: 'centre', cell: 0.01 }),
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Option | The box's |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `x`, `y` | place: two Float fields, or two Option<Float> fields whose `none` is a row not yet placed |
|
|
121
|
+
| `width`, `height` | size: two Float fields, or neither for a point |
|
|
122
|
+
| `at` | `'corner'`, the default — `x`, `y` is the box's minimum corner, as a zone's is — or `'centre'`, as an item's is |
|
|
123
|
+
| `cell` | finest cell: its side in world units, about the smallest thing drawn — 0.01 for a floor in metres, 1 for one in millimetres. It is rounded down to a power of two, so the keys' arithmetic is exact on every runtime, and has no default: a wrong one costs a silent tenfold read |
|
|
124
|
+
| `on` | exact leading fields, as the interval index's; a Set field fans out |
|
|
125
|
+
|
|
126
|
+
Each option takes only the row's fields of its type, so a wrong one does not
|
|
127
|
+
compile, and the definition checks them again, naming the option, the field and
|
|
128
|
+
its type. The domain is the square ±`cell`·2²⁷ world units around zero
|
|
129
|
+
(±1,048 km at a 0.01 cell, which rounds to 2⁻⁷), in 29 levels. Each row is
|
|
130
|
+
filed once, under the deepest level whose cell is at least twice its box's
|
|
131
|
+
larger side, in the cell holding its box's centre: the key
|
|
132
|
+
`{ on, area: Integer }`. A row not yet placed files under `area` −1, which the
|
|
133
|
+
record Slice reads as the unplaced clause; one outside the domain, not finite,
|
|
134
|
+
or larger than half of it files under the root, which every window reads. The index **carries its rows**: each
|
|
135
|
+
entry's projection is the whole row, so a window reads the index alone and no
|
|
136
|
+
record segment, and it takes no `value`. The record Slice reads a viewport,
|
|
137
|
+
grown by an eighth of its larger side on each side, as at most 266 disjoint
|
|
138
|
+
searches per exact prefix, sent in one request, which together hold every row
|
|
139
|
+
whose box meets it, each once — beside rows of the cells searched that it
|
|
140
|
+
misses, which the reader draws or not.
|
|
141
|
+
|
|
142
|
+
## Series and summary indexes — `index.series`, `index.summary`
|
|
143
|
+
|
|
144
|
+
What a planning board reads: the events on a block of keys — a Plan's
|
|
145
|
+
resources — over a span of time, and, zoomed out past what a screen can draw,
|
|
146
|
+
how busy each key is. Both file a row's event by the key it is on and by time
|
|
147
|
+
tiles — `day`, `week` (ISO, from Monday), `month`, `quarter` or `year`, in UTC,
|
|
148
|
+
half-open — the key before the time, so a key's entries over any span are
|
|
149
|
+
contiguous: a series index's `{ on, key, level, tile }`, a summary index's
|
|
150
|
+
`{ on, key, tile }`.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// JobsType: Dict<String, { press: String, start: Option<DateTime>, end: Option<DateTime>, … }>
|
|
154
|
+
const jobs = e3.record('jobs', JobsType, {
|
|
155
|
+
initial: new Map(),
|
|
156
|
+
indexes: (index) => ({
|
|
157
|
+
jobs_by_press_week: index.series({ key: 'press', from: 'start', to: 'end', tile: 'week' }),
|
|
158
|
+
jobs_by_press_month: index.summary({ key: 'press', from: 'start', to: 'end', tile: 'month' }),
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Option | |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `key` | the field naming what an event is on: any field but a Set |
|
|
166
|
+
| `from`, `to` | the event's start and end: DateTime or Option<DateTime> fields. A row with no start is filed nowhere; no end, or one at or before the start, is an instant |
|
|
167
|
+
| `tile` | a series index's finest level, and a summary index's tile: `day`, `week`, `month`, `quarter` or `year` |
|
|
168
|
+
| `on` | exact leading fields, before the key; a Set field fans out |
|
|
169
|
+
|
|
170
|
+
**A series index** is an ordinary index whose keys the SDK generates, and it
|
|
171
|
+
**carries its rows**, as the area index does. It files each event **once**, at
|
|
172
|
+
the finest level from its `tile` up to the year whose shortest tile holds the
|
|
173
|
+
event's length — a day, a week, 28 days, 90 days, 365 days — under that level's
|
|
174
|
+
tile holding its start; an instant at `tile`; past 365 days, under each year it
|
|
175
|
+
touches. An event filed at a level ends within the tile after the one holding
|
|
176
|
+
its start, so a reader reads a block of keys over any span from the index alone
|
|
177
|
+
as one range a key a level — that level's tiles from two before the span to one
|
|
178
|
+
after it — in one request, whatever the span's width. **A summary index** holds,
|
|
179
|
+
under each key and tile, `{ count, ms }`: how many rows touch the tile, and the
|
|
180
|
+
milliseconds of it they cover — a row's shares
|
|
181
|
+
add up to its length, and an instant's is none. Each entry stands for every row
|
|
182
|
+
of its key and names none, so its window's `key` is Null, a read of it joins
|
|
183
|
+
nothing, and a read that needs its rows — a joined read, a placement
|
|
184
|
+
(`datasetPlace`) or a membership read (`datasetWithin`) — is refused, naming the
|
|
185
|
+
index; the record's describe says it is a summary. A write
|
|
186
|
+
changes its totals by what its rows took away and gave — its program reads the
|
|
187
|
+
record, never the summary — and the apply adds that to each total, dropping one
|
|
188
|
+
whose count reaches zero; the totals are Integers, so a maintained summary is the
|
|
189
|
+
one `e3 reindex` sums, byte for byte, and a build split into pieces adds the
|
|
190
|
+
pieces' totals of a key. A change that would leave a total no rebuild sums — a
|
|
191
|
+
count below zero, or time with no row — fails the write, naming the total and
|
|
192
|
+
`e3 reindex`, which rebuilds it; a total past the largest Integer fails the
|
|
193
|
+
write or the build, naming a finer tile.
|
|
194
|
+
|
|
195
|
+
A series index files an event past a year under each year it touches, as many
|
|
196
|
+
as 4,096: one touching more is refused where it is written, naming it and its
|
|
197
|
+
span. A summary splits an event over its tiles, as many as 4,096 — eleven years
|
|
198
|
+
of days — and one touching more is refused the same way, naming the tile:
|
|
199
|
+
declare a coarser tile. Each option takes only the row's fields of its type, as
|
|
200
|
+
the area index's do, and the definition checks them again; a `value` is
|
|
201
|
+
refused, and so is an `on` field that is the key, the start or the end: the key
|
|
202
|
+
follows the leading fields, and the tiles are cut by the start and the end.
|
|
203
|
+
|
|
204
|
+
## Nested series indexes — `index.nested`
|
|
205
|
+
|
|
206
|
+
What a planning board reads when a record's rows nest what it draws: a
|
|
207
|
+
machine's jobs and its load a day at a time, a hall's members, an assembly's
|
|
208
|
+
parts to any depth. One declaration a record lists every collection its rows
|
|
209
|
+
nest, each field with a builder saying what it holds. e3 files each item apart
|
|
210
|
+
from its row — once, by its collection, its row, its node and its time — keeps
|
|
211
|
+
each node's totals a summary tile at a time, and each parent's subtotals at
|
|
212
|
+
every level; and the record's other indexes carry the rows without those
|
|
213
|
+
collections, so a page of rows reads none of them and a window of items reads
|
|
214
|
+
no row.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// MachinesType: Dict<String, { name, site, status, jobs: Dict<String, Job>, load: Dict<DateTime, Float> }>
|
|
218
|
+
const machines = e3.record('machines', MachinesType, {
|
|
219
|
+
initial: new Map(),
|
|
220
|
+
indexes: (index) => ({
|
|
221
|
+
machines_in_time: index.nested({
|
|
222
|
+
collections: {
|
|
223
|
+
jobs: index.nested.items({ from: 'start', to: 'end' }), // things people drag: by id, their times fields
|
|
224
|
+
load: index.nested.values(), // a value per instant: keyed by the instant
|
|
225
|
+
},
|
|
226
|
+
tile: 'week',
|
|
227
|
+
summary: 'month',
|
|
228
|
+
}),
|
|
229
|
+
machines_by_site: index.on(['site']), // pages each machine without its jobs and load
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
// Each member's load, and each hall's mean of its members', at every level from the day up.
|
|
233
|
+
const halls = e3.record('halls', HallsType, {
|
|
234
|
+
indexes: (index) => ({
|
|
235
|
+
halls_in_time: index.nested({
|
|
236
|
+
collections: { members: index.nested.rows({ load: index.nested.values({ rollup: 'mean' }) }) },
|
|
237
|
+
tile: 'day',
|
|
238
|
+
summary: 'month',
|
|
239
|
+
}),
|
|
240
|
+
}),
|
|
241
|
+
});
|
|
242
|
+
// Parts to any depth, each part's jobs filed under every part above it too.
|
|
243
|
+
const assemblies = e3.record('assemblies', AssembliesType, {
|
|
244
|
+
indexes: (index) => ({
|
|
245
|
+
assemblies_in_time: index.nested({
|
|
246
|
+
collections: {
|
|
247
|
+
parts: index.nested.rows({
|
|
248
|
+
jobs: index.nested.items({ from: 'start', to: 'end' }, { rollup: true }),
|
|
249
|
+
parts: index.nested.recursive(),
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
tile: 'week',
|
|
253
|
+
summary: 'month',
|
|
254
|
+
}),
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| Builder (`index.nested.…`) | A field holding |
|
|
260
|
+
|---|---|
|
|
261
|
+
| `items({ from, to })`, `items({ at })` | `Dict<String, Struct>`: things people drag, by id; their times are DateTime or Option<DateTime> fields — no start files an item nowhere, no end (or one at or before its start) makes it an instant. `{ rollup: true }` files each under every ancestor too |
|
|
262
|
+
| `values({ rollup? })` | `Dict<DateTime, V>`: a value per instant. `rollup` keeps every parent's subtotals |
|
|
263
|
+
| `rows({ … })` | `Dict<String, Struct>`: child rows, their own collections declared the same way |
|
|
264
|
+
| `recursive()` | `Dict<String, Self>`: more rows like the one listing it, to any depth |
|
|
265
|
+
|
|
266
|
+
TypeScript types `collections` field by field from the record's type alone: a
|
|
267
|
+
misnamed time, or a builder on the wrong kind of field, is an error where it is
|
|
268
|
+
written, and its message says what fits. The definition checks the same again,
|
|
269
|
+
in words, for a declaration TypeScript could not see. A record has one nested
|
|
270
|
+
series index, which takes no `on` and no `value`.
|
|
271
|
+
|
|
272
|
+
**What it files.** Every entry is keyed `{ collection, row, path, run, level,
|
|
273
|
+
tile, item }` (e3-types' `NestedEntryKeyType`): the collection's place, the
|
|
274
|
+
field names from the row down (`members` then `load`); the row's key; the Dict
|
|
275
|
+
keys from the row down to the node (`[]` for the row, `m-3` for a member); the
|
|
276
|
+
run; the level and tile; and the item. A place and a path are held as a key
|
|
277
|
+
holds a list, folded from the last: `.step (key="members", rest=.step
|
|
278
|
+
(key="load", rest=.end))`. Each run of a node is one contiguous range a level:
|
|
279
|
+
|
|
280
|
+
| Run | Holds |
|
|
281
|
+
|---|---|
|
|
282
|
+
| `items` | each item once, as a series index files an event — at the level its length fits, past a year under each year — carrying it (`.item .jobs (…)`); a value, and an `{ at }` item, at the declared tile alone |
|
|
283
|
+
| `totals` | where `summary` is declared, each node's `{ count, ms, values }` a summary tile at a time; for a Float or Integer collection, `values` is the sum, the least, the greatest and the last there, taken in key order |
|
|
284
|
+
| `subtotals(rollup)` | for `values({ rollup })`, every parent's subtotal by each rollup at each level from `tile` to the year, where a descendant's value touches the tile: a node holding values folds its own, a parent aggregates its children's, in key order |
|
|
285
|
+
| `subtree`, `subtreeTotals` | for `items(…, { rollup: true })`, each item under every ancestor too, `.under (path, id)`, and each ancestor's totals of them |
|
|
286
|
+
|
|
287
|
+
A rollup is `'sum'`, `'mean'` (a mean of means up the tree), `'min'`, `'max'`,
|
|
288
|
+
`'count'` (the leaf rows beneath that hold a value), a fold and an aggregate
|
|
289
|
+
named apart (`{ fold: 'last', aggregate: 'sum' }`, a balance), or several.
|
|
290
|
+
|
|
291
|
+
**Reading.** A reader searches the index's leading fields — `[collection, row,
|
|
292
|
+
path, run, level, tile]`, each as East prints it — as a range a row a level: a
|
|
293
|
+
block of fifty machines' jobs over three weeks is fifty times four ranges in one
|
|
294
|
+
`datasetFindKeys` request, and one `datasetGetRuns` read of the entries they
|
|
295
|
+
select, each job carried whole; a parent's week subtotals are one range; a
|
|
296
|
+
part's subtree is one range a level.
|
|
297
|
+
|
|
298
|
+
**The lean rows.** Beside a nested series index, every other index of the
|
|
299
|
+
record that carries nothing or the row carries the row without the listed
|
|
300
|
+
collections — each listed collection empty, child rows kept with theirs empty —
|
|
301
|
+
so a page of rows read without a join reads no nested item and no segment of
|
|
302
|
+
the record's own. An index with a `value` of its own carries its own. A
|
|
303
|
+
binding of the record's indexes lists the nested index by name beside any that
|
|
304
|
+
carry the lean rows — `Data.bindPaged(machines, { indexes: ['machines_by_site',
|
|
305
|
+
'machines_in_time'] })` (see **e3-ui**).
|
|
306
|
+
|
|
307
|
+
**Its span.** Beside every nested series index e3 keeps its span, an index of
|
|
308
|
+
the record's named `<index>_span` (e3-types' `nestedSpanName`), built with it
|
|
309
|
+
and never declared: under each collection's place and each month its items or
|
|
310
|
+
values touch (`NestedSpanKeyType`, `{ collection, tile }`), how many of them
|
|
311
|
+
touch it and the milliseconds of it its items cover, summed across the rows as a
|
|
312
|
+
summary index's totals are, and a month let go once nothing touches it. A
|
|
313
|
+
collection's first and last month held are its span across the record — what a
|
|
314
|
+
Plan's horizon covers for the series reading it — and its months' counts are
|
|
315
|
+
how much it holds in each, which the horizon's strip draws as its bars (see
|
|
316
|
+
**e3-ui**). Reading it is one search a collection, an exact match on its
|
|
317
|
+
place, and one read of the first and last entry of each. An index the record
|
|
318
|
+
declares under that name is refused.
|
|
319
|
+
|
|
320
|
+
**Writes.** A row a write updates by a patch is maintained by the patch: e3
|
|
321
|
+
walks it along the declaration — each collection's Dict patch, and each child
|
|
322
|
+
rows' — and compares the entries of only the items it names, with the totals of
|
|
323
|
+
the summary tiles they touch and the subtotals of the tiles they touch at each
|
|
324
|
+
level. A replace on the way, which a recursive type's patch always is, and an
|
|
325
|
+
inserted or deleted row are compared whole. Both ways give the same delta, and a maintained
|
|
326
|
+
index is the one `e3 reindex` rebuilds, byte for byte.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Record migrations — how a record's type changes
|
|
2
|
+
|
|
3
|
+
Part of the `e3` skill.
|
|
4
|
+
|
|
5
|
+
| Form | Function | Runs as |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `migration.value(name, fn)` | `(Old) => New`, over any record | one unit; its runner opens the state lazily |
|
|
8
|
+
| `migration.rows(name, fn)` | a Dict's rows, `(K, V1) => V2` with the keys kept, or an Array's elements, `(T1) => T2` | a task split over the state: a piece at a time, in parallel, each piece cached |
|
|
9
|
+
| `migration.rekey(name, fn)` | a Dict's entries, `(K1, V1) => { key: K2, value: V2 }`, or a Set's elements, `(T1) => T2` | the same split task, its pieces merged by key: two Dict rows landing on one key fail the deploy, naming it; two Set elements are one |
|
|
10
|
+
|
|
11
|
+
A record's steps are one chain: the list `migrations` returns, in order, each
|
|
12
|
+
step taking the record as the step before it leaves it, and the last leaving it
|
|
13
|
+
as its declared type — each checked where the record is declared.
|
|
14
|
+
A step's name is an identifier, unique on the record, and a workspace records the
|
|
15
|
+
steps it has applied by name — so moving a step's code, or re-exporting it with a
|
|
16
|
+
newer e3, does not run it again, and an edit to an applied step's body is not
|
|
17
|
+
detected. The functions are pure, synchronous East.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const RowV1Type = StructType({ title: StringType });
|
|
21
|
+
const RowV2Type = StructType({ title: StringType, owner: StringType });
|
|
22
|
+
const plans = e3.record('plans', DictType(StringType, RowV2Type), {
|
|
23
|
+
initial: new Map(),
|
|
24
|
+
migrations: (migration) => [
|
|
25
|
+
migration.rows('add_owner', East.function([StringType, RowV1Type], RowV2Type, ($, _id, row) => ({ title: row.title, owner: 'unassigned' }))),
|
|
26
|
+
// The next version adds its step here, after this one.
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
const pkg = e3.package('planning', '2.0.0', plans);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
What a deploy does with each record, decided before it writes anything:
|
|
33
|
+
|
|
34
|
+
| The workspace holds | Against the package's chain | The deploy |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| no state | — | mints `$init` from the initial value, the whole chain counted applied; declared with no initial value, leaves it unassigned (`unassigned`) for `e3 seed` |
|
|
37
|
+
| state | every step applied, the type unchanged | keeps it, with a `$deploy` commit when the package changed |
|
|
38
|
+
| state | a proper prefix applied | runs the rest, a `$migrate:<name>` commit each |
|
|
39
|
+
| state | every step applied, but the type changed | refused: a type change needs a migration, or `--schema reset` |
|
|
40
|
+
| state | applied steps the chain does not start with | refused, naming the steps applied and those declared |
|
|
41
|
+
| a record the package no longer declares | — | refused unless `--allow-drop-records` |
|
|
42
|
+
|
|
43
|
+
`--schema <policy>` on `e3 workspace deploy` and `e3 watch`: `migrate` (the
|
|
44
|
+
default), `fail` (run no migration; refuse) or `reset` (reset the record to its
|
|
45
|
+
initial value, with a `$reset` commit (`reset`); one declared with no initial
|
|
46
|
+
value goes back to unassigned (`unassign`), for `e3 seed` again). `--plan` prints what the deploy would do
|
|
47
|
+
to each record, index and input and writes nothing — from a zip or a source it
|
|
48
|
+
imports nothing, and a server plans only a package it holds. The steps run
|
|
49
|
+
before the deploy writes: a failed step leaves the workspace as it was, and a
|
|
50
|
+
deploy run again is served the steps that finished from the cache.
|
|
51
|
+
|
|
52
|
+
What a deploy does with each input — a dataset an `e3.input` declares, which
|
|
53
|
+
people set:
|
|
54
|
+
|
|
55
|
+
| The input now | Its type | The deploy |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| holds the value the deployed package gave it, or is new | — | the new package's value (`package`) |
|
|
58
|
+
| someone set it | — | the new package's value under `--inputs reset`, the default, and says it reset it (`reset`) |
|
|
59
|
+
| someone set it | the new package's | kept, under `--inputs keep-edited` (`keep`) |
|
|
60
|
+
| someone set it | changed | the new package's value, and says why (`reset`) |
|
|
61
|
+
| the deployed package took it from a file, and it holds a value | — | the new package's value under either policy, since a value set since cannot be told from the file's, and says so (`reset`) |
|
|
62
|
+
| the new package takes it from a file | — | its file, as every deploy does (`file`); a deploy that reads no file, a server's, leaves it unset for its client to send |
|
|
63
|
+
| the new package no longer declares it | — | gone, with its value (`drop`) |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# A local repository
|
|
2
|
+
|
|
3
|
+
Its layout on disk, and upgrades. Part of the `e3` skill.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
repo/
|
|
7
|
+
├── repository.beast2 # the release that last wrote it, and the upgrades it has had
|
|
8
|
+
├── repository-upgrade.beast2 # while an upgrade is under way: where its last part stopped
|
|
9
|
+
├── metadata.beast2 # its name and status
|
|
10
|
+
├── objects/ # content-addressed: values, segments, manifests, programs
|
|
11
|
+
├── packages/ # package refs
|
|
12
|
+
├── workspaces/ # each workspace's state, dataset refs, and its latest run's state and events
|
|
13
|
+
├── dataflows/ # run records
|
|
14
|
+
├── executions/ # execution attempts: status, owner, logs
|
|
15
|
+
├── running/ # an index of the attempts recorded running, by task: what a status reads
|
|
16
|
+
├── runs/ # an index of each task's runs, its units left out: what its history and logs read
|
|
17
|
+
├── adoptions/ # the manifest each delivered file, or piece of one, became
|
|
18
|
+
├── locks/ # locks, their holders, and how far each says it has got
|
|
19
|
+
├── gc/ # gc beside running work: unreachable notes, a stepped run's parts
|
|
20
|
+
├── envs/ # built execution environments
|
|
21
|
+
└── tmp/ # scratch and staged uploads
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
An e3 opening a repository an older release wrote applies the upgrades it has
|
|
25
|
+
not had, in place; one a newer e3 upgraded is refused, naming that release.
|