@spacefast/common 0.0.23 → 0.0.24

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 (176) hide show
  1. package/dist/config/domains.d.ts +1 -1
  2. package/dist/config/domains.js +1 -0
  3. package/dist/config/index.d.ts +0 -1
  4. package/dist/config/index.js +0 -1
  5. package/dist/contracts/abuse.d.ts +2 -0
  6. package/dist/contracts/abuse.js +12 -0
  7. package/dist/contracts/access.d.ts +121 -9
  8. package/dist/contracts/access.js +73 -9
  9. package/dist/contracts/activity.d.ts +1 -1
  10. package/dist/contracts/activity.js +0 -9
  11. package/dist/contracts/api-keys.d.ts +21 -21
  12. package/dist/contracts/beta.js +1 -1
  13. package/dist/contracts/billing.d.ts +1 -34
  14. package/dist/contracts/billing.js +4 -53
  15. package/dist/contracts/builds.d.ts +8 -8
  16. package/dist/contracts/builds.js +10 -3
  17. package/dist/contracts/cli.d.ts +10 -0
  18. package/dist/contracts/cli.js +10 -0
  19. package/dist/contracts/collab-public.d.ts +63 -0
  20. package/dist/contracts/collab-public.js +46 -0
  21. package/dist/contracts/comments.d.ts +283 -54
  22. package/dist/contracts/comments.js +173 -52
  23. package/dist/contracts/common.d.ts +3 -3
  24. package/dist/contracts/device-auth.d.ts +6 -6
  25. package/dist/contracts/device-auth.js +1 -1
  26. package/dist/contracts/docs.d.ts +5 -5
  27. package/dist/contracts/domains.d.ts +36 -36
  28. package/dist/contracts/enums.d.ts +33 -30
  29. package/dist/contracts/enums.js +52 -38
  30. package/dist/contracts/error-code-meta.d.ts +37 -126
  31. package/dist/contracts/error-code-meta.js +12 -41
  32. package/dist/contracts/error-codes.d.ts +1 -1
  33. package/dist/contracts/error-codes.js +10 -32
  34. package/dist/contracts/execution.d.ts +35 -57
  35. package/dist/contracts/execution.js +41 -40
  36. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  37. package/dist/contracts/feature-lifecycle-core.js +2 -31
  38. package/dist/contracts/feature-lifecycle.d.ts +8 -38
  39. package/dist/contracts/feature-lifecycle.js +16 -48
  40. package/dist/contracts/features.d.ts +15 -13
  41. package/dist/contracts/features.js +39 -38
  42. package/dist/contracts/functions.d.ts +97 -70
  43. package/dist/contracts/functions.js +101 -34
  44. package/dist/contracts/git.d.ts +1 -1
  45. package/dist/contracts/grants.d.ts +7 -3
  46. package/dist/contracts/grants.js +18 -9
  47. package/dist/contracts/internal.d.ts +14 -1
  48. package/dist/contracts/internal.js +17 -7
  49. package/dist/contracts/oauth-resources.d.ts +4 -3
  50. package/dist/contracts/oauth-resources.js +12 -3
  51. package/dist/contracts/operations.d.ts +90 -5
  52. package/dist/contracts/operations.js +75 -1
  53. package/dist/contracts/pages.d.ts +6 -1
  54. package/dist/contracts/pages.js +24 -1
  55. package/dist/contracts/plan-policy.d.ts +1 -3
  56. package/dist/contracts/plan-policy.js +1 -2
  57. package/dist/contracts/platform.d.ts +61 -148
  58. package/dist/contracts/platform.js +70 -126
  59. package/dist/contracts/publish-archive.d.ts +498 -0
  60. package/dist/contracts/publish-archive.js +25 -0
  61. package/dist/contracts/resources.d.ts +12 -24
  62. package/dist/contracts/resources.js +32 -12
  63. package/dist/contracts/runtime-api.d.ts +369 -175
  64. package/dist/contracts/runtime-api.js +372 -348
  65. package/dist/contracts/runtime-app.d.ts +127 -120
  66. package/dist/contracts/runtime-app.js +19 -10
  67. package/dist/contracts/runtime-db.d.ts +1 -1
  68. package/dist/contracts/runtime-services.d.ts +219 -0
  69. package/dist/contracts/runtime-services.js +437 -0
  70. package/dist/contracts/runtime-storage.d.ts +45 -32
  71. package/dist/contracts/runtime-storage.js +30 -8
  72. package/dist/contracts/space-config.d.ts +10 -16
  73. package/dist/contracts/space-config.js +14 -10
  74. package/dist/contracts/spaces.d.ts +241 -436
  75. package/dist/contracts/spaces.js +128 -145
  76. package/dist/contracts/superadmin-activity.d.ts +57 -4
  77. package/dist/contracts/superadmin-activity.js +48 -2
  78. package/dist/contracts/superadmin-search.d.ts +126 -0
  79. package/dist/contracts/superadmin-search.js +61 -0
  80. package/dist/contracts/superadmin-spaces.d.ts +358 -89
  81. package/dist/contracts/superadmin-spaces.js +97 -24
  82. package/dist/contracts/superadmin-teams.d.ts +28 -10
  83. package/dist/contracts/superadmin-teams.js +5 -8
  84. package/dist/contracts/superadmin.d.ts +0 -32
  85. package/dist/contracts/superadmin.js +3 -11
  86. package/dist/contracts/tags.d.ts +9 -9
  87. package/dist/contracts/teams.d.ts +38 -0
  88. package/dist/contracts/teams.js +34 -16
  89. package/dist/contracts/transfers.d.ts +0 -49
  90. package/dist/contracts/transfers.js +2 -6
  91. package/dist/contracts/usage.d.ts +61 -0
  92. package/dist/contracts/usage.js +64 -0
  93. package/dist/contracts/variables.d.ts +2 -7
  94. package/dist/contracts/variables.js +6 -5
  95. package/dist/contracts/webhooks.d.ts +2 -2
  96. package/dist/contracts/zero.d.ts +87 -56
  97. package/dist/contracts/zero.js +82 -20
  98. package/dist/dashboard-paths/index.d.ts +1 -1
  99. package/dist/dashboard-paths/index.js +1 -0
  100. package/dist/docs/agent-prose.d.ts +1 -1
  101. package/dist/docs/agent-prose.js +57 -61
  102. package/dist/docs/agent-setup.js +40 -13
  103. package/dist/docs/catalog.d.ts +19 -19
  104. package/dist/docs/catalog.js +30 -19
  105. package/dist/docs/error-docs.js +63 -151
  106. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  107. package/dist/test-helpers/fetch-stub.js +24 -0
  108. package/dist/utils/build-settings.js +9 -3
  109. package/dist/utils/canonical-json.d.ts +10 -0
  110. package/dist/utils/canonical-json.js +21 -0
  111. package/dist/utils/cast-transport.d.ts +28 -0
  112. package/dist/utils/cast-transport.js +52 -0
  113. package/dist/utils/claim-token.d.ts +0 -8
  114. package/dist/utils/claim-token.js +3 -15
  115. package/dist/utils/credential-policy.d.ts +1 -1
  116. package/dist/utils/env-file.d.ts +17 -0
  117. package/dist/utils/env-file.js +96 -0
  118. package/dist/utils/error-code.d.ts +2 -0
  119. package/dist/utils/error-code.js +7 -0
  120. package/dist/utils/generate-space-name.d.ts +8 -10
  121. package/dist/utils/generate-space-name.js +206 -21
  122. package/dist/utils/grant-decision.fixtures.json +2938 -0
  123. package/dist/utils/grants.d.ts +2 -0
  124. package/dist/utils/grants.js +25 -2
  125. package/dist/utils/gravatar.d.ts +9 -0
  126. package/dist/utils/gravatar.js +31 -0
  127. package/dist/utils/idempotency.d.ts +6 -0
  128. package/dist/utils/idempotency.js +28 -0
  129. package/dist/utils/local-space-state.d.ts +1 -5
  130. package/dist/utils/local-space-state.js +7 -26
  131. package/dist/utils/page-fonts.d.ts +2 -2
  132. package/dist/utils/page-fonts.js +22 -4
  133. package/dist/utils/pages.d.ts +1 -2
  134. package/dist/utils/pages.js +21 -5
  135. package/dist/utils/publish-form-data.d.ts +12 -0
  136. package/dist/utils/publish-form-data.js +10 -0
  137. package/dist/utils/publish-policy.d.ts +20 -0
  138. package/dist/utils/publish-policy.fixtures.json +397 -0
  139. package/dist/utils/publish-policy.js +36 -19
  140. package/dist/utils/push-new.d.ts +7 -0
  141. package/dist/utils/push-new.js +7 -0
  142. package/dist/utils/query-keys.d.ts +0 -1
  143. package/dist/utils/query-keys.js +0 -1
  144. package/dist/utils/runtime-paths.d.ts +5 -0
  145. package/dist/utils/runtime-paths.js +5 -0
  146. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  147. package/dist/utils/runtime-upload-batch.js +1 -27
  148. package/dist/utils/runtime-upload.d.ts +3 -13
  149. package/dist/utils/runtime-upload.js +4 -14
  150. package/dist/utils/space-config.d.ts +0 -33
  151. package/dist/utils/space-config.js +5 -326
  152. package/dist/utils/storage-policy.d.ts +1 -1
  153. package/dist/utils/storage-policy.js +3 -1
  154. package/dist/utils/structured-error.d.ts +14 -0
  155. package/dist/utils/structured-error.js +22 -0
  156. package/dist/utils/version-path.js +12 -3
  157. package/dist/vocabulary.d.ts +5 -3
  158. package/dist/vocabulary.js +13 -10
  159. package/package.json +10 -5
  160. package/dist/config/postgres-budget.d.ts +0 -129
  161. package/dist/config/postgres-budget.js +0 -140
  162. package/dist/config/postgres-budget.json +0 -51
  163. package/dist/contracts/archives.d.ts +0 -331
  164. package/dist/contracts/archives.js +0 -120
  165. package/dist/contracts/deployments.d.ts +0 -574
  166. package/dist/contracts/deployments.js +0 -97
  167. package/dist/contracts/sites.d.ts +0 -416
  168. package/dist/contracts/sites.js +0 -168
  169. package/dist/contracts/superadmin-queues.d.ts +0 -414
  170. package/dist/contracts/superadmin-queues.js +0 -202
  171. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  172. package/dist/contracts/superadmin-runtime.js +0 -38
  173. package/dist/index.d.ts +0 -1
  174. package/dist/index.js +0 -1
  175. package/dist/utils/api.d.ts +0 -4
  176. package/dist/utils/api.js +0 -13
@@ -1,9 +1 @@
1
- export type ClaimCredential = {
2
- kind: "token";
3
- claimToken: string;
4
- } | {
5
- kind: "nonce";
6
- nonce: string;
7
- };
8
- export declare function classifyClaimCredential(raw: string | null | undefined): ClaimCredential | undefined;
9
1
  export declare function normalizeClaimToken(token: string | null | undefined): string | undefined;
@@ -1,18 +1,6 @@
1
- const LEGACY_WORD_TOKEN_SHAPE = /^[a-z]+(?:-[a-z]+){2,}$/;
2
- export function classifyClaimCredential(raw) {
3
- const trimmed = raw?.trim();
4
- if (!trimmed) {
5
- return undefined;
6
- }
7
- if (trimmed.startsWith("sfc_")) {
8
- return { kind: "token", claimToken: trimmed };
9
- }
10
- const normalized = normalizeClaimToken(trimmed);
11
- if (normalized && LEGACY_WORD_TOKEN_SHAPE.test(normalized)) {
12
- return { kind: "token", claimToken: normalized };
13
- }
14
- return { kind: "nonce", nonce: trimmed };
15
- }
1
+ // The space key (`sfc_…`, or the legacy five-word form) is the one claim
2
+ // credential; it authenticates as a `Bearer` header and rides claim-link
3
+ // fragments verbatim.
16
4
  export function normalizeClaimToken(token) {
17
5
  const trimmed = token?.trim();
18
6
  if (!trimmed) {
@@ -16,7 +16,7 @@ export type PolicyAuthorizeInput = {
16
16
  resource: string | readonly string[];
17
17
  conditions?: RequestCondition;
18
18
  };
19
- export declare const publishActions: ("spaces:read" | "spaces:write" | "spaces:create" | "spaces:publish" | "versions:read" | "versions:promote" | "versions:download" | "builds:read" | "builds:trigger" | "builds:create")[];
19
+ export declare const publishActions: ("spaces:read" | "spaces:write" | "spaces:publish" | "builds:trigger" | "spaces:create" | "versions:read" | "versions:promote" | "versions:download" | "builds:read" | "builds:create")[];
20
20
  export declare function compileApiKeyPreset(preset: ApiKeyPreset, input: PolicyCompileInput): ApiCredentialPolicy;
21
21
  /** Expand a named API-key preset into the finite permissions sent on the public wire contract. */
22
22
  export declare function apiKeyPresetPermissions(preset: ApiKeyPreset): StatticAction[];
@@ -0,0 +1,17 @@
1
+ export type ParsedEnvFileEntry = {
2
+ name: string;
3
+ value: string;
4
+ line: number;
5
+ };
6
+ type ValidationErrorFactory = (message: string) => Error;
7
+ export declare function parseEnvFile(content: string, options?: {
8
+ reservedStatticPrefix?: boolean;
9
+ validationError?: ValidationErrorFactory;
10
+ }): ParsedEnvFileEntry[];
11
+ export declare function loadEnvFileSource<const File extends string>(root: string, files: readonly File[], options?: {
12
+ validationError?: ValidationErrorFactory;
13
+ }): Promise<{
14
+ file: File;
15
+ content: string;
16
+ } | null>;
17
+ export {};
@@ -0,0 +1,96 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { variableNameSchema } from "../contracts/variables.js";
4
+ import { hasErrorCode } from "./error-code.js";
5
+ import { structuredValidationError } from "./structured-error.js";
6
+ const localEnvNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
7
+ function stripInlineComment(value) {
8
+ let quoted = null;
9
+ for (let index = 0; index < value.length; index += 1) {
10
+ const char = value[index];
11
+ if (quoted) {
12
+ if (char === "\\" && quoted === '"')
13
+ index += 1;
14
+ else if (char === quoted)
15
+ quoted = null;
16
+ continue;
17
+ }
18
+ if (char === "'" || char === '"')
19
+ quoted = char;
20
+ else if (char === "#" && (index === 0 || /\s/.test(value[index - 1] ?? ""))) {
21
+ return value.slice(0, index).trimEnd();
22
+ }
23
+ }
24
+ return value.trimEnd();
25
+ }
26
+ function unescapeDoubleQuotedValue(value) {
27
+ return value.replace(/\\([nrt"\\$])/g, (_, escaped) => {
28
+ if (escaped === "n")
29
+ return "\n";
30
+ if (escaped === "r")
31
+ return "\r";
32
+ if (escaped === "t")
33
+ return "\t";
34
+ return escaped;
35
+ });
36
+ }
37
+ function parseEnvValue(rawValue, line, error) {
38
+ const value = stripInlineComment(rawValue.trim());
39
+ if (value.startsWith('"')) {
40
+ if (!value.endsWith('"') || value.length === 1) {
41
+ throw error(`Invalid .env value on line ${line}: missing closing double quote.`);
42
+ }
43
+ return unescapeDoubleQuotedValue(value.slice(1, -1));
44
+ }
45
+ if (value.startsWith("'")) {
46
+ if (!value.endsWith("'") || value.length === 1) {
47
+ throw error(`Invalid .env value on line ${line}: missing closing single quote.`);
48
+ }
49
+ return value.slice(1, -1);
50
+ }
51
+ return value;
52
+ }
53
+ export function parseEnvFile(content, options = { reservedStatticPrefix: true }) {
54
+ const error = options.validationError ?? structuredValidationError;
55
+ const entries = new Map();
56
+ const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
57
+ for (const [index, rawLine] of lines.entries()) {
58
+ const line = index + 1;
59
+ const trimmed = rawLine.trim();
60
+ if (!trimmed || trimmed.startsWith("#"))
61
+ continue;
62
+ const withoutExport = trimmed.startsWith("export ")
63
+ ? trimmed.slice("export ".length).trim()
64
+ : trimmed;
65
+ const equalsIndex = withoutExport.indexOf("=");
66
+ if (equalsIndex < 0)
67
+ throw error(`Invalid .env entry on line ${line}: expected NAME=value.`);
68
+ const name = withoutExport.slice(0, equalsIndex).trim();
69
+ const validName = options.reservedStatticPrefix === false
70
+ ? localEnvNamePattern.test(name)
71
+ : variableNameSchema.safeParse(name).success;
72
+ if (!validName)
73
+ throw error(`Invalid .env variable name on line ${line}: ${name}.`);
74
+ const value = parseEnvValue(withoutExport.slice(equalsIndex + 1), line, error);
75
+ if (value.length === 0) {
76
+ throw error(`Invalid .env value on line ${line}: empty values are not supported yet.`);
77
+ }
78
+ entries.set(name, { name, value, line });
79
+ }
80
+ return [...entries.values()];
81
+ }
82
+ export async function loadEnvFileSource(root, files, options = {}) {
83
+ const error = options.validationError ?? structuredValidationError;
84
+ const candidates = await Promise.all(files.map(async (file) => {
85
+ const content = await readFile(path.join(root, file), "utf8").catch((reason) => {
86
+ if (hasErrorCode(reason, "ENOENT"))
87
+ return null;
88
+ throw reason;
89
+ });
90
+ return content === null ? null : { file, content };
91
+ }));
92
+ const present = candidates.filter((candidate) => candidate !== null);
93
+ if (present.length > 1)
94
+ throw error(`Use either ${files[0]} or ${files[1]}, not both.`);
95
+ return present[0] ?? null;
96
+ }
@@ -0,0 +1,2 @@
1
+ /** True when `error` carries the given syscall-style `code` (ENOENT, EEXIST, …). */
2
+ export declare function hasErrorCode(error: unknown, code: string): boolean;
@@ -0,0 +1,7 @@
1
+ /** True when `error` carries the given syscall-style `code` (ENOENT, EEXIST, …). */
2
+ export function hasErrorCode(error, code) {
3
+ return (error !== null &&
4
+ typeof error === "object" &&
5
+ "code" in error &&
6
+ error.code === code);
7
+ }
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Generates a whimsical, WordPress-flavored space title.
2
+ * Generates a short, readable space title.
3
3
  *
4
- * The pool mixes WordPress jargon (Codex, Permalink, Gutenberg, Pingback)
5
- * with engineering/web adjectives so the output reads like a tiny inside
6
- * joke instead of an auto-increment. Users can rename later, so the only
7
- * requirement is that two back-to-back calls almost never collide.
4
+ * The pool mixes a little WordPress and web flavor with broad nature, color,
5
+ * motion, and craft vocabulary. The result should suit any site instead of
6
+ * reading like a CMS inside joke, while staying compact enough for a hostname.
8
7
  *
9
- * Pool size: 32 × 32 = 1024 combinations. Combined with the collision
10
- * handling in `uniqueSpaceSlug`, per-team conflicts are very
11
- * unlikely. The generator is intentionally dependency-free so it can be
12
- * shared by the control-plane, the CLI, and any future surface.
8
+ * The generator is intentionally dependency-free so it can be shared by the
9
+ * control-plane, dashboard, CLI, and future publishing surfaces.
13
10
  */
14
- export declare function generateSpaceTitle(): string;
11
+ export declare const SPACE_TITLE_COMBINATION_COUNT: number;
12
+ export declare function generateSpaceTitle(random?: () => number): string;
@@ -1,15 +1,12 @@
1
1
  /**
2
- * Generates a whimsical, WordPress-flavored space title.
2
+ * Generates a short, readable space title.
3
3
  *
4
- * The pool mixes WordPress jargon (Codex, Permalink, Gutenberg, Pingback)
5
- * with engineering/web adjectives so the output reads like a tiny inside
6
- * joke instead of an auto-increment. Users can rename later, so the only
7
- * requirement is that two back-to-back calls almost never collide.
4
+ * The pool mixes a little WordPress and web flavor with broad nature, color,
5
+ * motion, and craft vocabulary. The result should suit any site instead of
6
+ * reading like a CMS inside joke, while staying compact enough for a hostname.
8
7
  *
9
- * Pool size: 32 × 32 = 1024 combinations. Combined with the collision
10
- * handling in `uniqueSpaceSlug`, per-team conflicts are very
11
- * unlikely. The generator is intentionally dependency-free so it can be
12
- * shared by the control-plane, the CLI, and any future surface.
8
+ * The generator is intentionally dependency-free so it can be shared by the
9
+ * control-plane, dashboard, CLI, and future publishing surfaces.
13
10
  */
14
11
  const ADJECTIVES = [
15
12
  "Atomic",
@@ -44,6 +41,102 @@ const ADJECTIVES = [
44
41
  "Templated",
45
42
  "Responsive",
46
43
  "Trackbacked",
44
+ "Agile",
45
+ "Amber",
46
+ "Ancient",
47
+ "Arctic",
48
+ "Azure",
49
+ "Balanced",
50
+ "Bold",
51
+ "Bright",
52
+ "Brisk",
53
+ "Calm",
54
+ "Candid",
55
+ "Celestial",
56
+ "Clever",
57
+ "Coastal",
58
+ "Composed",
59
+ "Cosmic",
60
+ "Crisp",
61
+ "Curious",
62
+ "Daring",
63
+ "Deep",
64
+ "Electric",
65
+ "Elegant",
66
+ "Emerald",
67
+ "Endless",
68
+ "Evergreen",
69
+ "Fluid",
70
+ "Focused",
71
+ "Gentle",
72
+ "Golden",
73
+ "Grand",
74
+ "Harmonic",
75
+ "Hidden",
76
+ "Honest",
77
+ "Indigo",
78
+ "Infinite",
79
+ "Keen",
80
+ "Kind",
81
+ "Lively",
82
+ "Lucid",
83
+ "Lunar",
84
+ "Magnetic",
85
+ "Mellow",
86
+ "Modern",
87
+ "Nimble",
88
+ "Northern",
89
+ "Open",
90
+ "Pacific",
91
+ "Patient",
92
+ "Polished",
93
+ "Prime",
94
+ "Quiet",
95
+ "Radiant",
96
+ "Rapid",
97
+ "Rare",
98
+ "Ready",
99
+ "Regal",
100
+ "Remote",
101
+ "Restless",
102
+ "Robust",
103
+ "Royal",
104
+ "Sable",
105
+ "Silent",
106
+ "Simple",
107
+ "Solar",
108
+ "Steady",
109
+ "Stellar",
110
+ "Still",
111
+ "Swift",
112
+ "Tactile",
113
+ "Tidy",
114
+ "Timeless",
115
+ "Tranquil",
116
+ "True",
117
+ "Vivid",
118
+ "Warm",
119
+ "Wild",
120
+ "Wise",
121
+ "Woven",
122
+ "Young",
123
+ "Zealous",
124
+ "Airy",
125
+ "Autumn",
126
+ "Blue",
127
+ "Clear",
128
+ "Copper",
129
+ "Distant",
130
+ "Early",
131
+ "Fresh",
132
+ "Glowing",
133
+ "Green",
134
+ "Light",
135
+ "Misty",
136
+ "Neon",
137
+ "Silver",
138
+ "Spring",
139
+ "Summer",
47
140
  ];
48
141
  const NOUNS = [
49
142
  "Codex",
@@ -78,18 +171,110 @@ const NOUNS = [
78
171
  "Gravatar",
79
172
  "Reblog",
80
173
  "Opengraph",
174
+ "Acre",
175
+ "Arcade",
176
+ "Atlas",
177
+ "Beacon",
178
+ "Birch",
179
+ "Bloom",
180
+ "Bridge",
181
+ "Brook",
182
+ "Canyon",
183
+ "Cedar",
184
+ "Circuit",
185
+ "Cloud",
186
+ "Coast",
187
+ "Comet",
188
+ "Cove",
189
+ "Creek",
190
+ "Delta",
191
+ "Dune",
192
+ "Echo",
193
+ "Ember",
194
+ "Field",
195
+ "Fjord",
196
+ "Forest",
197
+ "Forge",
198
+ "Garden",
199
+ "Grove",
200
+ "Harbor",
201
+ "Haven",
202
+ "Hill",
203
+ "Horizon",
204
+ "Island",
205
+ "Junction",
206
+ "Kite",
207
+ "Lake",
208
+ "Lantern",
209
+ "Meadow",
210
+ "Mesa",
211
+ "Moon",
212
+ "Nest",
213
+ "Oak",
214
+ "Ocean",
215
+ "Orchard",
216
+ "Peak",
217
+ "Pine",
218
+ "Plain",
219
+ "Pond",
220
+ "Prairie",
221
+ "Prism",
222
+ "Reef",
223
+ "Ridge",
224
+ "River",
225
+ "Road",
226
+ "Rock",
227
+ "Sage",
228
+ "Shore",
229
+ "Signal",
230
+ "Sky",
231
+ "Spark",
232
+ "Star",
233
+ "Stone",
234
+ "Studio",
235
+ "Summit",
236
+ "Sun",
237
+ "Trail",
238
+ "Vale",
239
+ "Valley",
240
+ "Vault",
241
+ "Wave",
242
+ "Willow",
243
+ "Wind",
244
+ "Window",
245
+ "Wood",
246
+ "Bay",
247
+ "Branch",
248
+ "Breeze",
249
+ "Canvas",
250
+ "Castle",
251
+ "Cavern",
252
+ "Cliff",
253
+ "Dawn",
254
+ "Drift",
255
+ "Falcon",
256
+ "Flame",
257
+ "Glade",
258
+ "Harborlight",
259
+ "Loom",
260
+ "Maple",
261
+ "Marsh",
262
+ "North",
263
+ "Pebble",
264
+ "Quartz",
265
+ "Rain",
266
+ "Range",
267
+ "Terrace",
268
+ "Thicket",
269
+ "Wren",
81
270
  ];
82
- function pickRandom(list) {
83
- if (list.length === 0) {
84
- throw new Error("generateSpaceName: word list is empty.");
271
+ export const SPACE_TITLE_COMBINATION_COUNT = ADJECTIVES.length * NOUNS.length;
272
+ export function generateSpaceTitle(random = Math.random) {
273
+ const index = Math.floor(random() * SPACE_TITLE_COMBINATION_COUNT);
274
+ const adjective = ADJECTIVES[Math.floor(index / NOUNS.length)];
275
+ const noun = NOUNS[index % NOUNS.length];
276
+ if (!adjective || !noun) {
277
+ throw new Error("generateSpaceName: random source must return a value from 0 up to 1.");
85
278
  }
86
- const index = Math.floor(Math.random() * list.length);
87
- const value = list[index];
88
- if (value === undefined) {
89
- throw new Error("generateSpaceName: word list contained a hole.");
90
- }
91
- return value;
92
- }
93
- export function generateSpaceTitle() {
94
- return `${pickRandom(ADJECTIVES)} ${pickRandom(NOUNS)}`;
279
+ return `${adjective} ${noun}`;
95
280
  }