@boxxy/store-sqlite 0.1.0 → 0.1.3
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 +24 -3
- package/dist/codecs/parse-json-text.d.ts +1 -0
- package/dist/codecs/parse-store-row.d.ts +2 -0
- package/dist/codecs/table-codecs.d.ts +10 -0
- package/dist/codecs/to-column-name.d.ts +1 -0
- package/dist/codecs/to-store-columns.d.ts +3 -0
- package/dist/constants.d.ts +1 -0
- package/dist/create-sqlite-claim-store.d.ts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/migrations/create-tables-migration.d.ts +2 -0
- package/dist/migrations/rename-relations-migration.d.ts +2 -0
- package/dist/migrations/run-store-migrations.d.ts +3 -0
- package/dist/migrations/v1-tables-migration.d.ts +2 -0
- package/dist/reads/build-sqlite-reader.d.ts +4 -0
- package/dist/reads/is-latest-of-session.d.ts +3 -0
- package/dist/reads/read-item-views.d.ts +5 -0
- package/dist/reads/read-limit-fills.d.ts +4 -0
- package/dist/reads/read-wakes.d.ts +4 -0
- package/dist/test-utils/create-claim-bench.d.ts +8 -0
- package/dist/test-utils/create-test-claim-store.d.ts +2 -0
- package/dist/types.d.ts +172 -0
- package/dist/with-busy-retry.d.ts +1 -0
- package/dist/with-transaction.d.ts +3 -0
- package/dist/writes/apply-sqlite-change.d.ts +4 -0
- package/dist/writes/create-sqlite-tx.d.ts +4 -0
- package/dist/writes/take-write-lock.d.ts +3 -0
- package/package.json +15 -1
- package/src/codecs/parse-json-text.ts +5 -0
- package/src/codecs/parse-store-row.ts +36 -0
- package/src/codecs/table-codecs.ts +148 -0
- package/src/codecs/to-column-name.ts +3 -0
- package/src/codecs/to-store-columns.ts +44 -0
- package/src/constants.ts +1 -0
- package/src/create-sqlite-claim-store.ts +114 -0
- package/src/index.ts +1 -1
- package/src/migrations/create-tables-migration.ts +146 -0
- package/src/migrations/rename-relations-migration.ts +17 -0
- package/src/migrations/run-store-migrations.ts +42 -0
- package/src/migrations/v1-tables-migration.ts +184 -0
- package/src/reads/build-sqlite-reader.ts +484 -0
- package/src/reads/is-latest-of-session.ts +21 -0
- package/src/reads/read-item-views.ts +92 -0
- package/src/reads/read-limit-fills.ts +32 -0
- package/src/reads/read-wakes.ts +34 -0
- package/src/test-utils/create-claim-bench.ts +66 -0
- package/src/test-utils/create-test-claim-store.ts +23 -0
- package/src/types.ts +173 -0
- package/src/with-busy-retry.ts +32 -0
- package/src/with-transaction.ts +29 -0
- package/src/writes/apply-sqlite-change.ts +223 -0
- package/src/writes/create-sqlite-tx.ts +229 -0
- package/src/writes/take-write-lock.ts +8 -0
- package/dist/package-name.d.ts +0 -1
- package/src/package-name.ts +0 -1
package/README.md
CHANGED
|
@@ -5,12 +5,17 @@
|
|
|
5
5
|
<a href="https://www.npmjs.com/package/@boxxy/store-sqlite"><img src="https://img.shields.io/npm/v/@boxxy/store-sqlite" alt="npm version"></a>
|
|
6
6
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
7
7
|
</p>
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
<a href="../../docs/design/boxxy/README.md">Documentation</a>
|
|
11
|
+
</p>
|
|
8
12
|
</div>
|
|
9
13
|
|
|
10
|
-
boxxy's store on SQLite: the schema, the migrations and the transactions
|
|
14
|
+
boxxy's store on SQLite: the schema, the migrations and the transactions behind the `ClaimStore`
|
|
15
|
+
port.
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
`createSQLiteClaimStore(path)` opens one SQLite file, creates it when it's missing, runs the
|
|
18
|
+
migrations the file hasn't run, and returns the store with a `close` function.
|
|
14
19
|
|
|
15
20
|
## Install
|
|
16
21
|
|
|
@@ -21,3 +26,19 @@ bun add @boxxy/store-sqlite
|
|
|
21
26
|
```
|
|
22
27
|
|
|
23
28
|
The package runs on [Bun](https://bun.sh) 1.3 or newer.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Items, levers, attempts, leases, limit seats, notes, notices and the outbox in one file, with each
|
|
33
|
+
change applied whole or not at all
|
|
34
|
+
([one write path](../../docs/design/boxxy/slice-1.md#one-write-path)).
|
|
35
|
+
- `exclusive` calls that run one at a time in a `BEGIN IMMEDIATE` transaction, and `read` calls that
|
|
36
|
+
see the last commit ([store rules](../../docs/design/boxxy/slice-1.md#store-rules)).
|
|
37
|
+
- Each item's position and state derived on each read, from the rows and the loaded rules
|
|
38
|
+
([v1](../../docs/design/boxxy/v1.md)).
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
The [v1 tables migration](./src/migrations/v1-tables-migration.ts) creates each table, and the store
|
|
43
|
+
section of [`02-types.ts`](../../docs/design/boxxy/sketches/v1/core/02-types.ts) gives the port, the
|
|
44
|
+
seat take and the seat sync.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseJSONText(text: string): unknown;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TableName, Tables } from '@boxxy/model';
|
|
2
|
+
interface TableCodec<Row> {
|
|
3
|
+
readonly fields: readonly (keyof Row & string)[];
|
|
4
|
+
readonly json: readonly (keyof Row & string)[];
|
|
5
|
+
readonly flags: readonly (keyof Row & string)[];
|
|
6
|
+
}
|
|
7
|
+
export declare const tableCodecs: {
|
|
8
|
+
readonly [T in TableName]: TableCodec<Tables[T]>;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toColumnName(field: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const lockWaitLimitMs = 5000;
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createSQLiteClaimStore, type SQLiteClaimStore } from './create-sqlite-claim-store.ts';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Instant, ItemRules, ItemWithLevers } from '@boxxy/model';
|
|
2
|
+
import type { Expression, ExpressionBuilder, Kysely, SqlBool } from 'kysely';
|
|
3
|
+
import type { StoreDatabase } from '../types.ts';
|
|
4
|
+
export type ItemScope = (builder: ExpressionBuilder<StoreDatabase, 'items'>) => Expression<SqlBool>;
|
|
5
|
+
export declare function readItemViews(db: Kysely<StoreDatabase>, scope: ItemScope, now: Instant, rules: ItemRules): Promise<ItemWithLevers[]>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ItemId } from '@boxxy/model';
|
|
2
|
+
export interface ClaimBenchOptions {
|
|
3
|
+
readonly itemCount: number;
|
|
4
|
+
}
|
|
5
|
+
export interface ClaimBench extends AsyncDisposable {
|
|
6
|
+
readonly claim: () => Promise<ItemId>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createClaimBench(options: ClaimBenchOptions): Promise<ClaimBench>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Generated } from 'kysely';
|
|
2
|
+
export interface StoreDatabase {
|
|
3
|
+
items: {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
repo: string;
|
|
7
|
+
title: string;
|
|
8
|
+
outcome: string;
|
|
9
|
+
acceptance: string;
|
|
10
|
+
fields: string;
|
|
11
|
+
labels: string;
|
|
12
|
+
found_in_attempt: string | null;
|
|
13
|
+
filed_by: string;
|
|
14
|
+
filed_at: number;
|
|
15
|
+
closed_at: number | null;
|
|
16
|
+
close_reason: string | null;
|
|
17
|
+
kept_session: string | null;
|
|
18
|
+
};
|
|
19
|
+
relations: {
|
|
20
|
+
source: string;
|
|
21
|
+
target: string;
|
|
22
|
+
type: string;
|
|
23
|
+
added_by: string;
|
|
24
|
+
added_at: number;
|
|
25
|
+
removed_at: number | null;
|
|
26
|
+
};
|
|
27
|
+
levers: {
|
|
28
|
+
seq: number;
|
|
29
|
+
kind: string;
|
|
30
|
+
item: string;
|
|
31
|
+
until: number | null;
|
|
32
|
+
expires_at: number | null;
|
|
33
|
+
reason: string | null;
|
|
34
|
+
phase: string | null;
|
|
35
|
+
actor: string;
|
|
36
|
+
at: number;
|
|
37
|
+
};
|
|
38
|
+
attempts: {
|
|
39
|
+
id: string;
|
|
40
|
+
item: string;
|
|
41
|
+
phase: string;
|
|
42
|
+
number: number;
|
|
43
|
+
runner: string;
|
|
44
|
+
recipe_hash: string;
|
|
45
|
+
rules_version: string;
|
|
46
|
+
pool: string;
|
|
47
|
+
lease_gen: number;
|
|
48
|
+
spawn_request: string;
|
|
49
|
+
spawn_request_hash: string;
|
|
50
|
+
state: string;
|
|
51
|
+
failure: string | null;
|
|
52
|
+
atc_session: string | null;
|
|
53
|
+
caller: string | null;
|
|
54
|
+
claimed_at: number;
|
|
55
|
+
started_at: number | null;
|
|
56
|
+
ended_at: number | null;
|
|
57
|
+
fields: string;
|
|
58
|
+
mode: string;
|
|
59
|
+
round: number;
|
|
60
|
+
input: string | null;
|
|
61
|
+
hold_gen: number;
|
|
62
|
+
close_out: string | null;
|
|
63
|
+
close_out_hash: string | null;
|
|
64
|
+
close_out_fact: string | null;
|
|
65
|
+
closing: string | null;
|
|
66
|
+
closing_at: number | null;
|
|
67
|
+
suspend_requested_at: number | null;
|
|
68
|
+
slept_at: number | null;
|
|
69
|
+
wake_requested_at: number | null;
|
|
70
|
+
woken_at: number | null;
|
|
71
|
+
outcome: string | null;
|
|
72
|
+
returned_outcome: string | null;
|
|
73
|
+
strayed_at: number | null;
|
|
74
|
+
session_stopped_at: number | null;
|
|
75
|
+
session_forgotten_at: number | null;
|
|
76
|
+
};
|
|
77
|
+
attempt_sessions: {
|
|
78
|
+
attempt: string;
|
|
79
|
+
agent_session: string;
|
|
80
|
+
atc_session: string;
|
|
81
|
+
product: string;
|
|
82
|
+
source: string;
|
|
83
|
+
seen_at: number;
|
|
84
|
+
};
|
|
85
|
+
leases: {
|
|
86
|
+
item: string;
|
|
87
|
+
phase: string;
|
|
88
|
+
attempt: string;
|
|
89
|
+
gen: number;
|
|
90
|
+
taken_at: number;
|
|
91
|
+
released_at: number | null;
|
|
92
|
+
};
|
|
93
|
+
limits: {
|
|
94
|
+
name: string;
|
|
95
|
+
max: number;
|
|
96
|
+
counts: string;
|
|
97
|
+
};
|
|
98
|
+
limit_seats: {
|
|
99
|
+
limit: string;
|
|
100
|
+
attempt: string;
|
|
101
|
+
hold_gen: number;
|
|
102
|
+
over_cap: number;
|
|
103
|
+
taken_at: number;
|
|
104
|
+
released_at: number | null;
|
|
105
|
+
};
|
|
106
|
+
decisions: {
|
|
107
|
+
id: string;
|
|
108
|
+
attempt: string;
|
|
109
|
+
item: string;
|
|
110
|
+
kind: string;
|
|
111
|
+
pool: string;
|
|
112
|
+
rules_version: string;
|
|
113
|
+
snapshot_hash: string;
|
|
114
|
+
store_version: number;
|
|
115
|
+
pepys_offsets: string;
|
|
116
|
+
faults: string;
|
|
117
|
+
limits: string;
|
|
118
|
+
at: number;
|
|
119
|
+
};
|
|
120
|
+
evidence: {
|
|
121
|
+
fact_id: string;
|
|
122
|
+
attempt: string;
|
|
123
|
+
kind: string;
|
|
124
|
+
ref: string;
|
|
125
|
+
observed: string | null;
|
|
126
|
+
source: string;
|
|
127
|
+
at: number;
|
|
128
|
+
};
|
|
129
|
+
pull_requests: {
|
|
130
|
+
repo: string;
|
|
131
|
+
number: number;
|
|
132
|
+
attempt: string;
|
|
133
|
+
github_id: number;
|
|
134
|
+
url: string;
|
|
135
|
+
state: string;
|
|
136
|
+
merged_at: number | null;
|
|
137
|
+
merge_commit: string | null;
|
|
138
|
+
checked_at: number;
|
|
139
|
+
};
|
|
140
|
+
runners: {
|
|
141
|
+
id: string;
|
|
142
|
+
heartbeat_at: number;
|
|
143
|
+
paused: number;
|
|
144
|
+
};
|
|
145
|
+
notes: {
|
|
146
|
+
item: string;
|
|
147
|
+
seq: number;
|
|
148
|
+
text: string;
|
|
149
|
+
by: string;
|
|
150
|
+
at: number;
|
|
151
|
+
};
|
|
152
|
+
notices: {
|
|
153
|
+
id: Generated<number>;
|
|
154
|
+
item: string;
|
|
155
|
+
reason: string;
|
|
156
|
+
at: number;
|
|
157
|
+
tries: number;
|
|
158
|
+
sent_at: number | null;
|
|
159
|
+
};
|
|
160
|
+
outbox: {
|
|
161
|
+
seq: Generated<number>;
|
|
162
|
+
fact_id: string;
|
|
163
|
+
type: string;
|
|
164
|
+
entity: string;
|
|
165
|
+
actor: string;
|
|
166
|
+
at: number;
|
|
167
|
+
payload: string;
|
|
168
|
+
sent_at: number | null;
|
|
169
|
+
parked_at: number | null;
|
|
170
|
+
parked_reason: string | null;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withBusyRetry(waitLimitMs: number, work: () => unknown): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boxxy/store-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "boxxy's store on SQLite",
|
|
5
5
|
"homepage": "https://github.com/zgeoff/boxxy/tree/main/adapters/store-sqlite#readme",
|
|
6
6
|
"bugs": "https://github.com/zgeoff/boxxy/issues",
|
|
@@ -26,9 +26,23 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
+
"bench": "rig bench",
|
|
29
30
|
"build": "rm -rf dist && tsrs -p tsconfig.build.json",
|
|
30
31
|
"typecheck": "tsrs --noEmit"
|
|
31
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@boxxy/content-hash": "0.1.3",
|
|
35
|
+
"@boxxy/model": "0.1.3",
|
|
36
|
+
"@zgeoff/kysely-bun-sqlite": "0.3.2",
|
|
37
|
+
"kysely": "0.29.8",
|
|
38
|
+
"tiny-invariant": "1.3.3",
|
|
39
|
+
"ts-pattern": "5.9.2",
|
|
40
|
+
"zod": "4.6.7"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@boxxy/dispatch": "0.1.3",
|
|
44
|
+
"@zgeoff/rig": "0.2.3"
|
|
45
|
+
},
|
|
32
46
|
"engines": {
|
|
33
47
|
"bun": ">=1.3",
|
|
34
48
|
"node": ">=24"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { rowSchemas } from '@boxxy/model';
|
|
2
|
+
import type { TableName, Tables } from '@boxxy/model';
|
|
3
|
+
import { tableCodecs } from './table-codecs.ts';
|
|
4
|
+
import { toColumnName } from './to-column-name.ts';
|
|
5
|
+
|
|
6
|
+
export function parseStoreRow<T extends TableName>(table: T, record: object): Tables[T] {
|
|
7
|
+
const codec = tableCodecs[table];
|
|
8
|
+
|
|
9
|
+
const columns = new Map<string, unknown>(Object.entries(record));
|
|
10
|
+
const json = new Set<string>(codec.json);
|
|
11
|
+
const flags = new Set<string>(codec.flags);
|
|
12
|
+
|
|
13
|
+
const fields = Object.fromEntries(
|
|
14
|
+
codec.fields.map((field) => {
|
|
15
|
+
const value = columns.get(toColumnName(field));
|
|
16
|
+
|
|
17
|
+
return [field, parseStoreValue(value, json.has(field), flags.has(field))];
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return rowSchemas[table].parse(fields);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseStoreValue(value: unknown, isJSON: boolean, isFlag: boolean): unknown {
|
|
25
|
+
if (isJSON && typeof value === 'string') {
|
|
26
|
+
const parsed: unknown = JSON.parse(value);
|
|
27
|
+
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isFlag) {
|
|
32
|
+
return value === 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { TableName, Tables } from '@boxxy/model';
|
|
2
|
+
|
|
3
|
+
interface TableCodec<Row> {
|
|
4
|
+
readonly fields: readonly (keyof Row & string)[];
|
|
5
|
+
|
|
6
|
+
readonly json: readonly (keyof Row & string)[];
|
|
7
|
+
|
|
8
|
+
readonly flags: readonly (keyof Row & string)[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const tableCodecs: { readonly [T in TableName]: TableCodec<Tables[T]> } = {
|
|
12
|
+
items: {
|
|
13
|
+
fields: [
|
|
14
|
+
'id',
|
|
15
|
+
'kind',
|
|
16
|
+
'repo',
|
|
17
|
+
'title',
|
|
18
|
+
'outcome',
|
|
19
|
+
'acceptance',
|
|
20
|
+
'fields',
|
|
21
|
+
'labels',
|
|
22
|
+
'foundInAttempt',
|
|
23
|
+
'filedBy',
|
|
24
|
+
'filedAt',
|
|
25
|
+
'closedAt',
|
|
26
|
+
'closeReason',
|
|
27
|
+
'keptSession',
|
|
28
|
+
],
|
|
29
|
+
json: ['acceptance', 'fields', 'labels', 'keptSession'],
|
|
30
|
+
flags: [],
|
|
31
|
+
},
|
|
32
|
+
relations: {
|
|
33
|
+
fields: ['source', 'target', 'type', 'addedBy', 'addedAt', 'removedAt'],
|
|
34
|
+
json: [],
|
|
35
|
+
flags: [],
|
|
36
|
+
},
|
|
37
|
+
levers: {
|
|
38
|
+
fields: ['seq', 'kind', 'item', 'until', 'expiresAt', 'reason', 'phase', 'actor', 'at'],
|
|
39
|
+
json: [],
|
|
40
|
+
flags: [],
|
|
41
|
+
},
|
|
42
|
+
attempts: {
|
|
43
|
+
fields: [
|
|
44
|
+
'id',
|
|
45
|
+
'item',
|
|
46
|
+
'phase',
|
|
47
|
+
'number',
|
|
48
|
+
'runner',
|
|
49
|
+
'recipeHash',
|
|
50
|
+
'rulesVersion',
|
|
51
|
+
'pool',
|
|
52
|
+
'leaseGen',
|
|
53
|
+
'spawnRequest',
|
|
54
|
+
'spawnRequestHash',
|
|
55
|
+
'state',
|
|
56
|
+
'failure',
|
|
57
|
+
'atcSession',
|
|
58
|
+
'caller',
|
|
59
|
+
'claimedAt',
|
|
60
|
+
'startedAt',
|
|
61
|
+
'endedAt',
|
|
62
|
+
'fields',
|
|
63
|
+
'mode',
|
|
64
|
+
'round',
|
|
65
|
+
'input',
|
|
66
|
+
'holdGen',
|
|
67
|
+
'closeOut',
|
|
68
|
+
'closeOutHash',
|
|
69
|
+
'closeOutFact',
|
|
70
|
+
'closing',
|
|
71
|
+
'closingAt',
|
|
72
|
+
'suspendRequestedAt',
|
|
73
|
+
'sleptAt',
|
|
74
|
+
'wakeRequestedAt',
|
|
75
|
+
'wokenAt',
|
|
76
|
+
'outcome',
|
|
77
|
+
'returnedOutcome',
|
|
78
|
+
'strayedAt',
|
|
79
|
+
'sessionStoppedAt',
|
|
80
|
+
'sessionForgottenAt',
|
|
81
|
+
],
|
|
82
|
+
json: ['spawnRequest', 'caller', 'fields', 'input', 'closeOut'],
|
|
83
|
+
flags: [],
|
|
84
|
+
},
|
|
85
|
+
attempt_sessions: {
|
|
86
|
+
fields: ['attempt', 'agentSession', 'atcSession', 'product', 'source', 'seenAt'],
|
|
87
|
+
json: [],
|
|
88
|
+
flags: [],
|
|
89
|
+
},
|
|
90
|
+
leases: {
|
|
91
|
+
fields: ['item', 'phase', 'attempt', 'gen', 'takenAt', 'releasedAt'],
|
|
92
|
+
json: [],
|
|
93
|
+
flags: [],
|
|
94
|
+
},
|
|
95
|
+
decisions: {
|
|
96
|
+
fields: [
|
|
97
|
+
'id',
|
|
98
|
+
'attempt',
|
|
99
|
+
'item',
|
|
100
|
+
'kind',
|
|
101
|
+
'pool',
|
|
102
|
+
'rulesVersion',
|
|
103
|
+
'snapshotHash',
|
|
104
|
+
'storeVersion',
|
|
105
|
+
'pepysOffsets',
|
|
106
|
+
'faults',
|
|
107
|
+
'limits',
|
|
108
|
+
'at',
|
|
109
|
+
],
|
|
110
|
+
json: ['pepysOffsets', 'faults', 'limits'],
|
|
111
|
+
flags: [],
|
|
112
|
+
},
|
|
113
|
+
evidence: {
|
|
114
|
+
fields: ['factId', 'attempt', 'kind', 'ref', 'observed', 'source', 'at'],
|
|
115
|
+
json: [],
|
|
116
|
+
flags: [],
|
|
117
|
+
},
|
|
118
|
+
pull_requests: {
|
|
119
|
+
fields: [
|
|
120
|
+
'repo',
|
|
121
|
+
'number',
|
|
122
|
+
'attempt',
|
|
123
|
+
'githubId',
|
|
124
|
+
'url',
|
|
125
|
+
'state',
|
|
126
|
+
'mergedAt',
|
|
127
|
+
'mergeCommit',
|
|
128
|
+
'checkedAt',
|
|
129
|
+
],
|
|
130
|
+
json: [],
|
|
131
|
+
flags: [],
|
|
132
|
+
},
|
|
133
|
+
runners: {
|
|
134
|
+
fields: ['id', 'heartbeatAt', 'paused'],
|
|
135
|
+
json: [],
|
|
136
|
+
flags: ['paused'],
|
|
137
|
+
},
|
|
138
|
+
notes: {
|
|
139
|
+
fields: ['item', 'seq', 'text', 'by', 'at'],
|
|
140
|
+
json: [],
|
|
141
|
+
flags: [],
|
|
142
|
+
},
|
|
143
|
+
notices: {
|
|
144
|
+
fields: ['id', 'item', 'reason', 'at', 'tries', 'sentAt'],
|
|
145
|
+
json: [],
|
|
146
|
+
flags: [],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { formatCanonicalJson } from '@boxxy/content-hash';
|
|
2
|
+
import { toJSONValue } from '@boxxy/model';
|
|
3
|
+
import type { TableName } from '@boxxy/model';
|
|
4
|
+
import invariant from 'tiny-invariant';
|
|
5
|
+
import { tableCodecs } from './table-codecs.ts';
|
|
6
|
+
import { toColumnName } from './to-column-name.ts';
|
|
7
|
+
|
|
8
|
+
export type StoreValue = string | number | null;
|
|
9
|
+
|
|
10
|
+
// a field outside the table's codec fails, so no caller's key reaches the SQL as a column name
|
|
11
|
+
export function toStoreColumns(table: TableName, fields: object): [string, StoreValue][] {
|
|
12
|
+
const codec = tableCodecs[table];
|
|
13
|
+
|
|
14
|
+
const known = new Set<string>(codec.fields);
|
|
15
|
+
const json = new Set<string>(codec.json);
|
|
16
|
+
|
|
17
|
+
return Object.entries(fields).map(([field, value]: [string, unknown]) => {
|
|
18
|
+
invariant(known.has(field), `${table} has no field ${field}`);
|
|
19
|
+
|
|
20
|
+
return [toColumnName(field), toStoreValue(value, json.has(field))];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toStoreValue(value: unknown, isJSON: boolean): StoreValue {
|
|
25
|
+
if (value === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isJSON) {
|
|
30
|
+
// an update compares the stored text, so two objects with their keys in another order match
|
|
31
|
+
return formatCanonicalJson(toJSONValue(value));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof value === 'boolean') {
|
|
35
|
+
return value ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
invariant(
|
|
39
|
+
typeof value === 'string' || typeof value === 'number',
|
|
40
|
+
`a ${typeof value} needs a JSON column`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return value;
|
|
44
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const lockWaitLimitMs = 5000;
|