@workerstack/database 0.21.0 → 0.22.1
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 +243 -40
- package/docs/meta-contract.md +55 -1
- package/package.json +3 -7
- package/src/cli/args.ts +4 -2
- package/src/cli/codegen.ts +2 -2
- package/src/cli/comb-config.ts +8 -3
- package/src/cli/file-path.ts +25 -0
- package/src/cli/migrate-apply.ts +5 -21
- package/src/cli/migrate-d1.ts +5 -1
- package/src/cli/migrate-json.ts +48 -0
- package/src/cli/migrate.ts +26 -4
- package/src/codegen/analyzer-types.ts +6 -1
- package/src/codegen/annotations.ts +66 -9
- package/src/codegen/format-constraints.ts +114 -0
- package/src/codegen/generators/dtos/entity-file.ts +2 -1
- package/src/codegen/generators/dtos/example-meta.ts +22 -2
- package/src/codegen/generators/dtos/field-schema.ts +31 -8
- package/src/codegen/generators/dtos/ip-patterns.ts +25 -11
- package/src/codegen/generators/dtos/number-range.ts +22 -3
- package/src/codegen/generators/dtos/schema-aliases.ts +5 -3
- package/src/codegen/generators/dtos/text-format.ts +63 -0
- package/src/codegen/generators/dtos/text-schema.ts +49 -2
- package/src/codegen/generators/manifest.ts +9 -1
- package/src/codegen/runtime/read-comb.ts +71 -3
- package/src/codegen/runtime/read-tables.ts +31 -19
- package/src/codegen/runtime/snapshot-types.ts +3 -1
- package/src/codegen/runtime/to-meta.ts +6 -3
- package/src/formats/catalog.ts +255 -0
- package/src/formats/iana-time-zone.ts +117 -0
- package/src/formats/iso3166-1.ts +78 -0
- package/src/formats/iso4217.ts +42 -0
- package/src/formats/iso639-1.ts +41 -0
- package/src/formats/uts35.ts +143 -0
- package/src/index.ts +5 -0
- package/src/meta/column.ts +22 -9
- package/src/migrate/core/apply.ts +63 -22
- package/src/migrate/core/atlas-apply-runs.ts +15 -26
- package/src/migrate/core/cleanup.ts +22 -10
- package/src/migrate/core/d1-engine-child.mjs +27 -11
- package/src/migrate/core/d1-history.ts +23 -26
- package/src/migrate/core/d1-wrangler-error.ts +24 -1
- package/src/migrate/core/pg-replay-commit.ts +41 -38
- package/src/migrate/core/pg-replay-drops.ts +17 -32
- package/src/migrate/core/pg-replay-hidden.ts +75 -0
- package/src/migrate/core/pg-replay-roles.ts +2 -4
- package/src/migrate/core/pg-replay.ts +21 -19
- package/src/migrate/core/pg-server-snapshot.ts +4 -24
- package/src/migrate/core/proof-readers.ts +80 -0
- package/src/migrate/core/proof-run.ts +19 -5
- package/src/migrate/core/proof-scope.ts +27 -14
- package/src/migrate/core/sql-code.ts +36 -5
- package/src/migrate/core/sql-scan.ts +7 -1
- package/src/migrate/core/sqlite-keywords.ts +70 -0
- package/src/migrate/core/stopped.ts +70 -0
- package/src/migrate/core/trigger-write.d.mts +7 -0
- package/src/migrate/core/trigger-write.mjs +42 -0
- package/src/pg/columns.ts +6 -4
- package/src/query/cursor-mint.ts +11 -18
- package/src/query/cursor-refusals.ts +22 -11
- package/src/query/descriptions.ts +4 -9
- package/src/query/filter-parse.ts +80 -22
- package/src/query/filter-validate.ts +10 -13
- package/src/query/filter.ts +40 -18
- package/src/query/list-extras.ts +7 -10
- package/src/query/list-schema.ts +0 -2
- package/src/query/merged.ts +3 -2
- package/src/query/pagination.ts +30 -6
- package/src/query/rounded-key.ts +5 -8
- package/src/query/schema-params.ts +12 -5
- package/src/query/select-validate.ts +7 -4
- package/src/query/sqlite/cursor-fit.ts +10 -2
- package/src/query/trusted.ts +7 -2
- package/src/sqlite/columns.ts +6 -4
- package/src/sqlite/constraints.ts +120 -6
- package/types/cli/args.d.ts.map +1 -1
- package/types/cli/comb-config.d.ts +5 -2
- package/types/cli/comb-config.d.ts.map +1 -1
- package/types/cli/file-path.d.ts +11 -0
- package/types/cli/file-path.d.ts.map +1 -0
- package/types/cli/migrate-apply.d.ts +1 -0
- package/types/cli/migrate-apply.d.ts.map +1 -1
- package/types/cli/migrate-d1.d.ts.map +1 -1
- package/types/cli/migrate-json.d.ts +4 -0
- package/types/cli/migrate-json.d.ts.map +1 -0
- package/types/cli/migrate.d.ts.map +1 -1
- package/types/codegen/analyzer-types.d.ts +6 -1
- package/types/codegen/analyzer-types.d.ts.map +1 -1
- package/types/codegen/annotations.d.ts +12 -7
- package/types/codegen/annotations.d.ts.map +1 -1
- package/types/codegen/format-constraints.d.ts +22 -0
- package/types/codegen/format-constraints.d.ts.map +1 -0
- package/types/codegen/generators/dtos/entity-file.d.ts.map +1 -1
- package/types/codegen/generators/dtos/example-meta.d.ts.map +1 -1
- package/types/codegen/generators/dtos/field-schema.d.ts.map +1 -1
- package/types/codegen/generators/dtos/ip-patterns.d.ts +7 -0
- package/types/codegen/generators/dtos/ip-patterns.d.ts.map +1 -1
- package/types/codegen/generators/dtos/number-range.d.ts +8 -0
- package/types/codegen/generators/dtos/number-range.d.ts.map +1 -1
- package/types/codegen/generators/dtos/schema-aliases.d.ts +1 -1
- package/types/codegen/generators/dtos/schema-aliases.d.ts.map +1 -1
- package/types/codegen/generators/dtos/text-format.d.ts +31 -0
- package/types/codegen/generators/dtos/text-format.d.ts.map +1 -0
- package/types/codegen/generators/dtos/text-schema.d.ts +8 -1
- package/types/codegen/generators/dtos/text-schema.d.ts.map +1 -1
- package/types/codegen/generators/manifest.d.ts +5 -0
- package/types/codegen/generators/manifest.d.ts.map +1 -1
- package/types/codegen/runtime/read-comb.d.ts +17 -3
- package/types/codegen/runtime/read-comb.d.ts.map +1 -1
- package/types/codegen/runtime/read-tables.d.ts.map +1 -1
- package/types/codegen/runtime/snapshot-types.d.ts +3 -1
- package/types/codegen/runtime/snapshot-types.d.ts.map +1 -1
- package/types/codegen/runtime/to-meta.d.ts.map +1 -1
- package/types/formats/catalog.d.ts +51 -0
- package/types/formats/catalog.d.ts.map +1 -0
- package/types/formats/iana-time-zone.d.ts +7 -0
- package/types/formats/iana-time-zone.d.ts.map +1 -0
- package/types/formats/iso3166-1.d.ts +12 -0
- package/types/formats/iso3166-1.d.ts.map +1 -0
- package/types/formats/iso4217.d.ts +7 -0
- package/types/formats/iso4217.d.ts.map +1 -0
- package/types/formats/iso639-1.d.ts +6 -0
- package/types/formats/iso639-1.d.ts.map +1 -0
- package/types/formats/uts35.d.ts +13 -0
- package/types/formats/uts35.d.ts.map +1 -0
- package/types/index.d.ts +5 -0
- package/types/index.d.ts.map +1 -1
- package/types/meta/column.d.ts +7 -1
- package/types/meta/column.d.ts.map +1 -1
- package/types/migrate/core/apply.d.ts +3 -2
- package/types/migrate/core/apply.d.ts.map +1 -1
- package/types/migrate/core/atlas-apply-runs.d.ts +6 -0
- package/types/migrate/core/atlas-apply-runs.d.ts.map +1 -1
- package/types/migrate/core/cleanup.d.ts +16 -4
- package/types/migrate/core/cleanup.d.ts.map +1 -1
- package/types/migrate/core/d1-history.d.ts.map +1 -1
- package/types/migrate/core/d1-wrangler-error.d.ts +11 -0
- package/types/migrate/core/d1-wrangler-error.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-commit.d.ts +14 -9
- package/types/migrate/core/pg-replay-commit.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-drops.d.ts +7 -12
- package/types/migrate/core/pg-replay-drops.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-hidden.d.ts +36 -0
- package/types/migrate/core/pg-replay-hidden.d.ts.map +1 -0
- package/types/migrate/core/pg-replay-roles.d.ts +2 -4
- package/types/migrate/core/pg-replay-roles.d.ts.map +1 -1
- package/types/migrate/core/pg-replay.d.ts.map +1 -1
- package/types/migrate/core/pg-server-snapshot.d.ts +4 -11
- package/types/migrate/core/pg-server-snapshot.d.ts.map +1 -1
- package/types/migrate/core/proof-readers.d.ts +19 -0
- package/types/migrate/core/proof-readers.d.ts.map +1 -0
- package/types/migrate/core/proof-run.d.ts.map +1 -1
- package/types/migrate/core/proof-scope.d.ts +15 -7
- package/types/migrate/core/proof-scope.d.ts.map +1 -1
- package/types/migrate/core/sql-code.d.ts +7 -3
- package/types/migrate/core/sql-code.d.ts.map +1 -1
- package/types/migrate/core/sql-scan.d.ts.map +1 -1
- package/types/migrate/core/sqlite-keywords.d.ts +11 -0
- package/types/migrate/core/sqlite-keywords.d.ts.map +1 -0
- package/types/migrate/core/stopped.d.ts +23 -0
- package/types/migrate/core/stopped.d.ts.map +1 -0
- package/types/pg/columns.d.ts +3 -3
- package/types/pg/columns.d.ts.map +1 -1
- package/types/query/cursor-mint.d.ts.map +1 -1
- package/types/query/cursor-refusals.d.ts +17 -1
- package/types/query/cursor-refusals.d.ts.map +1 -1
- package/types/query/descriptions.d.ts +1 -1
- package/types/query/descriptions.d.ts.map +1 -1
- package/types/query/filter-parse.d.ts +15 -4
- package/types/query/filter-parse.d.ts.map +1 -1
- package/types/query/filter-validate.d.ts +1 -1
- package/types/query/filter-validate.d.ts.map +1 -1
- package/types/query/filter.d.ts +9 -1
- package/types/query/filter.d.ts.map +1 -1
- package/types/query/list-extras.d.ts.map +1 -1
- package/types/query/list-schema.d.ts.map +1 -1
- package/types/query/merged.d.ts.map +1 -1
- package/types/query/pagination.d.ts +13 -1
- package/types/query/pagination.d.ts.map +1 -1
- package/types/query/rounded-key.d.ts +2 -11
- package/types/query/rounded-key.d.ts.map +1 -1
- package/types/query/schema-params.d.ts.map +1 -1
- package/types/query/select-validate.d.ts.map +1 -1
- package/types/query/sqlite/cursor-fit.d.ts.map +1 -1
- package/types/query/trusted.d.ts +1 -1
- package/types/query/trusted.d.ts.map +1 -1
- package/types/sqlite/columns.d.ts +3 -3
- package/types/sqlite/columns.d.ts.map +1 -1
- package/types/sqlite/constraints.d.ts +117 -5
- package/types/sqlite/constraints.d.ts.map +1 -1
- package/src/sqlite/patterns.ts +0 -19
- package/types/sqlite/patterns.d.ts +0 -19
- package/types/sqlite/patterns.d.ts.map +0 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Written by `bun scripts/registries.ts` from the pinned release below: edit the script, not this file.
|
|
2
|
+
// Debian iso-codes v4.20.1: https://salsa.debian.org/iso-codes-team/iso-codes/-/raw/v4.20.1/data/iso_3166-1.json
|
|
3
|
+
// the alpha-2 and alpha-3 code of every officially assigned country
|
|
4
|
+
|
|
5
|
+
// lookup-table: the 249 codes, one line per first letter, read by isIso3166Alpha2
|
|
6
|
+
const ALPHA_2: ReadonlySet<string> = /* @__PURE__ */ new Set(
|
|
7
|
+
/* @__PURE__ */ `AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ
|
|
8
|
+
BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ
|
|
9
|
+
CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ
|
|
10
|
+
DE DJ DK DM DO DZ
|
|
11
|
+
EC EE EG EH ER ES ET
|
|
12
|
+
FI FJ FK FM FO FR
|
|
13
|
+
GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY
|
|
14
|
+
HK HM HN HR HT HU
|
|
15
|
+
ID IE IL IM IN IO IQ IR IS IT
|
|
16
|
+
JE JM JO JP
|
|
17
|
+
KE KG KH KI KM KN KP KR KW KY KZ
|
|
18
|
+
LA LB LC LI LK LR LS LT LU LV LY
|
|
19
|
+
MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ
|
|
20
|
+
NA NC NE NF NG NI NL NO NP NR NU NZ
|
|
21
|
+
OM
|
|
22
|
+
PA PE PF PG PH PK PL PM PN PR PS PT PW PY
|
|
23
|
+
QA
|
|
24
|
+
RE RO RS RU RW
|
|
25
|
+
SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ
|
|
26
|
+
TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ
|
|
27
|
+
UA UG UM US UY UZ
|
|
28
|
+
VA VC VE VG VI VN VU
|
|
29
|
+
WF WS
|
|
30
|
+
YE YT
|
|
31
|
+
ZA ZM ZW`.split(/\s/u),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// lookup-table: the 249 codes, one line per first letter, read by isIso3166Alpha3
|
|
35
|
+
const ALPHA_3: ReadonlySet<string> = /* @__PURE__ */ new Set(
|
|
36
|
+
/* @__PURE__ */ `ABW AFG AGO AIA ALA ALB AND ARE ARG ARM ASM ATA ATF ATG AUS AUT AZE
|
|
37
|
+
BDI BEL BEN BES BFA BGD BGR BHR BHS BIH BLM BLR BLZ BMU BOL BRA BRB BRN BTN BVT BWA
|
|
38
|
+
CAF CAN CCK CHE CHL CHN CIV CMR COD COG COK COL COM CPV CRI CUB CUW CXR CYM CYP CZE
|
|
39
|
+
DEU DJI DMA DNK DOM DZA
|
|
40
|
+
ECU EGY ERI ESH ESP EST ETH
|
|
41
|
+
FIN FJI FLK FRA FRO FSM
|
|
42
|
+
GAB GBR GEO GGY GHA GIB GIN GLP GMB GNB GNQ GRC GRD GRL GTM GUF GUM GUY
|
|
43
|
+
HKG HMD HND HRV HTI HUN
|
|
44
|
+
IDN IMN IND IOT IRL IRN IRQ ISL ISR ITA
|
|
45
|
+
JAM JEY JOR JPN
|
|
46
|
+
KAZ KEN KGZ KHM KIR KNA KOR KWT
|
|
47
|
+
LAO LBN LBR LBY LCA LIE LKA LSO LTU LUX LVA
|
|
48
|
+
MAC MAF MAR MCO MDA MDG MDV MEX MHL MKD MLI MLT MMR MNE MNG MNP MOZ MRT MSR MTQ MUS MWI MYS MYT
|
|
49
|
+
NAM NCL NER NFK NGA NIC NIU NLD NOR NPL NRU NZL
|
|
50
|
+
OMN
|
|
51
|
+
PAK PAN PCN PER PHL PLW PNG POL PRI PRK PRT PRY PSE PYF
|
|
52
|
+
QAT
|
|
53
|
+
REU ROU RUS RWA
|
|
54
|
+
SAU SDN SEN SGP SGS SHN SJM SLB SLE SLV SMR SOM SPM SRB SSD STP SUR SVK SVN SWE SWZ SXM SYC SYR
|
|
55
|
+
TCA TCD TGO THA TJK TKL TKM TLS TON TTO TUN TUR TUV TWN TZA
|
|
56
|
+
UGA UKR UMI URY USA UZB
|
|
57
|
+
VAT VCT VEN VGB VIR VNM VUT
|
|
58
|
+
WLF WSM
|
|
59
|
+
YEM
|
|
60
|
+
ZAF ZMB ZWE`.split(/\s/u),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether `value` is an officially assigned ISO 3166-1 alpha-2 country code, exactly as written: uppercase, such as
|
|
65
|
+
* HR or GB (UK is reserved, not assigned). The codes are those of Debian iso-codes v4.20.1. Any string is taken;
|
|
66
|
+
* this never throws.
|
|
67
|
+
*/
|
|
68
|
+
export function isIso3166Alpha2(value: string): boolean {
|
|
69
|
+
return ALPHA_2.has(value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether `value` is an officially assigned ISO 3166-1 alpha-3 country code, exactly as written: uppercase, such as
|
|
74
|
+
* HRV or GBR. The codes are those of Debian iso-codes v4.20.1. Any string is taken; this never throws.
|
|
75
|
+
*/
|
|
76
|
+
export function isIso3166Alpha3(value: string): boolean {
|
|
77
|
+
return ALPHA_3.has(value)
|
|
78
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Written by `bun scripts/registries.ts` from the pinned release below: edit the script, not this file.
|
|
2
|
+
// ISO 4217 list one as SIX published it on 2026-09-17: https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml
|
|
3
|
+
// the distinct alphabetic codes
|
|
4
|
+
|
|
5
|
+
// lookup-table: the 178 alphabetic codes of the 2026-09-17 list, one line per first letter, read by isIso4217
|
|
6
|
+
const CODES: ReadonlySet<string> = /* @__PURE__ */ new Set(
|
|
7
|
+
/* @__PURE__ */ `AED AFN ALL AMD AOA ARS AUD AWG AZN
|
|
8
|
+
BAM BBD BDT BHD BIF BMD BND BOB BOV BRL BSD BTN BWP BYN BZD
|
|
9
|
+
CAD CDF CHE CHF CHW CLF CLP CNY COP COU CRC CUP CVE CZK
|
|
10
|
+
DJF DKK DOP DZD
|
|
11
|
+
EGP ERN ETB EUR
|
|
12
|
+
FJD FKP
|
|
13
|
+
GBP GEL GHS GIP GMD GNF GTQ GYD
|
|
14
|
+
HKD HNL HTG HUF
|
|
15
|
+
IDR ILS INR IQD IRR ISK
|
|
16
|
+
JMD JOD JPY
|
|
17
|
+
KES KGS KHR KMF KPW KRW KWD KYD KZT
|
|
18
|
+
LAK LBP LKR LRD LSL LYD
|
|
19
|
+
MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN MXV MYR MZN
|
|
20
|
+
NAD NGN NIO NOK NPR NZD
|
|
21
|
+
OMR
|
|
22
|
+
PAB PEN PGK PHP PKR PLN PYG
|
|
23
|
+
QAR
|
|
24
|
+
RON RSD RUB RWF
|
|
25
|
+
SAR SBD SCR SDG SEK SGD SHP SLE SOS SRD SSP STN SVC SYP SZL
|
|
26
|
+
THB TJS TMT TND TOP TRY TTD TWD TZS
|
|
27
|
+
UAH UGX USD USN UYI UYU UYW UZS
|
|
28
|
+
VED VES VND VUV
|
|
29
|
+
WST
|
|
30
|
+
XAD XAF XAG XAU XBA XBB XBC XBD XCD XCG XDR XOF XPD XPF XPT XSU XTS XUA XXX
|
|
31
|
+
YER
|
|
32
|
+
ZAR ZMW ZWG`.split(/\s/u),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether `value` is a current ISO 4217 alphabetic currency code, exactly as written: uppercase, such as EUR or USD.
|
|
37
|
+
* The codes are those of the list published 2026-09-17, fund and precious metal codes such as XAU included;
|
|
38
|
+
* a withdrawn code such as HRK is not one. Any string is taken; this never throws.
|
|
39
|
+
*/
|
|
40
|
+
export function isIso4217(value: string): boolean {
|
|
41
|
+
return CODES.has(value)
|
|
42
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Written by `bun scripts/registries.ts` from the pinned release below: edit the script, not this file.
|
|
2
|
+
// Debian iso-codes v4.20.1: https://salsa.debian.org/iso-codes-team/iso-codes/-/raw/v4.20.1/data/iso_639-2.json
|
|
3
|
+
// the two-letter code of every ISO 639-2 language that has one, which is ISO 639-1
|
|
4
|
+
|
|
5
|
+
// lookup-table: the 183 two-letter language codes, one line per first letter, read by isIso639Alpha2
|
|
6
|
+
const CODES: ReadonlySet<string> = /* @__PURE__ */ new Set(
|
|
7
|
+
/* @__PURE__ */ `aa ab ae af ak am an ar as av ay az
|
|
8
|
+
ba be bg bi bm bn bo br bs
|
|
9
|
+
ca ce ch co cr cs cu cv cy
|
|
10
|
+
da de dv dz
|
|
11
|
+
ee el en eo es et eu
|
|
12
|
+
fa ff fi fj fo fr fy
|
|
13
|
+
ga gd gl gn gu gv
|
|
14
|
+
ha he hi ho hr ht hu hy hz
|
|
15
|
+
ia id ie ig ii ik io is it iu
|
|
16
|
+
ja jv
|
|
17
|
+
ka kg ki kj kk kl km kn ko kr ks ku kv kw ky
|
|
18
|
+
la lb lg li ln lo lt lu lv
|
|
19
|
+
mg mh mi mk ml mn mr ms mt my
|
|
20
|
+
na nb nd ne ng nl nn no nr nv ny
|
|
21
|
+
oc oj om or os
|
|
22
|
+
pa pi pl ps pt
|
|
23
|
+
qu
|
|
24
|
+
rm rn ro ru rw
|
|
25
|
+
sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw
|
|
26
|
+
ta te tg th ti tk tl tn to tr ts tt tw ty
|
|
27
|
+
ug uk ur uz
|
|
28
|
+
ve vi vo
|
|
29
|
+
wa wo
|
|
30
|
+
xh
|
|
31
|
+
yi yo
|
|
32
|
+
za zh zu`.split(/\s/u),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether `value` is an ISO 639-1 two-letter language code, exactly as written: lowercase, such as hr or en. The
|
|
37
|
+
* codes are those of Debian iso-codes v4.20.1. Any string is taken; this never throws.
|
|
38
|
+
*/
|
|
39
|
+
export function isIso639Alpha2(value: string): boolean {
|
|
40
|
+
return CODES.has(value)
|
|
41
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Written by `bun scripts/registries.ts` from the pinned release below: edit the script, not this file.
|
|
2
|
+
// Unicode CLDR release-49:
|
|
3
|
+
// https://raw.githubusercontent.com/unicode-org/cldr/release-49/common/bcp47/calendar.xml
|
|
4
|
+
// https://raw.githubusercontent.com/unicode-org/cldr/release-49/common/bcp47/measure.xml
|
|
5
|
+
// https://raw.githubusercontent.com/unicode-org/cldr/release-49/common/bcp47/number.xml
|
|
6
|
+
|
|
7
|
+
/** The 18 values of the `ca` (calendar) key of a Unicode locale extension, deprecated ones dropped. */
|
|
8
|
+
export const CALENDARS: readonly string[] = [
|
|
9
|
+
"buddhist",
|
|
10
|
+
"chinese",
|
|
11
|
+
"coptic",
|
|
12
|
+
"dangi",
|
|
13
|
+
"ethioaa",
|
|
14
|
+
"ethiopic",
|
|
15
|
+
"gregory",
|
|
16
|
+
"hebrew",
|
|
17
|
+
"indian",
|
|
18
|
+
"islamic",
|
|
19
|
+
"islamic-civil",
|
|
20
|
+
"islamic-rgsa",
|
|
21
|
+
"islamic-tbla",
|
|
22
|
+
"islamic-umalqura",
|
|
23
|
+
"iso8601",
|
|
24
|
+
"japanese",
|
|
25
|
+
"persian",
|
|
26
|
+
"roc",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
/** The 7 values of the `fw` (first day of the week) key of a Unicode locale extension, deprecated ones dropped. */
|
|
30
|
+
export const FIRST_DAYS: readonly string[] = ["fri", "mon", "sat", "sun", "thu", "tue", "wed"]
|
|
31
|
+
|
|
32
|
+
/** The 6 values of the `hc` (hour cycle) key of a Unicode locale extension, deprecated ones dropped. */
|
|
33
|
+
export const HOUR_CYCLES: readonly string[] = ["c12", "c24", "h11", "h12", "h23", "h24"]
|
|
34
|
+
|
|
35
|
+
/** The 3 values of the `ms` (measurement system) key of a Unicode locale extension, deprecated ones dropped. */
|
|
36
|
+
export const MEASUREMENT_SYSTEMS: readonly string[] = ["metric", "uksystem", "ussystem"]
|
|
37
|
+
|
|
38
|
+
/** The 3 values of the `mu` (temperature unit) key of a Unicode locale extension, deprecated ones dropped. */
|
|
39
|
+
export const MEASUREMENT_UNITS: readonly string[] = ["celsius", "fahrenhe", "kelvin"]
|
|
40
|
+
|
|
41
|
+
/** The 100 values of the `nu` (numbering system) key of a Unicode locale extension, deprecated ones dropped. */
|
|
42
|
+
export const NUMBERING_SYSTEMS: readonly string[] = [
|
|
43
|
+
"adlm",
|
|
44
|
+
"ahom",
|
|
45
|
+
"arab",
|
|
46
|
+
"arabext",
|
|
47
|
+
"armn",
|
|
48
|
+
"armnlow",
|
|
49
|
+
"bali",
|
|
50
|
+
"beng",
|
|
51
|
+
"bhks",
|
|
52
|
+
"brah",
|
|
53
|
+
"cakm",
|
|
54
|
+
"cham",
|
|
55
|
+
"cyrl",
|
|
56
|
+
"deva",
|
|
57
|
+
"diak",
|
|
58
|
+
"ethi",
|
|
59
|
+
"finance",
|
|
60
|
+
"fullwide",
|
|
61
|
+
"gara",
|
|
62
|
+
"geor",
|
|
63
|
+
"gong",
|
|
64
|
+
"gonm",
|
|
65
|
+
"grek",
|
|
66
|
+
"greklow",
|
|
67
|
+
"gujr",
|
|
68
|
+
"gukh",
|
|
69
|
+
"guru",
|
|
70
|
+
"hanidays",
|
|
71
|
+
"hanidec",
|
|
72
|
+
"hans",
|
|
73
|
+
"hansfin",
|
|
74
|
+
"hant",
|
|
75
|
+
"hantfin",
|
|
76
|
+
"hebr",
|
|
77
|
+
"hmng",
|
|
78
|
+
"hmnp",
|
|
79
|
+
"java",
|
|
80
|
+
"jpan",
|
|
81
|
+
"jpanfin",
|
|
82
|
+
"jpanyear",
|
|
83
|
+
"kali",
|
|
84
|
+
"kawi",
|
|
85
|
+
"khmr",
|
|
86
|
+
"knda",
|
|
87
|
+
"krai",
|
|
88
|
+
"lana",
|
|
89
|
+
"lanatham",
|
|
90
|
+
"laoo",
|
|
91
|
+
"latn",
|
|
92
|
+
"lepc",
|
|
93
|
+
"limb",
|
|
94
|
+
"mathbold",
|
|
95
|
+
"mathdbl",
|
|
96
|
+
"mathmono",
|
|
97
|
+
"mathsanb",
|
|
98
|
+
"mathsans",
|
|
99
|
+
"mlym",
|
|
100
|
+
"modi",
|
|
101
|
+
"mong",
|
|
102
|
+
"mroo",
|
|
103
|
+
"mtei",
|
|
104
|
+
"mymr",
|
|
105
|
+
"mymrepka",
|
|
106
|
+
"mymrpao",
|
|
107
|
+
"mymrshan",
|
|
108
|
+
"mymrtlng",
|
|
109
|
+
"nagm",
|
|
110
|
+
"native",
|
|
111
|
+
"newa",
|
|
112
|
+
"nkoo",
|
|
113
|
+
"olck",
|
|
114
|
+
"onao",
|
|
115
|
+
"orya",
|
|
116
|
+
"osma",
|
|
117
|
+
"outlined",
|
|
118
|
+
"rohg",
|
|
119
|
+
"roman",
|
|
120
|
+
"romanlow",
|
|
121
|
+
"saur",
|
|
122
|
+
"segment",
|
|
123
|
+
"shrd",
|
|
124
|
+
"sind",
|
|
125
|
+
"sinh",
|
|
126
|
+
"sora",
|
|
127
|
+
"sund",
|
|
128
|
+
"sunu",
|
|
129
|
+
"takr",
|
|
130
|
+
"talu",
|
|
131
|
+
"taml",
|
|
132
|
+
"tamldec",
|
|
133
|
+
"telu",
|
|
134
|
+
"thai",
|
|
135
|
+
"tibt",
|
|
136
|
+
"tirh",
|
|
137
|
+
"tnsa",
|
|
138
|
+
"tols",
|
|
139
|
+
"traditio",
|
|
140
|
+
"vaii",
|
|
141
|
+
"wara",
|
|
142
|
+
"wcho",
|
|
143
|
+
]
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export { isCidr } from "./cidr.ts"
|
|
2
|
+
export type { IntegerFormat, TextFormat } from "./formats/catalog.ts"
|
|
3
|
+
export { isIanaTimeZone } from "./formats/iana-time-zone.ts"
|
|
4
|
+
export { isIso3166Alpha2, isIso3166Alpha3 } from "./formats/iso3166-1.ts"
|
|
5
|
+
export { isIso4217 } from "./formats/iso4217.ts"
|
|
6
|
+
export { isIso639Alpha2 } from "./formats/iso639-1.ts"
|
|
2
7
|
export { jsonValue } from "./json-value.ts"
|
|
3
8
|
export { CombError } from "./error.ts"
|
|
4
9
|
export {
|
package/src/meta/column.ts
CHANGED
|
@@ -45,19 +45,32 @@ function combColumnMeta(column: unknown): CombColumnMeta | undefined {
|
|
|
45
45
|
return config?.[COMB_COLUMN] as CombColumnMeta | undefined
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/** `record` without the keys set to undefined, as a key set to undefined is one left out */
|
|
49
|
+
function defined(record: Readonly<Record<string, unknown>>): Record<string, unknown> {
|
|
50
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A c.enum's options as its column's record: its constraints (the flags, its `format`, and a `schema`
|
|
55
|
+
* a JavaScript caller passes, kept for codegen to refuse by name), the rest its state machine, none
|
|
56
|
+
* when it names no state
|
|
57
|
+
*/
|
|
49
58
|
function enumMeta(
|
|
50
59
|
values: readonly string[],
|
|
51
|
-
options: {
|
|
60
|
+
options: {
|
|
61
|
+
format?: string | undefined
|
|
62
|
+
nomutate?: boolean | undefined
|
|
63
|
+
private?: boolean | undefined
|
|
64
|
+
schema?: unknown
|
|
65
|
+
tenant?: boolean | undefined
|
|
66
|
+
} = {},
|
|
52
67
|
): { [K in keyof CombColumnMeta]: CombColumnMeta[K] | undefined } {
|
|
53
|
-
const { nomutate, private: hidden, tenant, ...
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const states = Object.fromEntries(Object.entries(given).filter(([, value]) => value !== undefined))
|
|
58
|
-
const whole = !flagged && Object.keys(states).length === Object.keys(options).length
|
|
68
|
+
const { format, nomutate, private: hidden, schema, tenant, ...rest } = options
|
|
69
|
+
const constraints = defined({ format, nomutate, private: hidden, schema, tenant })
|
|
70
|
+
const states = defined(rest)
|
|
71
|
+
const whole = Object.keys(constraints).length === 0 && Object.keys(states).length === Object.keys(options).length
|
|
59
72
|
return {
|
|
60
|
-
constraints:
|
|
73
|
+
constraints: Object.keys(constraints).length === 0 ? undefined : constraints,
|
|
61
74
|
kind: "enum",
|
|
62
75
|
/* a machine given alone is kept as the very object passed, as assertTransition takes it too */
|
|
63
76
|
states: Object.keys(states).length === 0 ? undefined : whole ? options : states,
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { chainText } from "../../error-chain.ts"
|
|
8
8
|
import type { Connection } from "../types.ts"
|
|
9
|
+
import { pastStop } from "./cleanup.ts"
|
|
9
10
|
import { HASH_VERSION } from "./parse.ts"
|
|
10
11
|
import { Refused } from "./refused.ts"
|
|
12
|
+
import { whileStoppable } from "./stopped.ts"
|
|
11
13
|
import { isTransient } from "./transient.ts"
|
|
12
14
|
import { type RetryOptions, withRetry } from "./utils.ts"
|
|
13
15
|
|
|
@@ -24,19 +26,39 @@ const TABLE = `(
|
|
|
24
26
|
hash_v INTEGER
|
|
25
27
|
)`
|
|
26
28
|
|
|
27
|
-
/** Whether a unique index of comb's history holds
|
|
28
|
-
|
|
29
|
+
/** Whether a unique index of comb's history holds `column` alone */
|
|
30
|
+
async function uniqueOn(conn: Connection, column: string): Promise<boolean> {
|
|
29
31
|
const list = await conn.execute("PRAGMA index_list(__drizzle_migrations)")
|
|
30
32
|
const unique = list.rows.filter((row) => Number(row[list.columns.indexOf("unique")]) === 1)
|
|
31
33
|
for (const index of unique) {
|
|
32
34
|
const name = String(index[list.columns.indexOf("name")])
|
|
33
35
|
const info = await conn.execute(`PRAGMA index_info("${name.replaceAll('"', '""')}")`)
|
|
34
36
|
const columns = info.rows.map((row) => String(row[info.columns.indexOf("name")]))
|
|
35
|
-
if (columns.length === 1 && columns[0] ===
|
|
37
|
+
if (columns.length === 1 && columns[0] === column) return true
|
|
36
38
|
}
|
|
37
39
|
return false
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
/** Whether a unique index of comb's history holds its hash alone, as comb's table did before 0.13 */
|
|
43
|
+
export const hashUnique = (conn: Connection): Promise<boolean> => uniqueOn(conn, "hash")
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A name recorded once, whoever made the table: a history drizzle's migrator made gains `name` by ALTER
|
|
47
|
+
* and no UNIQUE with it, so nothing would refuse a second run's record of a file the first applied.
|
|
48
|
+
* Refused, naming the files, when the history already holds one twice
|
|
49
|
+
*/
|
|
50
|
+
async function keepNameUnique(conn: Connection): Promise<void> {
|
|
51
|
+
if (await uniqueOn(conn, "name")) return
|
|
52
|
+
const twice = await conn.execute(
|
|
53
|
+
"SELECT name FROM __drizzle_migrations WHERE name IS NOT NULL GROUP BY name HAVING count(*) > 1 ORDER BY name",
|
|
54
|
+
)
|
|
55
|
+
if (twice.rows.length > 0)
|
|
56
|
+
throw new Error(
|
|
57
|
+
`__drizzle_migrations records ${twice.rows.map((row) => String(row[0])).join(", ")} more than once, so comb cannot keep a file from being recorded twice: delete the extra rows of each (keep the first), then run it again`,
|
|
58
|
+
)
|
|
59
|
+
await conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS __drizzle_migrations_name ON __drizzle_migrations (name)")
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
/**
|
|
41
63
|
* comb's history, made where it is missing, named from 0.11, and from a history before 0.13
|
|
42
64
|
* rebuilt in one batch: every row and id kept, each row `renamed` lists taking its new name,
|
|
@@ -55,7 +77,7 @@ export async function ensureMigrationsTable(
|
|
|
55
77
|
/* a history 0.20 or earlier wrote gains it too, every row it holds left without it */
|
|
56
78
|
if (!has("hash_v")) await conn.execute("ALTER TABLE __drizzle_migrations ADD COLUMN hash_v INTEGER")
|
|
57
79
|
/* a history from before 0.13 holds each hash once: rebuilt, in one batch, with every row and id kept */
|
|
58
|
-
if (!(await hashUnique(conn))) return
|
|
80
|
+
if (!(await hashUnique(conn))) return keepNameUnique(conn)
|
|
59
81
|
/* a history made without AUTOINCREMENT has no counter; its largest id is where the new one starts */
|
|
60
82
|
const sequenced = await conn.execute("SELECT 1 FROM sqlite_master WHERE name = 'sqlite_sequence'")
|
|
61
83
|
const counted =
|
|
@@ -151,7 +173,8 @@ class Unconfirmed extends Error {}
|
|
|
151
173
|
* record it wrote says it ran, so a retry reads the history first and runs nothing again.
|
|
152
174
|
* When no read of `history` answers after a batch that failed transiently, the failure is an Unconfirmed.
|
|
153
175
|
* The record goes first, so a batch whose file another run recorded while it waited for the lock
|
|
154
|
-
* fails on the name at once and runs nothing
|
|
176
|
+
* fails on the name's unique index at once and runs nothing (ensureMigrationsTable keeps one on every
|
|
177
|
+
* history, whoever made the table); the history then says so, and it is that run's.
|
|
155
178
|
* Returns who applied it.
|
|
156
179
|
*/
|
|
157
180
|
export async function applyRecorded(
|
|
@@ -207,24 +230,42 @@ export async function applyEach(
|
|
|
207
230
|
{ history, print, retry }: ApplyOptions & { print: (line: string) => void },
|
|
208
231
|
): Promise<void> {
|
|
209
232
|
const names = migrations.map(({ filename }) => filename)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
/* the files that committed, and the batch under way, which a stop waits for: it commits or fails whole */
|
|
234
|
+
let done = 0
|
|
235
|
+
let batching: Promise<unknown> = Promise.resolve()
|
|
236
|
+
const read = async () => {
|
|
237
|
+
await batching.catch(() => {
|
|
238
|
+
// reason: a batch that failed committed nothing, which the count it left already says
|
|
239
|
+
})
|
|
240
|
+
return { applied: names.slice(0, done), pending: names.slice(done) }
|
|
241
|
+
}
|
|
242
|
+
await whileStoppable({ names, print, read }, async () => {
|
|
243
|
+
for (const [at, migration] of migrations.entries()) {
|
|
244
|
+
/* a stop parks the run here, whichever connection the next file would open */
|
|
245
|
+
await pastStop()
|
|
246
|
+
print(`Apply: ${migration.filename}`)
|
|
214
247
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
248
|
+
const conn = await open()
|
|
249
|
+
try {
|
|
250
|
+
/* counted with the batch, so a stop that waited for it reads the count it made */
|
|
251
|
+
const batch = applyRecorded(conn, migration, { history, retry }).then((who) => {
|
|
252
|
+
done++
|
|
253
|
+
return who
|
|
254
|
+
})
|
|
255
|
+
batching = batch
|
|
256
|
+
if ((await batch) === "another run") print(`Skip (applied by another run): ${migration.filename}`)
|
|
257
|
+
} finally {
|
|
258
|
+
await conn.close()
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const applied = names.slice(0, at)
|
|
262
|
+
if (applied.length > 0) print(`Applied before the failure: ${applied.join(", ")}`)
|
|
263
|
+
/* withRetry throws an Error, whatever the batch threw; so does a connection that would not open */
|
|
264
|
+
const message = (error as Error).message
|
|
265
|
+
if (error instanceof Unconfirmed)
|
|
266
|
+
throw new Refused(message, { applied, pending: names.slice(at + 1), unknown: [migration.filename] })
|
|
267
|
+
throw new Refused(message, { applied, pending: names.slice(at) })
|
|
219
268
|
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
const done = names.slice(0, at)
|
|
222
|
-
if (done.length > 0) print(`Applied before the failure: ${done.join(", ")}`)
|
|
223
|
-
/* withRetry throws an Error, whatever the batch threw; so does a connection that would not open */
|
|
224
|
-
const message = (error as Error).message
|
|
225
|
-
if (error instanceof Unconfirmed)
|
|
226
|
-
throw new Refused(message, { applied: done, pending: names.slice(at + 1), unknown: [migration.filename] })
|
|
227
|
-
throw new Refused(message, { applied: done, pending: names.slice(at) })
|
|
228
269
|
}
|
|
229
|
-
}
|
|
270
|
+
})
|
|
230
271
|
}
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* reading of a file proves what it leaves on its session: a trigger, a default, a DO block's EXECUTE
|
|
5
5
|
* or code the target holds can each run a SET that outlives its COMMIT.
|
|
6
6
|
*/
|
|
7
|
-
import { hold } from "./cleanup.ts"
|
|
8
7
|
import { type Foreign, ownHistory } from "./atlas-foreign.ts"
|
|
9
8
|
import { type AtlasFile, type AtlasStatus, checkHistory } from "./atlas-history.ts"
|
|
10
9
|
import { APPLY_FORMAT, NON_LINEAR, reported } from "./atlas-report.ts"
|
|
11
10
|
import { atlas, type AtlasCommand, atlasStatus } from "./atlas-status.ts"
|
|
11
|
+
import { whileStoppable } from "./stopped.ts"
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* After an Atlas call that ran no file and said nothing, once this apply ran `applied`: done once
|
|
@@ -29,27 +29,17 @@ async function ranNothing(
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* runs a child's step before this
|
|
34
|
-
* files it records and which it does not, printed; no further call starts, and the process then ends
|
|
35
|
-
* by the signal
|
|
32
|
+
* The approved files the history records and those it does not, read once Atlas's call ended after a
|
|
33
|
+
* stop signal (cleanup.ts runs a child's step before this apply's): what stopped.ts reports
|
|
36
34
|
*/
|
|
37
|
-
const
|
|
35
|
+
const found =
|
|
38
36
|
(options: AtlasCommand, copy: string, { approved, foreign }: { approved: ReadonlySet<string>; foreign: Foreign }) =>
|
|
39
|
-
async (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[...approved].filter((name) => left.has(name)),
|
|
46
|
-
]
|
|
47
|
-
options.print(
|
|
48
|
-
`Stopped by ${signal}: applied ${ran.join(", ") || "none"}; not applied ${not.join(", ") || "none"}`,
|
|
49
|
-
)
|
|
50
|
-
} catch (error) {
|
|
51
|
-
// reason: a history read that fails must not hide the stop: the line says what comb could not read, and the process still ends by the signal
|
|
52
|
-
options.print(`Stopped by ${signal}: comb could not read what was applied: ${(error as Error).message}`)
|
|
37
|
+
async (): Promise<{ applied: string[]; pending: string[] }> => {
|
|
38
|
+
const { pending } = ownHistory(await atlasStatus(options, copy), foreign, copy)
|
|
39
|
+
const left = new Set(pending.map((file) => file.Name))
|
|
40
|
+
return {
|
|
41
|
+
applied: [...approved].filter((name) => !left.has(name)),
|
|
42
|
+
pending: [...approved].filter((name) => left.has(name)),
|
|
53
43
|
}
|
|
54
44
|
}
|
|
55
45
|
|
|
@@ -67,12 +57,11 @@ async function applyRuns(
|
|
|
67
57
|
{ foreign, report, status }: { foreign: Foreign; report: Parameters<typeof checkHistory>[2]; status: AtlasStatus },
|
|
68
58
|
): Promise<string[]> {
|
|
69
59
|
const approved = new Set(status.pending.map((file) => file.Name))
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
60
|
+
/* no further call starts, and the process then ends by the signal, after the report: a stop parks this loop */
|
|
61
|
+
return whileStoppable(
|
|
62
|
+
{ names: [...approved], print: options.print, read: found(options, copy, { approved, foreign }) },
|
|
63
|
+
() => callEach(options, copy, { approved, foreign, report }),
|
|
64
|
+
)
|
|
76
65
|
}
|
|
77
66
|
|
|
78
67
|
/** applyRuns' calls, each of the first file still pending, until every approved file ran */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* comb's one cleanup rule: what a run makes outside itself (a scratch
|
|
3
|
-
* directory, a replay database) is held here until the run lets it go, and
|
|
3
|
+
* directory, a replay database) is held here until the run lets it go, and a stop signal runs
|
|
4
4
|
* every step still held before the process ends as the signal would. A scratch directory is named
|
|
5
5
|
* for the process that made it, so a later run removes one a killed process left at once, by
|
|
6
6
|
* whether that process still runs.
|
|
@@ -11,14 +11,25 @@ import fs from "node:fs"
|
|
|
11
11
|
import os from "node:os"
|
|
12
12
|
import path from "node:path"
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* A step a signal runs, told which signal came and what the steps that ran before it returned, in
|
|
16
|
+
* that order: a step held first runs last, and reads what the steps held after it found out
|
|
17
|
+
*/
|
|
18
|
+
type Step = (signal: NodeJS.Signals, earlier: readonly unknown[]) => unknown
|
|
16
19
|
|
|
17
20
|
// per-process-constant: what this process holds now
|
|
18
21
|
const held = /* @__PURE__ */ new Set<Step>()
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The signals a held step runs on: every one whose default action ends the process and that a
|
|
25
|
+
* terminal or a supervisor sends. A Ctrl-C (SIGINT), a Ctrl-\ (SIGQUIT), a container stop or a CI
|
|
26
|
+
* step timeout (SIGTERM), and a terminal's hangup, as an SSH drop sends it (SIGHUP): a child in a
|
|
27
|
+
* group of its own gets none of them from the terminal, so comb's forward is its only signal.
|
|
28
|
+
*/
|
|
29
|
+
export const STOP_SIGNALS = ["SIGHUP", "SIGINT", "SIGQUIT", "SIGTERM"] as const
|
|
30
|
+
|
|
31
|
+
/** One of STOP_SIGNALS */
|
|
32
|
+
export type StopSignal = (typeof STOP_SIGNALS)[number]
|
|
22
33
|
|
|
23
34
|
// per-process-constant: the steps a signal is running, so a step's own work goes on while what it stops waits
|
|
24
35
|
const inStep = /* @__PURE__ */ new AsyncLocalStorage<true>()
|
|
@@ -45,9 +56,10 @@ function newGate(): Gate {
|
|
|
45
56
|
|
|
46
57
|
/** Every step, last held first, each once the one before ends: a child stops before the scratch it used goes */
|
|
47
58
|
async function runSteps(steps: readonly Step[], signal: NodeJS.Signals): Promise<void> {
|
|
59
|
+
const returned: unknown[] = []
|
|
48
60
|
for (const step of steps.toReversed()) {
|
|
49
61
|
try {
|
|
50
|
-
await step(signal)
|
|
62
|
+
returned.push(await step(signal, [...returned]))
|
|
51
63
|
} catch {
|
|
52
64
|
// reason: one step that fails must not keep the rest from cleaning up; the process ends either way
|
|
53
65
|
}
|
|
@@ -77,7 +89,7 @@ function stop(signal: NodeJS.Signals): void {
|
|
|
77
89
|
gate = undefined
|
|
78
90
|
if (process.listenerCount(signal) > 1) return current.let()
|
|
79
91
|
/* both gone before the signal comes again, so it ends the process, and what waits for this stop waits on */
|
|
80
|
-
for (const each of
|
|
92
|
+
for (const each of STOP_SIGNALS) process.off(each, stop)
|
|
81
93
|
process.kill(process.pid, signal)
|
|
82
94
|
})
|
|
83
95
|
}
|
|
@@ -101,11 +113,11 @@ export function pastStop(): Promise<void> {
|
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
/**
|
|
104
|
-
* Hold `step` until the release this returns, so
|
|
116
|
+
* Hold `step` until the release this returns, so a stop signal runs it before the process ends.
|
|
105
117
|
* The listener is read from the process, not remembered: a host that removed every listener gets it back.
|
|
106
118
|
*/
|
|
107
119
|
export function hold(step: Step): () => void {
|
|
108
|
-
for (const signal of
|
|
120
|
+
for (const signal of STOP_SIGNALS) if (!process.listeners(signal).includes(stop)) process.on(signal, stop)
|
|
109
121
|
held.add(step)
|
|
110
122
|
return () => void held.delete(step)
|
|
111
123
|
}
|
|
@@ -199,7 +211,7 @@ export type Scratch = { dir: string; remove(): void }
|
|
|
199
211
|
|
|
200
212
|
/**
|
|
201
213
|
* A new directory under the temp directory that only this user can open (0700), named `prefix`, this
|
|
202
|
-
* process's pid and host mark, and removed by `remove`, or by
|
|
214
|
+
* process's pid and host mark, and removed by `remove`, or by a stop signal before it. A
|
|
203
215
|
* process's first scratch of a prefix sweeps what killed runs left.
|
|
204
216
|
*/
|
|
205
217
|
export function scratchDir(prefix: string): Scratch {
|