@sequenceholdings/orm 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.
package/dist/cli.cjs CHANGED
@@ -26,7 +26,7 @@ var WIRE_TYPES = {
26
26
  };
27
27
  function generateNamespaceTypes(definition) {
28
28
  const lines = [
29
- `// Generated by \`seq-studio orm generate\` from the '${definition.namespace}' definitions \u2014 do not edit.`,
29
+ `// Generated by \`seq-studio orm apply\` from the '${definition.namespace}' definitions \u2014 do not edit.`,
30
30
  "",
31
31
  `export type ${typesInterfaceName(definition.namespace)} = {`
32
32
  ];
@@ -2056,10 +2056,12 @@ var USAGE = `usage:
2056
2056
  seq-studio orm plan [dir] -e <env> offline dry run: compile definitions and diff against the registry (no database, no writes)
2057
2057
  seq-studio orm apply [dir] -e <env> author the migration, register, apply, and refresh types.gen.ts (the everyday command)
2058
2058
  seq-studio orm apply --dry-run -e <env> rehearse the migration on a copy of the environment's real data, then discard it (no real apply)
2059
+ seq-studio orm apply --no-create -e <env> apply only if the namespace is already registered (CI guard; never provisions a new namespace)
2059
2060
  seq-studio orm diff [dir] write/verify the committed migration standalone (--check is the offline CI gate; --allow-destructive consents)
2060
2061
 
2061
2062
  A namespace directory holds one <table>.yaml per table (see the scaffold).
2062
- Authenticate with: seqapi login
2063
+ Environments: see \`seq-studio envs list\` (discovered after you authenticate).
2064
+ Authenticate with: seq-studio login
2063
2065
  `;
2064
2066
  var tokenProvider = null;
2065
2067
  function setTokenProvider(provider) {
@@ -2161,7 +2163,7 @@ async function planCommand(rest) {
2161
2163
  `);
2162
2164
  const registry = await fetchRegistryState(loaded.definition.namespace, loaded.contentHash);
2163
2165
  if (registry.kind === "offline") {
2164
- console.log("-- registry: pass -e <env> (and seqapi login) to compare against a deployment");
2166
+ console.log("-- registry: pass -e <env> (and seq-studio login) to compare against a deployment");
2165
2167
  return 0;
2166
2168
  }
2167
2169
  if (registry.kind === "missing") {
@@ -2179,6 +2181,7 @@ async function applyCommand(rest) {
2179
2181
  const dir = positional(rest) ?? ".";
2180
2182
  const allowDestructive = rest.includes("--allow-destructive");
2181
2183
  const dryRun = rest.includes("--dry-run");
2184
+ const noCreate = rest.includes("--no-create");
2182
2185
  if (await diffCommand({ dir, allowDestructive, check: false, log: (line) => console.log(line) }) !== 0) return 1;
2183
2186
  if (!await requireValidated(dir)) return 1;
2184
2187
  const loaded = await loadNamespaceDefinition(dir);
@@ -2188,7 +2191,13 @@ async function applyCommand(rest) {
2188
2191
  const registered = await apiRequestStrict(
2189
2192
  "POST",
2190
2193
  "/api/orm/namespaces",
2191
- { files: await readDefinitionFiles(dir, loaded.files), ...migration ? { migration } : {} }
2194
+ {
2195
+ files: await readDefinitionFiles(dir, loaded.files),
2196
+ ...migration ? { migration } : {},
2197
+ // The server refuses to create a new namespace under --no-create, atomically
2198
+ // inside the register transaction (no preflight TOCTOU).
2199
+ ...noCreate ? { no_create: true } : {}
2200
+ }
2192
2201
  );
2193
2202
  console.log(
2194
2203
  (registered.created ? `registered ${namespace}@${registered.content_hash.slice(0, 12)}` : `already registered ${namespace}@${registered.content_hash.slice(0, 12)}`) + (migration ? ` with committed migration ${migration.name}` : "")
@@ -2215,12 +2224,18 @@ async function applyCommand(rest) {
2215
2224
  var APPLY_POLL_INTERVAL_MS = 2500;
2216
2225
  async function waitForQueuedApply(namespace, contentHash, runId) {
2217
2226
  let lastStatus = null;
2227
+ const MISSING_RUN_POLL_LIMIT = 8;
2228
+ let missingRunPolls = 0;
2218
2229
  for (; ; ) {
2219
2230
  await new Promise((resolve) => setTimeout(resolve, APPLY_POLL_INTERVAL_MS));
2220
- const run = runId ? await apiRequestStrict(
2231
+ const run = runId ? await apiRequest(
2221
2232
  "GET",
2222
- `/api/orm/namespaces/${namespace}/apply?run=${runId}`
2233
+ `/api/orm/namespaces/${namespace}/apply?run=${runId}`,
2234
+ void 0,
2235
+ { allow404: true }
2223
2236
  ) : null;
2237
+ if (run) missingRunPolls = 0;
2238
+ else if (runId) missingRunPolls++;
2224
2239
  if (run && run.verdict === null) {
2225
2240
  if (run.status !== lastStatus) {
2226
2241
  lastStatus = run.status;
@@ -2230,6 +2245,7 @@ async function waitForQueuedApply(namespace, contentHash, runId) {
2230
2245
  }
2231
2246
  const body = await apiRequestStrict("GET", `/api/orm/namespaces/${namespace}?hash=${contentHash}`);
2232
2247
  const version = body.hash_version;
2248
+ const isActive = version != null && body.namespace.active_version_id === version.id;
2233
2249
  if (run?.verdict === "failed") {
2234
2250
  const detail = version?.status_detail ?? "no detail recorded";
2235
2251
  console.error(`apply failed (run ${run.status.toLowerCase()}): ${detail}`);
@@ -2238,14 +2254,24 @@ async function waitForQueuedApply(namespace, contentHash, runId) {
2238
2254
  }
2239
2255
  return 1;
2240
2256
  }
2241
- if (run?.verdict === "succeeded" || version?.status === "applied") {
2257
+ if (run?.verdict === "succeeded" || isActive) {
2242
2258
  console.log(`applied ${namespace} \u2014 the version is now active`);
2243
2259
  return 0;
2244
2260
  }
2245
- if (version?.status === "failed") {
2261
+ if (!runId && version?.status === "failed") {
2246
2262
  console.error(`apply failed: ${version.status_detail ?? "no detail recorded"}`);
2247
2263
  return 1;
2248
2264
  }
2265
+ if (runId && missingRunPolls >= MISSING_RUN_POLL_LIMIT) {
2266
+ if (version?.status === "failed") {
2267
+ console.error(`apply failed: ${version.status_detail ?? "no detail recorded"}`);
2268
+ } else {
2269
+ console.error(
2270
+ `apply status unknown \u2014 run ${runId} is no longer retrievable and ${namespace} is not active at this version (last durable status: ${version?.status ?? "unknown"}). Re-run \`seq-studio orm apply\`.`
2271
+ );
2272
+ }
2273
+ return 1;
2274
+ }
2249
2275
  }
2250
2276
  }
2251
2277
  async function dryRunRehearsal({
@@ -2324,7 +2350,7 @@ async function apiRequest(method, path, body, options) {
2324
2350
  const baseUrl = process.env[BASE_URL_ENV]?.trim();
2325
2351
  const token = await resolveToken();
2326
2352
  if (!baseUrl) throw new Error("no environment resolved \u2014 pass -e <env>");
2327
- if (!token) throw new Error("not authenticated \u2014 run: seqapi login");
2353
+ if (!token) throw new Error("not authenticated \u2014 run: seq-studio login");
2328
2354
  const response = await fetch(`${baseUrl}${path}`, {
2329
2355
  method,
2330
2356
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },