@sixb/core 0.1.0 → 0.1.1

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 (214) hide show
  1. package/README.md +24 -4
  2. package/dist/actions/index.js +17 -17
  3. package/dist/actions/worker.js +7 -7
  4. package/dist/actions/worker.js.map +1 -1
  5. package/dist/agents/adapters.d.ts.map +1 -1
  6. package/dist/agents/builders.d.ts +8 -1
  7. package/dist/agents/builders.d.ts.map +1 -1
  8. package/dist/agents/context.js +2 -2
  9. package/dist/agents/errors.d.ts +15 -0
  10. package/dist/agents/errors.d.ts.map +1 -1
  11. package/dist/agents/index.d.ts +4 -4
  12. package/dist/agents/index.d.ts.map +1 -1
  13. package/dist/agents/index.js +30 -11
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/agents/tool-definition.d.ts +9 -0
  16. package/dist/agents/tool-definition.d.ts.map +1 -0
  17. package/dist/agents/types.d.ts +64 -0
  18. package/dist/agents/types.d.ts.map +1 -1
  19. package/dist/agents/validation.d.ts +14 -1
  20. package/dist/agents/validation.d.ts.map +1 -1
  21. package/dist/auth/index.js +4 -4
  22. package/dist/authorization/index.js +3 -3
  23. package/dist/blob-storage/browser.js +3 -2
  24. package/dist/blob-storage/browser.js.map +1 -1
  25. package/dist/blob-storage/index.js +4 -3
  26. package/dist/blob-storage/index.js.map +1 -1
  27. package/dist/blob-storage/validation.d.ts.map +1 -1
  28. package/dist/bootstrap/index.js +25 -25
  29. package/dist/broker/index.js +2 -2
  30. package/dist/{chunk-590dejbp.js → chunk-4hwxq5e6.js} +1 -1
  31. package/dist/{chunk-wj01s5hr.js → chunk-4tvj3ate.js} +1 -1
  32. package/dist/{chunk-tsk2bxs3.js → chunk-53s4asfm.js} +13 -13
  33. package/dist/{chunk-tsk2bxs3.js.map → chunk-53s4asfm.js.map} +3 -3
  34. package/dist/{chunk-8r8cw2xw.js → chunk-63te770v.js} +40 -5
  35. package/dist/{chunk-8r8cw2xw.js.map → chunk-63te770v.js.map} +3 -3
  36. package/dist/{chunk-vj723r3p.js → chunk-6rzdtvn5.js} +1 -1
  37. package/dist/{chunk-5yny9z0e.js → chunk-6zrpewy4.js} +1 -1
  38. package/dist/{chunk-nvamz5dv.js → chunk-86459jn0.js} +294 -22
  39. package/dist/chunk-86459jn0.js.map +22 -0
  40. package/dist/{chunk-p6r5g77m.js → chunk-8kv2sez0.js} +1 -1
  41. package/dist/{chunk-k41ajtz7.js → chunk-9k3hgv8h.js} +1 -1
  42. package/dist/{chunk-jdwx7vzs.js → chunk-avppcgt6.js} +7 -7
  43. package/dist/{chunk-jdwx7vzs.js.map → chunk-avppcgt6.js.map} +1 -1
  44. package/dist/{chunk-2gpr6jsz.js → chunk-b1bqvhpe.js} +2 -2
  45. package/dist/{chunk-pv1yad91.js → chunk-b5dp7gaf.js} +17 -90
  46. package/dist/{chunk-pv1yad91.js.map → chunk-b5dp7gaf.js.map} +5 -6
  47. package/dist/{chunk-1vffvydj.js → chunk-be9vm29v.js} +6 -6
  48. package/dist/chunk-be9vm29v.js.map +10 -0
  49. package/dist/{chunk-wxhdnwh4.js → chunk-bh8bwma3.js} +1 -1
  50. package/dist/{chunk-0psb6kdw.js → chunk-btcsywk4.js} +12 -16
  51. package/dist/{chunk-0psb6kdw.js.map → chunk-btcsywk4.js.map} +3 -3
  52. package/dist/{chunk-0fygrz37.js → chunk-exrfbt2d.js} +1 -1
  53. package/dist/{chunk-c3zd880g.js → chunk-f6kyyeef.js} +2 -2
  54. package/dist/{chunk-q4qzq0ea.js → chunk-hb9xcx9c.js} +3 -3
  55. package/dist/{chunk-4pycxcj3.js → chunk-hz8m927w.js} +6 -9
  56. package/dist/{chunk-4pycxcj3.js.map → chunk-hz8m927w.js.map} +3 -3
  57. package/dist/{chunk-btpe64e4.js → chunk-m9w0artv.js} +1 -1
  58. package/dist/{chunk-45tw90k7.js → chunk-md9jbrm9.js} +1 -1
  59. package/dist/{chunk-dhmj53mx.js → chunk-nbywkytr.js} +3 -3
  60. package/dist/{chunk-t358gywy.js → chunk-p7vj8xgb.js} +8 -8
  61. package/dist/{chunk-2s6sydsh.js → chunk-qzyemcj7.js} +294 -4
  62. package/dist/chunk-qzyemcj7.js.map +13 -0
  63. package/dist/{chunk-r65yscvk.js → chunk-r82k4z7z.js} +131 -13
  64. package/dist/chunk-r82k4z7z.js.map +13 -0
  65. package/dist/{chunk-aebzcsxr.js → chunk-raq0x6bd.js} +11 -9
  66. package/dist/{chunk-aebzcsxr.js.map → chunk-raq0x6bd.js.map} +4 -4
  67. package/dist/{chunk-hsv43v58.js → chunk-rgzed50y.js} +77 -22
  68. package/dist/chunk-rgzed50y.js.map +22 -0
  69. package/dist/{chunk-yvef9xwc.js → chunk-rjqm6ksg.js} +7 -7
  70. package/dist/{chunk-f2b5e2gf.js → chunk-sb0shdvv.js} +18 -3
  71. package/dist/{chunk-f2b5e2gf.js.map → chunk-sb0shdvv.js.map} +3 -3
  72. package/dist/{chunk-dcgrzj1t.js → chunk-xt6qx9sh.js} +1 -1
  73. package/dist/{chunk-mx84y05q.js → chunk-y2ygpd0m.js} +2 -2
  74. package/dist/{chunk-yxss87qa.js → chunk-yn947ck3.js} +1 -1
  75. package/dist/datasets/builders.d.ts +18 -5
  76. package/dist/datasets/builders.d.ts.map +1 -1
  77. package/dist/datasets/changes.d.ts +18 -0
  78. package/dist/datasets/changes.d.ts.map +1 -0
  79. package/dist/datasets/index.d.ts +3 -1
  80. package/dist/datasets/index.d.ts.map +1 -1
  81. package/dist/datasets/types.d.ts +2 -0
  82. package/dist/datasets/types.d.ts.map +1 -1
  83. package/dist/datasets/validation.d.ts.map +1 -1
  84. package/dist/events/index.js +2 -2
  85. package/dist/http/api-routes.d.ts.map +1 -1
  86. package/dist/http/index.js +1 -1
  87. package/dist/index.d.ts +6 -6
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +64 -58
  90. package/dist/index.js.map +1 -1
  91. package/dist/json.d.ts +8 -1
  92. package/dist/json.d.ts.map +1 -1
  93. package/dist/lake-storage/definition-updates.d.ts.map +1 -1
  94. package/dist/lake-storage/in-memory.d.ts +15 -1
  95. package/dist/lake-storage/in-memory.d.ts.map +1 -1
  96. package/dist/lake-storage/index.d.ts +3 -1
  97. package/dist/lake-storage/index.d.ts.map +1 -1
  98. package/dist/lake-storage/index.js +15 -7
  99. package/dist/lake-storage/index.js.map +1 -1
  100. package/dist/lake-storage/merge-validation.d.ts +10 -0
  101. package/dist/lake-storage/merge-validation.d.ts.map +1 -0
  102. package/dist/lake-storage/merge.d.ts +32 -0
  103. package/dist/lake-storage/merge.d.ts.map +1 -0
  104. package/dist/lake-storage/types.d.ts +3 -1
  105. package/dist/lake-storage/types.d.ts.map +1 -1
  106. package/dist/logging/index.js +3 -3
  107. package/dist/logging/types.js +2 -2
  108. package/dist/materialization/index.js +2 -2
  109. package/dist/materializer/index.js +15 -15
  110. package/dist/materializer/index.js.map +1 -1
  111. package/dist/objects/index.js +17 -17
  112. package/dist/objects/query/index.js +8 -8
  113. package/dist/ontology/index.js +5 -5
  114. package/dist/ontology/internal.js +7 -6
  115. package/dist/ontology/internal.js.map +3 -3
  116. package/dist/ontology/validation/normalize.d.ts.map +1 -1
  117. package/dist/projections/internal.js +4 -4
  118. package/dist/runtime/sixb.d.ts.map +1 -1
  119. package/dist/storage/index.d.ts +1 -1
  120. package/dist/storage/index.d.ts.map +1 -1
  121. package/dist/storage/index.js +16 -16
  122. package/dist/storage/index.js.map +1 -1
  123. package/dist/storage/ontology/provider.js +4 -4
  124. package/dist/storage/sync-runs/index.d.ts +1 -1
  125. package/dist/storage/sync-runs/index.d.ts.map +1 -1
  126. package/dist/storage/sync-runs/types.d.ts +7 -3
  127. package/dist/storage/sync-runs/types.d.ts.map +1 -1
  128. package/dist/storage/types.d.ts +1 -1
  129. package/dist/storage/types.d.ts.map +1 -1
  130. package/dist/storage/workflow-runs/in-memory.d.ts.map +1 -1
  131. package/dist/storage/workflow-runs/types.d.ts +2 -0
  132. package/dist/storage/workflow-runs/types.d.ts.map +1 -1
  133. package/dist/syncs/builders.d.ts +11 -3
  134. package/dist/syncs/builders.d.ts.map +1 -1
  135. package/dist/syncs/index.d.ts +1 -1
  136. package/dist/syncs/index.d.ts.map +1 -1
  137. package/dist/syncs/types.d.ts +24 -19
  138. package/dist/syncs/types.d.ts.map +1 -1
  139. package/dist/syncs/validation.d.ts +14 -0
  140. package/dist/syncs/validation.d.ts.map +1 -0
  141. package/dist/testing/index.d.ts +1 -0
  142. package/dist/testing/index.d.ts.map +1 -1
  143. package/dist/testing/index.js +837 -583
  144. package/dist/testing/index.js.map +5 -4
  145. package/dist/testing/lake-merge-storage-contract.d.ts +7 -0
  146. package/dist/testing/lake-merge-storage-contract.d.ts.map +1 -0
  147. package/dist/testing/lake-storage-contract.d.ts.map +1 -1
  148. package/dist/workflows/index.js +23 -23
  149. package/package.json +1 -1
  150. package/src/agents/adapters.ts +1 -9
  151. package/src/agents/builders.ts +53 -3
  152. package/src/agents/errors.ts +22 -0
  153. package/src/agents/index.ts +22 -2
  154. package/src/agents/prompt.ts +2 -2
  155. package/src/agents/tool-definition.ts +93 -0
  156. package/src/agents/types.ts +101 -0
  157. package/src/agents/validation.ts +287 -3
  158. package/src/blob-storage/validation.ts +2 -10
  159. package/src/datasets/builders.ts +49 -5
  160. package/src/datasets/changes.ts +13 -0
  161. package/src/datasets/index.ts +3 -0
  162. package/src/datasets/types.ts +5 -0
  163. package/src/datasets/validation.ts +53 -11
  164. package/src/http/api-routes.ts +39 -4
  165. package/src/index.ts +18 -1
  166. package/src/json.ts +11 -4
  167. package/src/lake-storage/definition-updates.ts +21 -0
  168. package/src/lake-storage/in-memory.ts +284 -2
  169. package/src/lake-storage/index.ts +14 -0
  170. package/src/lake-storage/merge-validation.ts +117 -0
  171. package/src/lake-storage/merge.ts +35 -0
  172. package/src/lake-storage/types.ts +3 -1
  173. package/src/ontology/json-schema.ts +2 -1
  174. package/src/ontology/validation/normalize.ts +9 -15
  175. package/src/runtime/sixb.ts +16 -1
  176. package/src/storage/index.ts +1 -0
  177. package/src/storage/sync-runs/in-memory.ts +2 -2
  178. package/src/storage/sync-runs/index.ts +1 -0
  179. package/src/storage/sync-runs/types.ts +9 -4
  180. package/src/storage/types.ts +1 -0
  181. package/src/storage/workflow-runs/in-memory.ts +2 -0
  182. package/src/storage/workflow-runs/types.ts +2 -0
  183. package/src/syncs/builders.ts +31 -13
  184. package/src/syncs/index.ts +1 -0
  185. package/src/syncs/types.ts +44 -18
  186. package/src/syncs/validation.ts +77 -0
  187. package/src/testing/index.ts +4 -0
  188. package/src/testing/lake-merge-storage-contract.ts +302 -0
  189. package/src/testing/lake-storage-contract.ts +23 -0
  190. package/src/workflows/snapshot.ts +1 -10
  191. package/dist/chunk-1vffvydj.js.map +0 -10
  192. package/dist/chunk-2s6sydsh.js.map +0 -12
  193. package/dist/chunk-hsv43v58.js.map +0 -21
  194. package/dist/chunk-nvamz5dv.js.map +0 -21
  195. package/dist/chunk-r65yscvk.js.map +0 -11
  196. /package/dist/{chunk-590dejbp.js.map → chunk-4hwxq5e6.js.map} +0 -0
  197. /package/dist/{chunk-wj01s5hr.js.map → chunk-4tvj3ate.js.map} +0 -0
  198. /package/dist/{chunk-vj723r3p.js.map → chunk-6rzdtvn5.js.map} +0 -0
  199. /package/dist/{chunk-5yny9z0e.js.map → chunk-6zrpewy4.js.map} +0 -0
  200. /package/dist/{chunk-p6r5g77m.js.map → chunk-8kv2sez0.js.map} +0 -0
  201. /package/dist/{chunk-k41ajtz7.js.map → chunk-9k3hgv8h.js.map} +0 -0
  202. /package/dist/{chunk-2gpr6jsz.js.map → chunk-b1bqvhpe.js.map} +0 -0
  203. /package/dist/{chunk-wxhdnwh4.js.map → chunk-bh8bwma3.js.map} +0 -0
  204. /package/dist/{chunk-0fygrz37.js.map → chunk-exrfbt2d.js.map} +0 -0
  205. /package/dist/{chunk-c3zd880g.js.map → chunk-f6kyyeef.js.map} +0 -0
  206. /package/dist/{chunk-q4qzq0ea.js.map → chunk-hb9xcx9c.js.map} +0 -0
  207. /package/dist/{chunk-btpe64e4.js.map → chunk-m9w0artv.js.map} +0 -0
  208. /package/dist/{chunk-45tw90k7.js.map → chunk-md9jbrm9.js.map} +0 -0
  209. /package/dist/{chunk-dhmj53mx.js.map → chunk-nbywkytr.js.map} +0 -0
  210. /package/dist/{chunk-t358gywy.js.map → chunk-p7vj8xgb.js.map} +0 -0
  211. /package/dist/{chunk-yvef9xwc.js.map → chunk-rjqm6ksg.js.map} +0 -0
  212. /package/dist/{chunk-dcgrzj1t.js.map → chunk-xt6qx9sh.js.map} +0 -0
  213. /package/dist/{chunk-mx84y05q.js.map → chunk-y2ygpd0m.js.map} +0 -0
  214. /package/dist/{chunk-yxss87qa.js.map → chunk-yn947ck3.js.map} +0 -0
@@ -0,0 +1,77 @@
1
+ import type { DatasetDefinition } from "../datasets"
2
+ import type { PipelineDefinition } from "../pipelines"
3
+ import type { TelemetryProjectionDefinition } from "../projections"
4
+ import { SyncValidationError } from "./errors"
5
+ import type { SyncDefinition } from "./types"
6
+
7
+ interface DatasetWriter {
8
+ readonly datasetId: string
9
+ readonly label: string
10
+ }
11
+
12
+ export function validateKeyedDatasetWriterTopology(input: {
13
+ readonly datasetsById: ReadonlyMap<string, DatasetDefinition>
14
+ readonly syncs: readonly SyncDefinition[]
15
+ readonly pipelines: readonly PipelineDefinition[]
16
+ }): void {
17
+ for (const sync of input.syncs) {
18
+ const dataset = input.datasetsById.get(sync.target.dataset.id)
19
+ if (sync.config.mode === "merge" && dataset?.primaryKey === undefined) {
20
+ throw new SyncValidationError(
21
+ `[Sixb] Merge sync '${sync.id}' targets dataset '${sync.target.dataset.id}', which must define a primaryKey.`
22
+ )
23
+ }
24
+ }
25
+
26
+ const firstWriterByDataset = new Map<string, DatasetWriter>()
27
+ for (const writer of datasetWriters(input.syncs, input.pipelines)) {
28
+ const dataset = input.datasetsById.get(writer.datasetId)
29
+ if (dataset?.primaryKey === undefined) continue
30
+
31
+ const first = firstWriterByDataset.get(writer.datasetId)
32
+ if (!first) {
33
+ firstWriterByDataset.set(writer.datasetId, writer)
34
+ continue
35
+ }
36
+
37
+ throw new SyncValidationError(
38
+ `[Sixb] Keyed dataset '${writer.datasetId}' has multiple registered writers: ${first.label} and ${writer.label}. V1 allows one writer per keyed dataset.`
39
+ )
40
+ }
41
+ }
42
+
43
+ export function validateMergeSyncProjectionSafety(input: {
44
+ readonly syncs: readonly SyncDefinition[]
45
+ readonly telemetryProjections: readonly TelemetryProjectionDefinition[]
46
+ }): void {
47
+ const mergeSyncByDataset = new Map(
48
+ input.syncs
49
+ .filter((sync) => sync.config.mode === "merge")
50
+ .map((sync) => [sync.target.dataset.id, sync] as const)
51
+ )
52
+
53
+ for (const projection of input.telemetryProjections) {
54
+ const sync = mergeSyncByDataset.get(projection.datasetId)
55
+ if (!sync) continue
56
+ throw new SyncValidationError(
57
+ `[Sixb] Telemetry projection '${projection.id}' cannot read merge-written dataset '${projection.datasetId}' from sync '${sync.id}'. V1 merge syncs support object and link replacement projections only.`
58
+ )
59
+ }
60
+ }
61
+
62
+ function* datasetWriters(
63
+ syncs: readonly SyncDefinition[],
64
+ pipelines: readonly PipelineDefinition[]
65
+ ): Iterable<DatasetWriter> {
66
+ for (const sync of syncs) {
67
+ yield { datasetId: sync.target.dataset.id, label: `sync '${sync.id}'` }
68
+ }
69
+ for (const pipeline of pipelines) {
70
+ for (const node of pipeline.graph.nodes) {
71
+ yield {
72
+ datasetId: node.step.output.id,
73
+ label: `pipeline '${pipeline.id}' step '${node.step.id}'`,
74
+ }
75
+ }
76
+ }
77
+ }
@@ -18,6 +18,10 @@ export {
18
18
  type EffectiveStorageContractSuiteOptions,
19
19
  runEffectiveStorageContractSuite,
20
20
  } from "./effective-storage-contract"
21
+ export {
22
+ type LakeMergeStorageContractSuiteOptions,
23
+ runLakeMergeStorageContractSuite,
24
+ } from "./lake-merge-storage-contract"
21
25
  export {
22
26
  type LakeStorageContractSuiteOptions,
23
27
  type LakeStorageSchemaEvolutionCapability,
@@ -0,0 +1,302 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { change, col, defineDataset } from "../datasets"
3
+ import type { DatasetMergeCommitResult, DatasetRow, LakeStorage } from "../lake-storage"
4
+
5
+ export interface LakeMergeStorageContractSuiteOptions<TStorage extends LakeStorage = LakeStorage> {
6
+ readonly createStorage: () => TStorage | Promise<TStorage>
7
+ readonly teardown?: (storage: TStorage) => void | Promise<void>
8
+ }
9
+
10
+ const invoices = defineDataset("contract.merges.invoices", {
11
+ schema: [col("id", "string"), col("status", "string"), col("note", "string", { nullable: true })],
12
+ primaryKey: "id",
13
+ })
14
+
15
+ const lineItems = defineDataset("contract.merges.line_items", {
16
+ schema: [col("invoiceId", "string"), col("lineItemId", "string"), col("description", "string")],
17
+ primaryKey: ["invoiceId", "lineItemId"],
18
+ })
19
+
20
+ const unkeyed = defineDataset("contract.merges.unkeyed", {
21
+ schema: [col("id", "string")],
22
+ })
23
+
24
+ export function runLakeMergeStorageContractSuite<TStorage extends LakeStorage>(
25
+ label: string,
26
+ options: LakeMergeStorageContractSuiteOptions<TStorage>
27
+ ): void {
28
+ const withStorage = async (body: (storage: TStorage) => Promise<void>): Promise<void> => {
29
+ const storage = await options.createStorage()
30
+ try {
31
+ await body(storage)
32
+ } finally {
33
+ await options.teardown?.(storage)
34
+ }
35
+ }
36
+
37
+ describe(label, () => {
38
+ test("creates an initial merge version from async changes", async () => {
39
+ await withStorage(async (storage) => {
40
+ await storage.createDataset(invoices)
41
+ const merge = await storage.beginMerge({
42
+ dataset: invoices,
43
+ producer: { kind: "sync", id: "sync-invoices", runId: "run_1" },
44
+ inputs: [{ datasetId: "source.events", versionId: "cursor_1" }],
45
+ })
46
+
47
+ async function* changes() {
48
+ yield change.upsert({ id: "inv_1", status: "open", note: null })
49
+ yield change.upsert({ id: "inv_2", status: "paid", note: "settled" })
50
+ }
51
+
52
+ await merge.writeChanges(changes())
53
+ const result = await merge.commit({ commitMessage: "initial invoice changes" })
54
+ const version = expectCreated(result)
55
+
56
+ expect(version).toMatchObject({
57
+ datasetId: invoices.id,
58
+ mode: "merge",
59
+ rowCount: 2,
60
+ producer: { kind: "sync", id: "sync-invoices", runId: "run_1" },
61
+ inputs: [{ datasetId: "source.events", versionId: "cursor_1" }],
62
+ })
63
+ expect(version.parentVersionId).toBeUndefined()
64
+ await expect(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
65
+ { id: "inv_1", status: "open", note: null },
66
+ { id: "inv_2", status: "paid", note: "settled" },
67
+ ])
68
+ })
69
+ })
70
+
71
+ test("inserts, updates, and deletes while preserving the parent version", async () => {
72
+ await withStorage(async (storage) => {
73
+ await storage.createDataset(invoices)
74
+ const seed = await storage.beginWrite({ dataset: invoices, mode: "snapshot" })
75
+ await seed.writeRows([
76
+ { id: "inv_1", status: "open", note: null },
77
+ { id: "inv_2", status: "open", note: null },
78
+ ])
79
+ const parent = await seed.commit()
80
+
81
+ const merge = await storage.beginMerge({ dataset: invoices })
82
+ await merge.writeChanges([
83
+ change.upsert({ id: "inv_1", status: "paid", note: null }),
84
+ // Primary keys are immutable, so moving a row is an old-key delete plus a complete row.
85
+ change.delete({ id: "inv_2" }),
86
+ change.upsert({ id: "inv_2_moved", status: "open", note: "moved" }),
87
+ ])
88
+ const version = expectCreated(await merge.commit())
89
+
90
+ expect(version.parentVersionId).toBe(parent.versionId)
91
+ expect(version.mode).toBe("merge")
92
+ await expect(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
93
+ { id: "inv_1", status: "paid", note: null },
94
+ { id: "inv_2_moved", status: "open", note: "moved" },
95
+ ])
96
+ await expect(
97
+ collectRows(storage.readRows({ datasetId: invoices.id, versionId: parent.versionId }))
98
+ ).resolves.toEqual([
99
+ { id: "inv_1", status: "open", note: null },
100
+ { id: "inv_2", status: "open", note: null },
101
+ ])
102
+ })
103
+ })
104
+
105
+ test("applies the final change for repeated single and composite keys", async () => {
106
+ await withStorage(async (storage) => {
107
+ await storage.createDataset(lineItems)
108
+ const merge = await storage.beginMerge({ dataset: lineItems })
109
+ await merge.writeChanges([
110
+ change.upsert({ invoiceId: "a|b", lineItemId: "c", description: "first" }),
111
+ change.upsert({ invoiceId: "a", lineItemId: "b|c", description: "other key" }),
112
+ ])
113
+ await merge.writeChanges([
114
+ change.delete({ invoiceId: "a|b", lineItemId: "c" }),
115
+ change.upsert({ invoiceId: "a|b", lineItemId: "c", description: "final" }),
116
+ ])
117
+ expectCreated(await merge.commit())
118
+
119
+ await expect(collectRows(storage.readRows({ datasetId: lineItems.id }))).resolves.toEqual([
120
+ { invoiceId: "a|b", lineItemId: "c", description: "final" },
121
+ { invoiceId: "a", lineItemId: "b|c", description: "other key" },
122
+ ])
123
+ })
124
+ })
125
+
126
+ test("does not create versions when visible rows do not change", async () => {
127
+ await withStorage(async (storage) => {
128
+ await storage.createDataset(invoices)
129
+
130
+ const empty = await storage.beginMerge({ dataset: invoices })
131
+ expect(await empty.commit()).toEqual({ outcome: "unchanged", version: null })
132
+
133
+ const initialNoOp = await storage.beginMerge({ dataset: invoices })
134
+ await initialNoOp.writeChanges([change.delete({ id: "missing" })])
135
+ expect(await initialNoOp.commit()).toEqual({ outcome: "unchanged", version: null })
136
+ expect(await storage.listVersions(invoices.id)).toEqual([])
137
+
138
+ const seed = await storage.beginMerge({ dataset: invoices })
139
+ await seed.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })])
140
+ const seedVersion = expectCreated(await seed.commit())
141
+
142
+ const identical = await storage.beginMerge({ dataset: invoices })
143
+ await identical.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })])
144
+ expect(await identical.commit()).toMatchObject({
145
+ outcome: "unchanged",
146
+ version: { versionId: seedVersion.versionId },
147
+ })
148
+
149
+ const laterNoOp = await storage.beginMerge({ dataset: invoices })
150
+ await laterNoOp.writeChanges([
151
+ change.upsert({ id: "inv_1", status: "paid", note: null }),
152
+ change.upsert({ id: "inv_1", status: "open" }),
153
+ change.delete({ id: "missing" }),
154
+ ])
155
+ const noOpResult = await laterNoOp.commit()
156
+
157
+ expect(noOpResult).toMatchObject({ outcome: "unchanged" })
158
+ expect(noOpResult.version?.versionId).toBe(seedVersion.versionId)
159
+ expect(await storage.listVersions(invoices.id)).toHaveLength(1)
160
+ })
161
+ })
162
+
163
+ test("automatically rejects stale merge sessions, including an absent base", async () => {
164
+ await withStorage(async (storage) => {
165
+ await storage.createDataset(invoices)
166
+ const first = await storage.beginMerge({ dataset: invoices })
167
+ const stale = await storage.beginMerge({ dataset: invoices })
168
+
169
+ await first.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })])
170
+ await first.commit()
171
+ await stale.writeChanges([change.upsert({ id: "inv_2", status: "open" })])
172
+
173
+ await expect(stale.commit()).rejects.toThrow("Optimistic merge commit failed")
174
+ await expect(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
175
+ { id: "inv_1", status: "open", note: null },
176
+ ])
177
+ })
178
+ })
179
+
180
+ test("checks an explicit latest-version guard against the captured merge base", async () => {
181
+ await withStorage(async (storage) => {
182
+ await storage.createDataset(invoices)
183
+ const seed = await storage.beginMerge({ dataset: invoices })
184
+ await seed.writeChanges([change.upsert({ id: "inv_1", status: "open" })])
185
+ const version = expectCreated(await seed.commit())
186
+
187
+ await expect(
188
+ storage.beginMerge({ dataset: invoices, expectedLatestVersionId: "stale-version" })
189
+ ).rejects.toThrow("Optimistic merge start failed")
190
+
191
+ const guarded = await storage.beginMerge({
192
+ dataset: invoices,
193
+ expectedLatestVersionId: version.versionId,
194
+ })
195
+ await guarded.abort()
196
+ })
197
+ })
198
+
199
+ test("serializes concurrent commits from the same base", async () => {
200
+ // Regression guard: bypassing a provider's per-dataset commit lock lets both commits create
201
+ // versions from the same base and makes the fulfilled-count assertion fail.
202
+ await withStorage(async (storage) => {
203
+ await storage.createDataset(invoices)
204
+ const left = await storage.beginMerge({ dataset: invoices })
205
+ const right = await storage.beginMerge({ dataset: invoices })
206
+ await left.writeChanges([change.upsert({ id: "inv_left", status: "open" })])
207
+ await right.writeChanges([change.upsert({ id: "inv_right", status: "open" })])
208
+
209
+ const results = await Promise.allSettled([left.commit(), right.commit()])
210
+
211
+ expect(results.filter((result) => result.status === "fulfilled")).toHaveLength(1)
212
+ expect(results.filter((result) => result.status === "rejected")).toHaveLength(1)
213
+ expect(await storage.listVersions(invoices.id)).toHaveLength(1)
214
+ expect(await collectRows(storage.readRows({ datasetId: invoices.id }))).toHaveLength(1)
215
+ })
216
+ })
217
+
218
+ test("rejects unkeyed datasets and malformed changes", async () => {
219
+ await withStorage(async (storage) => {
220
+ await storage.createDataset(unkeyed)
221
+ await expect(storage.beginMerge({ dataset: unkeyed })).rejects.toThrow(
222
+ "must define a primaryKey"
223
+ )
224
+
225
+ await storage.createDataset(invoices)
226
+ const merge = await storage.beginMerge({ dataset: invoices })
227
+ await expect(merge.writeChanges([change.upsert({ id: "inv_1" })])).rejects.toThrow(
228
+ "missing required column 'status'"
229
+ )
230
+ await expect(merge.writeChanges([change.delete({})])).rejects.toThrow(
231
+ "missing primary-key column 'id'"
232
+ )
233
+ await expect(
234
+ merge.writeChanges([{ kind: "delete", key: { id: "inv_1", extra: "x" } }])
235
+ ).rejects.toThrow("unknown column 'extra'")
236
+ await merge.abort()
237
+ })
238
+ })
239
+
240
+ test("enforces unique keys for snapshot and append baselines", async () => {
241
+ await withStorage(async (storage) => {
242
+ await storage.createDataset(invoices)
243
+
244
+ const duplicateSnapshot = await storage.beginWrite({ dataset: invoices, mode: "snapshot" })
245
+ await expect(
246
+ duplicateSnapshot.writeRows([
247
+ { id: "inv_1", status: "open" },
248
+ { id: "inv_1", status: "paid" },
249
+ ])
250
+ ).rejects.toThrow("duplicate primary key")
251
+ await duplicateSnapshot.abort()
252
+
253
+ const seed = await storage.beginWrite({ dataset: invoices, mode: "snapshot" })
254
+ await seed.writeRows([{ id: "inv_1", status: "open" }])
255
+ await seed.commit()
256
+
257
+ const conflictingAppend = await storage.beginWrite({ dataset: invoices, mode: "append" })
258
+ await conflictingAppend.writeRows([{ id: "inv_1", status: "paid" }])
259
+ await expect(conflictingAppend.commit()).rejects.toThrow("duplicate primary key")
260
+ })
261
+ })
262
+
263
+ test("clones staged changes and enforces the session lifecycle", async () => {
264
+ await withStorage(async (storage) => {
265
+ await storage.createDataset(invoices)
266
+ const row = { id: "inv_1", status: "open", note: null }
267
+ const merge = await storage.beginMerge({ dataset: invoices })
268
+ await merge.writeChanges([change.upsert(row)])
269
+ row.status = "paid"
270
+ expectCreated(await merge.commit())
271
+ await merge.abort()
272
+
273
+ await expect(merge.writeChanges([])).rejects.toThrow("already closed")
274
+ await expect(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
275
+ { id: "inv_1", status: "open", note: null },
276
+ ])
277
+
278
+ const aborted = await storage.beginMerge({ dataset: invoices })
279
+ await aborted.writeChanges([change.delete({ id: "inv_1" })])
280
+ await aborted.abort()
281
+ await aborted.abort()
282
+ expect(await storage.listVersions(invoices.id)).toHaveLength(1)
283
+ })
284
+ })
285
+ })
286
+ }
287
+
288
+ function expectCreated(result: DatasetMergeCommitResult) {
289
+ expect(result.outcome).toBe("created")
290
+ if (result.outcome !== "created") {
291
+ throw new Error("Expected merge to create a dataset version")
292
+ }
293
+ return result.version
294
+ }
295
+
296
+ async function collectRows(rows: AsyncIterable<DatasetRow>): Promise<DatasetRow[]> {
297
+ const result: DatasetRow[] = []
298
+ for await (const row of rows) {
299
+ result.push(row)
300
+ }
301
+ return result
302
+ }
@@ -34,6 +34,12 @@ const reorderedDefinitionDataset = defineDataset("contract.definitions.orders",
34
34
  description: "Contract orders",
35
35
  })
36
36
 
37
+ const keyedDefinitionDataset = defineDataset("contract.definitions.keyed_orders", {
38
+ schema: [col("tenantId", "string"), col("orderId", "string"), col("customerName", "string")],
39
+ primaryKey: ["tenantId", "orderId"],
40
+ description: "Contract keyed orders",
41
+ })
42
+
37
43
  const writeDataset = defineDataset("contract.writes.orders", {
38
44
  schema: [col("orderId", "string"), col("customerName", "string")],
39
45
  })
@@ -101,6 +107,23 @@ export function runLakeStorageContractSuite<TStorage extends LakeStorage>(
101
107
  })
102
108
  })
103
109
 
110
+ test("round-trips keyed definitions through create, get, list, and reuse", async () => {
111
+ // Regression guard: omitting primaryKey from mergeDatasetDefinition makes reuse lose
112
+ // the key and fails this contract case.
113
+ await withStorage(async (storage) => {
114
+ const created = await storage.createDataset(keyedDefinitionDataset)
115
+ const repeated = await storage.createDataset(keyedDefinitionDataset)
116
+
117
+ expect(created).toEqual(keyedDefinitionDataset)
118
+ expect(repeated).toEqual(keyedDefinitionDataset)
119
+ expect(await storage.getDataset(keyedDefinitionDataset.id)).toEqual(
120
+ keyedDefinitionDataset
121
+ )
122
+ expect(await storage.listDatasets()).toEqual([keyedDefinitionDataset])
123
+ expect(await storage.listVersions(keyedDefinitionDataset.id)).toEqual([])
124
+ })
125
+ })
126
+
104
127
  test("treats same-column declaration reorders as a no-op", async () => {
105
128
  await withStorage(async (storage) => {
106
129
  await storage.createDataset(definitionDataset)
@@ -1,5 +1,5 @@
1
1
  import type { ActionSubject } from "../actions"
2
- import type { JsonValue } from "../json"
2
+ import { isPlainRecord, type JsonValue } from "../json"
3
3
  import { isObjectRefSchema, type Schema, type SchemaOrRef, type ValueType } from "../ontology"
4
4
  import { WorkflowValidationError } from "./errors"
5
5
  import type {
@@ -372,12 +372,3 @@ function cannotSerialize(path: string): WorkflowValidationError {
372
372
  function isRecord(value: unknown): value is Record<string, unknown> {
373
373
  return typeof value === "object" && value !== null && !Array.isArray(value)
374
374
  }
375
-
376
- function isPlainRecord(value: unknown): value is Record<string, unknown> {
377
- if (!isRecord(value)) {
378
- return false
379
- }
380
-
381
- const prototype = Object.getPrototypeOf(value)
382
- return prototype === Object.prototype || prototype === null
383
- }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/json.ts"],
4
- "sourcesContent": [
5
- "export type JsonPrimitive = string | number | boolean | null\nexport type JsonObject = { [key: string]: JsonValue }\nexport type JsonArray = JsonValue[]\nexport type JsonValue = JsonPrimitive | JsonObject | JsonArray\n\nexport function isJsonValue(value: unknown): value is JsonValue {\n return getInvalidJsonValueReason(value) === undefined\n}\n\nexport function assertJsonValue(value: unknown, label = \"value\"): asserts value is JsonValue {\n const reason = getInvalidJsonValueReason(value, label)\n if (reason) {\n throw new Error(`[Sixb] ${label} must be a JSON value; ${reason}`)\n }\n}\n\nexport function cloneJsonValue(value: JsonValue): JsonValue {\n assertJsonValue(value)\n return JSON.parse(JSON.stringify(value)) as JsonValue\n}\n\nexport function jsonValuesEqual(left: unknown, right: unknown): boolean {\n return stableJsonStringify(left) === stableJsonStringify(right)\n}\n\nexport function stableJsonStringify(value: unknown): string {\n if (value === undefined) return \"undefined\"\n if (value === null || typeof value !== \"object\") return JSON.stringify(value)\n if (Array.isArray(value)) return `[${value.map(stableJsonStringify).join(\",\")}]`\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => compareStrings(left, right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableJsonStringify(entry)}`)\n .join(\",\")}}`\n}\n\nexport function compareStrings(left: string, right: string): number {\n if (left < right) return -1\n if (left > right) return 1\n return 0\n}\n\nexport function getInvalidJsonValueReason(\n value: unknown,\n label = \"value\",\n seen: Set<object> = new Set()\n): string | undefined {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") {\n return undefined\n }\n\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? undefined : `${label} is not a finite number`\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n return `${label} contains a circular reference`\n }\n\n seen.add(value)\n for (let index = 0; index < value.length; index += 1) {\n if (!Object.hasOwn(value, index)) {\n seen.delete(value)\n return `${label}[${index}] is a sparse array slot`\n }\n\n const reason = getInvalidJsonValueReason(value[index], `${label}[${index}]`, seen)\n if (reason) {\n seen.delete(value)\n return reason\n }\n }\n seen.delete(value)\n return undefined\n }\n\n if (isPlainObject(value)) {\n if (seen.has(value)) {\n return `${label} contains a circular reference`\n }\n\n const symbolKeys = Object.getOwnPropertySymbols(value).filter((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key)\n )\n if (symbolKeys.length > 0) {\n return `${label} has an enumerable symbol key`\n }\n\n seen.add(value)\n for (const [key, entry] of Object.entries(value)) {\n const reason = getInvalidJsonValueReason(entry, jsonChildPath(label, key), seen)\n if (reason) {\n seen.delete(value)\n return reason\n }\n }\n seen.delete(value)\n return undefined\n }\n\n return `${label} is ${describeNonJsonValue(value)}`\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false\n }\n\n const prototype = Object.getPrototypeOf(value)\n return prototype === Object.prototype || prototype === null\n}\n\nfunction jsonChildPath(parent: string, key: string): string {\n return /^[A-Za-z_$][\\w$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`\n}\n\nfunction describeNonJsonValue(value: unknown): string {\n if (value === undefined) {\n return \"undefined\"\n }\n\n if (value instanceof Date) {\n return \"a Date\"\n }\n\n const type = typeof value\n if (type === \"bigint\" || type === \"function\" || type === \"symbol\") {\n return `a ${type}`\n }\n\n if (typeof value === \"object\" && value !== null) {\n const name = value.constructor?.name\n return name ? `a ${name}` : \"a non-plain object\"\n }\n\n return type\n}\n"
6
- ],
7
- "mappings": ";;AAKO,SAAS,WAAW,CAAC,OAAoC;AAAA,EAC9D,OAAO,0BAA0B,KAAK,MAAM;AAAA;AAGvC,SAAS,eAAe,CAAC,OAAgB,QAAQ,SAAqC;AAAA,EAC3F,MAAM,SAAS,0BAA0B,OAAO,KAAK;AAAA,EACrD,IAAI,QAAQ;AAAA,IACV,MAAM,IAAI,MAAM,UAAU,+BAA+B,QAAQ;AAAA,EACnE;AAAA;AAGK,SAAS,cAAc,CAAC,OAA6B;AAAA,EAC1D,gBAAgB,KAAK;AAAA,EACrB,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAAA;AAOlC,SAAS,mBAAmB,CAAC,OAAwB;AAAA,EAC1D,IAAI,UAAU;AAAA,IAAW,OAAO;AAAA,EAChC,IAAI,UAAU,QAAQ,OAAO,UAAU;AAAA,IAAU,OAAO,KAAK,UAAU,KAAK;AAAA,EAC5E,IAAI,MAAM,QAAQ,KAAK;AAAA,IAAG,OAAO,IAAI,MAAM,IAAI,mBAAmB,EAAE,KAAK,GAAG;AAAA,EAC5E,OAAO,IAAI,OAAO,QAAQ,KAAgC,EACvD,KAAK,EAAE,QAAQ,WAAW,eAAe,MAAM,KAAK,CAAC,EACrD,IAAI,EAAE,KAAK,WAAW,GAAG,KAAK,UAAU,GAAG,KAAK,oBAAoB,KAAK,GAAG,EAC5E,KAAK,GAAG;AAAA;AAGN,SAAS,cAAc,CAAC,MAAc,OAAuB;AAAA,EAClE,IAAI,OAAO;AAAA,IAAO,OAAO;AAAA,EACzB,IAAI,OAAO;AAAA,IAAO,OAAO;AAAA,EACzB,OAAO;AAAA;AAGF,SAAS,yBAAyB,CACvC,OACA,QAAQ,SACR,OAAoB,IAAI,KACJ;AAAA,EACpB,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;AAAA,IAC7E;AAAA,EACF;AAAA,EAEA,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,OAAO,OAAO,SAAS,KAAK,IAAI,YAAY,GAAG;AAAA,EACjD;AAAA,EAEA,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,IACxB,IAAI,KAAK,IAAI,KAAK,GAAG;AAAA,MACnB,OAAO,GAAG;AAAA,IACZ;AAAA,IAEA,KAAK,IAAI,KAAK;AAAA,IACd,SAAS,QAAQ,EAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AAAA,MACpD,IAAI,CAAC,OAAO,OAAO,OAAO,KAAK,GAAG;AAAA,QAChC,KAAK,OAAO,KAAK;AAAA,QACjB,OAAO,GAAG,SAAS;AAAA,MACrB;AAAA,MAEA,MAAM,SAAS,0BAA0B,MAAM,QAAQ,GAAG,SAAS,UAAU,IAAI;AAAA,MACjF,IAAI,QAAQ;AAAA,QACV,KAAK,OAAO,KAAK;AAAA,QACjB,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,KAAK,OAAO,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,IAAI,cAAc,KAAK,GAAG;AAAA,IACxB,IAAI,KAAK,IAAI,KAAK,GAAG;AAAA,MACnB,OAAO,GAAG;AAAA,IACZ;AAAA,IAEA,MAAM,aAAa,OAAO,sBAAsB,KAAK,EAAE,OAAO,CAAC,QAC7D,OAAO,UAAU,qBAAqB,KAAK,OAAO,GAAG,CACvD;AAAA,IACA,IAAI,WAAW,SAAS,GAAG;AAAA,MACzB,OAAO,GAAG;AAAA,IACZ;AAAA,IAEA,KAAK,IAAI,KAAK;AAAA,IACd,YAAY,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;AAAA,MAChD,MAAM,SAAS,0BAA0B,OAAO,cAAc,OAAO,GAAG,GAAG,IAAI;AAAA,MAC/E,IAAI,QAAQ;AAAA,QACV,KAAK,OAAO,KAAK;AAAA,QACjB,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,KAAK,OAAO,KAAK;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,OAAO,GAAG,YAAY,qBAAqB,KAAK;AAAA;AAGlD,SAAS,aAAa,CAAC,OAAkD;AAAA,EACvE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AAAA,IACvE,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,OAAO,eAAe,KAAK;AAAA,EAC7C,OAAO,cAAc,OAAO,aAAa,cAAc;AAAA;AAGzD,SAAS,aAAa,CAAC,QAAgB,KAAqB;AAAA,EAC1D,OAAO,qBAAqB,KAAK,GAAG,IAAI,GAAG,UAAU,QAAQ,GAAG,UAAU,KAAK,UAAU,GAAG;AAAA;AAG9F,SAAS,oBAAoB,CAAC,OAAwB;AAAA,EACpD,IAAI,UAAU,WAAW;AAAA,IACvB,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,iBAAiB,MAAM;AAAA,IACzB,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,OAAO;AAAA,EACpB,IAAI,SAAS,YAAY,SAAS,cAAc,SAAS,UAAU;AAAA,IACjE,OAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAAA,IAC/C,MAAM,OAAO,MAAM,aAAa;AAAA,IAChC,OAAO,OAAO,KAAK,SAAS;AAAA,EAC9B;AAAA,EAEA,OAAO;AAAA;",
8
- "debugId": "2AE4FE38572586A064756E2164756E21",
9
- "names": []
10
- }
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lake-storage/errors.ts", "../src/lake-storage/definition-updates.ts", "../src/lake-storage/in-memory.ts"],
4
- "sourcesContent": [
5
- "/**\n * Error for lake-storage invariants and invalid lake operations.\n */\nexport class LakeStorageError extends Error {\n readonly name = \"LakeStorageError\"\n}\n",
6
- "import type { DatasetColumnDefinition, DatasetDefinition } from \"../datasets\"\nimport { LakeStorageError } from \"./errors\"\nimport type { LakeStorage } from \"./types\"\n\nexport type DatasetSchemaUpdatePlan =\n | { readonly kind: \"none\" }\n | {\n readonly kind: \"addNullableColumns\"\n readonly columns: readonly DatasetColumnDefinition[]\n }\n\nexport interface DatasetMetadataUpdatePlan {\n readonly descriptionChanged: boolean\n readonly partitionByChanged: boolean\n}\n\nexport interface DatasetDefinitionUpdatePlan {\n readonly existing: DatasetDefinition\n readonly requested: DatasetDefinition\n readonly definition: DatasetDefinition\n readonly schema: DatasetSchemaUpdatePlan\n readonly metadata: DatasetMetadataUpdatePlan\n readonly changed: boolean\n}\n\nconst SCHEMA_UPDATE_V1_POLICY =\n \"Dataset definition updates V1 support only adding nullable columns.\"\n\n/**\n * Build a provider-neutral update plan for two versions of the same dataset\n * definition. Core owns the definition diff; storage providers own how much of\n * the plan they can apply.\n */\nexport function planDatasetDefinitionUpdate(\n existing: DatasetDefinition,\n requested: DatasetDefinition\n): DatasetDefinitionUpdatePlan {\n assertSameDataset(existing, requested)\n\n const addedColumns = getAddedColumnsAfterValidatingExistingColumns(existing, requested)\n assertAddedColumnsAreNullable(requested.id, addedColumns)\n\n const schema = toSchemaUpdatePlan(addedColumns)\n const definition = mergeDatasetDefinition(existing, requested, addedColumns)\n const metadata: DatasetMetadataUpdatePlan = {\n descriptionChanged: existing.description !== definition.description,\n partitionByChanged: !areStringArraysEqual(existing.partitionBy, definition.partitionBy),\n }\n\n return {\n existing: structuredClone(existing),\n requested: structuredClone(requested),\n definition,\n schema,\n metadata,\n changed: schema.kind !== \"none\" || metadata.descriptionChanged || metadata.partitionByChanged,\n }\n}\n\n/**\n * Strict providers can use this helper when they do not support schema\n * evolution. Metadata-only compatible additions are still merged.\n */\nexport function mergeStrictDatasetDefinition(options: {\n readonly existing?: DatasetDefinition | null\n readonly next: DatasetDefinition\n}): DatasetDefinition {\n if (!options.existing) {\n return structuredClone(options.next)\n }\n\n const plan = planDatasetDefinitionUpdate(options.existing, options.next)\n if (plan.schema.kind !== \"none\") {\n throw new LakeStorageError(\n `[SixbLake] Dataset '${options.next.id}' cannot be redefined with an incompatible schema.`\n )\n }\n\n return structuredClone(plan.definition)\n}\n\nexport interface AssertLakeDatasetDefinitionsCompatibleOptions {\n readonly lakeStorage: LakeStorage\n readonly definitions: readonly DatasetDefinition[]\n}\n\nexport async function assertLakeDatasetDefinitionsCompatible(\n options: AssertLakeDatasetDefinitionsCompatibleOptions\n): Promise<void> {\n await options.lakeStorage.assertDatasetDefinitionsCompatible(options.definitions)\n}\n\nfunction assertSameDataset(existing: DatasetDefinition, requested: DatasetDefinition): void {\n if (existing.id !== requested.id) {\n throw new LakeStorageError(\n `[SixbLake] Cannot update dataset '${existing.id}' with definition for '${requested.id}'.`\n )\n }\n}\n\nfunction getAddedColumnsAfterValidatingExistingColumns(\n existing: DatasetDefinition,\n requested: DatasetDefinition\n): readonly DatasetColumnDefinition[] {\n const existingColumns = existing.schema.columns\n const requestedColumnsByName = new Map(\n requested.schema.columns.map((column) => [column.name, column])\n )\n\n // Existing columns are matched by name, not position. Developers can reorder\n // declarations for readability without implying a storage-level schema change.\n for (const existingColumn of existingColumns) {\n const requestedColumn = requestedColumnsByName.get(existingColumn.name)\n if (requestedColumn === undefined) {\n throwUnsupportedSchemaUpdate(\n requested.id,\n `dropping column '${existingColumn.name}' is not supported`\n )\n }\n\n assertExistingColumnUnchanged(requested.id, existingColumn, requestedColumn)\n }\n\n const existingColumnNames = new Set(existingColumns.map((column) => column.name))\n return requested.schema.columns.filter((column) => !existingColumnNames.has(column.name))\n}\n\nfunction assertAddedColumnsAreNullable(\n datasetId: string,\n addedColumns: readonly DatasetColumnDefinition[]\n): void {\n for (const column of addedColumns) {\n // Existing rows have no value for a new column. Until DatasetDefinition\n // supports defaults or backfills, only nullable columns are safe to add.\n if (!isNullable(column)) {\n throwUnsupportedSchemaUpdate(\n datasetId,\n `adding required column '${column.name}' is not supported`\n )\n }\n }\n}\n\nfunction assertExistingColumnUnchanged(\n datasetId: string,\n existing: DatasetColumnDefinition,\n requested: DatasetColumnDefinition\n): void {\n if (existing.type !== requested.type) {\n throwUnsupportedSchemaUpdate(\n datasetId,\n `changing column '${existing.name}' type from '${existing.type}' to '${requested.type}' is not supported`\n )\n }\n\n if (isNullable(existing) !== isNullable(requested)) {\n throwUnsupportedSchemaUpdate(\n datasetId,\n `changing column '${existing.name}' nullability is not supported`\n )\n }\n}\n\nfunction toSchemaUpdatePlan(\n addedColumns: readonly DatasetColumnDefinition[]\n): DatasetSchemaUpdatePlan {\n if (addedColumns.length === 0) {\n return { kind: \"none\" }\n }\n\n return {\n kind: \"addNullableColumns\",\n columns: cloneColumns(addedColumns),\n }\n}\n\nfunction mergeDatasetDefinition(\n existing: DatasetDefinition,\n requested: DatasetDefinition,\n addedColumns: readonly DatasetColumnDefinition[]\n): DatasetDefinition {\n assertCompatibleMetadataField(\n requested.id,\n \"description\",\n existing.description,\n requested.description\n )\n assertCompatibleMetadataField(\n requested.id,\n \"partitionBy\",\n existing.partitionBy,\n requested.partitionBy\n )\n\n const columns = [...cloneColumns(existing.schema.columns), ...cloneColumns(addedColumns)]\n const partitionBy = requested.partitionBy ?? existing.partitionBy\n const description = requested.description ?? existing.description\n\n return {\n kind: \"dataset\",\n id: requested.id,\n schema: { columns },\n ...(partitionBy !== undefined ? { partitionBy: [...partitionBy] } : {}),\n ...(description !== undefined ? { description } : {}),\n }\n}\n\nfunction assertCompatibleMetadataField(\n datasetId: string,\n field: \"description\" | \"partitionBy\",\n existing: string | readonly string[] | undefined,\n requested: string | readonly string[] | undefined\n): void {\n if (\n existing === undefined ||\n requested === undefined ||\n areDefinitionValuesEqual(existing, requested)\n ) {\n return\n }\n\n throw new LakeStorageError(\n `[SixbLake] Dataset '${datasetId}' cannot be redefined with an incompatible ${field}.`\n )\n}\n\nfunction areStringArraysEqual(\n left: readonly string[] | undefined,\n right: readonly string[] | undefined\n): boolean {\n if (left === undefined || right === undefined) {\n return left === right\n }\n\n return left.length === right.length && left.every((value, index) => value === right[index])\n}\n\nfunction areDefinitionValuesEqual(\n left: string | readonly string[],\n right: string | readonly string[]\n): boolean {\n if (typeof left === \"string\" || typeof right === \"string\") {\n return left === right\n }\n\n return areStringArraysEqual(left, right)\n}\n\nfunction isNullable(column: DatasetColumnDefinition): boolean {\n return column.nullable === true\n}\n\nfunction cloneColumns(\n columns: readonly DatasetColumnDefinition[]\n): readonly DatasetColumnDefinition[] {\n return columns.map((column) => ({ ...column }))\n}\n\nfunction throwUnsupportedSchemaUpdate(datasetId: string, detail: string): never {\n throw new LakeStorageError(\n `[SixbLake] Dataset '${datasetId}' cannot apply schema update because ${detail}. ${SCHEMA_UPDATE_V1_POLICY}`\n )\n}\n",
7
- "import { randomUUID } from \"node:crypto\"\nimport type { DatasetDefinition, DatasetSchema } from \"../datasets\"\nimport { getDatasetRowValidationError } from \"../datasets/validation\"\nimport { mergeStrictDatasetDefinition } from \"./definition-updates\"\nimport { LakeStorageError } from \"./errors\"\nimport type {\n BeginDatasetWriteInput,\n CommitDatasetWriteInput,\n DatasetCatalogState,\n DatasetRow,\n DatasetVersion,\n DatasetWriteCommitResult,\n DatasetWriteMode,\n LakeStorage,\n LakeWriteSession,\n ReadDatasetRowsInput,\n} from \"./types\"\n\nfunction cloneDatasetDefinition(definition: DatasetDefinition): DatasetDefinition {\n return structuredClone(definition)\n}\n\nfunction cloneDatasetVersion(version: DatasetVersion): DatasetVersion {\n return {\n ...structuredClone(version),\n createdAt: new Date(version.createdAt),\n }\n}\n\nfunction cloneRow(row: DatasetRow): DatasetRow {\n return structuredClone(row)\n}\n\nfunction selectColumns(row: DatasetRow, columns?: readonly string[]): DatasetRow {\n if (!columns || columns.length === 0) {\n return cloneRow(row)\n }\n\n const selected: Record<string, unknown> = {}\n for (const column of columns) {\n selected[column] = row[column]\n }\n return selected\n}\n\n// Canonical content key for unchanged-write detection. JSON.stringify\n// serializes Date values via toJSON, matching how rows persist as JSON;\n// missing and undefined nullable values normalize to null. The unchanged-write\n// semantics are pinned by the shared lake-storage contract suite.\nfunction rowContentKey(row: DatasetRow, schema: DatasetSchema): string {\n return JSON.stringify(schema.columns.map((column) => row[column.name] ?? null))\n}\n\n// Order-insensitive multiset equality over canonical row keys.\nfunction sameRowContent(left: readonly string[], right: readonly string[]): boolean {\n if (left.length !== right.length) {\n return false\n }\n\n const sortedLeft = [...left].sort()\n const sortedRight = [...right].sort()\n return sortedLeft.every((key, index) => key === sortedRight[index])\n}\n\nfunction assertDatasetId(datasetId: string): void {\n if (datasetId.trim().length === 0) {\n throw new LakeStorageError(\"[LakeStorage] Dataset id must not be empty\")\n }\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nclass InMemoryLakeWriteSession implements LakeWriteSession {\n private readonly rows: DatasetRow[] = []\n private closed = false\n\n constructor(\n private readonly storage: InMemoryLakeStorage,\n private readonly input: BeginDatasetWriteInput\n ) {}\n\n async writeRows(rows: Iterable<DatasetRow> | AsyncIterable<DatasetRow>): Promise<void> {\n this.assertOpen()\n\n for await (const row of rows) {\n const validationError = getDatasetRowValidationError(row, this.input.dataset)\n if (validationError) {\n throw new LakeStorageError(`[LakeStorage] ${validationError}`)\n }\n\n this.rows.push(cloneRow(row))\n }\n }\n\n async commit(input?: CommitDatasetWriteInput): Promise<DatasetWriteCommitResult> {\n this.assertOpen()\n this.closed = true\n return this.storage.commitWrite({\n write: this.input,\n rows: this.rows,\n commit: input,\n })\n }\n\n async abort(): Promise<void> {\n this.closed = true\n this.rows.length = 0\n }\n\n private assertOpen(): void {\n if (this.closed) {\n throw new LakeStorageError(\"[LakeStorage] Write session is already closed\")\n }\n }\n}\n\nexport class InMemoryLakeStorage implements LakeStorage {\n private readonly datasets = new Map<string, DatasetDefinition>()\n private readonly versionsByDataset = new Map<string, DatasetVersion[]>()\n private readonly rowsByVersionId = new Map<string, readonly DatasetRow[]>()\n private readonly latestVersionIdByDataset = new Map<string, string>()\n\n async createDataset(definition: DatasetDefinition): Promise<DatasetDefinition> {\n assertDatasetId(definition.id)\n const existing = this.datasets.get(definition.id)\n const stored = mergeStrictDatasetDefinition({\n existing,\n next: definition,\n })\n this.datasets.set(definition.id, stored)\n return cloneDatasetDefinition(stored)\n }\n\n async assertDatasetDefinitionsCompatible(\n definitions: readonly DatasetDefinition[]\n ): Promise<void> {\n const failures: string[] = []\n\n for (const definition of definitions) {\n try {\n assertDatasetId(definition.id)\n const existing = this.datasets.get(definition.id)\n if (!existing) {\n continue\n }\n\n mergeStrictDatasetDefinition({\n existing,\n next: definition,\n })\n } catch (error) {\n failures.push(`- ${definition.id}: ${errorMessage(error)}`)\n }\n }\n\n if (failures.length > 0) {\n const details = failures.join(\"\\n\")\n throw new LakeStorageError(\n `[SixbLake] Lake dataset definition check failed for ${failures.length} dataset(s).\\n${details}`\n )\n }\n }\n\n async getDataset(datasetId: string): Promise<DatasetDefinition | null> {\n const definition = this.datasets.get(datasetId)\n return definition ? cloneDatasetDefinition(definition) : null\n }\n\n async listDatasets(): Promise<readonly DatasetDefinition[]> {\n return [...this.datasets.values()].map((definition) => cloneDatasetDefinition(definition))\n }\n\n async listDatasetCatalogState(\n datasetIds: readonly string[]\n ): Promise<readonly DatasetCatalogState[]> {\n return Promise.all(\n datasetIds.map(async (datasetId) => {\n if (!this.datasets.has(datasetId)) {\n return { datasetId, materialized: false, latestVersion: null }\n }\n\n const latest = await this.getLatestVersion(datasetId)\n return {\n datasetId,\n materialized: true,\n latestVersion: latest\n ? {\n datasetId: latest.datasetId,\n versionId: latest.versionId,\n mode: latest.mode,\n createdAt: latest.createdAt,\n ...(latest.rowCount !== undefined ? { rowCount: latest.rowCount } : {}),\n }\n : null,\n }\n })\n )\n }\n\n async listVersions(datasetId: string, limit?: number): Promise<readonly DatasetVersion[]> {\n const versions = [...(this.versionsByDataset.get(datasetId) ?? [])]\n .sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime())\n .map((version) => cloneDatasetVersion(version))\n\n return limit === undefined ? versions : versions.slice(0, Math.max(0, limit))\n }\n\n async beginWrite(input: BeginDatasetWriteInput): Promise<LakeWriteSession> {\n const definition = await this.getDataset(input.dataset.id)\n if (!definition) {\n throw new LakeStorageError(`[LakeStorage] Unknown dataset '${input.dataset.id}'`)\n }\n\n return new InMemoryLakeWriteSession(this, {\n ...input,\n dataset: definition,\n mode: input.mode ?? \"snapshot\",\n })\n }\n\n async getLatestVersion(datasetId: string): Promise<DatasetVersion | null> {\n const latestVersionId = this.latestVersionIdByDataset.get(datasetId)\n if (!latestVersionId) {\n return null\n }\n\n return this.getVersion(datasetId, latestVersionId)\n }\n\n async getVersion(datasetId: string, versionId: string): Promise<DatasetVersion | null> {\n const versions = this.versionsByDataset.get(datasetId) ?? []\n const version = versions.find((candidate) => candidate.versionId === versionId)\n return version ? cloneDatasetVersion(version) : null\n }\n\n async *readRows(input: ReadDatasetRowsInput): AsyncIterable<DatasetRow> {\n const definition = this.datasets.get(input.datasetId)\n if (!definition) {\n throw new LakeStorageError(`[LakeStorage] Unknown dataset '${input.datasetId}'`)\n }\n\n const version =\n input.versionId === undefined\n ? await this.getLatestVersion(input.datasetId)\n : await this.getVersion(input.datasetId, input.versionId)\n\n if (!version) {\n throw new LakeStorageError(\n `[LakeStorage] No committed version found for dataset '${input.datasetId}'`\n )\n }\n\n const offset = Math.max(0, input.offset ?? 0)\n const limit = input.limit === undefined ? undefined : Math.max(0, input.limit)\n const rows = this.rowsByVersionId\n .get(version.versionId)\n ?.slice(offset, limit === undefined ? undefined : offset + limit)\n\n for (const row of rows ?? []) {\n yield selectColumns(row, input.columns)\n }\n }\n\n async commitWrite(options: {\n write: BeginDatasetWriteInput\n rows: readonly DatasetRow[]\n commit?: CommitDatasetWriteInput\n }): Promise<DatasetWriteCommitResult> {\n const definition = this.datasets.get(options.write.dataset.id)\n if (!definition) {\n throw new LakeStorageError(`[LakeStorage] Unknown dataset '${options.write.dataset.id}'`)\n }\n\n const mode = options.write.mode ?? \"snapshot\"\n const latestVersion = await this.getLatestVersion(options.write.dataset.id)\n\n if (options.commit?.expectedLatestVersionId !== undefined) {\n const actual = latestVersion?.versionId\n if (actual !== options.commit.expectedLatestVersionId) {\n throw new LakeStorageError(\n `[LakeStorage] Optimistic commit failed for dataset '${options.write.dataset.id}': expected latest version '${options.commit.expectedLatestVersionId}', found '${actual ?? \"none\"}'`\n )\n }\n }\n\n // Content-identical snapshots and empty appends reuse the latest version\n // instead of creating a new one.\n if (\n latestVersion &&\n this.isUnchangedWrite(mode, latestVersion, options.rows, definition.schema)\n ) {\n return { ...latestVersion, outcome: \"unchanged\" }\n }\n\n const versionId = `ver_${randomUUID()}`\n const visibleRows =\n mode === \"append\"\n ? [...(this.rowsByVersionId.get(latestVersion?.versionId ?? \"\") ?? []), ...options.rows]\n : [...options.rows]\n\n const sizeBytes = new TextEncoder().encode(\n visibleRows.map((row) => JSON.stringify(row)).join(\"\\n\")\n ).byteLength\n\n const version: DatasetVersion = {\n datasetId: options.write.dataset.id,\n versionId,\n parentVersionId: mode === \"append\" ? latestVersion?.versionId : undefined,\n mode,\n createdAt: new Date(),\n schema: definition.schema,\n producer: options.write.producer ? structuredClone(options.write.producer) : undefined,\n inputs: options.write.inputs ? structuredClone(options.write.inputs) : undefined,\n rowCount: visibleRows.length,\n sizeBytes,\n }\n\n const versions = this.versionsByDataset.get(options.write.dataset.id) ?? []\n versions.push(version)\n this.versionsByDataset.set(options.write.dataset.id, versions)\n this.rowsByVersionId.set(\n versionId,\n visibleRows.map((row) => cloneRow(row))\n )\n this.latestVersionIdByDataset.set(options.write.dataset.id, versionId)\n\n return { ...cloneDatasetVersion(version), outcome: \"created\" }\n }\n\n private isUnchangedWrite(\n mode: DatasetWriteMode,\n latestVersion: DatasetVersion,\n rows: readonly DatasetRow[],\n schema: DatasetSchema\n ): boolean {\n if (mode === \"append\") {\n return rows.length === 0\n }\n\n const previousRows = this.rowsByVersionId.get(latestVersion.versionId) ?? []\n return sameRowContent(\n rows.map((row) => rowContentKey(row, schema)),\n previousRows.map((row) => rowContentKey(row, schema))\n )\n }\n}\n"
8
- ],
9
- "mappings": "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",
10
- "debugId": "ABB8A17B04F411C364756E2164756E21",
11
- "names": []
12
- }