@happyvertical/smrt-core 0.37.8 → 0.37.9

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.
Files changed (66) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/change-feed.d.ts +261 -0
  3. package/dist/change-feed.d.ts.map +1 -0
  4. package/dist/change-feed.js +482 -0
  5. package/dist/change-feed.js.map +1 -0
  6. package/dist/class.d.ts.map +1 -1
  7. package/dist/class.js +4 -2
  8. package/dist/class.js.map +1 -1
  9. package/dist/collection.js +2 -2
  10. package/dist/consumer-plugin/index.d.ts.map +1 -1
  11. package/dist/consumer-plugin/index.js +24 -19
  12. package/dist/consumer-plugin/index.js.map +1 -1
  13. package/dist/dispatch/bus.js +2 -2
  14. package/dist/dispatch/index.js +1 -1
  15. package/dist/generated-client-runtime.d.ts +26 -0
  16. package/dist/generated-client-runtime.d.ts.map +1 -0
  17. package/dist/generated-client-runtime.js +88 -0
  18. package/dist/generated-client-runtime.js.map +1 -0
  19. package/dist/generators/changes-route.d.ts +52 -0
  20. package/dist/generators/changes-route.d.ts.map +1 -0
  21. package/dist/generators/changes-route.js +111 -0
  22. package/dist/generators/changes-route.js.map +1 -0
  23. package/dist/generators/rest.d.ts +28 -0
  24. package/dist/generators/rest.d.ts.map +1 -1
  25. package/dist/generators/rest.js +53 -15
  26. package/dist/generators/rest.js.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +6 -5
  30. package/dist/manifest/static-manifest.js +2 -2
  31. package/dist/manifest/static-manifest.js.map +1 -1
  32. package/dist/manifest/store.js +1 -1
  33. package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
  34. package/dist/manifest/test-manifest-stub.js +502 -2
  35. package/dist/manifest/test-manifest-stub.js.map +1 -1
  36. package/dist/manifest.json +2 -2
  37. package/dist/object.js +2 -2
  38. package/dist/prebuild/index.d.ts.map +1 -1
  39. package/dist/prebuild/index.js +14 -7
  40. package/dist/prebuild/index.js.map +1 -1
  41. package/dist/smrt-knowledge.json +6 -6
  42. package/dist/system/schema.d.ts +24 -1
  43. package/dist/system/schema.d.ts.map +1 -1
  44. package/dist/system/schema.js +45 -3
  45. package/dist/system/schema.js.map +1 -1
  46. package/dist/testing/database.js +1 -1
  47. package/dist/vite-plugin/changes-route.d.ts +10 -0
  48. package/dist/vite-plugin/changes-route.d.ts.map +1 -0
  49. package/dist/vite-plugin/changes-route.js +174 -0
  50. package/dist/vite-plugin/changes-route.js.map +1 -0
  51. package/dist/vite-plugin/index.d.ts +7 -0
  52. package/dist/vite-plugin/index.d.ts.map +1 -1
  53. package/dist/vite-plugin/index.js +38 -20
  54. package/dist/vite-plugin/index.js.map +1 -1
  55. package/dist/vite-plugin/route-header.d.ts +10 -0
  56. package/dist/vite-plugin/route-header.d.ts.map +1 -0
  57. package/dist/vite-plugin/route-header.js +14 -0
  58. package/dist/vite-plugin/route-header.js.map +1 -0
  59. package/dist/vite-plugin/sveltekit-generator.d.ts +7 -0
  60. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  61. package/dist/vite-plugin/sveltekit-generator.js +5 -3
  62. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  63. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  64. package/dist/vite-plugin/sync-apply-route.js +2 -2
  65. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  66. package/package.json +4 -4
package/AGENTS.md CHANGED
@@ -73,6 +73,16 @@ admin auth.
73
73
  - Tables: `_smrt_dispatch`, `_smrt_dispatch_subscriptions`
74
74
  - Status: `pending → processing → completed` (or `failed`)
75
75
 
76
+ ## Change Feed (#1758)
77
+
78
+ Adapter-agnostic change-observation spine (`src/change-feed.ts`) — the server half of the client/mobile sync contract (PRD #1755):
79
+
80
+ - `_smrt_changes` system table: one append per framework save/delete via a GlobalInterceptors writer registered at framework init. Deletes are tombstones (`operation: 'delete'`). `_smrt_*` tables are skipped. Feed-append failures log and never fail the user's write. No dirty-check: a field-unchanged `.save()` appends a spurious `update` entry (diff-aware paths like `getOrUpsert`/sync-apply short-circuit before `save()` and append nothing); subscribers must tolerate spurious entries — they are convergent.
81
+ - Sequences: allocated as `MAX(seq)+1` inside the INSERT with conflict retry — committed rows stay contiguous, so commit order == seq order on SQLite/Postgres/DuckDB (deliberately NOT identity/serial: those allocate before commit and break the cursor guarantee under concurrent writers).
82
+ - `getChangesSince(db, { since, tables?, tenantId?, limit? }) → { changes, cursor, resyncRequired? }`: strictly monotonic cursor; polling with returned cursors misses no committed change and never repeats one. A cursor that cannot be served incrementally — pruned below the retained `[floor..horizon]` run, or foreign/ahead of the horizon — gets `resyncRequired: true` with empty `changes` and an unadvanced cursor; detection runs on the UNFILTERED log so `tables`/`tenantId` filters never trigger or mask it. `getTenantScopedChangesSince()` resolves tenant via the DispatchBus resolver hook (fail-closed: tenancy on + no context → global rows only; tenant `T` sees `T` + global rows, never another tenant).
83
+ - Generated `_changes` routes: REST (`GET {basePath}/_changes`, requires `authMiddleware`, otherwise 401 — per-model `api.public` does NOT apply) and SvelteKit (`{routesDir}/_changes/+server.ts`, requires an authenticated principal on `locals`; opt out via `sveltekit.changesRoute.enabled: false`). Query params: `since`, `tables` (comma-separated), `limit`. Responses stay HTTP 200 in the resync state — `resyncRequired` is protocol state, not an error.
84
+ - Retention: `pruneChangeFeed(db, { maxAgeMs?, maxRows? })` — schedule it. Pruning deletes oldest-first and always retains the newest entry (a non-empty feed is never emptied), which is what makes pruned-cursor detection provable and keeps caught-up consumers polling normally. Raw-SQL writes are invisible to the feed (same documented gap as the #1499 cache); `bumpChangeFeed(db, { table, rowId? })` is the manual escape hatch.
85
+
76
86
  ## Single Table Inheritance (STI)
77
87
 
78
88
  - Base: `@smrt({ tableStrategy: 'sti' })` — children inherit, share one table
@@ -0,0 +1,261 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ /** Name of the append-only change-feed system table. */
3
+ export declare const CHANGE_FEED_TABLE = "_smrt_changes";
4
+ /** Interceptor name of the framework's change-feed writer. */
5
+ export declare const CHANGE_FEED_INTERCEPTOR_NAME = "smrt-change-feed";
6
+ /**
7
+ * Change operations recorded in the feed. Deletes are tombstones —
8
+ * consumers can distinguish "row changed" from "row is gone" without
9
+ * consulting the source table.
10
+ */
11
+ export type ChangeOperation = 'create' | 'update' | 'delete';
12
+ /** One entry of the change feed. */
13
+ export interface ChangeFeedEntry {
14
+ /** Strictly monotonic per-database sequence (the cursor dimension). */
15
+ seq: number;
16
+ /** Physical table the change happened in (STI children report the shared base table). */
17
+ table: string;
18
+ /**
19
+ * Primary key of the changed row, or `null` for table-level synthetic
20
+ * bumps recorded via {@link bumpChangeFeed} without a row id.
21
+ */
22
+ rowId: string | null;
23
+ /** What happened. `'delete'` entries double as tombstones. */
24
+ operation: ChangeOperation;
25
+ /** Tenant the changed row belongs to, or `null` for global/non-tenant rows. */
26
+ tenantId: string | null;
27
+ /** ISO-8601 timestamp recorded when the change was appended. */
28
+ timestamp: string;
29
+ }
30
+ /** Options for {@link getChangesSince}. */
31
+ export interface GetChangesOptions {
32
+ /**
33
+ * Cursor to read after. Only rows with `seq` strictly greater than `since`
34
+ * are returned; pass a previously returned {@link ChangeFeedPage.cursor} to
35
+ * poll.
36
+ *
37
+ * `0` reads from the start of the log only while it has not been pruned past
38
+ * the beginning. Once retention has raised the retained floor above the
39
+ * start, `since: 0` (like any cursor older than the retained window) can no
40
+ * longer be served incrementally — the read returns
41
+ * {@link ChangeFeedPage.resyncRequired} and the caller must do a full
42
+ * resync.
43
+ */
44
+ since: number;
45
+ /** Restrict to these physical table names. Empty/omitted → all tables. */
46
+ tables?: string[];
47
+ /**
48
+ * Tenant visibility filter:
49
+ * - omitted/`undefined` → no tenant filter (all rows).
50
+ * - `null` → only global rows (`tenant_id IS NULL`).
51
+ * - `'<tenantId>'` → that tenant's rows **plus** global rows, matching the
52
+ * DispatchBus read rule (`tenant_id = T OR tenant_id IS NULL`). A tenant
53
+ * never sees another tenant's changes.
54
+ */
55
+ tenantId?: string | null;
56
+ /**
57
+ * Page size (default {@link DEFAULT_CHANGES_LIMIT}, capped at
58
+ * {@link MAX_CHANGES_LIMIT}). When a page fills up, the returned cursor
59
+ * stops at the last returned row so the next poll continues seamlessly.
60
+ */
61
+ limit?: number;
62
+ }
63
+ /** Result page of {@link getChangesSince}. */
64
+ export interface ChangeFeedPage {
65
+ /** Matching changes ordered by ascending `seq`. */
66
+ changes: ChangeFeedEntry[];
67
+ /**
68
+ * The next cursor. Monotonic: never lower than the `since` it was derived
69
+ * from. Equal to the committed horizon when the page was exhaustive, or to
70
+ * the last returned `seq` when the page hit `limit`. Feed the value back
71
+ * as `since` to observe every later change exactly once.
72
+ */
73
+ cursor: number;
74
+ /**
75
+ * Present (and `true`) when the supplied cursor cannot be served
76
+ * incrementally and the consumer must fall back to a full resync:
77
+ *
78
+ * - the cursor predates the retained window (entries at or below it were
79
+ * pruned away — the changes between it and the retained floor are gone
80
+ * for good), or
81
+ * - the cursor is ahead of the committed horizon / unknown to this
82
+ * database (a foreign or reset cursor).
83
+ *
84
+ * When set, `changes` is empty and `cursor` echoes `since` unchanged —
85
+ * the consumer must re-fetch its data in full and restart polling from
86
+ * the cursor returned by its post-resync read. Detection is computed on
87
+ * the **unfiltered** log: `tables`/`tenantId` filters legitimately hide
88
+ * rows and never trigger (or mask) a resync signal.
89
+ */
90
+ resyncRequired?: boolean;
91
+ }
92
+ /** Input for {@link appendChange} / {@link bumpChangeFeed}. */
93
+ export interface AppendChangeInput {
94
+ /** Physical table name the change refers to. */
95
+ table: string;
96
+ /** Changed row's primary key; `null`/omitted records a table-level change. */
97
+ rowId?: string | null;
98
+ /** Operation to record (default `'update'`). */
99
+ operation?: ChangeOperation;
100
+ /** Tenant the change belongs to (default `null` = global). */
101
+ tenantId?: string | null;
102
+ }
103
+ /** Retention bounds for {@link pruneChangeFeed}. At least one is required. */
104
+ export interface ChangeFeedRetention {
105
+ /** Prune entries older than this many milliseconds. */
106
+ maxAgeMs?: number;
107
+ /** Keep at most this many newest entries (by sequence). */
108
+ maxRows?: number;
109
+ }
110
+ /** Default page size for {@link getChangesSince}. */
111
+ export declare const DEFAULT_CHANGES_LIMIT = 500;
112
+ /** Hard cap on the page size for {@link getChangesSince}. */
113
+ export declare const MAX_CHANGES_LIMIT = 5000;
114
+ export declare function ensureChangeFeedTable(db: DatabaseInterface): Promise<void>;
115
+ /**
116
+ * Append one change entry with a database-allocated, strictly monotonic
117
+ * sequence.
118
+ *
119
+ * The sequence is allocated inside the INSERT itself
120
+ * (`COALESCE(MAX(seq), 0) + 1`) and retried on primary-key conflict, which
121
+ * keeps committed sequences contiguous and makes commit order equal
122
+ * sequence order — the property the cursor guarantee rests on (see the
123
+ * module docs). Throws after {@link MAX_APPEND_ATTEMPTS} consecutive
124
+ * conflicts or on any non-conflict database error; the framework's
125
+ * interceptor catches and logs instead of failing the user's write.
126
+ *
127
+ * **Transaction assumption.** The conflict retry assumes an autocommit
128
+ * handle, which is the default framework path: `save()`/`delete()` run their
129
+ * statements as independent autocommit calls, so a conflicting append aborts
130
+ * nothing and the retry recomputes `MAX(seq)` against the committed head. If a
131
+ * caller instead wraps `save()` in its own transaction on an MVCC engine
132
+ * (e.g. Postgres), a duplicate-key error can abort the *surrounding*
133
+ * transaction — the retry is then futile and the caller's write rolls back.
134
+ * Callers wrapping mutations in a transaction under genuine seq-head write
135
+ * contention should disable the feed writer for that path or accept that
136
+ * risk. Savepoint-scoped protection is a possible follow-up; it is
137
+ * intentionally out of scope for the v1 spine.
138
+ */
139
+ export declare function appendChange(db: DatabaseInterface, input: AppendChangeInput): Promise<void>;
140
+ /**
141
+ * Manual bump escape hatch for out-of-band writers.
142
+ *
143
+ * Framework mutation paths feed the log automatically, but raw SQL issued
144
+ * outside `save()`/`delete()` is invisible to it (documented gap, shared
145
+ * with the #1499 collection cache). Call this after such a write so feed
146
+ * consumers observe the change. Omitting `rowId` records a table-level
147
+ * change (`rowId: null`), which consumers should treat as "anything in this
148
+ * table may have changed".
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * await db.query(`UPDATE products SET price = price * 1.1`);
153
+ * await bumpChangeFeed(db, { table: 'products' });
154
+ * ```
155
+ */
156
+ export declare function bumpChangeFeed(db: DatabaseInterface, input: AppendChangeInput): Promise<void>;
157
+ /**
158
+ * Read committed changes after a cursor.
159
+ *
160
+ * Returns every committed change with `since < seq <= cursor` that matches
161
+ * the filters, ordered by ascending `seq`. The returned cursor is safe to
162
+ * persist and poll with: committed sequences are contiguous (see module
163
+ * docs), so nothing can commit at or below the observed horizon afterwards —
164
+ * reads miss no committed change under concurrent writers and never return
165
+ * the same change twice. When `since` is already at the horizon, returns an
166
+ * empty page with `cursor: since`.
167
+ *
168
+ * ## Resync detection (pruned / foreign cursors)
169
+ *
170
+ * A cursor that cannot be served incrementally is flagged with
171
+ * `resyncRequired: true` (empty `changes`, `cursor` echoed unchanged) so
172
+ * pollers never go silently, permanently stale:
173
+ *
174
+ * - **Pruned gap**: retained sequences always form a contiguous run
175
+ * `[floor..horizon]` and {@link pruneChangeFeed} deletes oldest-first
176
+ * while always retaining the newest entry, so `since < floor - 1` proves
177
+ * changes between the cursor and the retained window were pruned away.
178
+ * - **Foreign/reset cursor**: `since > horizon` (ahead of anything this
179
+ * database ever allocated), including any `since > 0` against a feed
180
+ * with no entries.
181
+ *
182
+ * Detection runs on the **unfiltered** log — `tables`/`tenantId` filters
183
+ * legitimately hide rows and never trigger (or mask) the signal. A caught-up
184
+ * consumer (`since === horizon`) is never asked to resync, even when
185
+ * retention has pruned everything older.
186
+ *
187
+ * Filters (`tables`, `tenantId`) affect which rows are *returned*, never how
188
+ * the cursor advances — an exhausted filtered page still advances to the
189
+ * horizon so pollers do not rescan filtered-out rows.
190
+ */
191
+ export declare function getChangesSince(db: DatabaseInterface, options: GetChangesOptions): Promise<ChangeFeedPage>;
192
+ /**
193
+ * {@link getChangesSince} scoped by the active tenant context.
194
+ *
195
+ * Resolves the tenant through the same dependency-inversion hook the
196
+ * DispatchBus uses ({@link resolveDispatchTenantScope}), so it works without
197
+ * core depending on `@happyvertical/smrt-tenancy`:
198
+ *
199
+ * - Tenancy disabled (no resolver registered) → no tenant filter.
200
+ * - Tenancy enabled with an active tenant `T` → `T`'s rows plus global rows.
201
+ * - Tenancy enabled with **no** active tenant → global rows only
202
+ * (**fail-closed**: a missing context never widens visibility to all
203
+ * tenants).
204
+ *
205
+ * This is the read the generated `_changes` routes call after establishing
206
+ * tenant context from the authenticated principal.
207
+ */
208
+ export declare function getTenantScopedChangesSince(db: DatabaseInterface, options: Omit<GetChangesOptions, 'tenantId'>): Promise<ChangeFeedPage>;
209
+ /**
210
+ * Prune the change feed to bound its growth.
211
+ *
212
+ * Applies whichever bounds are provided (at least one is required):
213
+ * - `maxRows`: keep only the newest N entries by sequence.
214
+ * - `maxAgeMs`: drop entries older than the cutoff.
215
+ *
216
+ * Pruning deletes oldest-first, never renumbers surviving entries, and
217
+ * **always retains the newest entry** (a non-empty feed is never emptied,
218
+ * whatever the bounds say). That invariant anchors pruned-cursor detection:
219
+ * retained sequences stay a contiguous run `[floor..horizon]`, so
220
+ * {@link getChangesSince} can prove a cursor predates the retained window
221
+ * (`resyncRequired`) — and a fully caught-up consumer keeps polling
222
+ * normally even after everything older was pruned.
223
+ *
224
+ * Cursors within the retained window keep working. Schedule pruning (e.g.
225
+ * via `@happyvertical/smrt-jobs`) with a retention window comfortably
226
+ * larger than the slowest consumer's polling interval; consumers whose
227
+ * cursor falls out of it are told to full-resync via `resyncRequired`.
228
+ *
229
+ * @returns The number of entries pruned (approximate under concurrent prunes).
230
+ */
231
+ export declare function pruneChangeFeed(db: DatabaseInterface, retention: ChangeFeedRetention): Promise<{
232
+ pruned: number;
233
+ }>;
234
+ /**
235
+ * Register the change-feed writer with {@link GlobalInterceptors}.
236
+ *
237
+ * Called automatically during framework initialization (every
238
+ * `SmrtClass.initialize()` passes through it), so applications never need
239
+ * to call it directly; it is exported for tests and for re-registering
240
+ * after `GlobalInterceptors.clear()`. Idempotent — a second call while the
241
+ * writer is registered is a no-op.
242
+ *
243
+ * The writer observes the same hooks the reports scheduler and tenancy
244
+ * interceptors use:
245
+ * - `beforeSave` stashes whether the instance was already persisted (this
246
+ * is what distinguishes `create` from `update` in the feed).
247
+ * - `afterSave`/`afterDelete` append exactly one change entry per framework
248
+ * save/delete. `_smrt_*` system tables are skipped — the feed observes
249
+ * application data, not framework bookkeeping (and never itself).
250
+ *
251
+ * Failure policy: appends run after the user's write succeeded and must not
252
+ * un-succeed it — failures are logged (deduped per database) and swallowed.
253
+ */
254
+ export declare function registerChangeFeedWriter(): void;
255
+ /** Unregister the change-feed writer (test helper). */
256
+ export declare function unregisterChangeFeedWriter(): boolean;
257
+ /**
258
+ * Reset the append-failure warning dedup (test helper).
259
+ */
260
+ export declare function resetChangeFeedWarnings(): void;
261
+ //# sourceMappingURL=change-feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-feed.d.ts","sourceRoot":"","sources":["../src/change-feed.ts"],"names":[],"mappings":"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"}