@pikku/kysely 0.13.24 → 0.13.27
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 +235 -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-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
|
@@ -222,6 +222,29 @@ export interface PikkuRolesTable {
|
|
|
222
222
|
declared: Generated<boolean>;
|
|
223
223
|
createdAt: Generated<Date>;
|
|
224
224
|
}
|
|
225
|
+
/** A feature flag's operator state. What code declared lives beside it. */
|
|
226
|
+
export interface PikkuFeatureFlagsTable {
|
|
227
|
+
name: string;
|
|
228
|
+
description: string | null;
|
|
229
|
+
/** The declared `anyOf` scope list as JSON, or null for a pure switch. */
|
|
230
|
+
anyOf: string | null;
|
|
231
|
+
enabled: Generated<boolean>;
|
|
232
|
+
rolloutPercent: number | null;
|
|
233
|
+
/** False for a flag whose declaration has gone: switchable, but not offered. */
|
|
234
|
+
declared: Generated<boolean>;
|
|
235
|
+
updatedBy: string | null;
|
|
236
|
+
note: string | null;
|
|
237
|
+
updatedAt: Generated<Date>;
|
|
238
|
+
}
|
|
239
|
+
/** One subject's short-circuit, in either direction, over a flag's rollout. */
|
|
240
|
+
export interface PikkuFeatureFlagOverridesTable {
|
|
241
|
+
flag: string;
|
|
242
|
+
subjectId: string;
|
|
243
|
+
subjectKind: string;
|
|
244
|
+
enabled: boolean;
|
|
245
|
+
grantedBy: string | null;
|
|
246
|
+
grantedAt: Generated<Date>;
|
|
247
|
+
}
|
|
225
248
|
export interface PikkuRoleScopesTable {
|
|
226
249
|
role: string;
|
|
227
250
|
scope: string;
|
|
@@ -317,6 +340,25 @@ export interface AuditTable {
|
|
|
317
340
|
old: string | null;
|
|
318
341
|
data: string | null;
|
|
319
342
|
}
|
|
343
|
+
/** One analytics event, as {@link KyselyAnalyticsService} appended it. */
|
|
344
|
+
export interface PikkuAnalyticsEventsTable {
|
|
345
|
+
eventId: string;
|
|
346
|
+
name: string;
|
|
347
|
+
/** ISO 8601, because string ordering is chronological ordering. */
|
|
348
|
+
occurredAt: string;
|
|
349
|
+
source: string;
|
|
350
|
+
functionId: string | null;
|
|
351
|
+
wireType: string | null;
|
|
352
|
+
traceId: string | null;
|
|
353
|
+
userId: string | null;
|
|
354
|
+
orgId: string | null;
|
|
355
|
+
pikkuUserId: string | null;
|
|
356
|
+
/** The device id for a visitor with no session yet. */
|
|
357
|
+
anonymousId: string | null;
|
|
358
|
+
vendorIds: string | null;
|
|
359
|
+
consent: string | null;
|
|
360
|
+
props: string | null;
|
|
361
|
+
}
|
|
320
362
|
/**
|
|
321
363
|
* One turn of a run's transcript.
|
|
322
364
|
*
|
|
@@ -366,6 +408,8 @@ export interface KyselyPikkuDB {
|
|
|
366
408
|
pikkuRoleScopes: PikkuRoleScopesTable;
|
|
367
409
|
pikkuUserRole: PikkuUserRoleTable;
|
|
368
410
|
pikkuUserScope: PikkuUserScopeTable;
|
|
411
|
+
pikkuFeatureFlags: PikkuFeatureFlagsTable;
|
|
412
|
+
pikkuFeatureFlagOverrides: PikkuFeatureFlagOverridesTable;
|
|
369
413
|
channels: ChannelsTable;
|
|
370
414
|
channelSubscriptions: ChannelSubscriptionsTable;
|
|
371
415
|
workflowRuns: WorkflowRunsTable;
|
|
@@ -393,4 +437,5 @@ export interface KyselyPikkuDB {
|
|
|
393
437
|
virtualUserRunStep: VirtualUserRunStepTable;
|
|
394
438
|
virtualUserSchedule: VirtualUserScheduleTable;
|
|
395
439
|
audit: AuditTable;
|
|
440
|
+
pikkuAnalyticsEvents: PikkuAnalyticsEventsTable;
|
|
396
441
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PikkuSchema } from './pikku-schema.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The `pikkuAnalyticsEvents` table {@link KyselyAnalyticsService} appends to.
|
|
4
|
+
*
|
|
5
|
+
* Gated by `ownedBy` so it arrives only for a project that declares events: a
|
|
6
|
+
* database that measures nothing should not carry a table for it.
|
|
7
|
+
*
|
|
8
|
+
* Every column is text, and `occurredAt` is an ISO 8601 string rather than a
|
|
9
|
+
* timestamp, for the same reason the audit trail is: a warehouse loader reading
|
|
10
|
+
* these rows has no shared type to agree on, and string ordering of ISO 8601 is
|
|
11
|
+
* chronological ordering.
|
|
12
|
+
*
|
|
13
|
+
* The identity is spread across columns rather than left inside the JSON
|
|
14
|
+
* because it is what every query groups by — one visitor's funnel, one org's
|
|
15
|
+
* usage — while `props` differs per event and is only ever read back whole.
|
|
16
|
+
*/
|
|
17
|
+
export declare const analyticsSchema: PikkuSchema;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `pikkuAnalyticsEvents` table {@link KyselyAnalyticsService} appends to.
|
|
3
|
+
*
|
|
4
|
+
* Gated by `ownedBy` so it arrives only for a project that declares events: a
|
|
5
|
+
* database that measures nothing should not carry a table for it.
|
|
6
|
+
*
|
|
7
|
+
* Every column is text, and `occurredAt` is an ISO 8601 string rather than a
|
|
8
|
+
* timestamp, for the same reason the audit trail is: a warehouse loader reading
|
|
9
|
+
* these rows has no shared type to agree on, and string ordering of ISO 8601 is
|
|
10
|
+
* chronological ordering.
|
|
11
|
+
*
|
|
12
|
+
* The identity is spread across columns rather than left inside the JSON
|
|
13
|
+
* because it is what every query groups by — one visitor's funnel, one org's
|
|
14
|
+
* usage — while `props` differs per event and is only ever read back whole.
|
|
15
|
+
*/
|
|
16
|
+
export const analyticsSchema = {
|
|
17
|
+
name: 'analytics',
|
|
18
|
+
ownedBy: ['analyticsService'],
|
|
19
|
+
statements: [
|
|
20
|
+
(db) => db.schema
|
|
21
|
+
.createTable('pikkuAnalyticsEvents')
|
|
22
|
+
.addColumn('eventId', 'text', (col) => col.primaryKey())
|
|
23
|
+
.addColumn('name', 'text', (col) => col.notNull())
|
|
24
|
+
.addColumn('occurredAt', 'text', (col) => col.notNull())
|
|
25
|
+
.addColumn('source', 'text', (col) => col.notNull())
|
|
26
|
+
.addColumn('functionId', 'text')
|
|
27
|
+
.addColumn('wireType', 'text')
|
|
28
|
+
.addColumn('traceId', 'text')
|
|
29
|
+
.addColumn('userId', 'text')
|
|
30
|
+
.addColumn('orgId', 'text')
|
|
31
|
+
.addColumn('pikkuUserId', 'text')
|
|
32
|
+
// The device id for a visitor with no session. Its own column beside
|
|
33
|
+
// `userId` and not folded into it: the whole point of the anonymous id
|
|
34
|
+
// is the traffic that has no user to be attributed to yet.
|
|
35
|
+
.addColumn('anonymousId', 'text')
|
|
36
|
+
.addColumn('vendorIds', 'text')
|
|
37
|
+
.addColumn('consent', 'text')
|
|
38
|
+
.addColumn('props', 'text'),
|
|
39
|
+
(db) => db.schema
|
|
40
|
+
.createIndex('idx_pikku_analytics_events_occurred_at')
|
|
41
|
+
.on('pikkuAnalyticsEvents')
|
|
42
|
+
.column('occurredAt'),
|
|
43
|
+
(db) => db.schema
|
|
44
|
+
.createIndex('idx_pikku_analytics_events_name')
|
|
45
|
+
.on('pikkuAnalyticsEvents')
|
|
46
|
+
.column('name'),
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PikkuSchema } from './pikku-schema.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Feature flags and their per-subject overrides.
|
|
4
|
+
*
|
|
5
|
+
* Two tables and not one because the two halves have different cardinality: a
|
|
6
|
+
* flag is one row an operator flips, an override is an unbounded set keyed by
|
|
7
|
+
* organization or user. Folding the overrides into a JSON column on the flag
|
|
8
|
+
* would make granting one tenant early access a read-modify-write of a blob two
|
|
9
|
+
* consoles can race on.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here references `user` or any organization table. A subject id is
|
|
12
|
+
* stored as opaque text on purpose: pikku does not own the organization
|
|
13
|
+
* concept, and a foreign key would make flags require a table shape that only
|
|
14
|
+
* some projects have.
|
|
15
|
+
*/
|
|
16
|
+
export declare const flagSchema: PikkuSchema;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sql } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Feature flags and their per-subject overrides.
|
|
4
|
+
*
|
|
5
|
+
* Two tables and not one because the two halves have different cardinality: a
|
|
6
|
+
* flag is one row an operator flips, an override is an unbounded set keyed by
|
|
7
|
+
* organization or user. Folding the overrides into a JSON column on the flag
|
|
8
|
+
* would make granting one tenant early access a read-modify-write of a blob two
|
|
9
|
+
* consoles can race on.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here references `user` or any organization table. A subject id is
|
|
12
|
+
* stored as opaque text on purpose: pikku does not own the organization
|
|
13
|
+
* concept, and a foreign key would make flags require a table shape that only
|
|
14
|
+
* some projects have.
|
|
15
|
+
*/
|
|
16
|
+
export const flagSchema = {
|
|
17
|
+
name: 'flag',
|
|
18
|
+
ownedBy: ['featureFlags'],
|
|
19
|
+
statements: [
|
|
20
|
+
(db) => db.schema
|
|
21
|
+
.createTable('pikkuFeatureFlags')
|
|
22
|
+
.addColumn('name', 'text', (col) => col.primaryKey())
|
|
23
|
+
.addColumn('description', 'text')
|
|
24
|
+
// The declared `anyOf` scope list, as JSON. Read by the console to say
|
|
25
|
+
// who would see the feature; never read on the request path, where the
|
|
26
|
+
// capability half comes from the session's own scopes.
|
|
27
|
+
.addColumn('anyOf', 'text')
|
|
28
|
+
// Off until somebody turns it on. A flag exists so a feature can ship
|
|
29
|
+
// dark, so the row a deploy creates has to start closed — the opposite
|
|
30
|
+
// default would make merging the declaration the moment of launch.
|
|
31
|
+
.addColumn('enabled', 'boolean', (col) => col.defaultTo(false).notNull())
|
|
32
|
+
// 0-100, or null for no rollout constraint. Null and 100 are not the
|
|
33
|
+
// same statement: null says the percentage is not part of this flag's
|
|
34
|
+
// story, 100 says a rollout reached everyone and could be wound back.
|
|
35
|
+
.addColumn('rolloutPercent', 'integer')
|
|
36
|
+
// False once the declaration has gone from code: still switchable, no
|
|
37
|
+
// longer offered, awaiting `pikku flags prune`. The same additive
|
|
38
|
+
// contract as `pikkuScopes.declared`.
|
|
39
|
+
.addColumn('declared', 'boolean', (col) => col.defaultTo(true).notNull())
|
|
40
|
+
.addColumn('updatedBy', 'text')
|
|
41
|
+
// Why it was flipped. A kill switch is pulled during an incident, and
|
|
42
|
+
// the one question afterwards is always who turned it off and why.
|
|
43
|
+
.addColumn('note', 'text')
|
|
44
|
+
.addColumn('updatedAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
|
|
45
|
+
(db) => db.schema
|
|
46
|
+
.createTable('pikkuFeatureFlagOverrides')
|
|
47
|
+
.addColumn('flag', 'text', (col) => col.notNull().references('pikkuFeatureFlags.name').onDelete('cascade'))
|
|
48
|
+
.addColumn('subjectId', 'text', (col) => col.notNull())
|
|
49
|
+
// Which id this is, for the console to render. Resolution keys on the
|
|
50
|
+
// id alone, because that is all the resolver has: it prefers the
|
|
51
|
+
// organization and falls back to the user, and by then the two are one
|
|
52
|
+
// string.
|
|
53
|
+
.addColumn('subjectKind', 'text', (col) => col.notNull())
|
|
54
|
+
.addColumn('enabled', 'boolean', (col) => col.notNull())
|
|
55
|
+
.addColumn('grantedBy', 'text')
|
|
56
|
+
.addColumn('grantedAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
57
|
+
.addPrimaryKeyConstraint('pikku_feature_flag_overrides_pk', [
|
|
58
|
+
'flag',
|
|
59
|
+
'subjectId',
|
|
60
|
+
]),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
@@ -18,10 +18,12 @@ export { rawStatement, requiredType, schemaContext, unqualifiedContext, } from '
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const pikkuSchemas: PikkuSchema[];
|
|
20
20
|
export { agentSchema } from './agent.schema.js';
|
|
21
|
+
export { analyticsSchema } from './analytics.schema.js';
|
|
21
22
|
export { auditSchema } from './audit.schema.js';
|
|
22
23
|
export { channelSchema } from './channel.schema.js';
|
|
23
24
|
export { credentialSchema } from './credential.schema.js';
|
|
24
25
|
export { deploymentSchema } from './deployment.schema.js';
|
|
26
|
+
export { flagSchema } from './flag.schema.js';
|
|
25
27
|
export { scopeSchema } from './scope.schema.js';
|
|
26
28
|
export { secretSchema } from './secret.schema.js';
|
|
27
29
|
export { sessionSchema } from './session.schema.js';
|
package/dist/src/schema/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { CamelCasePlugin } from 'kysely';
|
|
2
2
|
import { schemaContext, } from './pikku-schema.types.js';
|
|
3
3
|
import { agentSchema } from './agent.schema.js';
|
|
4
|
+
import { analyticsSchema } from './analytics.schema.js';
|
|
4
5
|
import { auditSchema } from './audit.schema.js';
|
|
5
6
|
import { channelSchema } from './channel.schema.js';
|
|
6
7
|
import { credentialSchema } from './credential.schema.js';
|
|
7
8
|
import { deploymentSchema } from './deployment.schema.js';
|
|
8
9
|
import { scopeSchema } from './scope.schema.js';
|
|
10
|
+
import { flagSchema } from './flag.schema.js';
|
|
9
11
|
import { secretSchema } from './secret.schema.js';
|
|
10
12
|
import { sessionSchema } from './session.schema.js';
|
|
11
13
|
import { webhookSchema } from './webhook.schema.js';
|
|
@@ -37,15 +39,19 @@ export const pikkuSchemas = [
|
|
|
37
39
|
workflowSchema,
|
|
38
40
|
agentSchema,
|
|
39
41
|
scopeSchema,
|
|
42
|
+
flagSchema,
|
|
40
43
|
auditSchema,
|
|
44
|
+
analyticsSchema,
|
|
41
45
|
virtualUserSchema,
|
|
42
46
|
virtualUserScheduleSchema,
|
|
43
47
|
];
|
|
44
48
|
export { agentSchema } from './agent.schema.js';
|
|
49
|
+
export { analyticsSchema } from './analytics.schema.js';
|
|
45
50
|
export { auditSchema } from './audit.schema.js';
|
|
46
51
|
export { channelSchema } from './channel.schema.js';
|
|
47
52
|
export { credentialSchema } from './credential.schema.js';
|
|
48
53
|
export { deploymentSchema } from './deployment.schema.js';
|
|
54
|
+
export { flagSchema } from './flag.schema.js';
|
|
49
55
|
export { scopeSchema } from './scope.schema.js';
|
|
50
56
|
export { secretSchema } from './secret.schema.js';
|
|
51
57
|
export { sessionSchema } from './session.schema.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.27",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
7
|
+
"directory": "packages/services/kysely"
|
|
8
|
+
},
|
|
4
9
|
"description": "Kysely-backed Pikku services — workflow state, secrets, sessions, scopes and audit",
|
|
5
10
|
"author": "yasser.fadl@gmail.com",
|
|
6
11
|
"license": "MIT",
|
|
@@ -18,23 +23,23 @@
|
|
|
18
23
|
"test:watch": "bash run-tests.sh --watch",
|
|
19
24
|
"test:coverage": "bash run-tests.sh --coverage",
|
|
20
25
|
"release": "npm run build && npm test",
|
|
21
|
-
"prepublishOnly": "
|
|
26
|
+
"prepublishOnly": "bun run build"
|
|
22
27
|
},
|
|
23
28
|
"peerDependencies": {
|
|
24
|
-
"@pikku/core": "^0.12.
|
|
29
|
+
"@pikku/core": "^0.12.111"
|
|
25
30
|
},
|
|
26
31
|
"dependencies": {
|
|
27
32
|
"kysely": "^0.29.5"
|
|
28
33
|
},
|
|
29
34
|
"devDependencies": {
|
|
35
|
+
"@electric-sql/pglite": "^0.5.5",
|
|
30
36
|
"@types/better-sqlite3": "^9.6.0",
|
|
31
37
|
"better-sqlite3": "^13.0.3",
|
|
32
38
|
"kysely-codegen": "^0.20.0",
|
|
33
39
|
"kysely-postgres-js": "^3.0.0",
|
|
34
|
-
"postgres": "^3.4.9"
|
|
35
|
-
"typescript": "^6.0.3"
|
|
40
|
+
"postgres": "^3.4.9"
|
|
36
41
|
},
|
|
37
42
|
"exports": {
|
|
38
43
|
".": "./dist/src/index.js"
|
|
39
44
|
}
|
|
40
|
-
}
|
|
45
|
+
}
|
package/run-tests.sh
CHANGED
|
@@ -34,10 +34,17 @@ files=($(find src -type f -name "*.test.ts"))
|
|
|
34
34
|
# Check if any files matched the pattern
|
|
35
35
|
if [ ${#files[@]} -eq 0 ]; then
|
|
36
36
|
echo "No test files found matching pattern: $pattern"
|
|
37
|
+
if [ "${STRICT_TEST_DISCOVERY}" = "1" ]; then
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
37
40
|
exit 0
|
|
38
41
|
fi
|
|
39
42
|
|
|
40
43
|
# Construct the node command
|
|
44
|
+
#
|
|
45
|
+
# Every other package runs its tests on bun. This one stays on node because
|
|
46
|
+
# better-sqlite3, which every one of these suites opens its fixture database
|
|
47
|
+
# with, aborts the bun process on load.
|
|
41
48
|
node_cmd=(node --import tsx --test)
|
|
42
49
|
|
|
43
50
|
# Append options based on flags
|
|
@@ -47,7 +54,8 @@ fi
|
|
|
47
54
|
|
|
48
55
|
if [ "$coverage_mode" = true ]; then
|
|
49
56
|
node_cmd+=(--test-coverage-include="src/**/*.{ts,js}" --test-coverage-exclude="**/dist/**" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info)
|
|
57
|
+
export PIKKU_TEST_COVERAGE=1
|
|
50
58
|
fi
|
|
51
59
|
|
|
52
60
|
# Execute the node command with the expanded list of files
|
|
53
|
-
"${node_cmd[@]}" "${files[@]}"
|
|
61
|
+
"${node_cmd[@]}" "${files[@]}"
|
package/bin/pikku-kysely-pure.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { mkdir, readFile, writeFile } from 'fs/promises'
|
|
4
|
-
import { dirname } from 'path'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
-
const __dirname = dirname(__filename)
|
|
9
|
-
|
|
10
|
-
const main = async (output: string, input?: string) => {
|
|
11
|
-
let execDirectory = process.cwd()
|
|
12
|
-
let inputPath = `${__dirname}/../node_modules/kysely-codegen/dist/db.d.ts`
|
|
13
|
-
if (input) {
|
|
14
|
-
inputPath = `${execDirectory}/${input}`
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let kysely: string
|
|
18
|
-
try {
|
|
19
|
-
kysely = await readFile(inputPath, 'utf8')
|
|
20
|
-
} catch {
|
|
21
|
-
console.error(`Could not read file ${input}`)
|
|
22
|
-
process.exit(1)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
kysely = kysely
|
|
26
|
-
.replace('import type { ColumnType } from "kysely";', '')
|
|
27
|
-
.replace(
|
|
28
|
-
new RegExp('^export\\s+type\\s+Generated<[^>]+>.*?;\\s*$', 'ms'),
|
|
29
|
-
''
|
|
30
|
-
)
|
|
31
|
-
.replace(/Generated<(.*)>/g, '$1')
|
|
32
|
-
.replace(
|
|
33
|
-
/export type Numeric =.*;/,
|
|
34
|
-
'export type Numeric = string | number'
|
|
35
|
-
)
|
|
36
|
-
.replace(/export type Timestamp =.*;/, 'export type Timestamp = Date')
|
|
37
|
-
.replace(
|
|
38
|
-
/export type ArrayType<T> = ArrayTypeImpl<T> extends \(infer U\)\[\]\s*\?\s*U\[\]\s*:\s*ArrayTypeImpl<T>;\s*export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U>\s*\?\s*ColumnType<S\[\], I\[\], U\[\]>\s*:\s*T\[\];/gs,
|
|
39
|
-
''
|
|
40
|
-
)
|
|
41
|
-
.replace(/ArrayType<([^>]+)>/g, '$1[]')
|
|
42
|
-
|
|
43
|
-
console.log('Writing kysely-pure.gen.ts')
|
|
44
|
-
|
|
45
|
-
const outputPath = `${execDirectory}/${output}`
|
|
46
|
-
const outputDirectory = dirname(outputPath)
|
|
47
|
-
await mkdir(outputDirectory, { recursive: true })
|
|
48
|
-
await writeFile(outputPath, kysely, 'utf-8')
|
|
49
|
-
console.log(outputPath)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (process.argv.length !== 4) {
|
|
53
|
-
console.error('Expected the following usage:')
|
|
54
|
-
console.error('node kysely-pure.js <output> <input>')
|
|
55
|
-
process.exit(1)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const [, , output, input] = process.argv
|
|
59
|
-
main(output!, input)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { LockRecord, LockVault } from '@pikku/core/classification';
|
|
3
|
-
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
4
|
-
/**
|
|
5
|
-
* Lock records kept in the application's own database.
|
|
6
|
-
*
|
|
7
|
-
* This is what makes a sealed column survive a restart. `createMemoryLockVault`
|
|
8
|
-
* is fine for a test, but its salts die with the process and a sealed row whose
|
|
9
|
-
* salt is gone is not recoverable by anyone — so a real deployment stores them
|
|
10
|
-
* next to the rows they protect, in the same backup and the same restore.
|
|
11
|
-
*
|
|
12
|
-
* The table holds no plaintext key material, only salts and verifiers, which is
|
|
13
|
-
* why it is readable while the store is locked.
|
|
14
|
-
*/
|
|
15
|
-
export declare class KyselyLockVault implements LockVault {
|
|
16
|
-
private readonly db;
|
|
17
|
-
constructor(db: Kysely<KyselyPikkuDB>);
|
|
18
|
-
read(): Promise<LockRecord[]>;
|
|
19
|
-
/**
|
|
20
|
-
* Replace the whole record set.
|
|
21
|
-
*
|
|
22
|
-
* Whole-set rather than per-row because the callers that write are
|
|
23
|
-
* initialization and rotation, and both produce a complete keyring: applying
|
|
24
|
-
* one as a partial update would leave records from the previous key behind,
|
|
25
|
-
* and a stale salt is indistinguishable from a current one.
|
|
26
|
-
*/
|
|
27
|
-
write(records: LockRecord[]): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Asked through introspection rather than by matching the driver's error
|
|
30
|
-
* text, so the answer does not depend on which engine phrased the failure or
|
|
31
|
-
* on the language it phrased it in.
|
|
32
|
-
*/
|
|
33
|
-
private tableExists;
|
|
34
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { unsafeAsWrapped } from '@pikku/core/classification';
|
|
2
|
-
/**
|
|
3
|
-
* Lock records kept in the application's own database.
|
|
4
|
-
*
|
|
5
|
-
* This is what makes a sealed column survive a restart. `createMemoryLockVault`
|
|
6
|
-
* is fine for a test, but its salts die with the process and a sealed row whose
|
|
7
|
-
* salt is gone is not recoverable by anyone — so a real deployment stores them
|
|
8
|
-
* next to the rows they protect, in the same backup and the same restore.
|
|
9
|
-
*
|
|
10
|
-
* The table holds no plaintext key material, only salts and verifiers, which is
|
|
11
|
-
* why it is readable while the store is locked.
|
|
12
|
-
*/
|
|
13
|
-
export class KyselyLockVault {
|
|
14
|
-
db;
|
|
15
|
-
constructor(db) {
|
|
16
|
-
this.db = db;
|
|
17
|
-
}
|
|
18
|
-
async read() {
|
|
19
|
-
try {
|
|
20
|
-
const rows = await this.db
|
|
21
|
-
.selectFrom('dataLocks')
|
|
22
|
-
.select(['keyId', 'keyVersion', 'salt', 'verifier'])
|
|
23
|
-
.execute();
|
|
24
|
-
return rows.map((row) => ({
|
|
25
|
-
keyId: row.keyId,
|
|
26
|
-
// SQLite hands back whatever it stored and postgres can widen an
|
|
27
|
-
// integer to a string over the wire, while `keyVersion` is compared
|
|
28
|
-
// and stamped as a number.
|
|
29
|
-
keyVersion: Number(row.keyVersion),
|
|
30
|
-
salt: row.salt,
|
|
31
|
-
verifier: unsafeAsWrapped(row.verifier),
|
|
32
|
-
}));
|
|
33
|
-
}
|
|
34
|
-
catch (cause) {
|
|
35
|
-
if (await this.tableExists()) {
|
|
36
|
-
throw cause;
|
|
37
|
-
}
|
|
38
|
-
// No table yet is a legitimate first run — the migration has not been
|
|
39
|
-
// applied, nobody has chosen a passphrase, and the server should come up
|
|
40
|
-
// and offer to initialize rather than refuse to start.
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Replace the whole record set.
|
|
46
|
-
*
|
|
47
|
-
* Whole-set rather than per-row because the callers that write are
|
|
48
|
-
* initialization and rotation, and both produce a complete keyring: applying
|
|
49
|
-
* one as a partial update would leave records from the previous key behind,
|
|
50
|
-
* and a stale salt is indistinguishable from a current one.
|
|
51
|
-
*/
|
|
52
|
-
async write(records) {
|
|
53
|
-
await this.db.transaction().execute(async (trx) => {
|
|
54
|
-
await trx.deleteFrom('dataLocks').execute();
|
|
55
|
-
if (records.length) {
|
|
56
|
-
await trx
|
|
57
|
-
.insertInto('dataLocks')
|
|
58
|
-
.values(records.map((record) => ({
|
|
59
|
-
keyId: record.keyId,
|
|
60
|
-
keyVersion: record.keyVersion,
|
|
61
|
-
salt: record.salt,
|
|
62
|
-
verifier: record.verifier,
|
|
63
|
-
})))
|
|
64
|
-
.execute();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Asked through introspection rather than by matching the driver's error
|
|
70
|
-
* text, so the answer does not depend on which engine phrased the failure or
|
|
71
|
-
* on the language it phrased it in.
|
|
72
|
-
*/
|
|
73
|
-
async tableExists() {
|
|
74
|
-
const tables = await this.db.introspection.getTables();
|
|
75
|
-
return tables.some((table) => table.name === 'data_locks');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { PikkuSchema } from './pikku-schema.types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Where the passphrase's salts and verifiers live.
|
|
4
|
-
*
|
|
5
|
-
* Deliberately the one table in a classified deployment that holds nothing
|
|
6
|
-
* encrypted. It has to be readable before anything has been unlocked — a store
|
|
7
|
-
* that needed its own key to find out how to open itself could never be opened
|
|
8
|
-
* — and that costs nothing, because a salt and a verifier reveal no key
|
|
9
|
-
* material.
|
|
10
|
-
*
|
|
11
|
-
* Losing this table is losing the data: every sealed row is opened by a KEK
|
|
12
|
-
* derived from the salt kept here, so a deployment that ships without the
|
|
13
|
-
* migration silently turns its own rows into noise the first time it restarts.
|
|
14
|
-
*/
|
|
15
|
-
export declare const dataLockSchema: PikkuSchema;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely';
|
|
2
|
-
/**
|
|
3
|
-
* Where the passphrase's salts and verifiers live.
|
|
4
|
-
*
|
|
5
|
-
* Deliberately the one table in a classified deployment that holds nothing
|
|
6
|
-
* encrypted. It has to be readable before anything has been unlocked — a store
|
|
7
|
-
* that needed its own key to find out how to open itself could never be opened
|
|
8
|
-
* — and that costs nothing, because a salt and a verifier reveal no key
|
|
9
|
-
* material.
|
|
10
|
-
*
|
|
11
|
-
* Losing this table is losing the data: every sealed row is opened by a KEK
|
|
12
|
-
* derived from the salt kept here, so a deployment that ships without the
|
|
13
|
-
* migration silently turns its own rows into noise the first time it restarts.
|
|
14
|
-
*/
|
|
15
|
-
export const dataLockSchema = {
|
|
16
|
-
name: 'data-lock',
|
|
17
|
-
ownedBy: ['dataLock'],
|
|
18
|
-
statements: [
|
|
19
|
-
(db) => db.schema
|
|
20
|
-
.createTable('dataLocks')
|
|
21
|
-
.addColumn('keyId', 'varchar(255)', (col) => col.primaryKey())
|
|
22
|
-
.addColumn('keyVersion', 'integer', (col) => col.notNull())
|
|
23
|
-
.addColumn('salt', 'varchar(64)', (col) => col.notNull())
|
|
24
|
-
.addColumn('verifier', 'text', (col) => col.notNull())
|
|
25
|
-
.addColumn('createdAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
|
|
26
|
-
],
|
|
27
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["../src/classification-crypto.ts","../src/classification-plugin.ts","../src/coercion-plugin.ts","../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-agent-run-scores.ts","../src/kysely-agent-run-service.ts","../src/kysely-agent-run-state-service.ts","../src/kysely-agent-storage-service.ts","../src/kysely-audit-service.ts","../src/kysely-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-scope-service.ts","../src/kysely-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-virtual-user-run-store.ts","../src/kysely-virtual-user-schedule-store.ts","../src/kysely-webhook-service.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../src/schema/agent.schema.ts","../src/schema/audit.schema.ts","../src/schema/channel.schema.ts","../src/schema/credential.schema.ts","../src/schema/deployment.schema.ts","../src/schema/index.ts","../src/schema/pikku-schema.types.ts","../src/schema/scope.schema.ts","../src/schema/secret.schema.ts","../src/schema/session.schema.ts","../src/schema/virtual-user-schedule.schema.ts","../src/schema/virtual-user.schema.ts","../src/schema/webhook.schema.ts","../src/schema/workflow.schema.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
|