neon 8.3.6 → 8.4.2

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.
@@ -2,10 +2,11 @@ import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { declaredNeonServices } from "../config_services.js";
4
4
  import { loadProjectConfig } from "../project.js";
5
- import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-XsUZj_Ay.js";
5
+ import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-BpP3Dqpe.js";
6
6
  import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
7
+ import { StagingOnlyError, assertPolicyAvailable, assertRealtimeAvailable, stagingFeaturesEnabled } from "../utils/staging.js";
7
8
  import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
8
- import { ErrorCode, createNeonApiFromOptions, isPlatformError } from "@neon/config";
9
+ import { ErrorCode, PlatformError, createNeonApiFromOptions, isPlatformError } from "@neon/config";
9
10
  import { credentialScopesSatisfied } from "@neon/config/v1";
10
11
  import { plan, pullConfig } from "@neon/config-runtime";
11
12
  //#region ../../internals/env-core/dist/reuse-secrets.js
@@ -238,7 +239,8 @@ var ServiceNotOnBranchError = class extends Error {
238
239
  const CLAIMABLE_UNSUPPORTED_SERVICES = /* @__PURE__ */ new Set([
239
240
  "ai-gateway",
240
241
  "functions",
241
- "object-storage"
242
+ "object-storage",
243
+ "realtime"
242
244
  ]);
243
245
  const CLAIMABLE_RESOLVE_ORDER = [
244
246
  "postgres",
@@ -366,7 +368,7 @@ const resolveClaimableNeonEnvVars = async (ctx) => {
366
368
  };
367
369
  /**
368
370
  * Resolve the branch's Neon env vars (pooled / direct `DATABASE_URL`, plus Auth /
369
- * Data API when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
371
+ * Data API / Realtime when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
370
372
  * injects them) and `neon env pull` (which writes them to a `.env` file).
371
373
  *
372
374
  * Tiered:
@@ -382,7 +384,7 @@ const resolveClaimableNeonEnvVars = async (ctx) => {
382
384
  * function still gets `NEON_FUNCTION_*_BASE_URL`. Otherwise `fetchEnv` evaluates
383
385
  * the policy.
384
386
  * 2. no `neon.ts`, but a project + branch are known -> `pullConfig` reads the
385
- * branch's live state (Auth / Data API enablement plus any object-storage
387
+ * branch's live state (Auth / Data API / Realtime enablement plus any object-storage
386
388
  * buckets) into a config, then `fetchEnv` resolves what is actually enabled —
387
389
  * so a branch with a bucket gets its `AWS_*` storage vars pulled with no policy.
388
390
  * Function invocation URLs are listed live (`functionUrls: "all-live"`) because
@@ -412,7 +414,8 @@ const resolveNeonEnvVars = async (ctx) => {
412
414
  const pulled = await pullConfig({
413
415
  projectId: ctx.projectId,
414
416
  branchId: ctx.branchId,
415
- ...apiOptions(ctx)
417
+ ...apiOptions(ctx),
418
+ realtime: stagingFeaturesEnabled(ctx.apiHost)
416
419
  });
417
420
  const resolvedBranch = {
418
421
  id: pulled.branch.id,
@@ -510,12 +513,14 @@ const resolveSelectedServices = async (ctx, directServices, envKeys) => {
510
513
  const impliedServices = servicesForEnvKeys(envKeys);
511
514
  const services = ENV_PULL_SERVICES.filter((service) => directServices.includes(service) || impliedServices.includes(service));
512
515
  const selectedKeys = envKeysForSelection(directServices, envKeys);
516
+ if (services.includes("realtime")) assertRealtimeAvailable(ctx.apiHost);
513
517
  const api = apiFor(ctx);
514
518
  const has = (service) => services.includes(service);
515
519
  const checkAiGateway = has("ai-gateway") && !selectedKeys.includes(NEON_ENV_VAR_KEYS.aiGateway.apiKey);
516
- const [auth, dataApiEnabled, buckets, aiGatewayAvailable, functions] = await Promise.all([
520
+ const [auth, dataApiEnabled, realtime, buckets, aiGatewayAvailable, functions] = await Promise.all([
517
521
  has("auth") ? api.getNeonAuth(projectId, branchId) : null,
518
522
  has("data-api") ? readDataApiEnabled(api, projectId, branchId) : null,
523
+ has("realtime") ? readRealtime(api, projectId, branchId) : null,
519
524
  has("object-storage") ? api.listBranchBuckets(projectId, branchId) : null,
520
525
  checkAiGateway ? readAiGatewayAvailable(api, projectId, branchId) : null,
521
526
  directServices.includes("functions") ? api.listBranchFunctions(projectId, branchId) : null
@@ -527,6 +532,7 @@ const resolveSelectedServices = async (ctx, directServices, envKeys) => {
527
532
  const config = configForServices(services, branchId, {
528
533
  authEnabled: auth !== null,
529
534
  dataApiEnabled,
535
+ realtimeEnabled: realtime === null ? false : realtime.enabled || realtime.pending,
530
536
  buckets: buckets ?? [],
531
537
  aiGatewayAvailable: aiGatewayAvailable ?? true
532
538
  }, {
@@ -567,6 +573,11 @@ const readAiGatewayAvailable = async (api, projectId, branchId) => {
567
573
  throw error;
568
574
  }
569
575
  };
576
+ const readRealtime = async (api, projectId, branchId) => {
577
+ const getRealtime = api.getProjectBranchRealtime;
578
+ if (!getRealtime) throw new PlatformError(ErrorCode.FeatureUnavailable, "This NeonApi adapter does not implement getProjectBranchRealtime.");
579
+ return await getRealtime.call(api, projectId, branchId);
580
+ };
570
581
  /** Neon's default database, and the one `fetchEnv` prefers when a branch has several. */
571
582
  const NEON_DEFAULT_DATABASE = "neondb";
572
583
  /**
@@ -580,6 +591,7 @@ const configForServices = (services, branchId, branch, selection) => {
580
591
  const provisionWith = {
581
592
  auth: `${getCliName()} neon-auth enable`,
582
593
  "data-api": `${getCliName()} data-api create`,
594
+ realtime: `${getCliName()} realtime enable`,
583
595
  "object-storage": `${getCliName()} buckets create <name>`
584
596
  };
585
597
  const notOnBranch = (service, what) => {
@@ -604,6 +616,10 @@ const configForServices = (services, branchId, branch, selection) => {
604
616
  if (branch.dataApiEnabled === false) notOnBranch("data-api", "Data API integration");
605
617
  config.dataApi = true;
606
618
  }
619
+ if (services.includes("realtime")) {
620
+ if (!branch.realtimeEnabled) notOnBranch("realtime", "Realtime integration");
621
+ config.preview = { realtime: true };
622
+ }
607
623
  if (services.includes("object-storage")) {
608
624
  if (branch.buckets.length === 0) notOnBranch("object-storage", "object-storage buckets");
609
625
  config.buckets = Object.fromEntries(branch.buckets.map((bucket) => [bucket.name, { access: bucket.accessLevel }]));
@@ -633,7 +649,7 @@ const resolveDevEnv = async (ctx) => {
633
649
  credential
634
650
  };
635
651
  } catch (err) {
636
- if (err instanceof DevEnvMismatchError) throw err;
652
+ if (err instanceof DevEnvMismatchError || err instanceof StagingOnlyError) throw err;
637
653
  if (err instanceof MissingBranchContextError) {
638
654
  log.debug("dev: %s; skipping env injection", err.message);
639
655
  return {
@@ -742,6 +758,7 @@ const loadNeonConfig = async (ctx) => {
742
758
  ...ctx.config ? { path: ctx.config } : {},
743
759
  ...ctx.omitUnsetFunctionEnv ? { unsetFunctionEnv: "omit" } : {}
744
760
  });
761
+ assertPolicyAvailable(config, ctx.apiHost);
745
762
  return config;
746
763
  } catch (err) {
747
764
  const message = err instanceof Error ? err.message : String(err);
@@ -20,7 +20,7 @@ import { ErrorCode, PlatformError, createNeonApiFromOptions, deriveCredentialSco
20
20
  * Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the
21
21
  * camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used
22
22
  * by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /
23
- * {@link dataApiEnvSchema}.
23
+ * {@link dataApiEnvSchema} / {@link realtimeEnvSchema}.
24
24
  */
25
25
  /**
26
26
  * Neon's default database, created with every project. When a branch has several databases
@@ -44,6 +44,10 @@ const NEON_ENV_VAR_KEYS = {
44
44
  jwksUrl: "NEON_AUTH_JWKS_URL"
45
45
  },
46
46
  dataApi: { url: "NEON_DATA_API_URL" },
47
+ realtime: {
48
+ url: "NEON_REALTIME_URL",
49
+ secret: "NEON_REALTIME_SECRET"
50
+ },
47
51
  /**
48
52
  * Object storage (Preview). The S3 SDKs read `AWS_*` from their standard config chain, so
49
53
  * a branch credential + `neon dev` / `env pull` makes object storage work from env alone.
@@ -101,6 +105,55 @@ function requiredValue(value, description) {
101
105
  if (value === null) throw new Error(`fetchEnv: missing ${description}.`);
102
106
  return value;
103
107
  }
108
+ const REALTIME_POLL_INTERVAL_MS = 1e3;
109
+ /**
110
+ * Wait for asynchronous Realtime provisioning/rotation before reading its secret. The
111
+ * secret endpoint deliberately returns 404 until provisioning finishes, and during a
112
+ * rotation it can still expose the previous key while `pending` is true.
113
+ */
114
+ async function resolveRealtimeEnv(args) {
115
+ const getRealtime = args.api.getProjectBranchRealtime;
116
+ if (!getRealtime) throw new PlatformError(ErrorCode.FeatureUnavailable, "fetchEnv cannot resolve Realtime: this NeonApi adapter does not implement getProjectBranchRealtime.");
117
+ const getSecret = args.api.getProjectBranchRealtimeSecret;
118
+ let resolvedEnv;
119
+ while (resolvedEnv === void 0) {
120
+ const snapshot = await getRealtime.call(args.api, args.projectId, args.branch.id);
121
+ if (!snapshot.pending) {
122
+ if (!snapshot.enabled) throw new PlatformError(ErrorCode.NotFound, [`fetchEnv: branch policy enables realtime but Realtime is disabled on branch ${args.branch.name} (${args.branch.id}).`, "Enable it via `apply(config, { projectId, branchId })` (or `npx neon deploy`) and re-run fetchEnv. Or set realtime to false or omit it."].join(" "), { details: {
123
+ projectId: args.projectId,
124
+ branchId: args.branch.id
125
+ } });
126
+ let secret;
127
+ if (args.wantsSecret) {
128
+ if (!getSecret) throw new PlatformError(ErrorCode.FeatureUnavailable, "fetchEnv cannot resolve NEON_REALTIME_SECRET: this NeonApi adapter does not implement getProjectBranchRealtimeSecret.");
129
+ secret = await getSecret.call(args.api, args.projectId, args.branch.id);
130
+ }
131
+ if (!secret?.pending) resolvedEnv = {
132
+ ...args.wantsUrl ? { url: realtimeWebSocketUrl(requiredRealtimeInvocationUrl(snapshot, args.branch)) } : {},
133
+ ...secret ? { secret: secret.secret } : {}
134
+ };
135
+ }
136
+ if (resolvedEnv === void 0) await new Promise((resolve) => setTimeout(resolve, REALTIME_POLL_INTERVAL_MS));
137
+ }
138
+ return resolvedEnv;
139
+ }
140
+ function requiredRealtimeInvocationUrl(snapshot, branch) {
141
+ if (snapshot.invocationUrl !== void 0 && snapshot.invocationUrl !== "") return snapshot.invocationUrl;
142
+ throw new PlatformError(ErrorCode.ServerError, `Realtime is enabled on branch ${branch.name} (${branch.id}), but the Neon API returned no invocation URL.`, { details: { branchId: branch.id } });
143
+ }
144
+ /** Normalize the control-plane invocation URL to the WebSocket URL the client SDK needs. */
145
+ function realtimeWebSocketUrl(value) {
146
+ let parsed;
147
+ try {
148
+ parsed = new URL(value);
149
+ } catch {
150
+ throw new PlatformError(ErrorCode.ServerError, `Realtime invocation URL is invalid: ${JSON.stringify(value)}.`);
151
+ }
152
+ if (parsed.protocol === "https:") parsed.protocol = "wss:";
153
+ else if (parsed.protocol === "http:") parsed.protocol = "ws:";
154
+ else if (parsed.protocol !== "wss:" && parsed.protocol !== "ws:") throw new PlatformError(ErrorCode.ServerError, `Realtime invocation URL must use http(s) or ws(s): ${JSON.stringify(value)}.`);
155
+ return parsed.toString();
156
+ }
104
157
  /**
105
158
  * The {@link fetchEnv} body, with the key selection as a plain argument and no generic
106
159
  * narrowing. Exists for callers that compute the selection at runtime — notably
@@ -130,6 +183,7 @@ async function fetchEnvKeysState(config, options, keys, policy) {
130
183
  const wantsUnpooled = wants(K.postgres.databaseUrlUnpooled);
131
184
  const wantsAuth = desired.authEnabled && (wants(K.auth.baseUrl) || wants(K.auth.jwksUrl));
132
185
  const wantsDataApi = desired.dataApiEnabled && wants(K.dataApi.url);
186
+ const wantsRealtime = desired.realtimePolicy === "enabled" && (wants(K.realtime.url) || wants(K.realtime.secret));
133
187
  const gatewayEnabled = desired.preview?.aiGatewayEnabled ?? false;
134
188
  const functionUrlMode = options.functionUrls ?? "policy";
135
189
  const declaredSlugs = (desired.preview?.functions ?? []).map((fn) => fn.slug);
@@ -196,6 +250,19 @@ async function fetchEnvKeysState(config, options, keys, policy) {
196
250
  }
197
251
  result.dataApi = { url: dataApiSnapshot.url };
198
252
  }
253
+ if (wantsRealtime) {
254
+ const realtime = await resolveRealtimeEnv({
255
+ api,
256
+ projectId,
257
+ branch,
258
+ wantsUrl: wants(K.realtime.url),
259
+ wantsSecret: wants(K.realtime.secret)
260
+ });
261
+ const realtimeEnv = {};
262
+ if (wants(K.realtime.url)) realtimeEnv.url = requiredValue(realtime.url, "WebSocket URL for enabled Realtime");
263
+ if (wants(K.realtime.secret)) realtimeEnv.secret = requiredValue(realtime.secret, "shared secret for enabled Realtime");
264
+ result.realtime = realtimeEnv;
265
+ }
199
266
  const wantsGateway = gatewayEnabled && (wants(K.aiGateway.apiKey) || wants(K.aiGateway.baseUrl));
200
267
  const wantsStorageCredential = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey));
201
268
  const wantsGatewayCredential = gatewayEnabled && wants(K.aiGateway.apiKey);
@@ -360,6 +427,7 @@ function policyEnvKeys(desired) {
360
427
  K.branch.name,
361
428
  ...desired.authEnabled ? [K.auth.baseUrl, K.auth.jwksUrl] : [],
362
429
  ...desired.dataApiEnabled ? [K.dataApi.url] : [],
430
+ ...desired.realtimePolicy === "enabled" ? [K.realtime.url, K.realtime.secret] : [],
363
431
  ...(desired.preview?.buckets.length ?? 0) > 0 ? [
364
432
  K.storage.accessKeyId,
365
433
  K.storage.secretAccessKey,
@@ -572,6 +640,8 @@ function toEntries(env) {
572
640
  put(K.auth.baseUrl, env.auth?.baseUrl);
573
641
  put(K.auth.jwksUrl, env.auth?.jwksUrl);
574
642
  put(K.dataApi.url, env.dataApi?.url);
643
+ put(K.realtime.url, env.realtime?.url);
644
+ put(K.realtime.secret, env.realtime?.secret);
575
645
  put(K.storage.accessKeyId, env.storage?.accessKeyId);
576
646
  put(K.storage.secretAccessKey, env.storage?.secretAccessKey);
577
647
  put(K.storage.endpoint, env.storage?.endpoint);
package/dist/api.js CHANGED
@@ -644,6 +644,21 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
644
644
  database_name: databaseName
645
645
  }
646
646
  })),
647
+ enableProjectBranchRealtime: (projectId, branchId, data) => call(() => raw.enableProjectBranchRealtime({
648
+ client,
649
+ path: {
650
+ project_id: projectId,
651
+ branch_id: branchId
652
+ },
653
+ body: data
654
+ })),
655
+ disableProjectBranchRealtime: (projectId, branchId) => call(() => raw.disableProjectBranchRealtime({
656
+ client,
657
+ path: {
658
+ project_id: projectId,
659
+ branch_id: branchId
660
+ }
661
+ })),
647
662
  listProjectVpcEndpoints: (projectId) => call(() => raw.listProjectVpcEndpoints({
648
663
  client,
649
664
  path: { project_id: projectId }
@@ -7,7 +7,7 @@ import { t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
7
7
  import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
8
8
  import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, writeClaimableCredentials } from "../claimable/state.js";
9
9
  import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
10
- import { claimableDataApiCreateBody, declaredNeonServices } from "../config_services.js";
10
+ import { claimableDataApiCreateBody, declaredClaimableNeonServices, isClaimableService } from "../config_services.js";
11
11
  import { projectConfigPath } from "../project.js";
12
12
  import { resolveEnvFilePath } from "../env_file.js";
13
13
  import { writer } from "../writer.js";
@@ -30,6 +30,7 @@ var claim_exports = /* @__PURE__ */ __exportAll({
30
30
  describe: () => describe,
31
31
  handler: () => handler
32
32
  });
33
+ const CLAIMABLE_SERVICES = NEON_SERVICES.filter(isClaimableService);
33
34
  const CAPABILITY_FOR_SERVICE = {
34
35
  postgres: "postgres",
35
36
  auth: "auth",
@@ -106,7 +107,7 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
106
107
  hidden: true
107
108
  }).command("create", "Create a temporary Neon project without an account", (y) => y.option("service", servicesOption({
108
109
  key: "service",
109
- allowed: NEON_SERVICES,
110
+ allowed: CLAIMABLE_SERVICES,
110
111
  describe: "Services to request for the claimable project",
111
112
  also: "Postgres is always included. Services unavailable before claim are recorded and reported."
112
113
  })).option("file", {
@@ -122,12 +123,12 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
122
123
  }).strict().check(noPassthrough("claim create")), async (args) => {
123
124
  const rawServices = servicesFlagValue(args.service);
124
125
  const services = rawServices ? parseServices(rawServices, {
125
- allowed: NEON_SERVICES,
126
+ allowed: CLAIMABLE_SERVICES,
126
127
  flag: "--service",
127
128
  onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
128
129
  }) : [];
129
130
  const policy = await loadCreatePolicy(typeof args.config === "string" ? args.config : void 0, typeof args.contextFile === "string" ? args.contextFile : void 0);
130
- const configuredServices = policy ? declaredNeonServices(policy.config) : [];
131
+ const configuredServices = policy ? declaredClaimableNeonServices(policy.config) : [];
131
132
  const dataApi = policy ? claimableDataApiCreateBody(policy.config) : void 0;
132
133
  await create({
133
134
  ...args,
@@ -11,6 +11,7 @@ import { toNeonConfigView } from "../config_format.js";
11
11
  import { declaredNeonServices } from "../config_services.js";
12
12
  import { loadProjectConfig, neonConfigFilename, projectConfigPath, projectDir } from "../project.js";
13
13
  import { loadEnvFileIntoProcess } from "../env_file.js";
14
+ import { assertPolicyAvailable, assertRealtimeAvailable, listedServices, stagingFeaturesEnabled, stagingFeaturesEnabledForArgv } from "../utils/staging.js";
14
15
  import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
15
16
  import { writer } from "../writer.js";
16
17
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
@@ -26,7 +27,7 @@ import { autoPullEnvAfterPin } from "./env.js";
26
27
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
27
28
  import { dirname, join, relative, resolve } from "node:path";
28
29
  import chalk from "chalk";
29
- import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
30
+ import { defineConfig, packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
30
31
  import { PushAbortedError, PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
31
32
  //#region src/commands/config.ts
32
33
  var config_exports = /* @__PURE__ */ __exportAll({
@@ -158,7 +159,11 @@ const missingDependencies = (cwd) => {
158
159
  * starter policy, which is what `config init` has always written.
159
160
  */
160
161
  const resolveServices = async (props) => {
161
- if (props.services !== void 0) return parseConfigInitServices(props.services, (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical)));
162
+ if (props.services !== void 0) {
163
+ const services = parseConfigInitServices(props.services, (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical)));
164
+ if (services.includes("realtime")) assertRealtimeAvailable(props.apiHost);
165
+ return services;
166
+ }
162
167
  if (props.pickServices) return props.pickServices();
163
168
  if (isCi() || !process.stdout.isTTY) return [];
164
169
  return pickServicesInteractively();
@@ -264,7 +269,10 @@ const initCmd = async (props) => {
264
269
  writeFileSync(join(cwd, "neon.ts"), renderNeonConfig(services));
265
270
  if (!props.silent) {
266
271
  if (services.length === 0) log.info("Created neon.ts with a starter policy.");
267
- else log.info("Created neon.ts declaring %s.", services.join(", "));
272
+ else {
273
+ const declared = services.map((service) => service === "realtime" ? "Realtime" : service);
274
+ log.info("Created neon.ts declaring %s.", declared.join(", "));
275
+ }
268
276
  }
269
277
  if (services.includes("functions")) scaffoldFunction(cwd);
270
278
  }
@@ -287,6 +295,18 @@ const planAdd = (target, configPath, explicitConfig) => {
287
295
  const flag = explicitConfig ? ` --config ${shellQuote(explicitConfig)}` : "";
288
296
  const provision = `Next: \`${cli} config plan${flag}\` to preview, \`${cli} config apply${flag}\` to provision.`;
289
297
  switch (target.kind) {
298
+ case "realtime": {
299
+ const realtime = target.allowedOrigins === void 0 ? true : { allowedOrigins: target.allowedOrigins };
300
+ defineConfig({ preview: { realtime } });
301
+ return {
302
+ edit: {
303
+ kind: "realtime",
304
+ enabled: true,
305
+ ...target.allowedOrigins !== void 0 ? { allowedOrigins: target.allowedOrigins } : {}
306
+ },
307
+ next: provision
308
+ };
309
+ }
290
310
  case "service": return {
291
311
  edit: {
292
312
  kind: "service",
@@ -326,7 +346,7 @@ const planAdd = (target, configPath, explicitConfig) => {
326
346
  }
327
347
  };
328
348
  /**
329
- * Declare a service, function, or bucket in the project's `neon.ts` with a text edit that
349
+ * Declare a service, feature, function, or bucket in the project's `neon.ts` with a text edit that
330
350
  * leaves the rest of the file alone, creating the file (and installing the config packages)
331
351
  * when the project has none. Local only, like `config init`.
332
352
  */
@@ -346,8 +366,8 @@ const addCmd = async (props) => {
346
366
  const plan = planAdd(props.target, configPath, props.config);
347
367
  const { source, changes } = editNeonConfig(existing ? readFileSync(existing, "utf8") : renderNeonConfig([]), plan.edit);
348
368
  if (changes.length === 0) {
349
- if (props.target.kind === "service") {
350
- log.info("%s already enables that; nothing to change.", shown(configPath));
369
+ if (props.target.kind === "service" || props.target.kind === "realtime") {
370
+ log.info("%s already has the requested configuration; nothing to change.", shown(configPath));
351
371
  return;
352
372
  }
353
373
  throw new Error(`${shown(configPath)} already declares ${props.target.kind} "${props.target.kind === "function" ? props.target.slug : props.target.name}". Edit it there, or pick another name.`);
@@ -436,7 +456,22 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
436
456
  }))).command("ai-gateway", "Enable the AI Gateway (aiGateway: true)", addSharedOptions, (args) => addCmd(addProps(args, {
437
457
  kind: "service",
438
458
  service: "ai-gateway"
439
- }))).command("function <slug>", "Declare a Neon Function and create its handler file", (yargs) => addSharedOptions(yargs).example("$0 config add function sendemail", "Creates functions/sendemail.ts and declares it").example("$0 config add function sendemail --source src/send.ts", "Declares an existing handler").positional("slug", {
459
+ }))).command({
460
+ command: "realtime",
461
+ describe: stagingFeaturesEnabledForArgv() ? "Enable Realtime (preview.realtime: true)" : false,
462
+ builder: (yargs) => addSharedOptions(yargs).option("allowed-origin", {
463
+ describe: "Allowed browser origin (repeatable). \"*\" allows every origin",
464
+ type: "array",
465
+ string: true
466
+ }),
467
+ handler: (args) => {
468
+ assertRealtimeAvailable();
469
+ return addCmd(addProps(args, {
470
+ kind: "realtime",
471
+ ...args.allowedOrigin !== void 0 ? { allowedOrigins: args.allowedOrigin } : {}
472
+ }));
473
+ }
474
+ }).command("function <slug>", "Declare a Neon Function and create its handler file", (yargs) => addSharedOptions(yargs).example("$0 config add function sendemail", "Creates functions/sendemail.ts and declares it").example("$0 config add function sendemail --source src/send.ts", "Declares an existing handler").positional("slug", {
440
475
  describe: "Function slug: 1-20 lowercase letters and digits",
441
476
  type: "string",
442
477
  demandOption: true
@@ -467,7 +502,7 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
467
502
  kind: "bucket",
468
503
  name: args.name,
469
504
  access: args.access
470
- })))).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
505
+ })))).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.strict().options({
471
506
  install: {
472
507
  describe: "Install @neon/config and @neon/env if they're missing. On by default; use --no-install to just print the command.",
473
508
  type: "boolean",
@@ -475,7 +510,7 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
475
510
  },
476
511
  services: servicesOption({
477
512
  key: "services",
478
- allowed: NEON_SERVICES,
513
+ allowed: listedServices(NEON_SERVICES),
479
514
  describe: "Services the scaffolded neon.ts declares",
480
515
  also: "postgres alone writes the starter policy; every branch has Postgres. Omitted: pick interactively on a terminal, starter policy in CI or without a TTY."
481
516
  }),
@@ -506,7 +541,8 @@ const liveConfigView = async (opts) => {
506
541
  branchId: opts.branchId,
507
542
  ...opts.apiKey ? { apiKey: opts.apiKey } : {},
508
543
  ...opts.apiHost ? { apiHost: opts.apiHost } : {},
509
- ...opts.runtimeApi ? { api: opts.runtimeApi } : {}
544
+ ...opts.runtimeApi ? { api: opts.runtimeApi } : {},
545
+ realtime: stagingFeaturesEnabled(opts.apiHost)
510
546
  });
511
547
  const resolved = resolveConfig(live.config, {
512
548
  name: live.branch.name,
@@ -533,6 +569,7 @@ const loadConfig = async (props) => {
533
569
  contextFile: props.contextFile,
534
570
  ...props.config ? { path: props.config } : {}
535
571
  });
572
+ assertPolicyAvailable(config, props.apiHost);
536
573
  return config;
537
574
  };
538
575
  const status = async (props) => {
@@ -957,6 +994,7 @@ const applyPolicyOnCreate = async (props) => {
957
994
  if (/Could not find a Neon config file/i.test(message)) return;
958
995
  throw err;
959
996
  }
997
+ assertPolicyAvailable(config, props.apiHost);
960
998
  try {
961
999
  warnDeprecatedPreview(config, await warningTargetForExistingBranch({
962
1000
  projectId: props.projectId,
@@ -1041,6 +1079,7 @@ const createBranchFromPolicyOnCheckout = async (props) => {
1041
1079
  if (/Could not find a Neon config file/i.test(message)) return null;
1042
1080
  throw err;
1043
1081
  }
1082
+ assertPolicyAvailable(config, props.apiHost);
1044
1083
  await assertAiGatewayProvisionableFromCreds({
1045
1084
  projectId: props.projectId,
1046
1085
  ...props.apiKey ? { apiKey: props.apiKey } : {},
@@ -3,10 +3,10 @@ import { log } from "../log.js";
3
3
  import { isClaimableEnvTarget } from "../claimable/state.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
- import { l as functionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
6
+ import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
7
7
  import { branchIdResolve } from "../utils/enrichers.js";
8
8
  import { bundleEntry } from "../utils/esbuild.js";
9
- import { a as resolveDevEnv } from "../_chunks/env-L2DPzDAK.js";
9
+ import { a as resolveDevEnv } from "../_chunks/env-4WGRW2nw.js";
10
10
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
11
11
  import { resolveWatchInputs } from "../dev/inputs.js";
12
12
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -5,11 +5,12 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
5
5
  import { getCliName } from "../utils/cli_name.js";
6
6
  import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
7
7
  import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
8
- import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
8
+ import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
9
9
  import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
10
+ import { listedServices } from "../utils/staging.js";
10
11
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
12
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
12
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-L2DPzDAK.js";
13
+ import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
13
14
  import { formatPulledEnv, pulledBranch } from "./env_output.js";
14
15
  import { existsSync } from "node:fs";
15
16
  import chalk from "chalk";
@@ -47,7 +48,7 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
47
48
  },
48
49
  service: servicesOption({
49
50
  key: "service",
50
- allowed: ENV_PULL_SERVICES,
51
+ allowed: listedServices(ENV_PULL_SERVICES),
51
52
  describe: "Pull only these services' variables",
52
53
  also: "Overrides neon.ts, and prunes only within the services you name. Combines with --env as a union."
53
54
  }),
@@ -73,7 +74,7 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
73
74
  "",
74
75
  "On an unclaimed Claimable Neon project, neon.ts is still the source",
75
76
  "of truth when it only declares Postgres, Auth, and the Data API.",
76
- "A neon.ts that declares AI Gateway, Functions, or Object Storage",
77
+ "A neon.ts that declares Realtime, AI Gateway, Functions, or Object Storage",
77
78
  "fails: those cannot be used until the project is claimed. Without",
78
79
  "a neon.ts, a bare pull writes provisioned Postgres, Auth, and Data",
79
80
  "API. Naming unsupported services with --service / --env warns",
@@ -107,8 +108,8 @@ const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Objec
107
108
  * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
108
109
  * it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
109
110
  * vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
110
- * directory to a project/branch without Auth / the Data API drops the now-stale
111
- * `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
111
+ * directory to a project/branch without Auth / the Data API / Realtime drops the now-stale
112
+ * `NEON_AUTH_*` / `NEON_DATA_API_*` / `NEON_REALTIME_*` lines instead of leaving credentials for features that
112
113
  * aren't enabled.
113
114
  *
114
115
  * Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
@@ -120,6 +121,7 @@ const NEON_OWNED_ENV_KEYS = [
120
121
  ...Object.values(NEON_ENV_VAR_KEYS.postgres),
121
122
  ...Object.values(NEON_ENV_VAR_KEYS.auth),
122
123
  ...Object.values(NEON_ENV_VAR_KEYS.dataApi),
124
+ ...Object.values(NEON_ENV_VAR_KEYS.realtime),
123
125
  ...Object.values(NEON_ENV_VAR_KEYS.aiGateway)
124
126
  ];
125
127
  const pull = async (props, opts = {}) => {
@@ -1,5 +1,5 @@
1
1
  import { NEON_SERVICES, NEON_SERVICE_LABELS } from "../neon_services.js";
2
- import { u as isFunctionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
2
+ import { u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
3
3
  import { BRANCH_ENV_KEY, ENV_PULL_KEYS, serviceForEnvKey } from "../env_services.js";
4
4
  import { isAbsolute, relative } from "node:path";
5
5
  import { homedir } from "node:os";
@@ -34,10 +34,12 @@ import { t as operations_exports } from "./operations.js";
34
34
  import { t as orgs_exports } from "./orgs.js";
35
35
  import { t as profile_exports } from "./profile.js";
36
36
  import { t as psql_exports } from "./psql.js";
37
+ import { t as realtime_exports } from "./realtime.js";
37
38
  import { t as roles_exports } from "./roles.js";
38
39
  import { t as set_context_exports } from "./set_context.js";
39
40
  import { t as snapshots_exports } from "./snapshots.js";
40
41
  import { t as status_exports } from "./status.js";
42
+ import { t as subscribe_exports } from "./subscribe.js";
41
43
  import { t as triggers_exports } from "./triggers.js";
42
44
  import { t as user_exports } from "./user.js";
43
45
  import { t as vpc_endpoints_exports } from "./vpc_endpoints.js";
@@ -75,6 +77,8 @@ var commands_default = [
75
77
  ask_exports,
76
78
  feedback_exports,
77
79
  data_api_exports,
80
+ realtime_exports,
81
+ subscribe_exports,
78
82
  functions_exports,
79
83
  triggers_exports,
80
84
  credentials_exports,
@@ -2,6 +2,7 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
4
4
  import { NEON_SERVICES, servicesOption } from "../neon_services.js";
5
+ import { listedServices } from "../utils/staging.js";
5
6
  import { coerceAgentFlag } from "../utils/agent_flag.js";
6
7
  import { initPluginAgents, initSkillsMcpAgents } from "../init/plan.js";
7
8
  import { listSkillIds } from "../skills/catalog.js";
@@ -51,7 +52,7 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
51
52
  describe: "Create neon.ts after linking. Use --no-config to skip. Omitted in Custom: you will be asked"
52
53
  }).option("services", servicesOption({
53
54
  key: "services",
54
- allowed: NEON_SERVICES,
55
+ allowed: listedServices(NEON_SERVICES),
55
56
  describe: "Services to declare in neon.ts",
56
57
  also: "postgres alone writes the starter policy; every branch has Postgres. Implies creating neon.ts. Cannot be combined with --no-config."
57
58
  })).option("org-id", {
@@ -1,4 +1,5 @@
1
1
  import { getCliName } from "../utils/cli_name.js";
2
+ import { REALTIME_COMING_SOON } from "../utils/staging.js";
2
3
  import { Parser } from "yargs/helpers";
3
4
  //#region src/commands/manifest.ts
4
5
  /**
@@ -186,6 +187,18 @@ const commandManifest = [
186
187
  describe: "Manage the Neon Data API for a database",
187
188
  load: () => import("./data_api.js")
188
189
  },
190
+ {
191
+ command: "realtime",
192
+ describe: "Manage Neon Realtime for a branch",
193
+ load: () => import("./realtime.js"),
194
+ stagingOnly: REALTIME_COMING_SOON
195
+ },
196
+ {
197
+ command: "subscribe <query>",
198
+ describe: "Subscribe to a live SQL query with Neon Realtime",
199
+ load: () => import("./subscribe.js"),
200
+ stagingOnly: REALTIME_COMING_SOON
201
+ },
189
202
  {
190
203
  command: "functions",
191
204
  aliases: ["function"],
@@ -302,7 +315,8 @@ const preloadCommand = async (cli, args) => {
302
315
  * command still runs, because yargs 17 awaits a builder's promise when it runs a command; shell
303
316
  * completion does not await it, so a mispredicted completion lists no subcommands.
304
317
  */
305
- const lazyCommands = commandManifest.map((entry) => ({
318
+ const lazyCommands = ({ staging }) => commandManifest.map((entry) => entry.stagingOnly && !staging ? comingSoonCommand(entry, entry.stagingOnly) : lazyCommand(entry));
319
+ const lazyCommand = (entry) => ({
306
320
  command: entry.command,
307
321
  aliases: entry.aliases,
308
322
  describe: entry.describe,
@@ -316,6 +330,15 @@ const lazyCommands = commandManifest.map((entry) => ({
316
330
  handler: async (args) => {
317
331
  return (loaded.get(entry) ?? await entry.load()).handler?.(args);
318
332
  }
319
- }));
333
+ });
334
+ const comingSoonCommand = (entry, message) => ({
335
+ command: `${verbOf(entry)} [args..]`,
336
+ aliases: entry.aliases,
337
+ describe: false,
338
+ builder: () => {
339
+ throw new Error(message);
340
+ },
341
+ handler: () => {}
342
+ });
320
343
  //#endregion
321
344
  export { commandManifest, lazyCommands, preloadCommand, selectCommand, selectCompletionCommand };