@reventlessdev/reventless-seed 1.0.0-alpha.2 → 1.0.0-alpha.3

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
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 1.0.0-alpha.3 (2026-07-27)
7
+
8
+ ### Features
9
+
10
+ * **seed:** refuse to seed onto a non-empty store (fresh-store guard) ([987613f](https://github.com/ReventlessDev/reventless-core/commit/987613f0767fc4391297dfeed3a6b19cd76d8157))
11
+
12
+
6
13
  # 1.0.0-alpha.2 (2026-07-26)
7
14
 
8
15
  ### Features
@@ -1,2 +1,2 @@
1
- #Start(1785092483117)
2
- #Done(1785092488019)
1
+ #Start(1785139469133)
2
+ #Done(1785139473598)
@@ -4,5 +4,5 @@
4
4
  "bsc_hash": "75e3a59c95cc953608fd3dd6ea6ed68141bba4414a20a52f114261b8a48385fa",
5
5
  "rescript_config_hash": "ad3a9d479584c1d6e4fe10cd993d9e290f74732e9b0cb3dd5d7429e88b4bba38",
6
6
  "runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
7
- "generated_at": "1785092488035"
7
+ "generated_at": "1785139473607"
8
8
  }
Binary file
Binary file
Binary file
@@ -27,6 +27,7 @@ type dataSet = Seed_Runner.dataSet = {
27
27
  name: string,
28
28
  label: string,
29
29
  seed: connection => promise<unit>,
30
+ probeViews?: array<string>,
30
31
  }
31
32
 
32
33
  type rec value = Seed_Types.value =
Binary file
@@ -90,6 +90,33 @@ let warn = (warnings: array<string>): unit =>
90
90
  warnings->Array.forEach(w => Console.log(` - ${w}`))
91
91
  }
92
92
 
93
+ /**
94
+ * Pre-flight: refuse to seed onto a store that already holds data.
95
+ *
96
+ * The harness is a one-shot against a fresh store — a re-run would hit the
97
+ * domain's own duplicate rejections (`CategoryAlreadyExists`, …) and abort
98
+ * mid-phase. Catching it here, before any command is sent, turns that into an
99
+ * honest "nothing was written" startup abort instead of a misleading
100
+ * half-seeded one. It is the inverse of `verifyViews`: seeding refuses to start
101
+ * unless the store reads empty. Stops at the first non-empty probe view, so a
102
+ * populated store aborts after a single query.
103
+ */
104
+ let assertStoreEmpty = async (client: Seed_Client.t, ~probeViews: array<string>): unit =>
105
+ for i in 0 to probeViews->Array.length - 1 {
106
+ switch probeViews->Array.get(i) {
107
+ | Some(name) =>
108
+ let count = await Seed_Client.countNodes(client, ~field=name)
109
+ if count > 0 {
110
+ throw(
111
+ Failed(
112
+ `the target store is not empty — "${name}" already holds ${count->Int.toString} row(s). Seeding is a one-shot against a fresh store; reset the store before re-running.`,
113
+ ),
114
+ )
115
+ }
116
+ | None => ()
117
+ }
118
+ }
119
+
93
120
  /**
94
121
  * Wraps a seed run. On failure it prints the cause and exits non-zero, saying
95
122
  * plainly that the store is now half-seeded — recovery is a reset, not a re-run.
@@ -128,6 +155,9 @@ type dataSet = {
128
155
  name: string,
129
156
  label: string,
130
157
  seed: Seed_Connect.connection => promise<unit>,
158
+ // View names probed by the fresh-store guard before any command is sent; a
159
+ // non-empty one aborts the run at startup. Omit to skip the guard.
160
+ probeViews?: array<string>,
131
161
  }
132
162
 
133
163
  /**
@@ -179,6 +209,9 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
179
209
  }
180
210
  let connection = await connect()
181
211
  Seed_Prompt.close()
212
+ // Runs inside this pre-seed `try`, so refusing a non-empty store (or a
213
+ // failed probe query) is reported as "did not start", not "half-seeded".
214
+ await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
182
215
  Some((chosen, connection))
183
216
  } catch {
184
217
  | Failed(message) =>
@@ -83,6 +83,22 @@ function warn(warnings) {
83
83
  }
84
84
  }
85
85
 
86
+ async function assertStoreEmpty(client, probeViews) {
87
+ for (let i = 0, i_finish = probeViews.length; i < i_finish; ++i) {
88
+ let name = probeViews[i];
89
+ if (name !== undefined) {
90
+ let count = await Seed_Client$ReventlessSeed.countNodes(client, name);
91
+ if (count > 0) {
92
+ throw {
93
+ RE_EXN_ID: Seed_Types$ReventlessSeed.Failed,
94
+ _1: `the target store is not empty — "` + name + `" already holds ` + count.toString() + ` row(s). Seeding is a one-shot against a fresh store; reset the store before re-running.`,
95
+ Error: new Error()
96
+ };
97
+ }
98
+ }
99
+ }
100
+ }
101
+
86
102
  async function run(main) {
87
103
  try {
88
104
  await main();
@@ -146,6 +162,7 @@ function seed(sets, connect) {
146
162
  }
147
163
  let connection = await connect();
148
164
  Seed_Prompt$ReventlessSeed.close();
165
+ await assertStoreEmpty(connection.client, Stdlib_Option.getOr(chosen.probeViews, []));
149
166
  started = [
150
167
  chosen,
151
168
  connection
@@ -179,6 +196,7 @@ export {
179
196
  verifyViews,
180
197
  unfillableWarnings,
181
198
  warn,
199
+ assertStoreEmpty,
182
200
  run,
183
201
  abortStartup,
184
202
  seed,
@@ -1,2 +1,2 @@
1
- #Start(1785092483117)
2
- #Done(1785092488019)
1
+ #Start(1785139469133)
2
+ #Done(1785139473598)
Binary file
Binary file
Binary file
@@ -27,6 +27,7 @@ type dataSet = Seed_Runner.dataSet = {
27
27
  name: string,
28
28
  label: string,
29
29
  seed: connection => promise<unit>,
30
+ probeViews?: array<string>,
30
31
  }
31
32
 
32
33
  type rec value = Seed_Types.value =
Binary file
@@ -90,6 +90,33 @@ let warn = (warnings: array<string>): unit =>
90
90
  warnings->Array.forEach(w => Console.log(` - ${w}`))
91
91
  }
92
92
 
93
+ /**
94
+ * Pre-flight: refuse to seed onto a store that already holds data.
95
+ *
96
+ * The harness is a one-shot against a fresh store — a re-run would hit the
97
+ * domain's own duplicate rejections (`CategoryAlreadyExists`, …) and abort
98
+ * mid-phase. Catching it here, before any command is sent, turns that into an
99
+ * honest "nothing was written" startup abort instead of a misleading
100
+ * half-seeded one. It is the inverse of `verifyViews`: seeding refuses to start
101
+ * unless the store reads empty. Stops at the first non-empty probe view, so a
102
+ * populated store aborts after a single query.
103
+ */
104
+ let assertStoreEmpty = async (client: Seed_Client.t, ~probeViews: array<string>): unit =>
105
+ for i in 0 to probeViews->Array.length - 1 {
106
+ switch probeViews->Array.get(i) {
107
+ | Some(name) =>
108
+ let count = await Seed_Client.countNodes(client, ~field=name)
109
+ if count > 0 {
110
+ throw(
111
+ Failed(
112
+ `the target store is not empty — "${name}" already holds ${count->Int.toString} row(s). Seeding is a one-shot against a fresh store; reset the store before re-running.`,
113
+ ),
114
+ )
115
+ }
116
+ | None => ()
117
+ }
118
+ }
119
+
93
120
  /**
94
121
  * Wraps a seed run. On failure it prints the cause and exits non-zero, saying
95
122
  * plainly that the store is now half-seeded — recovery is a reset, not a re-run.
@@ -128,6 +155,9 @@ type dataSet = {
128
155
  name: string,
129
156
  label: string,
130
157
  seed: Seed_Connect.connection => promise<unit>,
158
+ // View names probed by the fresh-store guard before any command is sent; a
159
+ // non-empty one aborts the run at startup. Omit to skip the guard.
160
+ probeViews?: array<string>,
131
161
  }
132
162
 
133
163
  /**
@@ -179,6 +209,9 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
179
209
  }
180
210
  let connection = await connect()
181
211
  Seed_Prompt.close()
212
+ // Runs inside this pre-seed `try`, so refusing a non-empty store (or a
213
+ // failed probe query) is reported as "did not start", not "half-seeded".
214
+ await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
182
215
  Some((chosen, connection))
183
216
  } catch {
184
217
  | Failed(message) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-seed",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "Reusable harness for seeding a Reventless platform through its public GraphQL command API",
5
5
  "license": "Apache-2.0",
6
6
  "devDependencies": {
package/src/Seed.res CHANGED
@@ -27,6 +27,7 @@ type dataSet = Seed_Runner.dataSet = {
27
27
  name: string,
28
28
  label: string,
29
29
  seed: connection => promise<unit>,
30
+ probeViews?: array<string>,
30
31
  }
31
32
 
32
33
  type rec value = Seed_Types.value =
@@ -90,6 +90,33 @@ let warn = (warnings: array<string>): unit =>
90
90
  warnings->Array.forEach(w => Console.log(` - ${w}`))
91
91
  }
92
92
 
93
+ /**
94
+ * Pre-flight: refuse to seed onto a store that already holds data.
95
+ *
96
+ * The harness is a one-shot against a fresh store — a re-run would hit the
97
+ * domain's own duplicate rejections (`CategoryAlreadyExists`, …) and abort
98
+ * mid-phase. Catching it here, before any command is sent, turns that into an
99
+ * honest "nothing was written" startup abort instead of a misleading
100
+ * half-seeded one. It is the inverse of `verifyViews`: seeding refuses to start
101
+ * unless the store reads empty. Stops at the first non-empty probe view, so a
102
+ * populated store aborts after a single query.
103
+ */
104
+ let assertStoreEmpty = async (client: Seed_Client.t, ~probeViews: array<string>): unit =>
105
+ for i in 0 to probeViews->Array.length - 1 {
106
+ switch probeViews->Array.get(i) {
107
+ | Some(name) =>
108
+ let count = await Seed_Client.countNodes(client, ~field=name)
109
+ if count > 0 {
110
+ throw(
111
+ Failed(
112
+ `the target store is not empty — "${name}" already holds ${count->Int.toString} row(s). Seeding is a one-shot against a fresh store; reset the store before re-running.`,
113
+ ),
114
+ )
115
+ }
116
+ | None => ()
117
+ }
118
+ }
119
+
93
120
  /**
94
121
  * Wraps a seed run. On failure it prints the cause and exits non-zero, saying
95
122
  * plainly that the store is now half-seeded — recovery is a reset, not a re-run.
@@ -128,6 +155,9 @@ type dataSet = {
128
155
  name: string,
129
156
  label: string,
130
157
  seed: Seed_Connect.connection => promise<unit>,
158
+ // View names probed by the fresh-store guard before any command is sent; a
159
+ // non-empty one aborts the run at startup. Omit to skip the guard.
160
+ probeViews?: array<string>,
131
161
  }
132
162
 
133
163
  /**
@@ -179,6 +209,9 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
179
209
  }
180
210
  let connection = await connect()
181
211
  Seed_Prompt.close()
212
+ // Runs inside this pre-seed `try`, so refusing a non-empty store (or a
213
+ // failed probe query) is reported as "did not start", not "half-seeded".
214
+ await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
182
215
  Some((chosen, connection))
183
216
  } catch {
184
217
  | Failed(message) =>
@@ -83,6 +83,22 @@ function warn(warnings) {
83
83
  }
84
84
  }
85
85
 
86
+ async function assertStoreEmpty(client, probeViews) {
87
+ for (let i = 0, i_finish = probeViews.length; i < i_finish; ++i) {
88
+ let name = probeViews[i];
89
+ if (name !== undefined) {
90
+ let count = await Seed_Client$ReventlessSeed.countNodes(client, name);
91
+ if (count > 0) {
92
+ throw {
93
+ RE_EXN_ID: Seed_Types$ReventlessSeed.Failed,
94
+ _1: `the target store is not empty — "` + name + `" already holds ` + count.toString() + ` row(s). Seeding is a one-shot against a fresh store; reset the store before re-running.`,
95
+ Error: new Error()
96
+ };
97
+ }
98
+ }
99
+ }
100
+ }
101
+
86
102
  async function run(main) {
87
103
  try {
88
104
  await main();
@@ -146,6 +162,7 @@ function seed(sets, connect) {
146
162
  }
147
163
  let connection = await connect();
148
164
  Seed_Prompt$ReventlessSeed.close();
165
+ await assertStoreEmpty(connection.client, Stdlib_Option.getOr(chosen.probeViews, []));
149
166
  started = [
150
167
  chosen,
151
168
  connection
@@ -179,6 +196,7 @@ export {
179
196
  verifyViews,
180
197
  unfillableWarnings,
181
198
  warn,
199
+ assertStoreEmpty,
182
200
  run,
183
201
  abortStartup,
184
202
  seed,