@finbheara/domain 0.1.0 → 0.3.0
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/README.md +90 -2
- package/dist/age.js +55 -13
- package/dist/ceili.js +2 -2
- package/dist/chunks/{place-7gqecaxd.js → place-0ehc2q69.js} +1 -1
- package/dist/chunks/{place-kr42294h.js → place-2ja9yx08.js} +1 -1
- package/dist/chunks/{place-g6yw4cq6.js → place-htxgptyv.js} +1 -0
- package/dist/dance-types.js +1 -1
- package/dist/dance.js +3 -3
- package/dist/noun-index.js +233 -0
- package/dist/school.js +29 -0
- package/dist/tune.js +2 -2
- package/dist/types/age-types.d.ts +4 -1
- package/dist/types/age.d.ts +16 -4
- package/dist/types/noun-index.d.ts +67 -0
- package/dist/types/school-compare.d.ts +3 -0
- package/dist/types/school-encoder.d.ts +8 -0
- package/dist/types/school.d.ts +4 -2
- package/dist/types/validated.d.ts +4 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ person names.
|
|
|
16
16
|
team's `averageAge`.
|
|
17
17
|
- **Dances**: a spoken or printed set dance or céilí dance in, the dances it may name out,
|
|
18
18
|
with tempo rules and hand counts.
|
|
19
|
+
- **A database-backed index** of any of these nouns: the slots an in-memory name map hashes
|
|
20
|
+
on, stored as indexed columns of a SQLite table you own.
|
|
19
21
|
|
|
20
22
|
It was built on the stage announcements and results sheets of Irish dance competitions,
|
|
21
23
|
where one school arrives as `Ardvarna Academy of Irish Dance (Calgary, AB)` and as
|
|
@@ -33,11 +35,12 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
|
|
|
33
35
|
|---|---|
|
|
34
36
|
| `@finbheara/domain/place` | `place()`, `createPlaceReader`, `placeRelation`, the `Place` types |
|
|
35
37
|
| `@finbheara/domain/clrg-regions` | `REGIONS`, `regionOf`, `resolvePhrase` |
|
|
36
|
-
| `@finbheara/domain/school` | `createSchoolNameReader`, `DEFAULT_SCHOOL_NAME_READER`, `compareSchoolNames`, `foldSchoolName` |
|
|
38
|
+
| `@finbheara/domain/school` | `createSchoolNameReader`, `DEFAULT_SCHOOL_NAME_READER`, `compareSchoolNames`, `foldSchoolName`, `schoolEncoder`, `schoolColumns` |
|
|
37
39
|
| `@finbheara/domain/tune` | `tune()`, `tuneRelation`, `tempoLegal`, `settingAt`, `TUNE_NODES`: the CLRG set dance list |
|
|
38
40
|
| `@finbheara/domain/ceili` | `ceili()`, `ceiliRelation`, `CEILI_NODES`: the céilí handbook dances |
|
|
39
41
|
| `@finbheara/domain/dance` | `dance()`: a print read as a tune and a céilí dance, narrowed by its round |
|
|
40
|
-
| `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, the age constants |
|
|
42
|
+
| `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, `intersectBirthYears`, the `Validated` type, the age constants |
|
|
43
|
+
| `@finbheara/domain/noun-index` | `createIndex`, `indexSchema`, `personEncoder`, and the `SqlHandle`/`NounEncoder` types: a database-backed name map (below) |
|
|
41
44
|
| `@finbheara/domain/dance-types` | `danceTypes()`, `DANCE_TYPES`, `rhythmOfTime`, and the shared `DanceMatch`/`DanceRelation`/`SetRhythm`/`TimeSignature` types |
|
|
42
45
|
|
|
43
46
|
The library does no I/O and installs nothing process-wide. Every reader has a default and a
|
|
@@ -177,6 +180,19 @@ Age.fromYOB(1999).fitsIn(Age.from("Over 21", { year: 2026, top: true })); // tru
|
|
|
177
180
|
canOverlap(u12, Age.from("U14", { year: 2026 }), dancer); // true
|
|
178
181
|
```
|
|
179
182
|
|
|
183
|
+
A value the reader cannot stand behind is returned, not thrown. Every `Age` answers
|
|
184
|
+
`isValid()` and `invalidReason()`: an age group under `U3` admits nobody old enough to dance,
|
|
185
|
+
and a year of birth outside 1900 to 2099 is not one. An invalid `Age` has no birth years and
|
|
186
|
+
rules nothing out.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
Age.from("U2", { year: 2026 }).isValid(); // false
|
|
190
|
+
Age.from("U2", { year: 2026 }).invalidReason(); // "below-youngest-band"
|
|
191
|
+
Age.fromYOB(2222).invalidReason(); // "year-out-of-range"
|
|
192
|
+
Age.from("All", { year: 2026 }).invalidReason(); // "not-a-band"
|
|
193
|
+
Age.from("", { year: 2026 }).isValid(); // true: empty, not invalid
|
|
194
|
+
```
|
|
195
|
+
|
|
180
196
|
A team's age is the average of its dancers' ages, rounded to two decimals. `averageAge`
|
|
181
197
|
takes exact ages only and returns an `AverageAge`, a separate type that refuses, by name,
|
|
182
198
|
any use that would treat it as one dancer's age.
|
|
@@ -189,6 +205,78 @@ team.fitsIn(Age.from("U40", { year: 2026 })); // true
|
|
|
189
205
|
averageAge([u12]); // throws AgeMisuseError "not-exact"
|
|
190
206
|
```
|
|
191
207
|
|
|
208
|
+
## Index names in a database
|
|
209
|
+
|
|
210
|
+
`createNameMap` (from `@finbheara/names`) and `mapOfSchools` hold their names in memory. An
|
|
211
|
+
index holds them in a SQLite table, as columns: every slot those maps hash a name on, indexed,
|
|
212
|
+
so a lookup at a level is one equality and nothing is loaded. It is a multi-map: a row is one
|
|
213
|
+
name and one value, and a lookup answers every value filed at the name's slot.
|
|
214
|
+
|
|
215
|
+
The package depends on no SQLite module. Hand it a handle with `prepare(sql)` returning a
|
|
216
|
+
statement with `run(...)` and `all(...)`: `bun:sqlite`'s `Database` and `node:sqlite`'s
|
|
217
|
+
`DatabaseSync` (Node ≥ 22.5) are both one as they are.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { Database } from "bun:sqlite"; // or: import { DatabaseSync } from "node:sqlite"
|
|
221
|
+
import { createNameMap, personName } from "@finbheara/names/normalize";
|
|
222
|
+
import { createIndex, indexSchema, personEncoder } from "@finbheara/domain/noun-index";
|
|
223
|
+
|
|
224
|
+
const db = new Database(":memory:");
|
|
225
|
+
const people = personEncoder();
|
|
226
|
+
const { table, indexes } = indexSchema(people, { table: "person" });
|
|
227
|
+
db.prepare(table).run();
|
|
228
|
+
for (const s of indexes) db.prepare(s).run(); // after a bulk load, if you have one
|
|
229
|
+
|
|
230
|
+
const index = createIndex(db, people, { table: "person" });
|
|
231
|
+
index.add(personName("Aoife Ní Mhaolvarna"), 1);
|
|
232
|
+
index.add(personName("Mhaolvarna, Aoife Ni"), 2);
|
|
233
|
+
index.add(personName("Kestrel Evbatyrov"), 3);
|
|
234
|
+
|
|
235
|
+
index.get(personName("AOIFE NI MHAOLVARNA"), "nn3"); // [1, 2]
|
|
236
|
+
index.get(personName("Aoife Ní Mhaolvarna"), "nn0"); // [1]
|
|
237
|
+
index.find("surname_key", personName("Aoife Ní Mhaolvarna").surnameKey()); // [1, 2]
|
|
238
|
+
index.nouns(2)[0]?.nn2; // "NiMhaolvarna<<Aoife": the parse, read back
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`map(level)` is the single-valued view, with `createNameMap`'s interface and its answers:
|
|
242
|
+
the first name filed at a slot keeps it, a later `set` replaces the value, and a print that
|
|
243
|
+
names no person is held and never found.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
const byName = index.map("nn3");
|
|
247
|
+
byName.get(personName("Aoife Ni Mhaolvarna")); // 1
|
|
248
|
+
byName.size; // 2
|
|
249
|
+
createNameMap<number>("nn3", [...byName]).size; // 2
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The person columns are the print, the reading it was parsed under, and `nameColumns()` from
|
|
253
|
+
`@finbheara/names`; the levels are `nn0`-`nn4`. A school's (`schoolEncoder` from
|
|
254
|
+
`@finbheara/domain/school`) are the print, `sn0`-`sn4`, `realWords` and `matchKey`, and its
|
|
255
|
+
levels are `mapOfSchools`'s. One print may be filed under several schools, and a school under
|
|
256
|
+
many prints:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { DEFAULT_SCHOOL_NAME_READER, schoolEncoder } from "@finbheara/domain/school";
|
|
260
|
+
|
|
261
|
+
const read = (p: string) => DEFAULT_SCHOOL_NAME_READER.schoolName(p);
|
|
262
|
+
const schools = createIndex(db, schoolEncoder(), { table: "school" }); // after its indexSchema
|
|
263
|
+
schools.add(read("Ardvarna Academy"), 10);
|
|
264
|
+
schools.add(read("Ardvarna Academy"), 11);
|
|
265
|
+
schools.add(read("Ardvarna Academy (Ontario)"), 11);
|
|
266
|
+
schools.add(read("ARDVARNA ACADEMY - Calgary, Alberta"), 10);
|
|
267
|
+
|
|
268
|
+
schools.get(read("Ardvarna Academy"), "sn0"); // [10, 11]
|
|
269
|
+
schools.nouns(11).map((s) => s.sn0); // ["Ardvarna Academy", "Ardvarna Academy (Ontario)"]
|
|
270
|
+
schools.get(read("Ardvarna Academy of Irish Dance (Calgary, AB)"), "sn3"); // [10]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`rows({ column: keys, ... })` answers every row where any column holds one of its keys, and
|
|
274
|
+
`glob(column, pattern)` a SQLite `GLOB` on one column. An encoder is a plain object: add a
|
|
275
|
+
column of your own by spreading one, `{ ...enc, columns: [...enc.columns, "k"], encode: (x) =>
|
|
276
|
+
({ ...enc.encode(x), k: f(x) }) }`. `indexSchema` takes the table, the value column and its
|
|
277
|
+
type (`{ value: "id", valueType: "INTEGER PRIMARY KEY" }` makes the value the row id); on such a table `map(level)` iterates in value order, and a `set` to a value another row
|
|
278
|
+
holds is refused by the key).
|
|
279
|
+
|
|
192
280
|
## Known gaps
|
|
193
281
|
|
|
194
282
|
- `place()` reads `"Derry in Ireland"` and `"Calgary in Western Canada"` as `unread`: it does
|
package/dist/age.js
CHANGED
|
@@ -6,6 +6,8 @@ var UNDER_CAP_MAX_TOP = 21;
|
|
|
6
6
|
var YOUNG_FLOOR = Object.freeze({ hard: 2, soft: 4 });
|
|
7
7
|
var MIN_MAJOR_AGE = 4;
|
|
8
8
|
var MIN_FEIS_AGE = 3;
|
|
9
|
+
var YOUNGEST_BAND_TOP = MAJORS_FLOOR_YEARS;
|
|
10
|
+
var YOB_RANGE = Object.freeze({ first: 1900, last: 2099 });
|
|
9
11
|
var DANCE_UP_YEARS = 1;
|
|
10
12
|
var ageOnJanuary1 = (year, born) => year - born - 1;
|
|
11
13
|
var yearsSinceBirth = (year, born) => year - born;
|
|
@@ -116,6 +118,11 @@ function read(token, ladder, top) {
|
|
|
116
118
|
}
|
|
117
119
|
return { kind: null, interval: null, refusal: "not-a-band", clauses: [], parts: [] };
|
|
118
120
|
}
|
|
121
|
+
function refuseBelowYoungest(r) {
|
|
122
|
+
if (r.interval === null || r.interval.hi >= YOUNGEST_BAND_TOP)
|
|
123
|
+
return r;
|
|
124
|
+
return { ...r, interval: null, refusal: "below-youngest-band", clauses: [] };
|
|
125
|
+
}
|
|
119
126
|
|
|
120
127
|
class AgeMisuseError extends Error {
|
|
121
128
|
misuse;
|
|
@@ -146,7 +153,7 @@ function fromPrint(print, context = {}) {
|
|
|
146
153
|
const ladder = context.ladder ?? NO_LADDER;
|
|
147
154
|
const year = context.year === undefined ? currentYear() : context.year;
|
|
148
155
|
const ag1 = print.trim().toLowerCase();
|
|
149
|
-
const reading = ag1 === "" ? { kind: null, interval: null, refusal: null, clauses: [], parts: [] } : read(print.trim(), ladder, context.top === true);
|
|
156
|
+
const reading = ag1 === "" ? { kind: null, interval: null, refusal: null, clauses: [], parts: [] } : refuseBelowYoungest(read(print.trim(), ladder, context.top === true));
|
|
150
157
|
const iv = reading.interval;
|
|
151
158
|
const arm = ag1 === "" ? "empty" : iv === null ? "open" : "band";
|
|
152
159
|
const ag2 = iv !== null ? display(iv) : ag1 === "" ? "" : `${ag1}?`;
|
|
@@ -162,6 +169,8 @@ function fromPrint(print, context = {}) {
|
|
|
162
169
|
parts: reading.parts,
|
|
163
170
|
length: ag2.length,
|
|
164
171
|
refusal: reading.refusal,
|
|
172
|
+
isValid: () => reading.refusal === null,
|
|
173
|
+
invalidReason: () => reading.refusal,
|
|
165
174
|
source,
|
|
166
175
|
clauses: reading.clauses,
|
|
167
176
|
ladder,
|
|
@@ -186,7 +195,7 @@ function fromPrint(print, context = {}) {
|
|
|
186
195
|
floorObserved: () => reading.kind !== "under" || iv === null ? null : reading.clauses.includes("under-floored-on-rung")
|
|
187
196
|
});
|
|
188
197
|
}
|
|
189
|
-
function fromYOB(from, to = from) {
|
|
198
|
+
function fromYOB(from, to = from, context = {}) {
|
|
190
199
|
for (const y of [from, to])
|
|
191
200
|
if (y !== null && !Number.isInteger(y))
|
|
192
201
|
throw new RangeError(`Age.fromYOB(${from}, ${to}): a year of birth is a whole number`);
|
|
@@ -194,29 +203,34 @@ function fromYOB(from, to = from) {
|
|
|
194
203
|
throw new RangeError("Age.fromYOB(null, null): at least one end is a year");
|
|
195
204
|
if (from !== null && to !== null && from > to)
|
|
196
205
|
throw new RangeError(`Age.fromYOB(${from}, ${to}): the earliest year comes first`);
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
206
|
+
const at = context.year ?? null;
|
|
207
|
+
const valid = [from, to].every((y) => y === null || y >= YOB_RANGE.first && y <= YOB_RANGE.last) && (at === null || to === null || yearsSinceBirth(at, to) >= YOUNG_FLOOR.hard);
|
|
208
|
+
const refusal = valid ? null : "year-out-of-range";
|
|
209
|
+
const exact = valid && from !== null && from === to;
|
|
210
|
+
const ag0 = from !== null && from === to ? String(from) : `${from ?? ""}..${to ?? ""}`;
|
|
211
|
+
const ag2 = valid ? `b${ag0}` : `b${ag0}?`;
|
|
212
|
+
const years = Object.freeze(valid ? { from, to } : { from: null, to: null });
|
|
201
213
|
return withRelations({
|
|
202
214
|
[Symbol.toStringTag]: "Age",
|
|
203
215
|
toString: () => ag2,
|
|
204
|
-
arm: "yob",
|
|
216
|
+
arm: valid ? "yob" : "open",
|
|
205
217
|
kind: "yob",
|
|
206
218
|
ag0,
|
|
207
219
|
ag1: ag0,
|
|
208
220
|
ag2,
|
|
209
|
-
parts:
|
|
221
|
+
parts: from !== null && from === to ? [from] : [from, to].filter((y) => y !== null),
|
|
210
222
|
length: ag2.length,
|
|
211
|
-
refusal
|
|
212
|
-
|
|
223
|
+
refusal,
|
|
224
|
+
isValid: () => valid,
|
|
225
|
+
invalidReason: () => refusal,
|
|
226
|
+
source: valid ? "printed-yob" : "open",
|
|
213
227
|
clauses: [],
|
|
214
228
|
ladder: NO_LADDER,
|
|
215
229
|
year: null,
|
|
216
230
|
isEmpty: () => false,
|
|
217
|
-
isAge: () =>
|
|
231
|
+
isAge: () => valid,
|
|
218
232
|
isExact: () => exact,
|
|
219
|
-
isAndOver: () => from === null,
|
|
233
|
+
isAndOver: () => valid && from === null,
|
|
220
234
|
isAdult: () => false,
|
|
221
235
|
interval: () => null,
|
|
222
236
|
birthYears: () => years,
|
|
@@ -250,7 +264,7 @@ function birthYearsOfInterval(iv, year) {
|
|
|
250
264
|
to: Math.min(year - iv.lo - 1, year - MAJORS_FLOOR_YEARS)
|
|
251
265
|
};
|
|
252
266
|
}
|
|
253
|
-
var inBirthYears = (a) => a.
|
|
267
|
+
var inBirthYears = (a) => a.kind === "yob" || a.year !== null;
|
|
254
268
|
function spanOf(a) {
|
|
255
269
|
if (!a.isAge())
|
|
256
270
|
return null;
|
|
@@ -355,6 +369,25 @@ function birthYearsDisjoint(a, b) {
|
|
|
355
369
|
return true;
|
|
356
370
|
return false;
|
|
357
371
|
}
|
|
372
|
+
function intersectBirthYears(windows) {
|
|
373
|
+
let from = null;
|
|
374
|
+
let to = null;
|
|
375
|
+
let n = 0;
|
|
376
|
+
for (const w of windows) {
|
|
377
|
+
if (w.from === null && w.to === null)
|
|
378
|
+
continue;
|
|
379
|
+
n++;
|
|
380
|
+
if (w.from !== null)
|
|
381
|
+
from = from === null ? w.from : Math.max(from, w.from);
|
|
382
|
+
if (w.to !== null)
|
|
383
|
+
to = to === null ? w.to : Math.min(to, w.to);
|
|
384
|
+
}
|
|
385
|
+
if (n === 0)
|
|
386
|
+
return { from: null, to: null, n: 0 };
|
|
387
|
+
if (from !== null && to !== null && from > to)
|
|
388
|
+
return null;
|
|
389
|
+
return { from, to, n };
|
|
390
|
+
}
|
|
358
391
|
function birthYearsMeet(card, other, slack = 0) {
|
|
359
392
|
if (card.from !== null && other.to !== null && card.from > other.to)
|
|
360
393
|
return false;
|
|
@@ -378,6 +411,11 @@ function underCappedYears(b, year, n = UNDER_CAP.plain) {
|
|
|
378
411
|
function underCapRefuses(bandFrom, identityFrom, n) {
|
|
379
412
|
return identityFrom > bandFrom + n;
|
|
380
413
|
}
|
|
414
|
+
function validYOB(y, year) {
|
|
415
|
+
if (y === null || y === undefined || !Number.isInteger(y))
|
|
416
|
+
return null;
|
|
417
|
+
return Age.fromYOB(y, y, { year: year ?? null }).isValid() ? y : null;
|
|
418
|
+
}
|
|
381
419
|
function youngFloor(year, oldest) {
|
|
382
420
|
const s = yearsSinceBirth(year, oldest);
|
|
383
421
|
if (s >= YOUNG_FLOOR.soft)
|
|
@@ -399,6 +437,8 @@ export {
|
|
|
399
437
|
MIN_MAJOR_AGE,
|
|
400
438
|
UNDER_CAP,
|
|
401
439
|
UNDER_CAP_MAX_TOP,
|
|
440
|
+
YOB_RANGE,
|
|
441
|
+
YOUNGEST_BAND_TOP,
|
|
402
442
|
YOUNGEST_FLOOR,
|
|
403
443
|
YOUNG_FLOOR,
|
|
404
444
|
ageOnJanuary1,
|
|
@@ -408,6 +448,7 @@ export {
|
|
|
408
448
|
birthYearsMeet,
|
|
409
449
|
birthYearsOfInterval,
|
|
410
450
|
canOverlap,
|
|
451
|
+
intersectBirthYears,
|
|
411
452
|
isAveraged,
|
|
412
453
|
roundedMeanHundredths,
|
|
413
454
|
tooYoungForFeis,
|
|
@@ -415,6 +456,7 @@ export {
|
|
|
415
456
|
underCapApplies,
|
|
416
457
|
underCapRefuses,
|
|
417
458
|
underCappedYears,
|
|
459
|
+
validYOB,
|
|
418
460
|
yearsSinceBirth,
|
|
419
461
|
youngFloor
|
|
420
462
|
};
|
package/dist/ceili.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
ceiliById2,
|
|
5
5
|
ceili2,
|
|
6
6
|
ceiliRelation2
|
|
7
|
-
} from "./chunks/place-
|
|
8
|
-
import"./chunks/place-
|
|
7
|
+
} from "./chunks/place-0ehc2q69.js";
|
|
8
|
+
import"./chunks/place-htxgptyv.js";
|
|
9
9
|
export {
|
|
10
10
|
CEILI_NODES2 as CEILI_NODES,
|
|
11
11
|
allCeilis2 as allCeilis,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
matchNodes,
|
|
4
4
|
candidateRelation,
|
|
5
5
|
isDanceTypePrint2
|
|
6
|
-
} from "./place-
|
|
6
|
+
} from "./place-htxgptyv.js";
|
|
7
7
|
|
|
8
8
|
function node(id, name, irish, family, formation, opts = {}) {
|
|
9
9
|
const kind = "round" in formation ? "round" : ("long" in formation) ? "long" : "set";
|
package/dist/dance-types.js
CHANGED
package/dist/dance.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import"./chunks/place-
|
|
1
|
+
import"./chunks/place-htxgptyv.js";
|
|
2
2
|
import {
|
|
3
3
|
ceili2
|
|
4
|
-
} from "./chunks/place-
|
|
4
|
+
} from "./chunks/place-0ehc2q69.js";
|
|
5
5
|
import {
|
|
6
6
|
tune2
|
|
7
|
-
} from "./chunks/place-
|
|
7
|
+
} from "./chunks/place-2ja9yx08.js";
|
|
8
8
|
|
|
9
9
|
function dance(print, round) {
|
|
10
10
|
const setting = round === "traditional-set" ? "traditional" : undefined;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NAME_COLUMNS,
|
|
3
|
+
NAME_LEVEL_COLUMNS,
|
|
4
|
+
nameColumns,
|
|
5
|
+
personName
|
|
6
|
+
} from "@finbheara/names/normalize";
|
|
7
|
+
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
8
|
+
var RESERVED = new Set(`rowid oid _rowid_ __r __v all and as by check create default delete distinct drop
|
|
9
|
+
from group in index insert into is key limit not null on or order primary references
|
|
10
|
+
select set table to union unique update values where`.split(/\s+/u));
|
|
11
|
+
function ident(s, what) {
|
|
12
|
+
if (!IDENT.test(s) || RESERVED.has(s.toLowerCase()))
|
|
13
|
+
throw new Error(`noun-index: ${what} ${JSON.stringify(s)} is not a usable identifier`);
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
16
|
+
function resolve(encoder, options) {
|
|
17
|
+
const table = ident(options.table ?? "noun_index", "table");
|
|
18
|
+
const value = ident(options.value ?? "value", "value column");
|
|
19
|
+
const valueType = (options.valueType ?? "").trim();
|
|
20
|
+
if (!/^[A-Za-z ()0-9]*$/u.test(valueType))
|
|
21
|
+
throw new Error(`noun-index: value type ${JSON.stringify(valueType)} is not a type`);
|
|
22
|
+
const seen = new Set([value.toLowerCase()]);
|
|
23
|
+
for (const c of encoder.columns) {
|
|
24
|
+
ident(c, "column");
|
|
25
|
+
if (seen.has(c.toLowerCase()))
|
|
26
|
+
throw new Error(`noun-index: column ${c} is declared twice`);
|
|
27
|
+
seen.add(c.toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
for (const c of [...encoder.levels, ...encoder.indexed])
|
|
30
|
+
if (!encoder.columns.includes(c))
|
|
31
|
+
throw new Error(`noun-index: ${c} is not a column`);
|
|
32
|
+
return { table, value, valueType, isKey: /primary\s+key/iu.test(valueType) };
|
|
33
|
+
}
|
|
34
|
+
function indexSchema(encoder, options = {}) {
|
|
35
|
+
const r = resolve(encoder, options);
|
|
36
|
+
const cols = [`${r.value}${r.valueType === "" ? "" : ` ${r.valueType}`}`];
|
|
37
|
+
for (const c of encoder.columns)
|
|
38
|
+
cols.push(`${c} TEXT`);
|
|
39
|
+
const indexes = [];
|
|
40
|
+
if (!r.isKey)
|
|
41
|
+
indexes.push(`CREATE INDEX IF NOT EXISTS ${r.table}_${r.value} ON ${r.table}(${r.value})`);
|
|
42
|
+
for (const c of encoder.indexed)
|
|
43
|
+
indexes.push(`CREATE INDEX IF NOT EXISTS ${r.table}_${c} ON ${r.table}(${c})`);
|
|
44
|
+
return { table: `CREATE TABLE IF NOT EXISTS ${r.table}(${cols.join(", ")})`, indexes };
|
|
45
|
+
}
|
|
46
|
+
var CHUNK = 500;
|
|
47
|
+
function createIndex(handle, encoder, options = {}) {
|
|
48
|
+
const r = resolve(encoder, options);
|
|
49
|
+
const { table: T, value: VC } = r;
|
|
50
|
+
const cols = encoder.columns;
|
|
51
|
+
const prepared = new Map;
|
|
52
|
+
const stmt = (sql) => {
|
|
53
|
+
let s = prepared.get(sql);
|
|
54
|
+
if (s === undefined) {
|
|
55
|
+
s = handle.prepare(sql);
|
|
56
|
+
prepared.set(sql, s);
|
|
57
|
+
}
|
|
58
|
+
return s;
|
|
59
|
+
};
|
|
60
|
+
const column = (c) => {
|
|
61
|
+
if (!cols.includes(c))
|
|
62
|
+
throw new Error(`noun-index: ${c} is not a column of ${T}`);
|
|
63
|
+
return c;
|
|
64
|
+
};
|
|
65
|
+
const level = (l) => {
|
|
66
|
+
if (!encoder.levels.includes(l))
|
|
67
|
+
throw new Error(`noun-index: ${l} is not a level of ${T}`);
|
|
68
|
+
return l;
|
|
69
|
+
};
|
|
70
|
+
const insertSql = `INSERT INTO ${T}(${VC}, ${cols.join(", ")}) VALUES(${cols.map(() => "?").join(", ")}, ?)`;
|
|
71
|
+
const params = (x) => {
|
|
72
|
+
const e = encoder.encode(x);
|
|
73
|
+
return cols.map((c) => e[c] ?? null);
|
|
74
|
+
};
|
|
75
|
+
const insert = (x, value) => {
|
|
76
|
+
stmt(insertSql).run(value, ...params(x));
|
|
77
|
+
};
|
|
78
|
+
const keyAt = (x, l) => {
|
|
79
|
+
level(l);
|
|
80
|
+
return encoder.key === undefined ? encoder.encode(x)[l] ?? null : encoder.key(x, l);
|
|
81
|
+
};
|
|
82
|
+
const valuesWhere = (col, key) => stmt(`SELECT ${VC} AS v FROM ${T} WHERE ${col} = ? ORDER BY rowid`).all(key).map((o) => o.v);
|
|
83
|
+
const pick = (columns) => columns === undefined ? cols : columns.map(column);
|
|
84
|
+
const toRows = (raw, picked) => raw.map((o) => {
|
|
85
|
+
const row = {};
|
|
86
|
+
for (const c of picked)
|
|
87
|
+
row[c] = o[c] ?? null;
|
|
88
|
+
return { value: o.__v, row };
|
|
89
|
+
});
|
|
90
|
+
const selectList = (picked) => ["rowid AS __r", `${VC} AS __v`, ...picked].join(", ");
|
|
91
|
+
const index = {
|
|
92
|
+
table: T,
|
|
93
|
+
encoder,
|
|
94
|
+
add: insert,
|
|
95
|
+
addAll(entries) {
|
|
96
|
+
for (const [x, v] of entries)
|
|
97
|
+
insert(x, v);
|
|
98
|
+
},
|
|
99
|
+
get(x, l) {
|
|
100
|
+
const k = keyAt(x, l);
|
|
101
|
+
return k === null ? [] : valuesWhere(l, k);
|
|
102
|
+
},
|
|
103
|
+
has(x, l) {
|
|
104
|
+
const k = keyAt(x, l);
|
|
105
|
+
return k !== null && stmt(`SELECT 1 FROM ${T} WHERE ${l} = ? LIMIT 1`).all(k).length > 0;
|
|
106
|
+
},
|
|
107
|
+
find: (c, key) => valuesWhere(column(c), key),
|
|
108
|
+
rows(where, columns) {
|
|
109
|
+
const picked = pick(columns);
|
|
110
|
+
const seen = new Map;
|
|
111
|
+
for (const [c, keys] of Object.entries(where)) {
|
|
112
|
+
column(c);
|
|
113
|
+
const uniq = [...new Set(keys)];
|
|
114
|
+
for (let i = 0;i < uniq.length; i += CHUNK) {
|
|
115
|
+
const part = uniq.slice(i, i + CHUNK);
|
|
116
|
+
const sql = `SELECT ${selectList(picked)} FROM ${T} WHERE ${c} IN (${part.map(() => "?").join(",")})`;
|
|
117
|
+
for (const o of stmt(sql).all(...part))
|
|
118
|
+
seen.set(Number(o.__r), o);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return toRows([...seen].sort((a, b) => a[0] - b[0]).map(([, o]) => o), picked);
|
|
122
|
+
},
|
|
123
|
+
glob(c, pattern, columns) {
|
|
124
|
+
const picked = pick(columns);
|
|
125
|
+
const raw = stmt(`SELECT ${selectList(picked)} FROM ${T} WHERE ${column(c)} GLOB ? ORDER BY rowid`).all(pattern);
|
|
126
|
+
return toRows(raw, picked);
|
|
127
|
+
},
|
|
128
|
+
nouns(value) {
|
|
129
|
+
const raw = stmt(`SELECT ${cols.join(", ")} FROM ${T} WHERE ${VC} = ? ORDER BY rowid`).all(value);
|
|
130
|
+
return raw.map((o) => encoder.decode(o));
|
|
131
|
+
},
|
|
132
|
+
remove(value) {
|
|
133
|
+
const n = stmt(`SELECT count(*) AS n FROM ${T} WHERE ${VC} = ?`).all(value)[0].n;
|
|
134
|
+
if (n > 0)
|
|
135
|
+
stmt(`DELETE FROM ${T} WHERE ${VC} = ?`).run(value);
|
|
136
|
+
return Number(n);
|
|
137
|
+
},
|
|
138
|
+
size: () => Number(stmt(`SELECT count(*) AS n FROM ${T}`).all()[0].n),
|
|
139
|
+
map: (l) => viewAt(l)
|
|
140
|
+
};
|
|
141
|
+
function viewAt(l) {
|
|
142
|
+
const lc = level(l);
|
|
143
|
+
const heads = `SELECT rowid AS __r, ${VC} AS __v, ${cols.join(", ")} FROM ${T}
|
|
144
|
+
WHERE ${lc} IS NULL OR rowid IN (SELECT min(rowid) FROM ${T} WHERE ${lc} IS NOT NULL GROUP BY ${lc})
|
|
145
|
+
ORDER BY rowid`;
|
|
146
|
+
const head = (k) => stmt(`SELECT rowid AS __r, ${VC} AS __v FROM ${T} WHERE ${lc} = ? ORDER BY rowid LIMIT 1`).all(k)[0];
|
|
147
|
+
const all = () => stmt(heads).all();
|
|
148
|
+
const view = {
|
|
149
|
+
dop: l,
|
|
150
|
+
get size() {
|
|
151
|
+
const o = stmt(`SELECT (SELECT count(DISTINCT ${lc}) FROM ${T}) + (SELECT count(*) FROM ${T} WHERE ${lc} IS NULL) AS n`).all()[0];
|
|
152
|
+
return Number(o.n);
|
|
153
|
+
},
|
|
154
|
+
get(x) {
|
|
155
|
+
const k = keyAt(x, l);
|
|
156
|
+
return k === null ? undefined : head(k)?.__v;
|
|
157
|
+
},
|
|
158
|
+
has(x) {
|
|
159
|
+
const k = keyAt(x, l);
|
|
160
|
+
return k !== null && head(k) !== undefined;
|
|
161
|
+
},
|
|
162
|
+
set(x, value) {
|
|
163
|
+
const k = keyAt(x, l);
|
|
164
|
+
const h = k === null ? undefined : head(k);
|
|
165
|
+
if (h === undefined)
|
|
166
|
+
insert(x, value);
|
|
167
|
+
else
|
|
168
|
+
stmt(`UPDATE ${T} SET ${VC} = ? WHERE rowid = ?`).run(value, h.__r);
|
|
169
|
+
return this;
|
|
170
|
+
},
|
|
171
|
+
delete(x) {
|
|
172
|
+
const k = keyAt(x, l);
|
|
173
|
+
if (k === null || head(k) === undefined)
|
|
174
|
+
return false;
|
|
175
|
+
stmt(`DELETE FROM ${T} WHERE ${lc} = ?`).run(k);
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
clear() {
|
|
179
|
+
stmt(`DELETE FROM ${T}`).run();
|
|
180
|
+
},
|
|
181
|
+
*keys() {
|
|
182
|
+
for (const o of all())
|
|
183
|
+
yield encoder.decode(o);
|
|
184
|
+
},
|
|
185
|
+
*values() {
|
|
186
|
+
for (const o of all())
|
|
187
|
+
yield o.__v;
|
|
188
|
+
},
|
|
189
|
+
*entries() {
|
|
190
|
+
for (const o of all())
|
|
191
|
+
yield [encoder.decode(o), o.__v];
|
|
192
|
+
},
|
|
193
|
+
[Symbol.iterator]() {
|
|
194
|
+
return this.entries();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
return view;
|
|
198
|
+
}
|
|
199
|
+
return index;
|
|
200
|
+
}
|
|
201
|
+
var sameColumns = (a, b) => NAME_COLUMNS.every((c) => a[c] === b[c]);
|
|
202
|
+
function personEncoder(options = {}) {
|
|
203
|
+
const read = options.read ?? ((p, o) => personName(p, o));
|
|
204
|
+
const printCol = options.print ?? "print";
|
|
205
|
+
const readingCol = options.reading ?? "reading";
|
|
206
|
+
const readingOf = (x, cols) => {
|
|
207
|
+
const g = read(x.nn0, "given-first");
|
|
208
|
+
if (g === x || sameColumns(cols, nameColumns(g)))
|
|
209
|
+
return "given-first";
|
|
210
|
+
if (sameColumns(cols, nameColumns(read(x.nn0, "surname-first"))))
|
|
211
|
+
return "surname-first";
|
|
212
|
+
throw new Error(`personEncoder: ${JSON.stringify(x.nn0)} was not read by this encoder's reader in either order`);
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
columns: [printCol, readingCol, ...NAME_COLUMNS],
|
|
216
|
+
levels: NAME_LEVEL_COLUMNS,
|
|
217
|
+
indexed: NAME_COLUMNS.filter((c) => c !== "given_key"),
|
|
218
|
+
encode(x) {
|
|
219
|
+
const cols = nameColumns(x);
|
|
220
|
+
return { [printCol]: x.nn0, [readingCol]: readingOf(x, cols), ...cols };
|
|
221
|
+
},
|
|
222
|
+
key: (x, level) => nameColumns(x)[level],
|
|
223
|
+
decode(row) {
|
|
224
|
+
const order = row[readingCol] === "surname-first" ? "surname-first" : "given-first";
|
|
225
|
+
return read(row[printCol] ?? "", order);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
createIndex,
|
|
231
|
+
indexSchema,
|
|
232
|
+
personEncoder
|
|
233
|
+
};
|
package/dist/school.js
CHANGED
|
@@ -2172,9 +2172,35 @@ function mapOfSchools(dop, entries) {
|
|
|
2172
2172
|
map.set(name, value);
|
|
2173
2173
|
return map;
|
|
2174
2174
|
}
|
|
2175
|
+
var SCHOOL_LEVEL_COLUMNS = ["sn0", "sn1", "sn2", "sn3", "sn4", "realWords"];
|
|
2176
|
+
function schoolColumns(name) {
|
|
2177
|
+
return {
|
|
2178
|
+
sn0: slotOf(name, "sn0"),
|
|
2179
|
+
sn1: slotOf(name, "sn1"),
|
|
2180
|
+
sn2: slotOf(name, "sn2"),
|
|
2181
|
+
sn3: slotOf(name, "sn3"),
|
|
2182
|
+
sn4: slotOf(name, "sn4"),
|
|
2183
|
+
realWords: slotOf(name, "realWords")
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2175
2186
|
function isLadderLevel(dop) {
|
|
2176
2187
|
return LADDER.has(dop);
|
|
2177
2188
|
}
|
|
2189
|
+
function schoolEncoder(options = {}) {
|
|
2190
|
+
const reader = options.reader ?? DEFAULT_SCHOOL_NAME_READER;
|
|
2191
|
+
const printCol = options.print ?? "print";
|
|
2192
|
+
return {
|
|
2193
|
+
columns: [printCol, ...SCHOOL_LEVEL_COLUMNS, "matchKey"],
|
|
2194
|
+
levels: SCHOOL_LEVEL_COLUMNS,
|
|
2195
|
+
indexed: [...SCHOOL_LEVEL_COLUMNS, "matchKey"],
|
|
2196
|
+
encode(x) {
|
|
2197
|
+
const key = foldSchoolName(x.sn0);
|
|
2198
|
+
return { [printCol]: x.sn0, ...schoolColumns(x), matchKey: key === "" ? null : key };
|
|
2199
|
+
},
|
|
2200
|
+
key: (x, level) => schoolColumns(x)[level],
|
|
2201
|
+
decode: (row) => reader.schoolName(row[printCol] ?? "")
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2178
2204
|
function compareSchoolNames(a, b, reader) {
|
|
2179
2205
|
return compareSchools(reader.schoolName(a), reader.schoolName(b));
|
|
2180
2206
|
}
|
|
@@ -2217,6 +2243,7 @@ export {
|
|
|
2217
2243
|
DEFAULT_SCHOOL_NAME_CAPACITY,
|
|
2218
2244
|
DEFAULT_SCHOOL_NAME_READER,
|
|
2219
2245
|
NOT_A_SCHOOL,
|
|
2246
|
+
SCHOOL_LEVEL_COLUMNS,
|
|
2220
2247
|
SCHOOL_NAME_EXCEPTIONS,
|
|
2221
2248
|
compareSchoolNames,
|
|
2222
2249
|
compareSchools,
|
|
@@ -2231,7 +2258,9 @@ export {
|
|
|
2231
2258
|
realWords,
|
|
2232
2259
|
realWordsInPrintOrder,
|
|
2233
2260
|
sameInEveryPart,
|
|
2261
|
+
schoolColumns,
|
|
2234
2262
|
schoolCompare,
|
|
2263
|
+
schoolEncoder,
|
|
2235
2264
|
schoolInitials,
|
|
2236
2265
|
schoolKeyTokens,
|
|
2237
2266
|
schoolPrintSplit,
|
package/dist/tune.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
settingAt2,
|
|
9
9
|
tune2,
|
|
10
10
|
tuneRelation2
|
|
11
|
-
} from "./chunks/place-
|
|
12
|
-
import"./chunks/place-
|
|
11
|
+
} from "./chunks/place-2ja9yx08.js";
|
|
12
|
+
import"./chunks/place-htxgptyv.js";
|
|
13
13
|
export {
|
|
14
14
|
PLAUSIBLE_SET_TEMPO_MAX2 as PLAUSIBLE_SET_TEMPO_MAX,
|
|
15
15
|
SET_MIN_SPEED2 as SET_MIN_SPEED,
|
|
@@ -11,6 +11,9 @@ export interface AgeLadder {
|
|
|
11
11
|
readonly overs: readonly number[];
|
|
12
12
|
readonly ranges: readonly number[];
|
|
13
13
|
}
|
|
14
|
+
export interface YOBContext {
|
|
15
|
+
readonly year?: number | null;
|
|
16
|
+
}
|
|
14
17
|
export interface AgeContext {
|
|
15
18
|
readonly ladder?: AgeLadder;
|
|
16
19
|
readonly year?: number | null;
|
|
@@ -18,7 +21,7 @@ export interface AgeContext {
|
|
|
18
21
|
}
|
|
19
22
|
export type AgeArm = "band" | "yob" | "open" | "empty";
|
|
20
23
|
export type AgeKind = "under" | "over" | "range" | "adult" | "senior" | "yob";
|
|
21
|
-
export type AgeRefusal = "not-a-band" | "backwards-range" | "senior-without-ladder";
|
|
24
|
+
export type AgeRefusal = "not-a-band" | "backwards-range" | "senior-without-ladder" | "below-youngest-band" | "year-out-of-range";
|
|
22
25
|
export type AgeSource = "sibling-narrowed" | "printed-band" | "printed-range" | "printed-yob" | "open";
|
|
23
26
|
export type AgeClause = "under-floored-on-rung" | "youngest-absorbs" | "over-ceiling-from-next-over" | "over-ceiling-refused-gap" | "and-over-no-ceiling" | "range-overlapping" | "range-inclusive" | "range-floor-on-rung" | "range-floor-widened" | "range-adult-floor";
|
|
24
27
|
export type AgeRelation = "same" | "contains" | "overlaps" | "disjoint" | "silent";
|
package/dist/types/age.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, YoungFloorVerdict } from "./age-types.ts";
|
|
2
|
-
|
|
1
|
+
import type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, YOBContext, YoungFloorVerdict } from "./age-types.ts";
|
|
2
|
+
import type { Validated } from "./validated.ts";
|
|
3
|
+
export type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, Validated, YOBContext, YoungFloorVerdict, };
|
|
3
4
|
export declare const YOUNGEST_FLOOR = 0;
|
|
4
5
|
export declare const ADULT_FLOOR = 18;
|
|
5
6
|
export declare const MAJORS_FLOOR_YEARS = 3;
|
|
@@ -14,10 +15,15 @@ export declare const YOUNG_FLOOR: Readonly<{
|
|
|
14
15
|
}>;
|
|
15
16
|
export declare const MIN_MAJOR_AGE = 4;
|
|
16
17
|
export declare const MIN_FEIS_AGE = 3;
|
|
18
|
+
export declare const YOUNGEST_BAND_TOP = 3;
|
|
19
|
+
export declare const YOB_RANGE: Readonly<{
|
|
20
|
+
readonly first: 1900;
|
|
21
|
+
readonly last: 2099;
|
|
22
|
+
}>;
|
|
17
23
|
export declare const DANCE_UP_YEARS = 1;
|
|
18
24
|
export declare const ageOnJanuary1: (year: number, born: number) => number;
|
|
19
25
|
export declare const yearsSinceBirth: (year: number, born: number) => number;
|
|
20
|
-
export interface Age {
|
|
26
|
+
export interface Age extends Validated<AgeRefusal> {
|
|
21
27
|
readonly [Symbol.toStringTag]: "Age";
|
|
22
28
|
toString(): string;
|
|
23
29
|
readonly arm: AgeArm;
|
|
@@ -48,7 +54,7 @@ export declare class AgeMisuseError extends Error {
|
|
|
48
54
|
constructor(misuse: AgeMisuse, detail: string);
|
|
49
55
|
}
|
|
50
56
|
declare function fromPrint(print: string, context?: AgeContext): Age;
|
|
51
|
-
declare function fromYOB(from: number | null, to?: number | null): Age;
|
|
57
|
+
declare function fromYOB(from: number | null, to?: number | null, context?: YOBContext): Age;
|
|
52
58
|
export declare const Age: Readonly<{
|
|
53
59
|
from: typeof fromPrint;
|
|
54
60
|
fromYOB: typeof fromYOB;
|
|
@@ -75,10 +81,16 @@ export interface AverageAge {
|
|
|
75
81
|
export declare function isAveraged(a: Age | AverageAge): a is AverageAge;
|
|
76
82
|
export declare function averageAge(ages: readonly Age[]): AverageAge;
|
|
77
83
|
export declare function birthYearsDisjoint(a: BirthYears, b: BirthYears): boolean;
|
|
84
|
+
export declare function intersectBirthYears(windows: readonly BirthYears[]): {
|
|
85
|
+
from: number | null;
|
|
86
|
+
to: number | null;
|
|
87
|
+
n: number;
|
|
88
|
+
} | null;
|
|
78
89
|
export declare function birthYearsMeet(card: BirthYears, other: BirthYears, slack?: number): boolean;
|
|
79
90
|
export declare function underCapApplies(bandFrom: number | null, year: number): boolean;
|
|
80
91
|
export declare function underCappedYears(b: BirthYears, year: number, n?: number): BirthYears;
|
|
81
92
|
export declare function underCapRefuses(bandFrom: number, identityFrom: number, n: number): boolean;
|
|
93
|
+
export declare function validYOB(y: number | null | undefined, year?: number | null): number | null;
|
|
82
94
|
export declare function youngFloor(year: number, oldest: number): YoungFloorVerdict;
|
|
83
95
|
export declare const tooYoungForMajor: (year: number, oldest: number) => boolean;
|
|
84
96
|
export declare const tooYoungForFeis: (year: number, oldest: number) => boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type PersonName, type ResolvedNameOrder, type TransitiveLevel } from "@finbheara/names/normalize";
|
|
2
|
+
export type SqlValue = string | number | bigint | null;
|
|
3
|
+
export interface SqlStatement {
|
|
4
|
+
run(...params: SqlValue[]): unknown;
|
|
5
|
+
all(...params: SqlValue[]): unknown[];
|
|
6
|
+
}
|
|
7
|
+
export interface SqlHandle {
|
|
8
|
+
prepare(sql: string): SqlStatement;
|
|
9
|
+
}
|
|
10
|
+
export type EncodedRow = Readonly<Record<string, string | null>>;
|
|
11
|
+
export interface NounEncoder<X, L extends string = string> {
|
|
12
|
+
readonly columns: readonly string[];
|
|
13
|
+
readonly levels: readonly L[];
|
|
14
|
+
readonly indexed: readonly string[];
|
|
15
|
+
encode(x: X): EncodedRow;
|
|
16
|
+
key?(x: X, level: L): string | null;
|
|
17
|
+
decode(row: EncodedRow): X;
|
|
18
|
+
}
|
|
19
|
+
export interface IndexOptions {
|
|
20
|
+
readonly table?: string;
|
|
21
|
+
readonly value?: string;
|
|
22
|
+
readonly valueType?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface IndexRow<V> {
|
|
25
|
+
readonly value: V;
|
|
26
|
+
readonly row: EncodedRow;
|
|
27
|
+
}
|
|
28
|
+
export interface IndexMap<X, V, L extends string> {
|
|
29
|
+
readonly dop: L;
|
|
30
|
+
readonly size: number;
|
|
31
|
+
get(x: X): V | undefined;
|
|
32
|
+
has(x: X): boolean;
|
|
33
|
+
set(x: X, value: V): this;
|
|
34
|
+
delete(x: X): boolean;
|
|
35
|
+
clear(): void;
|
|
36
|
+
keys(): IterableIterator<X>;
|
|
37
|
+
values(): IterableIterator<V>;
|
|
38
|
+
entries(): IterableIterator<[X, V]>;
|
|
39
|
+
[Symbol.iterator](): IterableIterator<[X, V]>;
|
|
40
|
+
}
|
|
41
|
+
export interface NounIndex<X, L extends string, V extends SqlValue> {
|
|
42
|
+
readonly table: string;
|
|
43
|
+
readonly encoder: NounEncoder<X, L>;
|
|
44
|
+
add(x: X, value: V): void;
|
|
45
|
+
addAll(entries: Iterable<readonly [X, V]>): void;
|
|
46
|
+
get(x: X, level: L): V[];
|
|
47
|
+
has(x: X, level: L): boolean;
|
|
48
|
+
find(column: string, key: string): V[];
|
|
49
|
+
rows(where: Readonly<Record<string, readonly string[]>>, columns?: readonly string[]): IndexRow<V>[];
|
|
50
|
+
glob(column: string, pattern: string, columns?: readonly string[]): IndexRow<V>[];
|
|
51
|
+
nouns(value: V): X[];
|
|
52
|
+
remove(value: V): number;
|
|
53
|
+
size(): number;
|
|
54
|
+
map(level: L): IndexMap<X, V, L>;
|
|
55
|
+
}
|
|
56
|
+
export declare function indexSchema(encoder: NounEncoder<unknown>, options?: IndexOptions): {
|
|
57
|
+
table: string;
|
|
58
|
+
indexes: string[];
|
|
59
|
+
};
|
|
60
|
+
export declare function createIndex<X, L extends string, V extends SqlValue = SqlValue>(handle: SqlHandle, encoder: NounEncoder<X, L>, options?: IndexOptions): NounIndex<X, L, V>;
|
|
61
|
+
export type PersonReader = (print: string, order: ResolvedNameOrder) => PersonName;
|
|
62
|
+
export interface PersonEncoderOptions {
|
|
63
|
+
readonly read?: PersonReader;
|
|
64
|
+
readonly print?: string;
|
|
65
|
+
readonly reading?: string;
|
|
66
|
+
}
|
|
67
|
+
export declare function personEncoder(options?: PersonEncoderOptions): NounEncoder<PersonName, TransitiveLevel>;
|
|
@@ -33,4 +33,7 @@ export interface SchoolMap<V> {
|
|
|
33
33
|
}
|
|
34
34
|
export declare function setOfSchools(dop: SchoolDop): SchoolSet;
|
|
35
35
|
export declare function mapOfSchools<V>(dop: SchoolDop, entries?: Iterable<readonly [SchoolName, V]>): SchoolMap<V>;
|
|
36
|
+
export declare const SCHOOL_LEVEL_COLUMNS: readonly ["sn0", "sn1", "sn2", "sn3", "sn4", "realWords"];
|
|
37
|
+
export type SchoolLevelColumn = (typeof SCHOOL_LEVEL_COLUMNS)[number];
|
|
38
|
+
export declare function schoolColumns(name: SchoolName): Readonly<Record<SchoolLevelColumn, string | null>>;
|
|
36
39
|
export declare function isLadderLevel(dop: SchoolDop): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NounEncoder } from "./noun-index.ts";
|
|
2
|
+
import { type SchoolLevelColumn } from "./school-compare.ts";
|
|
3
|
+
import { type SchoolName, type SchoolNameParser } from "./school-name.ts";
|
|
4
|
+
export interface SchoolEncoderOptions {
|
|
5
|
+
readonly reader?: SchoolNameParser;
|
|
6
|
+
readonly print?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function schoolEncoder(options?: SchoolEncoderOptions): NounEncoder<SchoolName, SchoolLevelColumn>;
|
package/dist/types/school.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { createSchoolNameReader, DEFAULT_SCHOOL_NAME_CAPACITY, DEFAULT_SCHOOL_NAME_READER, realWords, realWordsInPrintOrder, schoolInitials, schoolPrintSplit, } from "./school-name.ts";
|
|
2
2
|
export type { LexiconFlags, SchoolConcern, SchoolInitials, SchoolLexicon, SchoolName, SchoolNameParser, SchoolNameReader, SchoolNameReaderOptions, SchoolPart, SchoolPrintSplit, SchoolRefusal, } from "./school-name.ts";
|
|
3
|
-
export { isColumnCutOf, isInitialismOf, isLadderLevel, mapOfSchools, probablySameSchool, sameInEveryPart, schoolCompare, schoolTokensAt, setOfSchools, } from "./school-compare.ts";
|
|
4
|
-
export type { SchoolDop, SchoolMap, SchoolSet } from "./school-compare.ts";
|
|
3
|
+
export { isColumnCutOf, isInitialismOf, isLadderLevel, mapOfSchools, probablySameSchool, SCHOOL_LEVEL_COLUMNS, schoolColumns, sameInEveryPart, schoolCompare, schoolTokensAt, setOfSchools, } from "./school-compare.ts";
|
|
4
|
+
export type { SchoolDop, SchoolLevelColumn, SchoolMap, SchoolSet } from "./school-compare.ts";
|
|
5
|
+
export { schoolEncoder } from "./school-encoder.ts";
|
|
6
|
+
export type { SchoolEncoderOptions } from "./school-encoder.ts";
|
|
5
7
|
export { compareSchoolNames, compareSchools } from "./school-identity.ts";
|
|
6
8
|
export type { IdentityResult, IdentityVerdict } from "./school-identity.ts";
|
|
7
9
|
export { foldSchoolName, hasSpecificToken, schoolKeyTokens } from "./school-match-key.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/domain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Places, CLRG regions, school names, ages and dances as they are printed: read, compared and told apart",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"places",
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"types": "./dist/types/dance-types.d.ts",
|
|
50
50
|
"default": "./dist/dance-types.js"
|
|
51
51
|
},
|
|
52
|
+
"./noun-index": {
|
|
53
|
+
"types": "./dist/types/noun-index.d.ts",
|
|
54
|
+
"default": "./dist/noun-index.js"
|
|
55
|
+
},
|
|
52
56
|
"./package.json": "./package.json"
|
|
53
57
|
},
|
|
54
58
|
"files": ["dist/*.js", "dist/chunks/*.js", "dist/types/**/*.d.ts", "README.md", "LICENSE"],
|
|
@@ -65,6 +69,6 @@
|
|
|
65
69
|
"prepublishOnly": "bun scripts/build.ts && bun scripts/guard-tarball.ts"
|
|
66
70
|
},
|
|
67
71
|
"dependencies": {
|
|
68
|
-
"@finbheara/names": "0.
|
|
72
|
+
"@finbheara/names": "0.17.0"
|
|
69
73
|
}
|
|
70
74
|
}
|