@spacefast/common 0.0.21 → 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 (182) hide show
  1. package/dist/agents/connect-targets.d.ts +9 -6
  2. package/dist/agents/connect-targets.js +9 -6
  3. package/dist/config/domains.d.ts +1 -1
  4. package/dist/config/domains.js +1 -0
  5. package/dist/config/index.d.ts +0 -1
  6. package/dist/config/index.js +0 -1
  7. package/dist/contracts/abuse.d.ts +2 -0
  8. package/dist/contracts/abuse.js +12 -0
  9. package/dist/contracts/access.d.ts +121 -9
  10. package/dist/contracts/access.js +73 -9
  11. package/dist/contracts/activity.d.ts +1 -1
  12. package/dist/contracts/activity.js +0 -9
  13. package/dist/contracts/api-keys.d.ts +21 -21
  14. package/dist/contracts/beta.js +1 -1
  15. package/dist/contracts/billing.d.ts +1 -34
  16. package/dist/contracts/billing.js +4 -53
  17. package/dist/contracts/builds.d.ts +8 -8
  18. package/dist/contracts/builds.js +10 -3
  19. package/dist/contracts/cli.d.ts +10 -0
  20. package/dist/contracts/cli.js +10 -0
  21. package/dist/contracts/collab-public.d.ts +63 -0
  22. package/dist/contracts/collab-public.js +46 -0
  23. package/dist/contracts/comments.d.ts +283 -54
  24. package/dist/contracts/comments.js +173 -52
  25. package/dist/contracts/common.d.ts +3 -3
  26. package/dist/contracts/device-auth.d.ts +6 -6
  27. package/dist/contracts/device-auth.js +1 -1
  28. package/dist/contracts/docs.d.ts +5 -5
  29. package/dist/contracts/domains.d.ts +36 -36
  30. package/dist/contracts/enums.d.ts +33 -30
  31. package/dist/contracts/enums.js +52 -38
  32. package/dist/contracts/error-code-meta.d.ts +37 -126
  33. package/dist/contracts/error-code-meta.js +12 -41
  34. package/dist/contracts/error-codes.d.ts +1 -1
  35. package/dist/contracts/error-codes.js +10 -32
  36. package/dist/contracts/execution.d.ts +35 -57
  37. package/dist/contracts/execution.js +41 -40
  38. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  39. package/dist/contracts/feature-lifecycle-core.js +2 -31
  40. package/dist/contracts/feature-lifecycle.d.ts +8 -38
  41. package/dist/contracts/feature-lifecycle.js +16 -48
  42. package/dist/contracts/features.d.ts +15 -13
  43. package/dist/contracts/features.js +39 -38
  44. package/dist/contracts/functions.d.ts +97 -70
  45. package/dist/contracts/functions.js +101 -34
  46. package/dist/contracts/git.d.ts +1 -1
  47. package/dist/contracts/grants.d.ts +7 -3
  48. package/dist/contracts/grants.js +18 -9
  49. package/dist/contracts/internal.d.ts +14 -1
  50. package/dist/contracts/internal.js +17 -7
  51. package/dist/contracts/oauth-resources.d.ts +4 -3
  52. package/dist/contracts/oauth-resources.js +12 -3
  53. package/dist/contracts/operations.d.ts +90 -5
  54. package/dist/contracts/operations.js +75 -1
  55. package/dist/contracts/pages.d.ts +6 -1
  56. package/dist/contracts/pages.js +24 -1
  57. package/dist/contracts/plan-policy.d.ts +1 -3
  58. package/dist/contracts/plan-policy.js +1 -2
  59. package/dist/contracts/platform.d.ts +61 -148
  60. package/dist/contracts/platform.js +70 -126
  61. package/dist/contracts/publish-archive.d.ts +498 -0
  62. package/dist/contracts/publish-archive.js +25 -0
  63. package/dist/contracts/resources.d.ts +12 -24
  64. package/dist/contracts/resources.js +32 -12
  65. package/dist/contracts/runtime-api.d.ts +369 -175
  66. package/dist/contracts/runtime-api.js +372 -348
  67. package/dist/contracts/runtime-app.d.ts +127 -120
  68. package/dist/contracts/runtime-app.js +19 -10
  69. package/dist/contracts/runtime-db.d.ts +1 -1
  70. package/dist/contracts/runtime-services.d.ts +219 -0
  71. package/dist/contracts/runtime-services.js +437 -0
  72. package/dist/contracts/runtime-storage.d.ts +45 -32
  73. package/dist/contracts/runtime-storage.js +30 -8
  74. package/dist/contracts/space-config.d.ts +10 -16
  75. package/dist/contracts/space-config.js +14 -10
  76. package/dist/contracts/spaces.d.ts +241 -436
  77. package/dist/contracts/spaces.js +128 -145
  78. package/dist/contracts/superadmin-activity.d.ts +57 -4
  79. package/dist/contracts/superadmin-activity.js +48 -2
  80. package/dist/contracts/superadmin-search.d.ts +126 -0
  81. package/dist/contracts/superadmin-search.js +61 -0
  82. package/dist/contracts/superadmin-spaces.d.ts +358 -89
  83. package/dist/contracts/superadmin-spaces.js +97 -24
  84. package/dist/contracts/superadmin-teams.d.ts +28 -10
  85. package/dist/contracts/superadmin-teams.js +5 -8
  86. package/dist/contracts/superadmin.d.ts +0 -32
  87. package/dist/contracts/superadmin.js +3 -11
  88. package/dist/contracts/tags.d.ts +9 -9
  89. package/dist/contracts/teams.d.ts +44 -2
  90. package/dist/contracts/teams.js +43 -23
  91. package/dist/contracts/transfers.d.ts +0 -49
  92. package/dist/contracts/transfers.js +2 -6
  93. package/dist/contracts/usage.d.ts +61 -0
  94. package/dist/contracts/usage.js +64 -0
  95. package/dist/contracts/variables.d.ts +2 -7
  96. package/dist/contracts/variables.js +6 -5
  97. package/dist/contracts/webhooks.d.ts +2 -2
  98. package/dist/contracts/zero.d.ts +87 -56
  99. package/dist/contracts/zero.js +82 -20
  100. package/dist/dashboard-paths/index.d.ts +1 -1
  101. package/dist/dashboard-paths/index.js +1 -0
  102. package/dist/docs/agent-prose.d.ts +1 -1
  103. package/dist/docs/agent-prose.js +62 -66
  104. package/dist/docs/agent-setup.js +49 -23
  105. package/dist/docs/agent-solutions.d.ts +4 -4
  106. package/dist/docs/agent-solutions.js +8 -8
  107. package/dist/docs/catalog.d.ts +19 -19
  108. package/dist/docs/catalog.js +30 -19
  109. package/dist/docs/error-docs.js +63 -151
  110. package/dist/docs/skills.d.ts +3 -3
  111. package/dist/docs/skills.js +1 -1
  112. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  113. package/dist/test-helpers/fetch-stub.js +24 -0
  114. package/dist/utils/build-settings.js +9 -3
  115. package/dist/utils/canonical-json.d.ts +10 -0
  116. package/dist/utils/canonical-json.js +21 -0
  117. package/dist/utils/cast-transport.d.ts +28 -0
  118. package/dist/utils/cast-transport.js +52 -0
  119. package/dist/utils/claim-token.d.ts +0 -8
  120. package/dist/utils/claim-token.js +3 -15
  121. package/dist/utils/credential-policy.d.ts +1 -1
  122. package/dist/utils/env-file.d.ts +17 -0
  123. package/dist/utils/env-file.js +96 -0
  124. package/dist/utils/error-code.d.ts +2 -0
  125. package/dist/utils/error-code.js +7 -0
  126. package/dist/utils/generate-space-name.d.ts +8 -10
  127. package/dist/utils/generate-space-name.js +206 -21
  128. package/dist/utils/grant-decision.fixtures.json +2938 -0
  129. package/dist/utils/grants.d.ts +2 -0
  130. package/dist/utils/grants.js +25 -2
  131. package/dist/utils/gravatar.d.ts +9 -0
  132. package/dist/utils/gravatar.js +31 -0
  133. package/dist/utils/idempotency.d.ts +6 -0
  134. package/dist/utils/idempotency.js +28 -0
  135. package/dist/utils/local-space-state.d.ts +1 -5
  136. package/dist/utils/local-space-state.js +7 -26
  137. package/dist/utils/page-fonts.d.ts +2 -2
  138. package/dist/utils/page-fonts.js +22 -4
  139. package/dist/utils/pages.d.ts +1 -2
  140. package/dist/utils/pages.js +21 -5
  141. package/dist/utils/publish-form-data.d.ts +12 -0
  142. package/dist/utils/publish-form-data.js +10 -0
  143. package/dist/utils/publish-policy.d.ts +20 -0
  144. package/dist/utils/publish-policy.fixtures.json +397 -0
  145. package/dist/utils/publish-policy.js +36 -19
  146. package/dist/utils/push-new.d.ts +7 -0
  147. package/dist/utils/push-new.js +7 -0
  148. package/dist/utils/query-keys.d.ts +0 -1
  149. package/dist/utils/query-keys.js +0 -1
  150. package/dist/utils/runtime-paths.d.ts +5 -0
  151. package/dist/utils/runtime-paths.js +5 -0
  152. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  153. package/dist/utils/runtime-upload-batch.js +1 -27
  154. package/dist/utils/runtime-upload.d.ts +3 -13
  155. package/dist/utils/runtime-upload.js +4 -14
  156. package/dist/utils/space-config.d.ts +0 -33
  157. package/dist/utils/space-config.js +5 -326
  158. package/dist/utils/storage-policy.d.ts +1 -1
  159. package/dist/utils/storage-policy.js +3 -1
  160. package/dist/utils/structured-error.d.ts +14 -0
  161. package/dist/utils/structured-error.js +22 -0
  162. package/dist/utils/version-path.js +12 -3
  163. package/dist/vocabulary.d.ts +5 -3
  164. package/dist/vocabulary.js +13 -10
  165. package/package.json +10 -5
  166. package/dist/config/postgres-budget.d.ts +0 -129
  167. package/dist/config/postgres-budget.js +0 -140
  168. package/dist/config/postgres-budget.json +0 -51
  169. package/dist/contracts/archives.d.ts +0 -331
  170. package/dist/contracts/archives.js +0 -120
  171. package/dist/contracts/deployments.d.ts +0 -574
  172. package/dist/contracts/deployments.js +0 -97
  173. package/dist/contracts/sites.d.ts +0 -416
  174. package/dist/contracts/sites.js +0 -168
  175. package/dist/contracts/superadmin-queues.d.ts +0 -414
  176. package/dist/contracts/superadmin-queues.js +0 -202
  177. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  178. package/dist/contracts/superadmin-runtime.js +0 -38
  179. package/dist/index.d.ts +0 -1
  180. package/dist/index.js +0 -1
  181. package/dist/utils/api.d.ts +0 -4
  182. package/dist/utils/api.js +0 -13
@@ -0,0 +1,52 @@
1
+ /**
2
+ * How a client addresses Cast over Phoenix. Both Spacefast clients — the
3
+ * dashboard's notice subscription and the injected Collab SDK — build their
4
+ * socket and their topics here, and nowhere else.
5
+ */
6
+ /**
7
+ * `~n` escapes, the inverse of `CastWeb.ApiContext.decode_path_segment/1`.
8
+ * `~` goes first in intent: the map is applied in one pass, so an already
9
+ * escaped marker can never be escaped twice.
10
+ */
11
+ const ESCAPED = new Map([
12
+ ["~", "~0"],
13
+ ["/", "~1"],
14
+ [":", "~2"],
15
+ ["\\", "~3"],
16
+ ["%", "~4"],
17
+ ["\0", "~5"],
18
+ ]);
19
+ /**
20
+ * The Phoenix topic a Cast room's messages travel on.
21
+ *
22
+ * Phoenix routes a broadcast by the *literal* topic string. Cast broadcasts
23
+ * anything computed outside a channel process — Space notices, REST-published
24
+ * room events — to the topic `Cast.RoomTopic.for/2` builds, so a client that
25
+ * encodes the room key any other way joins a real and permanently silent room.
26
+ * Both sides therefore encode exactly once, here and in that module: the `~n`
27
+ * table first (it survives round-tripping a key that itself contains `~` or
28
+ * `%`), then `encodeURIComponent`.
29
+ *
30
+ * Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
31
+ * strings, and on the other side `room_channel_notice_test.exs` and
32
+ * `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
33
+ * Cast broadcast arrives on them.
34
+ */
35
+ export function castRoomTopic(resourceKey, roomKey) {
36
+ let escaped = "";
37
+ for (const character of roomKey)
38
+ escaped += ESCAPED.get(character) ?? character;
39
+ return `room:${resourceKey}:${encodeURIComponent(escaped)}`;
40
+ }
41
+ /**
42
+ * Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
43
+ * so a configured URL that already carries the suffix would otherwise become
44
+ * `/websocket/websocket`.
45
+ */
46
+ export function phoenixEndpoint(url) {
47
+ const parsed = new URL(url);
48
+ if (parsed.pathname.endsWith("/websocket")) {
49
+ parsed.pathname = parsed.pathname.slice(0, -"/websocket".length);
50
+ }
51
+ return parsed.href.replace(/\/$/, "");
52
+ }
@@ -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
  }