@pikku/kysely 0.13.24 → 0.13.28
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/CHANGELOG.md +271 -0
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.js +3 -1
- package/dist/src/kysely-analytics-service.d.ts +30 -0
- package/dist/src/kysely-analytics-service.js +80 -0
- package/dist/src/kysely-feature-flag-store.d.ts +37 -0
- package/dist/src/kysely-feature-flag-store.js +239 -0
- package/dist/src/kysely-scope-service.d.ts +1 -0
- package/dist/src/kysely-scope-service.js +29 -0
- package/dist/src/kysely-tables.d.ts +45 -0
- package/dist/src/schema/analytics.schema.d.ts +17 -0
- package/dist/src/schema/analytics.schema.js +48 -0
- package/dist/src/schema/flag.schema.d.ts +16 -0
- package/dist/src/schema/flag.schema.js +62 -0
- package/dist/src/schema/index.d.ts +2 -0
- package/dist/src/schema/index.js +6 -0
- package/package.json +11 -6
- package/run-tests.sh +9 -1
- package/bin/pikku-kysely-pure.ts +0 -59
- package/dist/src/kysely-lock-vault.d.ts +0 -34
- package/dist/src/kysely-lock-vault.js +0 -77
- package/dist/src/schema/data-lock.schema.d.ts +0 -15
- package/dist/src/schema/data-lock.schema.js +0 -27
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/classification-crypto.test.ts +0 -143
- package/src/classification-crypto.ts +0 -163
- package/src/classification-plugin.pglite.test.ts +0 -226
- package/src/classification-plugin.test.ts +0 -265
- package/src/classification-plugin.ts +0 -249
- package/src/coercion-plugin.test.ts +0 -160
- package/src/coercion-plugin.ts +0 -187
- package/src/create-audited-kysely.test.ts +0 -560
- package/src/create-audited-kysely.ts +0 -254
- package/src/index.ts +0 -81
- package/src/kysely-agent-run-scores.ts +0 -47
- package/src/kysely-agent-run-service.ts +0 -244
- package/src/kysely-agent-run-state-service.ts +0 -200
- package/src/kysely-agent-storage-service.ts +0 -632
- package/src/kysely-audit-service.test.ts +0 -352
- package/src/kysely-audit-service.ts +0 -237
- package/src/kysely-channel-store.ts +0 -107
- package/src/kysely-credential-service.ts +0 -286
- package/src/kysely-deployment-service.ts +0 -163
- package/src/kysely-eventhub-store.ts +0 -53
- package/src/kysely-json.ts +0 -5
- package/src/kysely-scope-service.test.ts +0 -418
- package/src/kysely-scope-service.ts +0 -425
- package/src/kysely-secret-service.ts +0 -242
- package/src/kysely-services.test.ts +0 -405
- package/src/kysely-session-store.ts +0 -57
- package/src/kysely-tables.test.ts +0 -110
- package/src/kysely-tables.ts +0 -434
- package/src/kysely-virtual-user-run-store.test.ts +0 -369
- package/src/kysely-virtual-user-run-store.ts +0 -235
- package/src/kysely-virtual-user-schedule-store.test.ts +0 -187
- package/src/kysely-virtual-user-schedule-store.ts +0 -187
- package/src/kysely-webhook-service.test.ts +0 -153
- package/src/kysely-webhook-service.ts +0 -139
- package/src/kysely-workflow-mirror.test.ts +0 -259
- package/src/kysely-workflow-mirror.ts +0 -374
- package/src/kysely-workflow-run-service.ts +0 -305
- package/src/kysely-workflow-service.test.ts +0 -544
- package/src/kysely-workflow-service.ts +0 -820
- package/src/schema/agent.schema.ts +0 -162
- package/src/schema/audit.schema.ts +0 -57
- package/src/schema/channel.schema.ts +0 -38
- package/src/schema/credential.schema.ts +0 -58
- package/src/schema/deployment.schema.ts +0 -47
- package/src/schema/index.ts +0 -441
- package/src/schema/pikku-schema.types.ts +0 -169
- package/src/schema/schema.test.ts +0 -591
- package/src/schema/scope.schema.ts +0 -110
- package/src/schema/secret.schema.ts +0 -49
- package/src/schema/session.schema.ts +0 -20
- package/src/schema/virtual-user-schedule.schema.ts +0 -48
- package/src/schema/virtual-user.schema.ts +0 -106
- package/src/schema/webhook.schema.ts +0 -58
- package/src/schema/workflow.schema.ts +0 -150
- package/src/serialize-plugin.ts +0 -233
- package/tsconfig.json +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,274 @@
|
|
|
1
|
+
## 0.13.28
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 4a9dcd2: `admin:listUsers` now pages, counts and can carry roles.
|
|
6
|
+
|
|
7
|
+
`ListUsersInput` gains `offset` and `includeRoles`; `ListUsersOutput` gains `total`, and each `User` gains `roles` and `fields`. `total` is how many users match `search`, which is what a pager counts against — `users.length` never was, because it is capped by `limit`.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const { users, total } = await rpc.invoke('admin:listUsers', {
|
|
11
|
+
search: 'example.com',
|
|
12
|
+
limit: 50,
|
|
13
|
+
offset: 50,
|
|
14
|
+
includeRoles: true,
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Paging only means something over a stable order, so the query now sorts newest first rather than however the database felt like returning rows.
|
|
19
|
+
|
|
20
|
+
Synthetic principals — the platform credential owner, Fabric service users, scenario actors — are excluded by the query instead of dropped from the page afterwards. Filtering after the fact broke both halves of paging: `limit` had already counted the rows it then discarded, so a page came back short, and `offset` skipped synthetic rows as though they were people, so the same person could appear on two pages or on none.
|
|
21
|
+
|
|
22
|
+
`includeRoles` is refused without `admin:scopes:read`. `admin:users:list` says who may see the directory; it does not say who may see what each of those users can do.
|
|
23
|
+
|
|
24
|
+
`ScopeService` gains `listRolesForUsers(userIds)`, implemented in `@pikku/kysely`. It answers for every id asked for — an empty array for a user holding no roles, so a caller cannot read a missing key as "holds nothing" — and chunks its `in` list to stay inside the bound-parameter cap. A page of users used to cost one query per row, which on a database reached over the network is a round trip per row.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
listRolesForUsers(userIds: string[]): Promise<Record<string, string[]>>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Anything implementing `ScopeService` outside this repository has to add it.
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [4a9dcd2]
|
|
33
|
+
- Updated dependencies [5ab24ad]
|
|
34
|
+
- Updated dependencies [42b7ac3]
|
|
35
|
+
- @pikku/core@0.12.120
|
|
36
|
+
|
|
37
|
+
## 0.13.27
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- 78237ac: The console shows feature flags as a launch board, and analytics events as a
|
|
42
|
+
declared catalog.
|
|
43
|
+
|
|
44
|
+
A flag's state is four fields — `enabled`, `rolloutPercent`, `declared`,
|
|
45
|
+
`backed` — and none of them is the question an operator actually asks, which is
|
|
46
|
+
how far this has got. The board answers that directly: every declared flag sits
|
|
47
|
+
in the lane its rollout has reached, `dark` → `rolling` → `live`, with
|
|
48
|
+
`attention` for the flags that are not resolving in a way anybody chose.
|
|
49
|
+
|
|
50
|
+
Attention is evaluated before every other reading, and that ordering is the
|
|
51
|
+
whole point rather than an implementation detail. A flag declared in code with
|
|
52
|
+
no row in the store fails open, and it reports `enabled: true` with no rollout
|
|
53
|
+
limit — byte-identical to a deliberate full launch. Read the fields in the
|
|
54
|
+
obvious order and the one production-severity state on the board renders as the
|
|
55
|
+
healthiest one. `groupFlagsByLane` also returns all four lanes whether or not
|
|
56
|
+
they hold anything, because a board read by shape must not rearrange itself the
|
|
57
|
+
moment a lane empties.
|
|
58
|
+
|
|
59
|
+
Selecting a flag opens its controls beside the board rather than over it: the
|
|
60
|
+
switch, the rollout bucket, and the subjects pinned on or off past that bucket.
|
|
61
|
+
They share one surface because they are read together — "off for everyone except
|
|
62
|
+
these three" is a switch and an override row, and an operator who has to leave
|
|
63
|
+
one screen to see the other is the operator who turns a flag on for everybody by
|
|
64
|
+
mistake. The panel tracks the open flag by name and re-reads it from the list, so
|
|
65
|
+
committing a rollout moves the card and the panel together instead of leaving the
|
|
66
|
+
panel describing the flag as it was before the operator changed it.
|
|
67
|
+
|
|
68
|
+
A board whose flags come from a `FeatureFlagSource` — a provider Pikku reads but
|
|
69
|
+
does not own — says so and offers no controls, rather than offering controls that
|
|
70
|
+
would fail.
|
|
71
|
+
|
|
72
|
+
The analytics screen is a catalog of what the code declares, read from build-time
|
|
73
|
+
meta: the events a client may emit and the props each carries, with every prop
|
|
74
|
+
rendered as the schema source text the declaration wrote. It is deliberately not
|
|
75
|
+
a volume report — nothing here counts what was emitted — and saying which of the
|
|
76
|
+
two it is turned out to be the only design question the screen had.
|
|
77
|
+
|
|
78
|
+
Turning a flag on while it carries no rollout limit now asks first. That press
|
|
79
|
+
is the one irreversible thing the board can do — with no bucket left to hold
|
|
80
|
+
anybody back it admits everyone the flag's capabilities allow — and it used to
|
|
81
|
+
be a single click on a switch. The panel also reports what a mutation is doing
|
|
82
|
+
and what it refused to do: the controls it is waiting on go into a pending
|
|
83
|
+
state, and a failed write raises an inline `<Alert>` rather than reverting in
|
|
84
|
+
silence. The console mounts no notification provider, so the feedback is inline
|
|
85
|
+
where the action is, which is the idiom the rest of the console already uses.
|
|
86
|
+
|
|
87
|
+
`@pikku/mantine`'s theme gains four aliases, and they close a contrast defect
|
|
88
|
+
that predates this board:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
'--mantine-color-orange-light': 'var(--app-surface-warning)',
|
|
92
|
+
'--mantine-color-orange-light-color': 'var(--app-amber)',
|
|
93
|
+
'--mantine-color-teal-light': 'var(--app-surface-success)',
|
|
94
|
+
'--mantine-color-teal-light-color': 'var(--app-green)',
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The theme already documents two colour tiers — TEXT must clear AA, GRAPHIC only
|
|
98
|
+
3:1 — and already pulls Mantine's own variables into that contract rather than
|
|
99
|
+
linting call sites. What it had not covered was Mantine's _light variants_:
|
|
100
|
+
`<Alert color="orange">` renders #d9480f on #ffe8cc at 3.62:1, and
|
|
101
|
+
`<Badge variant="light" color="teal">` at 4.33:1, neither reachable from an
|
|
102
|
+
`--app-*` token because the component never asks for one. The ramp itself is
|
|
103
|
+
deliberately untouched: Mantine conflates `--mantine-color-orange-filled`
|
|
104
|
+
between `c="orange"` text and a `variant="filled"` background, so darkening it
|
|
105
|
+
for light-mode text would make an existing filled badge unreadable in dark mode.
|
|
106
|
+
Eight pre-existing `c="orange"` call sites moved onto the amber TEXT tier in the
|
|
107
|
+
same pass.
|
|
108
|
+
|
|
109
|
+
The analytics catalog is grouped by the file that declares each event, and its
|
|
110
|
+
rows are real buttons rather than table cells carrying a tabindex.
|
|
111
|
+
|
|
112
|
+
`ShellHeaderAction` gains `loading` and `testId`, and `ActionCluster` gains a
|
|
113
|
+
`measurement` flag. `ShellHeader` sizes its actions by rendering them a second
|
|
114
|
+
time off-screen, and that measuring copy was carrying the same `data-testid` and
|
|
115
|
+
`data-help` as the real one — so every action on a page with a collapsing header
|
|
116
|
+
resolved to two elements. The flags board also moved its two header actions from
|
|
117
|
+
`actionsNode` (documented as the non-collapsing escape hatch) onto the structured
|
|
118
|
+
`actions` array, which is what stopped them clipping off the right edge at 400px.
|
|
119
|
+
|
|
120
|
+
- Updated dependencies [78237ac]
|
|
121
|
+
- Updated dependencies [add56d2]
|
|
122
|
+
- @pikku/core@0.12.111
|
|
123
|
+
|
|
124
|
+
## 0.13.26
|
|
125
|
+
|
|
126
|
+
### Patch Changes
|
|
127
|
+
|
|
128
|
+
- 1c9a55a: Review fixes across the feature-flag and analytics primitives:
|
|
129
|
+
|
|
130
|
+
- An addon function's `featureFlag:` gate now reads the consuming application's
|
|
131
|
+
flag source rather than the package's own services, which never carry one.
|
|
132
|
+
- A flag named `__proto__`, `constructor` or `prototype` is rejected instead of
|
|
133
|
+
being silently dropped from the generated metadata.
|
|
134
|
+
- Two declarations of one flag with different descriptions are a hard error,
|
|
135
|
+
rather than the inspector's traversal order deciding what operators read.
|
|
136
|
+
- `subjectIdOf` falls through an empty organization id to the user, instead of
|
|
137
|
+
resolving to no subject and skipping every override and rollout bucket.
|
|
138
|
+
- `CachedFlagSource.invalidate()` retires the in-flight read, so a webhook that
|
|
139
|
+
lands mid-fetch is not answered by the pre-webhook snapshot for another TTL.
|
|
140
|
+
- The generated `/feature-flags` wire resolves against the compiled fallback
|
|
141
|
+
when no source is wired, so a scope-gated flag is not reported to a caller
|
|
142
|
+
who cannot hold it.
|
|
143
|
+
- `pruneFlags()` rechecks `declared = false` in the delete and reports only
|
|
144
|
+
what it removed, so a redeclaration between the two keeps its overrides.
|
|
145
|
+
- A destination's `accepts` throwing no longer costs every later sink its batch.
|
|
146
|
+
- The React analytics client drops events at collection time when analytics is
|
|
147
|
+
disabled, so a later consent grant cannot send what was gathered before it.
|
|
148
|
+
- The React flag client keeps its map when the wire answers a non-object.
|
|
149
|
+
- `KyselyAnalyticsService` mints event ids from `crypto` rather than a
|
|
150
|
+
timestamp with a short random tail.
|
|
151
|
+
|
|
152
|
+
- 1c9a55a: Add `KyselyAnalyticsService`, so declared events have somewhere to land.
|
|
153
|
+
|
|
154
|
+
Every other pikku primitive backed by a table had a Kysely implementation and
|
|
155
|
+
analytics did not, which meant a project that declared events either wired a
|
|
156
|
+
vendor sink or watched them go to the logger — and the logger is not somewhere
|
|
157
|
+
you can answer a question from. The new service appends to `pikkuAnalyticsEvents`,
|
|
158
|
+
gated by `ownedBy: ['analyticsService']` so only a project that declares events
|
|
159
|
+
carries the table.
|
|
160
|
+
|
|
161
|
+
- 1c9a55a: Export `flagSchema` beside the other schemas.
|
|
162
|
+
|
|
163
|
+
`KyselyFeatureFlagStore.init()` expects its tables to exist, and the only way
|
|
164
|
+
an app creates them is `applyPikkuSchemas(db, [flagSchema])`. Every other store
|
|
165
|
+
in this package exports the schema that backs it; this one did not, so the
|
|
166
|
+
store was unusable outside the package that defined it.
|
|
167
|
+
|
|
168
|
+
- 1c9a55a: Feature flags, declared in source and resolved from two independent booleans.
|
|
169
|
+
|
|
170
|
+
`defineFeatureFlags` declares a flag the way `defineScopes` declares a scope —
|
|
171
|
+
the inspector collects it, the CLI emits a `FeatureFlagName` union, and a
|
|
172
|
+
misspelled `featureFlag:` is a type error rather than a gate that silently fails
|
|
173
|
+
open.
|
|
174
|
+
|
|
175
|
+
A flag answers two questions that are not the same question. `capable` comes
|
|
176
|
+
from the session's scopes and is per-user, advisory, and protects nothing — it
|
|
177
|
+
hides UI. `available` comes from one global config snapshot, is caller-blind,
|
|
178
|
+
and is the only half the runner enforces: `override(subject) ?? (enabled AND
|
|
179
|
+
bucket)`, so "off for everyone except these three organizations" is one row, and
|
|
180
|
+
a kill is one write rather than a fan-out. Authorization stays where it was, in
|
|
181
|
+
the function's own `scopes:` — enforcing capability would make a flag a second
|
|
182
|
+
authorization path OR-ing against them. An unavailable feature throws 503, not
|
|
183
|
+
403, because the caller was allowed; the feature was not on.
|
|
184
|
+
|
|
185
|
+
`featureFlag:` is deliberately available on sessionless functions too. It reads
|
|
186
|
+
the config, not the session, so the kill switch reaches a cron task, a queue
|
|
187
|
+
worker and a webhook — which is where it matters most, since nobody is watching
|
|
188
|
+
a UI to notice the feature is off.
|
|
189
|
+
|
|
190
|
+
Refresh is pull-on-demand, so the TTL is the kill-switch latency. Where that is
|
|
191
|
+
too long, `invalidate()` is public: a provider's change webhook lands on an HTTP
|
|
192
|
+
wiring, drops the cache, and the next request does the read. It deliberately
|
|
193
|
+
does not fetch — a burst of webhooks would be N round trips, and on a serverless
|
|
194
|
+
runtime the isolate that took the signal may be gone before anything reads the
|
|
195
|
+
result.
|
|
196
|
+
|
|
197
|
+
Availability fails open through three layers: a fresh read, then the last good
|
|
198
|
+
cached read, then the compiled declaration. The middle layer is load-bearing —
|
|
199
|
+
dropping straight to the compiled fallback on a blip would switch on every flag
|
|
200
|
+
that was deliberately dark.
|
|
201
|
+
|
|
202
|
+
Backing stores split along what they can honestly do. `FeatureFlagSource` is
|
|
203
|
+
read-only (`snapshot()`) and is what a third-party provider implements;
|
|
204
|
+
`FeatureFlagStore` adds the write half and is for stores Pikku owns.
|
|
205
|
+
`@pikku/kysely` ships the store, with declarations synced additively — a removed
|
|
206
|
+
declaration is marked undeclared, never revoked, and a sync never re-enables a
|
|
207
|
+
killed flag. Third-party providers implement the read-only half in the addons
|
|
208
|
+
repository, over plain `fetch` rather than a vendor SDK — those poll on a timer
|
|
209
|
+
belonging to a long-lived process, which a serverless isolate cannot hold
|
|
210
|
+
between requests.
|
|
211
|
+
|
|
212
|
+
A client asks for its flags once per session rather than per flag:
|
|
213
|
+
`scaffold.featureFlags` generates a `GET /feature-flags` returning every
|
|
214
|
+
declared flag resolved for the caller, keyed by this app's `FeatureFlagName`.
|
|
215
|
+
Generated into the app rather than shipped in an addon precisely for that union
|
|
216
|
+
— an addon never sees the host's, and could only answer
|
|
217
|
+
`Record<string, boolean>`. It returns `show` alone: sending `available` apart
|
|
218
|
+
from `capable` would tell every visitor which features exist but are dark.
|
|
219
|
+
|
|
220
|
+
The write half is the operator's, and lives in `@pikku/addon-admin` beside the
|
|
221
|
+
scope RPCs, under a new `admin:flags` scope. `flagList` reports `writable:
|
|
222
|
+
false` rather than failing when flags come from a provider, because a provider's
|
|
223
|
+
own UI is its operator surface and a console full of buttons that 500 is worse
|
|
224
|
+
than a read-only tab. It still lists the flags: a source may report its declared
|
|
225
|
+
set through `declaredFlags()`, and each row carries `backed`, false where the
|
|
226
|
+
provider has never heard of a declared flag. That row is the one worth seeing —
|
|
227
|
+
an absent row fails open, so a dark launch nobody created in PostHog is already
|
|
228
|
+
live for everyone, and a store pikku owns can reconcile that on deploy where a
|
|
229
|
+
provider cannot.
|
|
230
|
+
|
|
231
|
+
On the client, `createFeatureFlags` fetches that map once and `useFeatureFlag`
|
|
232
|
+
reads it synchronously after, through the same provider the analytics client
|
|
233
|
+
hangs off. It takes a `bootstrap` map so a server-rendered page hydrates onto
|
|
234
|
+
the answer it already computed: without one there is a gap in which neither
|
|
235
|
+
default is right, since false hides a feature the user has and true flashes one
|
|
236
|
+
they do not. A failed refresh keeps the map already on screen rather than
|
|
237
|
+
relabelling every flag on a blip.
|
|
238
|
+
|
|
239
|
+
- Updated dependencies [1c9a55a]
|
|
240
|
+
- Updated dependencies [1c9a55a]
|
|
241
|
+
- Updated dependencies [1c9a55a]
|
|
242
|
+
- @pikku/core@0.12.110
|
|
243
|
+
|
|
244
|
+
## 0.13.25
|
|
245
|
+
|
|
246
|
+
### Patch Changes
|
|
247
|
+
|
|
248
|
+
- 4c7a1b5: Run the monorepo's own scripts through bun instead of yarn. What moves is the
|
|
249
|
+
package manager each package's `prepublishOnly` and build scripts invoke, plus
|
|
250
|
+
the two manifest fixes bun needs to resolve the tree: `uWebSockets.js` is
|
|
251
|
+
declared with an explicit `github:` specifier, and `@pikku/uws-handler` marks
|
|
252
|
+
its `uWebSockets.js` peer optional so a bun install of a consumer that brings
|
|
253
|
+
its own uWS app does not try to fetch it from the registry.
|
|
254
|
+
|
|
255
|
+
Three published behaviours change, all of them cases where an isolated
|
|
256
|
+
`node_modules` or bun as the runtime had been papered over by yarn's hoisting:
|
|
257
|
+
|
|
258
|
+
- `@pikku/migrator-sql` turns foreign keys on when it opens a sqlite database
|
|
259
|
+
through bun. `node:sqlite` enforces them by default and `bun:sqlite` does not,
|
|
260
|
+
which silently turned every `ON DELETE CASCADE` into a no-op under
|
|
261
|
+
`bunx --bun pikku`.
|
|
262
|
+
- `@pikku/cli` resolves a deploy provider against the project being deployed
|
|
263
|
+
rather than against wherever the CLI itself is installed, which is what its
|
|
264
|
+
own "is not installed" error asks the user to arrange.
|
|
265
|
+
- `@pikku/cli` treats a specifier a runtime hands straight back — bun does this
|
|
266
|
+
for the modules it implements itself — as not resolved from the project, so
|
|
267
|
+
it falls back rather than loading the runtime's own copy.
|
|
268
|
+
|
|
269
|
+
- Updated dependencies [4c7a1b5]
|
|
270
|
+
- @pikku/core@0.12.108
|
|
271
|
+
|
|
1
272
|
## 0.13.24
|
|
2
273
|
|
|
3
274
|
### Patch Changes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -11,16 +11,19 @@ export { KyselySecretService } from './kysely-secret-service.js';
|
|
|
11
11
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
12
12
|
export { KyselySessionStore } from './kysely-session-store.js';
|
|
13
13
|
export { KyselyScopeService } from './kysely-scope-service.js';
|
|
14
|
+
export { KyselyFeatureFlagStore } from './kysely-feature-flag-store.js';
|
|
15
|
+
export type { KyselyFeatureFlagStoreOptions } from './kysely-feature-flag-store.js';
|
|
14
16
|
export { KyselyWebhookService } from './kysely-webhook-service.js';
|
|
15
17
|
export { createAuditedKysely, type CreateAuditedKyselyOptions, } from './create-audited-kysely.js';
|
|
16
18
|
export { KyselyAuditService } from './kysely-audit-service.js';
|
|
19
|
+
export { KyselyAnalyticsService } from './kysely-analytics-service.js';
|
|
17
20
|
export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js';
|
|
18
21
|
export { KyselyVirtualUserScheduleStore } from './kysely-virtual-user-schedule-store.js';
|
|
19
22
|
export { SerializePlugin, BaseSerializePlugin, type Serializer, type Deserializer, } from './serialize-plugin.js';
|
|
20
23
|
export { createCoercionPlugin, type ColumnKind, type CoercionMap, type CreateCoercionPluginOptions, } from './coercion-plugin.js';
|
|
21
24
|
export { createClassificationPlugin, type CreateClassificationPluginOptions, } from './classification-plugin.js';
|
|
22
25
|
export { ClassificationCrypto, DEFAULT_KEY_ID, isColumnEnvelope, parseColumnEnvelope, type ClassificationCryptoOptions, type ColumnEnvelope, type KEKResolver, type ResolvedKEK, } from './classification-crypto.js';
|
|
23
|
-
export { agentSchema, auditSchema, channelSchema, credentialSchema, deploymentSchema, scopeSchema, secretSchema, sessionSchema, virtualUserSchema, webhookSchema, workflowSchema, pikkuSchemas, requiredPikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, requirePikkuSchema, resolveRequirements, type PikkuSchema, type RequiredTypes, type UnmetRequirement, type SchemaStatementFactory, } from './schema/index.js';
|
|
26
|
+
export { agentSchema, analyticsSchema, auditSchema, channelSchema, credentialSchema, deploymentSchema, flagSchema, scopeSchema, secretSchema, sessionSchema, virtualUserSchema, webhookSchema, workflowSchema, pikkuSchemas, requiredPikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, requirePikkuSchema, resolveRequirements, type PikkuSchema, type RequiredTypes, type UnmetRequirement, type SchemaStatementFactory, } from './schema/index.js';
|
|
24
27
|
export type { KyselyPikkuDB } from './kysely-tables.js';
|
|
25
28
|
export type { WorkflowRunService } from '@pikku/core/workflow';
|
|
26
29
|
export type { AgentRunService, AgentRunRow } from '@pikku/core/agent';
|
package/dist/src/index.js
CHANGED
|
@@ -11,13 +11,15 @@ export { KyselySecretService } from './kysely-secret-service.js';
|
|
|
11
11
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
12
12
|
export { KyselySessionStore } from './kysely-session-store.js';
|
|
13
13
|
export { KyselyScopeService } from './kysely-scope-service.js';
|
|
14
|
+
export { KyselyFeatureFlagStore } from './kysely-feature-flag-store.js';
|
|
14
15
|
export { KyselyWebhookService } from './kysely-webhook-service.js';
|
|
15
16
|
export { createAuditedKysely, } from './create-audited-kysely.js';
|
|
16
17
|
export { KyselyAuditService } from './kysely-audit-service.js';
|
|
18
|
+
export { KyselyAnalyticsService } from './kysely-analytics-service.js';
|
|
17
19
|
export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js';
|
|
18
20
|
export { KyselyVirtualUserScheduleStore } from './kysely-virtual-user-schedule-store.js';
|
|
19
21
|
export { SerializePlugin, BaseSerializePlugin, } from './serialize-plugin.js';
|
|
20
22
|
export { createCoercionPlugin, } from './coercion-plugin.js';
|
|
21
23
|
export { createClassificationPlugin, } from './classification-plugin.js';
|
|
22
24
|
export { ClassificationCrypto, DEFAULT_KEY_ID, isColumnEnvelope, parseColumnEnvelope, } from './classification-crypto.js';
|
|
23
|
-
export { agentSchema, auditSchema, channelSchema, credentialSchema, deploymentSchema, scopeSchema, secretSchema, sessionSchema, virtualUserSchema, webhookSchema, workflowSchema, pikkuSchemas, requiredPikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, requirePikkuSchema, resolveRequirements, } from './schema/index.js';
|
|
25
|
+
export { agentSchema, analyticsSchema, auditSchema, channelSchema, credentialSchema, deploymentSchema, flagSchema, scopeSchema, secretSchema, sessionSchema, virtualUserSchema, webhookSchema, workflowSchema, pikkuSchemas, requiredPikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, requirePikkuSchema, resolveRequirements, } from './schema/index.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnalyticsRecord, AnalyticsService } from '@pikku/core/analytics';
|
|
2
|
+
import type { Kysely } from 'kysely';
|
|
3
|
+
/**
|
|
4
|
+
* A durable {@link AnalyticsService} that appends events to a table pikku owns.
|
|
5
|
+
*
|
|
6
|
+
* The destination of last resort, and the one a project gets for free: without
|
|
7
|
+
* it an app that declares events either wires a vendor sink or watches them go
|
|
8
|
+
* to the logger, and neither is somewhere you can answer a question from. It is
|
|
9
|
+
* append-only by design — there is no read side here, because querying events
|
|
10
|
+
* is what a warehouse or the console does with the rows, not what the ingest
|
|
11
|
+
* path needs.
|
|
12
|
+
*
|
|
13
|
+
* Every column is text on every engine, so the same table reads the same
|
|
14
|
+
* whether it was filled by a locally-run project or a deployed stage. Names are
|
|
15
|
+
* written camelCase, as the declaration spells them, and `CamelCasePlugin`
|
|
16
|
+
* turns both into the physical snake_case the migration created.
|
|
17
|
+
*/
|
|
18
|
+
export declare class KyselyAnalyticsService implements AnalyticsService {
|
|
19
|
+
private db;
|
|
20
|
+
private initialized;
|
|
21
|
+
constructor(db: Kysely<any>);
|
|
22
|
+
/**
|
|
23
|
+
* Refuse to start unless the table is already there.
|
|
24
|
+
*
|
|
25
|
+
* Never creates it: `pikku db generate` writes the migration and `pikku db
|
|
26
|
+
* migrate` applies it, and those are the only authors of this table.
|
|
27
|
+
*/
|
|
28
|
+
init(): Promise<void>;
|
|
29
|
+
write(batch: AnalyticsRecord[]): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { requirePikkuSchema } from './schema/index.js';
|
|
2
|
+
import { analyticsSchema } from './schema/analytics.schema.js';
|
|
3
|
+
const jsonOrNull = (value) => value != null ? JSON.stringify(value) : null;
|
|
4
|
+
/**
|
|
5
|
+
* The primary key. `AnalyticsRecord` carries no id, so every row is given one
|
|
6
|
+
* here, and a whole batch is minted inside one millisecond — a timestamp with
|
|
7
|
+
* a short random tail would collide and `doNothing()` would drop the loser
|
|
8
|
+
* without a word. `randomUUID` where the runtime has it, a 128-bit random hex
|
|
9
|
+
* where it does not.
|
|
10
|
+
*/
|
|
11
|
+
const eventId = () => {
|
|
12
|
+
const c = globalThis.crypto;
|
|
13
|
+
if (typeof c?.randomUUID === 'function') {
|
|
14
|
+
return `ana_${c.randomUUID()}`;
|
|
15
|
+
}
|
|
16
|
+
if (typeof c?.getRandomValues === 'function') {
|
|
17
|
+
const bytes = c.getRandomValues(new Uint8Array(16));
|
|
18
|
+
return `ana_${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
|
|
19
|
+
}
|
|
20
|
+
return `ana_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A durable {@link AnalyticsService} that appends events to a table pikku owns.
|
|
24
|
+
*
|
|
25
|
+
* The destination of last resort, and the one a project gets for free: without
|
|
26
|
+
* it an app that declares events either wires a vendor sink or watches them go
|
|
27
|
+
* to the logger, and neither is somewhere you can answer a question from. It is
|
|
28
|
+
* append-only by design — there is no read side here, because querying events
|
|
29
|
+
* is what a warehouse or the console does with the rows, not what the ingest
|
|
30
|
+
* path needs.
|
|
31
|
+
*
|
|
32
|
+
* Every column is text on every engine, so the same table reads the same
|
|
33
|
+
* whether it was filled by a locally-run project or a deployed stage. Names are
|
|
34
|
+
* written camelCase, as the declaration spells them, and `CamelCasePlugin`
|
|
35
|
+
* turns both into the physical snake_case the migration created.
|
|
36
|
+
*/
|
|
37
|
+
export class KyselyAnalyticsService {
|
|
38
|
+
db;
|
|
39
|
+
initialized = false;
|
|
40
|
+
constructor(db) {
|
|
41
|
+
this.db = db;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Refuse to start unless the table is already there.
|
|
45
|
+
*
|
|
46
|
+
* Never creates it: `pikku db generate` writes the migration and `pikku db
|
|
47
|
+
* migrate` applies it, and those are the only authors of this table.
|
|
48
|
+
*/
|
|
49
|
+
async init() {
|
|
50
|
+
if (this.initialized)
|
|
51
|
+
return;
|
|
52
|
+
await requirePikkuSchema(this.db, analyticsSchema);
|
|
53
|
+
this.initialized = true;
|
|
54
|
+
}
|
|
55
|
+
async write(batch) {
|
|
56
|
+
if (!batch.length)
|
|
57
|
+
return;
|
|
58
|
+
const rows = batch.map((record) => ({
|
|
59
|
+
eventId: eventId(),
|
|
60
|
+
name: record.name,
|
|
61
|
+
occurredAt: record.occurredAt ?? new Date().toISOString(),
|
|
62
|
+
source: record.source ?? 'server',
|
|
63
|
+
functionId: record.functionId ?? null,
|
|
64
|
+
wireType: record.wireType ?? null,
|
|
65
|
+
traceId: record.traceId ?? null,
|
|
66
|
+
userId: record.userIdentity?.userId ?? null,
|
|
67
|
+
orgId: record.userIdentity?.orgId ?? null,
|
|
68
|
+
pikkuUserId: record.userIdentity?.pikkuUserId ?? null,
|
|
69
|
+
anonymousId: record.userIdentity?.anonymousId ?? null,
|
|
70
|
+
vendorIds: jsonOrNull(record.userIdentity?.vendorIds),
|
|
71
|
+
consent: jsonOrNull(record.userIdentity?.consent),
|
|
72
|
+
props: jsonOrNull(record.props),
|
|
73
|
+
}));
|
|
74
|
+
await this.db
|
|
75
|
+
.insertInto('pikkuAnalyticsEvents')
|
|
76
|
+
.values(rows)
|
|
77
|
+
.onConflict((oc) => oc.doNothing())
|
|
78
|
+
.execute();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FeatureFlagStore, FlagOverrideRow, FlagRow } from '@pikku/core/services';
|
|
2
|
+
import type { CachedFlagSourceOptions, DeclaredFlag, FlagConfigSnapshot, FlagSubject } from '@pikku/core/flag';
|
|
3
|
+
import { CachedFlagSource } from '@pikku/core/flag';
|
|
4
|
+
import type { Kysely } from 'kysely';
|
|
5
|
+
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
6
|
+
export type KyselyFeatureFlagStoreOptions = CachedFlagSourceOptions;
|
|
7
|
+
/**
|
|
8
|
+
* Feature flags backed by two tables pikku owns.
|
|
9
|
+
*
|
|
10
|
+
* The full store, not just a source: the declaration lifecycle and the operator
|
|
11
|
+
* controls both live here, which is what makes the console's Flags tab
|
|
12
|
+
* writable. A third-party provider implements `FeatureFlagSource` alone.
|
|
13
|
+
*/
|
|
14
|
+
export declare class KyselyFeatureFlagStore extends CachedFlagSource implements FeatureFlagStore {
|
|
15
|
+
private db;
|
|
16
|
+
private initialized;
|
|
17
|
+
constructor(db: Kysely<KyselyPikkuDB>, options?: KyselyFeatureFlagStoreOptions);
|
|
18
|
+
init(): Promise<void>;
|
|
19
|
+
protected fetchSnapshot(): Promise<FlagConfigSnapshot>;
|
|
20
|
+
syncFlags(flags: DeclaredFlag[]): Promise<void>;
|
|
21
|
+
listFlags(): Promise<FlagRow[]>;
|
|
22
|
+
listOverrides(key: string): Promise<FlagOverrideRow[]>;
|
|
23
|
+
setEnabled(key: string, enabled: boolean, actor?: string, note?: string): Promise<void>;
|
|
24
|
+
setRollout(key: string, percent: number | null, actor?: string): Promise<void>;
|
|
25
|
+
setOverride(key: string, subject: FlagSubject, enabled: boolean, actor?: string): Promise<void>;
|
|
26
|
+
clearOverride(key: string, subject: FlagSubject): Promise<void>;
|
|
27
|
+
findStaleFlags(): Promise<string[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Deletes what is still undeclared, and reports only what it deleted.
|
|
30
|
+
*
|
|
31
|
+
* The `declared = false` predicate is repeated on the delete rather than
|
|
32
|
+
* carried over from the read: a deploy running `syncFlags` between the two
|
|
33
|
+
* would redeclare a flag, and deleting on the earlier answer would take a
|
|
34
|
+
* live flag and cascade away every override an operator had set on it.
|
|
35
|
+
*/
|
|
36
|
+
pruneFlags(): Promise<string[]>;
|
|
37
|
+
}
|