@cosmicdrift/kumiko-samples 0.338.0 → 0.340.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.
Files changed (147) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +21 -1
  3. package/packages/bundled-features/src/auth-email-password/changes.json +7 -0
  4. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +2 -6
  5. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +2 -6
  6. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +3 -5
  7. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +2 -8
  8. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +1 -1
  9. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +1 -1
  10. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +35 -3
  11. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +60 -0
  12. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +10 -0
  13. package/packages/bundled-features/src/auth-email-password/web/index.ts +8 -0
  14. package/packages/bundled-features/src/auth-email-password/web/session.tsx +15 -2
  15. package/packages/bundled-features/src/auth-mfa/feature.ts +1 -1
  16. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -7
  17. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -5
  18. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -7
  19. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +1 -5
  20. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +1 -7
  21. package/packages/bundled-features/src/billing-foundation/changes.json +41 -0
  22. package/packages/bundled-features/src/billing-foundation/constants.ts +11 -0
  23. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +3 -3
  24. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +11 -3
  25. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-text.ts +2 -2
  26. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +48 -2
  27. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +10 -3
  28. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +44 -0
  29. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +32 -2
  30. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +129 -33
  31. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +13 -59
  32. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +20 -21
  33. package/packages/bundled-features/src/billing-foundation/events.ts +18 -0
  34. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +4 -5
  35. package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +6 -5
  36. package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
  37. package/packages/bundled-features/src/billing-foundation/projection.ts +35 -41
  38. package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +4 -4
  39. package/packages/bundled-features/src/billing-foundation/types.ts +6 -0
  40. package/packages/bundled-features/src/billing-foundation/validate-options.ts +6 -0
  41. package/packages/bundled-features/src/channel-discord/discord-channel.ts +32 -0
  42. package/packages/bundled-features/src/channel-discord/feature.ts +40 -0
  43. package/packages/bundled-features/src/channel-discord/index.ts +2 -0
  44. package/packages/bundled-features/src/channel-slack/feature.ts +38 -0
  45. package/packages/bundled-features/src/channel-slack/index.ts +2 -0
  46. package/packages/bundled-features/src/channel-slack/slack-channel.ts +29 -0
  47. package/packages/bundled-features/src/channel-teams/feature.ts +42 -0
  48. package/packages/bundled-features/src/channel-teams/index.ts +2 -0
  49. package/packages/bundled-features/src/channel-teams/teams-channel.ts +50 -0
  50. package/packages/bundled-features/src/channel-telegram/feature.ts +35 -0
  51. package/packages/bundled-features/src/channel-telegram/index.ts +2 -0
  52. package/packages/bundled-features/src/channel-telegram/telegram-channel.ts +74 -0
  53. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +6 -18
  54. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  55. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +1 -4
  56. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  57. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +1 -1
  58. package/packages/bundled-features/src/data-retention/feature.ts +2 -3
  59. package/packages/bundled-features/src/delivery/changes.json +7 -0
  60. package/packages/bundled-features/src/delivery/channel-context.ts +3 -1
  61. package/packages/bundled-features/src/delivery/chat-webhook-channel.ts +76 -0
  62. package/packages/bundled-features/src/delivery/chat-webhook-sender.ts +135 -0
  63. package/packages/bundled-features/src/delivery/delivery-service.ts +3 -0
  64. package/packages/bundled-features/src/delivery/index.ts +15 -0
  65. package/packages/bundled-features/src/delivery/jobs.ts +1 -1
  66. package/packages/bundled-features/src/delivery/testing.ts +2 -0
  67. package/packages/bundled-features/src/delivery/types.ts +8 -3
  68. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +1 -1
  69. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  70. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  71. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  72. package/packages/bundled-features/src/form-draft/handlers/save.write.ts +1 -5
  73. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +14 -0
  74. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +29 -13
  75. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  76. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -1
  77. package/packages/bundled-features/src/inbound-mail-foundation/index.ts +7 -0
  78. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +201 -0
  79. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +57 -58
  80. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +1 -1
  81. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +17 -1
  82. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +74 -36
  83. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +1 -1
  84. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +7 -10
  85. package/packages/bundled-features/src/renderer-simple/changes.json +9 -1
  86. package/packages/bundled-features/src/renderer-simple/feature.ts +14 -4
  87. package/packages/bundled-features/src/renderer-simple/index.ts +2 -2
  88. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +94 -31
  89. package/packages/bundled-features/src/secrets/feature.ts +1 -1
  90. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  91. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
  92. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -4
  93. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -3
  94. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +3 -5
  95. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +1 -1
  96. package/packages/bundled-features/src/tier-engine/feature.ts +1 -1
  97. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +1 -1
  98. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
  99. package/packages/bundled-features/src/user/changes.json +13 -6
  100. package/packages/bundled-features/src/user/handlers/update-roles.ts +7 -23
  101. package/packages/bundled-features/src/user-data-rights/feature.ts +4 -6
  102. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +1 -1
  103. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -3
  104. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +1 -1
  105. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +1 -1
  106. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +1 -1
  107. package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +2 -6
  108. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
  109. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +1 -1
  110. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  111. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +1 -1
  112. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +2 -8
  113. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +1 -1
  114. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +1 -1
  115. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  116. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +1 -1
  117. package/packages/bundled-features/src/workflow-runner/changes.json +14 -0
  118. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +10 -13
  119. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +7 -2
  120. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +5 -0
  121. package/packages/bundled-features/src/workflow-runner/feature.ts +4 -11
  122. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +231 -77
  123. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +1 -0
  124. package/packages/bundled-features/src/workflow-runner/runner.ts +12 -2
  125. package/packages/bundled-features/src/workflow-runner/tables.ts +8 -1
  126. package/samples/apps/styleguide/src/features/widgets/i18n.ts +5 -0
  127. package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +11 -0
  128. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +16 -0
  129. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +152 -0
  130. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +82 -0
  131. package/samples/apps/use-all-bundled/feature-manifest.json +146 -1
  132. package/samples/apps/use-all-bundled/package.json +1 -0
  133. package/samples/apps/use-all-bundled/playwright.billing-gallery.config.ts +14 -0
  134. package/samples/apps/use-all-bundled/schema/generate.ts +20 -0
  135. package/samples/apps/use-all-bundled/scripts/gen-feature-manifest.ts +1 -0
  136. package/samples/apps/use-all-bundled/src/run-config.ts +10 -0
  137. package/samples/apps/use-all-bundled/tsconfig.json +1 -0
  138. package/samples/recipes/chat-channels/README.md +19 -0
  139. package/samples/recipes/chat-channels/package.json +9 -0
  140. package/samples/recipes/chat-channels/src/feature.ts +40 -0
  141. package/samples/recipes/public-share-token/src/feature.ts +1 -1
  142. package/samples/recipes/workflow-engine/src/event-trigger.ts +5 -0
  143. package/samples/recipes/workflow-engine/src/postgres-resume-loop.ts +50 -9
  144. package/samples/recipes/workflow-engine/src/workflow-runner.ts +12 -2
  145. package/packages/bundled-features/src/billing-foundation/db/queries/payment-projection.ts +0 -19
  146. package/packages/bundled-features/src/billing-foundation/db/queries/subscription-projection.ts +0 -15
  147. package/packages/bundled-features/src/inbound-mail-foundation/db/queries/inbound-projections.ts +0 -38
@@ -4,19 +4,23 @@
4
4
  // 1. createSuspendedRunFetcher(db, workflowRegistry) — Postgres-backed
5
5
  // 2. createInMemorySuspendedRunFetcher(runs) — for unit tests
6
6
  //
7
- // The fetcher extracts the trigger-event snapshot + Q7 fingerprint directly
8
- // from the suspension event payload (the wait/waitForEvent/retry steps
9
- // stamp them there), avoiding a per-run lookup against run.started.
7
+ // The fetcher reads the Q7 fingerprint from the suspension event payload and
8
+ // re-loads the trigger event from the event store through the
9
+ // triggerEventRef on the run's own run.started event (no payload copy lives
10
+ // on the run stream).
10
11
 
11
12
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
12
- import type { WorkflowDefinition } from "@cosmicdrift/kumiko-framework/engine";
13
+ import type { TenantId, WorkflowDefinition } from "@cosmicdrift/kumiko-framework/engine";
13
14
  import {
14
15
  WORKFLOW_RETRY_SCHEDULED_TYPE,
16
+ WORKFLOW_RUN_STARTED_TYPE,
15
17
  WORKFLOW_WAITING_FOR_EVENT_TYPE,
16
18
  WORKFLOW_WAITING_TYPE,
17
19
  } from "@cosmicdrift/kumiko-framework/engine";
20
+ import { loadAggregate } from "@cosmicdrift/kumiko-framework/event-store";
18
21
  import { selectExpiredSuspensionEvents } from "./db/queries/suspended-runs";
19
22
  import type { SuspendableRun } from "./resume-loop";
23
+ import type { TriggerEventRef } from "./workflow-runner";
20
24
 
21
25
  export type WorkflowRegistry = ReadonlyMap<string, WorkflowDefinition>;
22
26
 
@@ -26,6 +30,42 @@ const SUSPEND_EVENT_TYPES = [
26
30
  WORKFLOW_RETRY_SCHEDULED_TYPE,
27
31
  ] as const;
28
32
 
33
+ function isTriggerEventRef(value: unknown): value is TriggerEventRef {
34
+ return (
35
+ typeof value === "object" &&
36
+ value !== null &&
37
+ "eventId" in value &&
38
+ typeof value.eventId === "string" &&
39
+ "aggregateId" in value &&
40
+ typeof value.aggregateId === "string" &&
41
+ "version" in value &&
42
+ typeof value.version === "number"
43
+ );
44
+ }
45
+
46
+ // undefined when the run has no usable reference or the trigger event can no
47
+ // longer be loaded (archived stream): the run is skipped, never resumed on
48
+ // made-up data.
49
+ async function loadTriggerEvent(
50
+ db: DbRunner,
51
+ tenantId: TenantId,
52
+ runId: string,
53
+ ): Promise<{ aggregateId: string; type: string; payload: unknown } | undefined> {
54
+ const runEvents = await loadAggregate(db, runId, tenantId);
55
+ const started = runEvents.find((e) => e.type === WORKFLOW_RUN_STARTED_TYPE);
56
+ if (!started) return undefined;
57
+ const ref = started.payload["triggerEventRef"];
58
+ if (ref === undefined) {
59
+ // Cron run: the synthetic trigger event was never stored.
60
+ return { aggregateId: runId, type: String(started.payload["triggerEventType"]), payload: {} };
61
+ }
62
+ if (!isTriggerEventRef(ref)) return undefined;
63
+ const source = (await loadAggregate(db, ref.aggregateId, tenantId)).find(
64
+ (e) => e.id === ref.eventId,
65
+ );
66
+ return source && { aggregateId: source.aggregateId, type: source.type, payload: source.payload };
67
+ }
68
+
29
69
  /**
30
70
  * Postgres-backed fetcher. Queries kumiko_events for suspension events with
31
71
  * expired wakeAt/timeoutAt timestamps. The resume-loop applies the Q7
@@ -64,11 +104,12 @@ export function createSuspendedRunFetcher(
64
104
  const wakeAt = (payload["wakeAt"] ?? payload["timeoutAt"]) as string | undefined;
65
105
  if (!wakeAt) continue;
66
106
 
67
- const triggerEvent = {
68
- aggregateId: (payload["triggerAggregateId"] as string | undefined) ?? aggregateId,
69
- type: (payload["triggerEventType"] as string | undefined) ?? "kumiko:system:resume",
70
- payload: payload["triggerPayload"] ?? {},
71
- };
107
+ const triggerEvent = await loadTriggerEvent(
108
+ db,
109
+ (row["tenantId"] ?? row["tenant_id"]) as TenantId, // @cast-boundary raw-sql-row
110
+ aggregateId,
111
+ );
112
+ if (!triggerEvent) continue;
72
113
 
73
114
  results.push({
74
115
  runId: aggregateId,
@@ -26,10 +26,19 @@ import {
26
26
  WORKFLOW_RUN_STARTED_TYPE,
27
27
  } from "@cosmicdrift/kumiko-framework/engine";
28
28
 
29
+ // Reference into the event store; the run stream never copies a foreign
30
+ // payload, so erasing the source event also covers the workflow.
31
+ export type TriggerEventRef = {
32
+ readonly eventId: string;
33
+ readonly aggregateId: string;
34
+ readonly version: number;
35
+ };
36
+
29
37
  export type WorkflowRunStartedPayload = {
30
38
  readonly workflowName: string;
31
39
  readonly triggerEventType: string;
32
- readonly triggerPayload: unknown;
40
+ // Absent for cron runs: their synthetic trigger event has no stored source.
41
+ readonly triggerEventRef?: TriggerEventRef;
33
42
  readonly definitionFingerprint: string;
34
43
  readonly idempotencyKey?: string;
35
44
  };
@@ -47,6 +56,7 @@ export async function startAndRunWorkflow(args: {
47
56
  readonly runId: string;
48
57
  readonly workflow: WorkflowDefinition;
49
58
  readonly triggerEvent: WriteEvent;
59
+ readonly triggerEventRef?: TriggerEventRef;
50
60
  readonly idempotencyKey?: string;
51
61
  readonly handlerCtx: HandlerContext;
52
62
  }): Promise<{ readonly outcome: "completed" | "suspended" }> {
@@ -55,7 +65,7 @@ export async function startAndRunWorkflow(args: {
55
65
  const startedPayload: WorkflowRunStartedPayload = {
56
66
  workflowName: args.workflow.name,
57
67
  triggerEventType: args.triggerEvent.type,
58
- triggerPayload: args.triggerEvent.payload,
68
+ ...(args.triggerEventRef && { triggerEventRef: args.triggerEventRef }),
59
69
  definitionFingerprint: fingerprint,
60
70
  ...(args.idempotencyKey && { idempotencyKey: args.idempotencyKey }),
61
71
  };
@@ -1,19 +0,0 @@
1
- import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
-
4
- // INSERT-once, not UPSERT: unlike subscriptionsProjectionTable (one row per
5
- // tenant, state overwritten on every event), a payment row is an immutable
6
- // fact keyed by paymentRowId(tenantId, providerName, providerEventId) — ON
7
- // CONFLICT DO NOTHING makes a projection rebuild/replay idempotent without
8
- // ever mutating an existing row.
9
- export async function insertPaymentProjectionRow(
10
- tx: DbRunner,
11
- tableName: string,
12
- insertCols: Record<string, unknown>,
13
- ): Promise<void> {
14
- const insertKeys = Object.keys(insertCols);
15
- const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
16
- const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO NOTHING`;
17
- // kumiko-lint-ignore raw-sql tableName + column keys are code-controlled, not user input: tableName is buildEntityTable()'s static table name and insertKeys come from applyPaymentReceived's hardcoded object-literal columns in projection.ts — only values flow through $1..$n
18
- await asRawClient(tx).unsafe(sqlText, Object.values(insertCols));
19
- }
@@ -1,15 +0,0 @@
1
- import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
-
4
- export async function upsertSubscriptionProjectionRow(
5
- tx: DbRunner,
6
- tableName: string,
7
- insertCols: Record<string, unknown>,
8
- setClauses: readonly string[],
9
- params: readonly unknown[],
10
- ): Promise<void> {
11
- const insertKeys = Object.keys(insertCols);
12
- const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
13
- const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO UPDATE SET ${setClauses.join(", ")}`;
14
- await asRawClient(tx).unsafe(sqlText, params);
15
- }
@@ -1,38 +0,0 @@
1
- // Raw-SQL-Helper für die inline projections — Muster
2
- // billing-foundation/db/queries/subscription-projection.ts.
3
- //
4
- // Warum raw statt drizzle-insert: die apply-fns laufen in der event-TX
5
- // mit einem DbRunner, und die ON-CONFLICT-Semantik (UPSERT vs.
6
- // DO NOTHING) ist der fachliche Kern — explizit hingeschrieben statt
7
- // hinter einem ORM-Builder versteckt.
8
-
9
- import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
10
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
11
-
12
- /** INSERT ... ON CONFLICT ("id") DO UPDATE SET <setClauses>. */
13
- export async function upsertProjectionRow(
14
- tx: DbRunner,
15
- tableName: string,
16
- insertCols: Record<string, unknown>,
17
- setClauses: readonly string[],
18
- params: readonly unknown[],
19
- ): Promise<void> {
20
- const insertKeys = Object.keys(insertCols);
21
- const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
22
- const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO UPDATE SET ${setClauses.join(", ")}`;
23
- await asRawClient(tx).unsafe(sqlText, params);
24
- }
25
-
26
- /** INSERT ... ON CONFLICT ("id") DO NOTHING — für append-only-Rows
27
- * (inbound-message: genau EIN received-event pro Stream, Replays
28
- * no-op'en auf der PK). */
29
- export async function insertIgnoreProjectionRow(
30
- tx: DbRunner,
31
- tableName: string,
32
- insertCols: Record<string, unknown>,
33
- ): Promise<void> {
34
- const insertKeys = Object.keys(insertCols);
35
- const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
36
- const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO NOTHING`;
37
- await asRawClient(tx).unsafe(sqlText, Object.values(insertCols));
38
- }