@camox/api-contract 0.47.0 → 0.51.0

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/index.js CHANGED
@@ -32,6 +32,38 @@ function artifactTitle(source) {
32
32
  return artifactSourceTitle(source) ?? "Untitled artifact";
33
33
  }
34
34
 
35
+ //#endregion
36
+ //#region src/record-templates.ts
37
+ /**
38
+ * A collection's `toTitle` and `toDescription` are record templates: text with `{{field}}` tokens
39
+ * naming the record's String fields, which the SDK writes and the API fills with each record's
40
+ * plain text. Both sides read them the same way through these helpers.
41
+ */
42
+ const TOKEN = /\{\{([^}]*)\}\}/g;
43
+ /** The fields a record template's tokens name, in order, such as `firstName` and `lastName`. */
44
+ function templateFields(template) {
45
+ return [...template.matchAll(TOKEN)].map(([, name]) => name);
46
+ }
47
+ /**
48
+ * The fields a record template names that aren't String fields of the content schema's
49
+ * properties: templates read fields as plain text, so they name no other field.
50
+ */
51
+ function nonStringTemplateFields(template, properties) {
52
+ const isString = (field) => field !== void 0 && "fieldType" in field && field.fieldType === "String";
53
+ return templateFields(template).filter((name) => !isString(properties[name]));
54
+ }
55
+ /**
56
+ * A record's title and description: its collection's templates filled with the plain text
57
+ * `textOf` reads from each field they name.
58
+ */
59
+ function recordText(templates, textOf) {
60
+ const fill = (template) => template.replace(TOKEN, (_token, name) => textOf(name)).trim();
61
+ return {
62
+ title: fill(templates.toTitle),
63
+ description: templates.toDescription && fill(templates.toDescription) || null
64
+ };
65
+ }
66
+
35
67
  //#endregion
36
68
  //#region src/reference-query.ts
37
69
  /**
@@ -67,4 +99,38 @@ function referenceQueryProblem(query, fieldTypeOf) {
67
99
  }
68
100
 
69
101
  //#endregion
70
- export { ICON_CATALOG_VERSION, PAGE_TEXT_LINK_PREFIX, QUERY_LIMIT_CAP, SYSTEM_ORDER_KEYS, UNTITLED_ARTIFACT, artifactSourceTitle, artifactTitle, generateUuidv7, isQueryBacked, isSystemOrderKey, projectIdSchema, referenceQueryProblem, scopeIconSvgIds, uuidv7JsonSchema, uuidv7Schema, validateIconValue };
102
+ //#region src/structured-data.ts
103
+ const RECORD_LINK_PREFIX = "camox:record:";
104
+ /** A link to the node of the record with id `id`. */
105
+ function recordLink(id) {
106
+ return { "@id": `${RECORD_LINK_PREFIX}${id}` };
107
+ }
108
+ /** The id of the record a value links, when it is a record link. */
109
+ function linkedRecordId(value) {
110
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
111
+ if (Object.keys(value).length !== 1) return void 0;
112
+ const id = value["@id"];
113
+ if (typeof id !== "string" || !id.startsWith(RECORD_LINK_PREFIX)) return void 0;
114
+ return id.slice(13);
115
+ }
116
+ /** The Article types, whose name is their `headline`. */
117
+ const ARTICLE_TYPES = [
118
+ "Article",
119
+ "BlogPosting",
120
+ "NewsArticle",
121
+ "TechArticle",
122
+ "ScholarlyArticle",
123
+ "Report",
124
+ "SocialMediaPosting"
125
+ ];
126
+ /** Whether a value is a marker for a page's URL. */
127
+ function isPageUrlMarker(value) {
128
+ return typeof value === "object" && value !== null && value["@camox"] === "pageUrl" && typeof value.path === "string";
129
+ }
130
+ /** Whether a template's value is shown only under a setting. */
131
+ function isSettingCondition(value) {
132
+ return typeof value === "object" && value !== null && value["@camox"] === "if" && typeof value.setting === "string";
133
+ }
134
+
135
+ //#endregion
136
+ export { ARTICLE_TYPES, ICON_CATALOG_VERSION, PAGE_TEXT_LINK_PREFIX, QUERY_LIMIT_CAP, SYSTEM_ORDER_KEYS, UNTITLED_ARTIFACT, artifactSourceTitle, artifactTitle, generateUuidv7, isPageUrlMarker, isQueryBacked, isSettingCondition, isSystemOrderKey, linkedRecordId, nonStringTemplateFields, projectIdSchema, recordLink, recordText, referenceQueryProblem, scopeIconSvgIds, templateFields, uuidv7JsonSchema, uuidv7Schema, validateIconValue };
@@ -0,0 +1,25 @@
1
+ //#region src/locales.d.ts
2
+ /**
3
+ * The URL names the locale (ADR 0015): a non-default locale's paths sit under its prefix
4
+ * (`/de/preise`), the default locale's have none. Pure, so the API, the runtime and the browser
5
+ * read a path the same way.
6
+ */
7
+ /** A locale of a project, as `locales.list` returns it. */
8
+ export type LocaleView = {
9
+ tag: string;
10
+ isDefault: boolean;
11
+ };
12
+ /** A runtime path split into the locale it names and the locale-relative path after its prefix. */
13
+ export type LocalePath = {
14
+ locale: LocaleView;
15
+ path: string;
16
+ };
17
+ /** A locale's URL prefix: its tag in lowercase. The default locale has none. */
18
+ export declare function localePrefix(locale: LocaleView): string;
19
+ /**
20
+ * The locale a runtime path names, and the path after its prefix, without search or hash. A first
21
+ * segment that isn't a non-default locale's prefix leaves the path the default locale's. A
22
+ * locale's home is `/de`; `/de/` names it too.
23
+ */
24
+ export declare function parseLocalePath(pathname: string, locales: readonly LocaleView[]): LocalePath;
25
+ //#endregion
@@ -0,0 +1,29 @@
1
+ //#region src/locales.ts
2
+ /** A locale's URL prefix: its tag in lowercase. The default locale has none. */
3
+ function localePrefix(locale) {
4
+ return locale.isDefault ? "" : `/${locale.tag.toLowerCase()}`;
5
+ }
6
+ /**
7
+ * The locale a runtime path names, and the path after its prefix, without search or hash. A first
8
+ * segment that isn't a non-default locale's prefix leaves the path the default locale's. A
9
+ * locale's home is `/de`; `/de/` names it too.
10
+ */
11
+ function parseLocalePath(pathname, locales) {
12
+ const path = pathname.replace(/[?#].*$/s, "") || "/";
13
+ const end = path.indexOf("/", 1);
14
+ const segment = end === -1 ? path.slice(1) : path.slice(1, end);
15
+ const named = locales.find((locale) => !locale.isDefault && localePrefix(locale) === `/${segment}`);
16
+ if (named) return {
17
+ locale: named,
18
+ path: end === -1 ? "/" : path.slice(end)
19
+ };
20
+ const fallback = locales.find((locale) => locale.isDefault);
21
+ if (!fallback) throw new Error("A project's locales include its default locale.");
22
+ return {
23
+ locale: fallback,
24
+ path
25
+ };
26
+ }
27
+
28
+ //#endregion
29
+ export { localePrefix, parseLocalePath };
@@ -10,6 +10,8 @@ export declare const RESERVED_PROJECT_SLUGS: ReadonlySet<string>;
10
10
  * made of letters, digits and inner hyphens.
11
11
  */
12
12
  export declare const projectSlugSchema: z.ZodString;
13
+ /** The address a project's website is served at: its Project Slug's `camox.site` subdomain. */
14
+ export declare const projectWebsiteUrl: (slug: string) => string;
13
15
  /** Why a slug can't be given to a new project. */
14
16
  export type UnavailableSlugReason = "invalid" | "reserved" | "taken";
15
17
  /** What a member is told when a slug can't be used, by reason. */
@@ -25,4 +27,9 @@ export type ProjectStatus = "settingUp" | "live" | "setupFailed";
25
27
  */
26
28
  export type ProjectSetupStep = "copyTemplate" | "build" | "serve";
27
29
  export declare const PROJECT_SETUP_STEPS: readonly ProjectSetupStep[];
30
+ /**
31
+ * A social profile's address: an absolute `https` URL. Shared so the studio checks a profile by
32
+ * the API's own rule before saving it.
33
+ */
34
+ export declare const socialProfileUrlSchema: z.ZodString;
28
35
  //#endregion
package/dist/projects.js CHANGED
@@ -34,6 +34,8 @@ const RESERVED_PROJECT_SLUGS = /* @__PURE__ */ new Set([
34
34
  * made of letters, digits and inner hyphens.
35
35
  */
36
36
  const projectSlugSchema = /* @__PURE__ */ z.string().min(1, "Enter a slug").max(63, "Use at most 63 characters").regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase letters, digits and hyphens, without a hyphen at either end");
37
+ /** The address a project's website is served at: its Project Slug's `camox.site` subdomain. */
38
+ const projectWebsiteUrl = (slug) => `https://${slug}.camox.site`;
37
39
  /** What a member is told when a slug can't be used, by reason. */
38
40
  const UNAVAILABLE_SLUG_MESSAGES = {
39
41
  invalid: "Use lowercase letters, digits and hyphens, without a hyphen at either end.",
@@ -45,6 +47,19 @@ const PROJECT_SETUP_STEPS = [
45
47
  "build",
46
48
  "serve"
47
49
  ];
50
+ /**
51
+ * A social profile's address: an absolute `https` URL. Shared so the studio checks a profile by
52
+ * the API's own rule before saving it.
53
+ */
54
+ const socialProfileUrlSchema = /* @__PURE__ */ z.string().max(2048, "Use a shorter URL").refine(isHttpsUrl, "Enter a URL starting with https://");
55
+ function isHttpsUrl(value) {
56
+ if (!value.startsWith("https://")) return false;
57
+ try {
58
+ return new URL(value).hostname !== "";
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
48
63
 
49
64
  //#endregion
50
- export { PROJECT_SETUP_STEPS, RESERVED_PROJECT_SLUGS, UNAVAILABLE_SLUG_MESSAGES, projectSlugSchema };
65
+ export { PROJECT_SETUP_STEPS, RESERVED_PROJECT_SLUGS, UNAVAILABLE_SLUG_MESSAGES, projectSlugSchema, projectWebsiteUrl, socialProfileUrlSchema };