@oimlsmart/platform-server 0.2.6 → 0.2.7
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.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- The per-store journal high-water (the ServerStore seam's
|
|
2
|
+
-- latestChangeSeqFor(store), the 2026-09-07 performance audit's G1):
|
|
3
|
+
-- the seam's DOC-ONLY contract since 0.2.2 ("the (store, seq) walk —
|
|
4
|
+
-- the index rides the same commit") lands. The read answers MAX(seq)
|
|
5
|
+
-- over one store's slice of the ONE global journal — the seq stays
|
|
6
|
+
-- global and monotone, this is a projection, never a second sequence.
|
|
7
|
+
-- Without the index the projection scans the journal's rowid walk and
|
|
8
|
+
-- filters; with it the read is a single indexed probe, which is the
|
|
9
|
+
-- contract's whole point (per-store conditional revalidation / SSE
|
|
10
|
+
-- resume probes paying one read, never a payload fetch).
|
|
11
|
+
--
|
|
12
|
+
-- CREATE INDEX IF NOT EXISTS is the idempotent guard (the migration
|
|
13
|
+
-- contract's expand-only discipline; a consumer that already carries
|
|
14
|
+
-- the index converges). schema.sql's mirror lands in the same commit.
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_entity_changes_store_seq ON entity_changes (store, seq);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oimlsmart/platform-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "The OIML SMART platform server kernel: the store seam (ServerStore + the D1 and SQLite implementations), the canonical D1 migration set both deployments apply, the instance profile, the mailer, the RBAC map, the OIDC/OAuth client cones, and the shared role/permission vocabulary. Consumed by the smart monorepo (browser/) and the identity service.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/store/d1.ts
CHANGED
|
@@ -3480,6 +3480,16 @@ export class D1ServerStore implements ServerStore {
|
|
|
3480
3480
|
return row?.seq ?? 0
|
|
3481
3481
|
}
|
|
3482
3482
|
|
|
3483
|
+
async latestChangeSeqFor(store: string): Promise<number> {
|
|
3484
|
+
// One indexed probe over idx_entity_changes_store_seq (migration
|
|
3485
|
+
// 0026) — the per-store projection of the one journal; 0 when the
|
|
3486
|
+
// store carries no rows (MAX answers NULL on the empty set).
|
|
3487
|
+
const row = await this.stmt(
|
|
3488
|
+
'SELECT MAX(seq) AS seq FROM entity_changes WHERE store = ?', store,
|
|
3489
|
+
).first<{ seq: number | null }>()
|
|
3490
|
+
return row?.seq ?? 0
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3483
3493
|
// ── the platform event store (TODO.notify/01) ─────────────────────
|
|
3484
3494
|
// The event rows port directly (D1 is SQLite) — the same statements
|
|
3485
3495
|
// as sqlite/events.ts's sync half.
|
|
@@ -74,3 +74,14 @@ export function latestChangeSeq(): number {
|
|
|
74
74
|
const row = getDb().prepare('SELECT MAX(seq) AS seq FROM entity_changes').get() as { seq: number | null }
|
|
75
75
|
return row.seq ?? 0
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/** The per-store projection of the one journal (the seam's contract):
|
|
79
|
+
* MAX(seq) over the store's own slice, 0 on a store with no writes.
|
|
80
|
+
* Walks idx_entity_changes_store_seq — one indexed probe, never a
|
|
81
|
+
* journal scan. */
|
|
82
|
+
export function latestChangeSeqFor(store: string): number {
|
|
83
|
+
const row = getDb()
|
|
84
|
+
.prepare('SELECT MAX(seq) AS seq FROM entity_changes WHERE store = ?')
|
|
85
|
+
.get(store) as { seq: number | null }
|
|
86
|
+
return row.seq ?? 0
|
|
87
|
+
}
|
|
@@ -132,6 +132,12 @@ CREATE TABLE IF NOT EXISTS entity_changes (
|
|
|
132
132
|
id TEXT NOT NULL,
|
|
133
133
|
at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
134
134
|
);
|
|
135
|
+
-- The per-store high-water's walk (migration 0026, the seam's
|
|
136
|
+
-- latestChangeSeqFor(store)): MAX(seq) over one store's slice of the
|
|
137
|
+
-- one global journal costs a single indexed probe, never a journal
|
|
138
|
+
-- scan. The seq stays global and monotone — a projection, never a
|
|
139
|
+
-- second sequence.
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_entity_changes_store_seq ON entity_changes (store, seq);
|
|
135
141
|
|
|
136
142
|
-- TODO.notify/01 — the platform event store (the notification system's
|
|
137
143
|
-- source of truth): one row per DECLARED notifiable act (the catalog,
|
package/src/store/sqlite.ts
CHANGED
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
deleteEntity,
|
|
85
85
|
getEntity,
|
|
86
86
|
latestChangeSeq,
|
|
87
|
+
latestChangeSeqFor,
|
|
87
88
|
listEntities,
|
|
88
89
|
putEntity,
|
|
89
90
|
} from './sqlite/entities'
|
|
@@ -1022,6 +1023,9 @@ export function createSqliteServerStore(): ServerStore {
|
|
|
1022
1023
|
async latestChangeSeq(): Promise<number> {
|
|
1023
1024
|
return latestChangeSeq()
|
|
1024
1025
|
},
|
|
1026
|
+
async latestChangeSeqFor(store: string): Promise<number> {
|
|
1027
|
+
return latestChangeSeqFor(store)
|
|
1028
|
+
},
|
|
1025
1029
|
|
|
1026
1030
|
// ── the platform event store (TODO.notify/01) ──
|
|
1027
1031
|
async appendEvent(input: {
|
package/src/store.ts
CHANGED
|
@@ -2353,20 +2353,20 @@ export interface ServerStore {
|
|
|
2353
2353
|
changesAfter(seq: number, limit?: number): Promise<EntityChange[]>
|
|
2354
2354
|
/** The GLOBAL journal high-water: the bootstrap snapshot's ETag leg
|
|
2355
2355
|
* (any write anywhere bumps the one seq — conservative, never
|
|
2356
|
-
* stale).
|
|
2357
|
-
*
|
|
2358
|
-
* NAMED FOLLOW-UP, DOC ONLY (the smart side references it from
|
|
2359
|
-
* server/routes/bootstrap.ts: the per-store `seqs` snapshot carries
|
|
2360
|
-
* the journal position a delta-loading client would resume from):
|
|
2361
|
-
* the PER-STORE high-water — `latestChangeSeqFor(store)` — answering
|
|
2362
|
-
* MAX(seq) over one store's slice of the SAME journal. The contract
|
|
2363
|
-
* when it lands: the seq stays global and monotone (the per-store
|
|
2364
|
-
* read is a PROJECTION of the one journal, never a second
|
|
2365
|
-
* sequence); it exists so a per-store conditional revalidation /
|
|
2366
|
-
* SSE resume probe costs one indexed read instead of a payload
|
|
2367
|
-
* fetch; and its query wants the (store, seq) walk — the index
|
|
2368
|
-
* rides the same commit, expand-only per the migration contract. */
|
|
2356
|
+
* stale). */
|
|
2369
2357
|
latestChangeSeq(): Promise<number>
|
|
2358
|
+
/** The PER-STORE high-water: MAX(seq) over one store's slice of the
|
|
2359
|
+
* SAME journal. The seq stays global and monotone — the per-store
|
|
2360
|
+
* read is a PROJECTION of the one journal, never a second sequence.
|
|
2361
|
+
* A store with no writes answers 0 (the empty journal's floor, the
|
|
2362
|
+
* same as the global form's). The read is one indexed probe over
|
|
2363
|
+
* idx_entity_changes_store_seq (migration 0026 — the (store, seq)
|
|
2364
|
+
* walk rode that commit, expand-only per the migration contract),
|
|
2365
|
+
* so a per-store conditional revalidation / SSE resume probe never
|
|
2366
|
+
* pays a journal scan. Landed for the 2026-09-07 performance
|
|
2367
|
+
* audit's G1: the smart side's bootstrap composite ETag switches
|
|
2368
|
+
* from the global seq to the per-set composite of these. */
|
|
2369
|
+
latestChangeSeqFor(store: string): Promise<number>
|
|
2370
2370
|
|
|
2371
2371
|
// ── the platform event store (TODO.notify/01) ──
|
|
2372
2372
|
/** Append one declared event (the emitter's write; one row inside the
|