@pikku/kysely 0.12.18 → 0.13.0

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 CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.13.0
2
+
3
+ ### Minor Changes
4
+
5
+ - 241e6cf: Add `KyselyAuditService` — a durable `AuditService` that persists AuditEvents to an `audit` table via Kysely (the companion sink to `createAuditedKysely`). Its column mapping matches Fabric's platform audit-queue consumer, so a locally-run project and a deployed stage produce identical rows. Use it as the local/dev audit sink so audit events persist and are queryable without the platform queue.
6
+
7
+ ### Patch Changes
8
+
9
+ - 41ce2cb: Upgrade to TypeScript 6 and raise the minimum Node.js version to 22.
10
+
11
+ All packages now build against `typescript@^6.0.3` and declare `engines.node >= 22`. Internal tooling (`ts-json-schema-generator`, `zod-to-ts`) was bumped to TypeScript 6-compatible releases.
12
+
13
+ - Updated dependencies [41ce2cb]
14
+ - @pikku/core@0.12.44
15
+
1
16
  ## 0.12.18
2
17
 
3
18
  ### Patch Changes
@@ -13,6 +13,7 @@ export { KyselyCredentialService } from './kysely-credential-service.js';
13
13
  export type { KyselyCredentialServiceConfig } from './kysely-credential-service.js';
14
14
  export { KyselySessionStore } from './kysely-session-store.js';
15
15
  export { createAuditedKysely, type CreateAuditedKyselyOptions, } from './create-audited-kysely.js';
16
+ export { KyselyAuditService } from './kysely-audit-service.js';
16
17
  export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, defaultSerializer, defaultDeserializer, maybeJson, skipTransform, dateRegex, type Serializer, type Deserializer, type SerializePluginOptions, } from './serialize-plugin.js';
17
18
  export type { KyselyPikkuDB } from './kysely-tables.js';
18
19
  export type { WorkflowRunService } from '@pikku/core/workflow';
package/dist/src/index.js CHANGED
@@ -11,4 +11,5 @@ 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 { createAuditedKysely, } from './create-audited-kysely.js';
14
+ export { KyselyAuditService } from './kysely-audit-service.js';
14
15
  export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, defaultSerializer, defaultDeserializer, maybeJson, skipTransform, dateRegex, } from './serialize-plugin.js';
@@ -0,0 +1,19 @@
1
+ import type { AuditEvent, AuditEventBatch, AuditService } from '@pikku/core';
2
+ import type { Kysely } from 'kysely';
3
+ /**
4
+ * Durable {@link AuditService} that persists AuditEvents to an `audit` table
5
+ * via Kysely — the companion sink to {@link createAuditedKysely}.
6
+ *
7
+ * The column mapping matches a platform audit-queue consumer, so a locally-run
8
+ * project and a deployed stage write identical rows (the read side relies only
9
+ * on `type`, `occurred_at`, `actor_user_id`, and the `data` JSON that holds
10
+ * `metadata`). The `audit` table is an optional per-project migration (not in
11
+ * the generated schema), so rows are inserted untyped; all columns are TEXT on
12
+ * every engine and ON CONFLICT DO NOTHING keeps writes idempotent on retries.
13
+ */
14
+ export declare class KyselyAuditService implements AuditService {
15
+ private db;
16
+ constructor(db: Kysely<any>);
17
+ audit(event: AuditEvent): Promise<void>;
18
+ write(batch: AuditEventBatch): Promise<void>;
19
+ }
@@ -0,0 +1,55 @@
1
+ const jsonOrNull = (v) => v != null ? JSON.stringify(v) : null;
2
+ // No global `crypto` is guaranteed across every runtime, and audit_id is the
3
+ // PK — any collision is dropped by ON CONFLICT DO NOTHING.
4
+ const fallbackId = () => `evt_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
5
+ /**
6
+ * Durable {@link AuditService} that persists AuditEvents to an `audit` table
7
+ * via Kysely — the companion sink to {@link createAuditedKysely}.
8
+ *
9
+ * The column mapping matches a platform audit-queue consumer, so a locally-run
10
+ * project and a deployed stage write identical rows (the read side relies only
11
+ * on `type`, `occurred_at`, `actor_user_id`, and the `data` JSON that holds
12
+ * `metadata`). The `audit` table is an optional per-project migration (not in
13
+ * the generated schema), so rows are inserted untyped; all columns are TEXT on
14
+ * every engine and ON CONFLICT DO NOTHING keeps writes idempotent on retries.
15
+ */
16
+ export class KyselyAuditService {
17
+ db;
18
+ constructor(db) {
19
+ this.db = db;
20
+ }
21
+ async audit(event) {
22
+ await this.write([event]);
23
+ }
24
+ async write(batch) {
25
+ if (!batch.length)
26
+ return;
27
+ const rows = batch.map((e) => {
28
+ const metadata = e.metadata;
29
+ return {
30
+ audit_id: e.eventId ?? e.queryId ?? fallbackId(),
31
+ occurred_at: e.occurredAt ?? new Date().toISOString(),
32
+ type: e.type ?? 'unknown',
33
+ source: e.source ?? 'auto',
34
+ outcome: e.outcome ?? null,
35
+ function_id: e.functionId ?? null,
36
+ wire_type: e.wireType ?? null,
37
+ trace_id: e.traceId ?? null,
38
+ transaction_id: e.transactionId ?? null,
39
+ query_id: e.queryId ?? null,
40
+ actor_user_id: e.actor?.userId ?? null,
41
+ actor_org_id: e.actor?.orgId ?? null,
42
+ tables: jsonOrNull(metadata?.tables),
43
+ changed_cols: jsonOrNull(metadata?.changedColumns),
44
+ event: e.eventId ?? null,
45
+ old: null,
46
+ data: jsonOrNull(e.metadata),
47
+ };
48
+ });
49
+ await this.db
50
+ .insertInto('audit')
51
+ .values(rows)
52
+ .onConflict((oc) => oc.doNothing())
53
+ .execute();
54
+ }
55
+ }
@@ -308,7 +308,11 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
308
308
  if (latestHistory) {
309
309
  await this.db
310
310
  .updateTable('workflowStepHistory')
311
- .set({ status: 'succeeded', result: resultJson, succeededAt: new Date() })
311
+ .set({
312
+ status: 'succeeded',
313
+ result: resultJson,
314
+ succeededAt: new Date(),
315
+ })
312
316
  .where('historyId', '=', latestHistory.historyId)
313
317
  .execute();
314
318
  }
@@ -1 +1 @@
1
- {"root":["../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-ai-agent-run-service.ts","../src/kysely-ai-run-state-service.ts","../src/kysely-ai-storage-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-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../bin/pikku-kysely-pure.ts"],"version":"5.9.3"}
1
+ {"root":["../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-ai-agent-run-service.ts","../src/kysely-ai-run-state-service.ts","../src/kysely-ai-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-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.12.18",
3
+ "version": "0.13.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/src/index.js",
@@ -20,7 +20,7 @@
20
20
  "prepublishOnly": "yarn build"
21
21
  },
22
22
  "peerDependencies": {
23
- "@pikku/core": "^0.12.39"
23
+ "@pikku/core": "^0.12.44"
24
24
  },
25
25
  "dependencies": {
26
26
  "kysely": "^0.29.0"
@@ -31,7 +31,7 @@
31
31
  "kysely-codegen": "^0.20.0",
32
32
  "kysely-postgres-js": "^3.0.0",
33
33
  "postgres": "^3.4.8",
34
- "typescript": "^5.9"
34
+ "typescript": "^6.0.3"
35
35
  },
36
36
  "exports": {
37
37
  ".": "./dist/src/index.js"
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export {
16
16
  createAuditedKysely,
17
17
  type CreateAuditedKyselyOptions,
18
18
  } from './create-audited-kysely.js'
19
+ export { KyselyAuditService } from './kysely-audit-service.js'
19
20
 
20
21
  export {
21
22
  SerializePlugin,
@@ -0,0 +1,62 @@
1
+ import type { AuditEvent, AuditEventBatch, AuditService } from '@pikku/core'
2
+ import type { Kysely } from 'kysely'
3
+
4
+ const jsonOrNull = (v: unknown): string | null =>
5
+ v != null ? JSON.stringify(v) : null
6
+
7
+ // No global `crypto` is guaranteed across every runtime, and audit_id is the
8
+ // PK — any collision is dropped by ON CONFLICT DO NOTHING.
9
+ const fallbackId = (): string =>
10
+ `evt_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`
11
+
12
+ /**
13
+ * Durable {@link AuditService} that persists AuditEvents to an `audit` table
14
+ * via Kysely — the companion sink to {@link createAuditedKysely}.
15
+ *
16
+ * The column mapping matches a platform audit-queue consumer, so a locally-run
17
+ * project and a deployed stage write identical rows (the read side relies only
18
+ * on `type`, `occurred_at`, `actor_user_id`, and the `data` JSON that holds
19
+ * `metadata`). The `audit` table is an optional per-project migration (not in
20
+ * the generated schema), so rows are inserted untyped; all columns are TEXT on
21
+ * every engine and ON CONFLICT DO NOTHING keeps writes idempotent on retries.
22
+ */
23
+ export class KyselyAuditService implements AuditService {
24
+ constructor(private db: Kysely<any>) {}
25
+
26
+ async audit(event: AuditEvent): Promise<void> {
27
+ await this.write([event])
28
+ }
29
+
30
+ async write(batch: AuditEventBatch): Promise<void> {
31
+ if (!batch.length) return
32
+ const rows = batch.map((e) => {
33
+ const metadata = e.metadata as
34
+ | { tables?: unknown; changedColumns?: unknown }
35
+ | undefined
36
+ return {
37
+ audit_id: e.eventId ?? e.queryId ?? fallbackId(),
38
+ occurred_at: e.occurredAt ?? new Date().toISOString(),
39
+ type: e.type ?? 'unknown',
40
+ source: e.source ?? 'auto',
41
+ outcome: e.outcome ?? null,
42
+ function_id: e.functionId ?? null,
43
+ wire_type: e.wireType ?? null,
44
+ trace_id: e.traceId ?? null,
45
+ transaction_id: e.transactionId ?? null,
46
+ query_id: e.queryId ?? null,
47
+ actor_user_id: e.actor?.userId ?? null,
48
+ actor_org_id: e.actor?.orgId ?? null,
49
+ tables: jsonOrNull(metadata?.tables),
50
+ changed_cols: jsonOrNull(metadata?.changedColumns),
51
+ event: e.eventId ?? null,
52
+ old: null,
53
+ data: jsonOrNull(e.metadata),
54
+ }
55
+ })
56
+ await (this.db as any)
57
+ .insertInto('audit')
58
+ .values(rows)
59
+ .onConflict((oc: any) => oc.doNothing())
60
+ .execute()
61
+ }
62
+ }