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