@soulcraft/kit-schema 2.0.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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @module @soulcraft/kit-schema/variables
3
+ * @description Template variable substitution engine for Soulcraft kit files.
4
+ *
5
+ * Kit template files use `{{variableKey}}` placeholders that are replaced with
6
+ * the values collected during the onboarding wizard. This module provides the
7
+ * substitution engine used when seeding a new venue from a kit.
8
+ *
9
+ * Variable syntax: `{{key}}` — double curly braces, no spaces.
10
+ * Unknown variables are left as-is (no error) so that partial substitution
11
+ * is possible and templates can be applied incrementally.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { substituteVariables } from '@soulcraft/kit-schema/variables';
16
+ *
17
+ * const template = 'Welcome to {{businessName}} in {{city}}!';
18
+ * const result = substituteVariables(template, {
19
+ * businessName: 'Wicks & Whiskers',
20
+ * city: 'Charlotte'
21
+ * });
22
+ * // 'Welcome to Wicks & Whiskers in Charlotte!'
23
+ * ```
24
+ */
25
+
26
+ /**
27
+ * Substitution context mapping variable keys to their resolved values.
28
+ */
29
+ export type VariableContext = Record<string, string>;
30
+
31
+ /**
32
+ * Replace all `{{key}}` placeholders in a template string with values from the context.
33
+ *
34
+ * Unknown keys (no matching context entry) are left as the original `{{key}}` string.
35
+ * This is intentional — it allows templates to be applied in stages where not all
36
+ * variables are known yet.
37
+ *
38
+ * @param template - The template string containing `{{key}}` placeholders.
39
+ * @param context - Key-value map of variable names to substitution values.
40
+ * @returns The template with all known `{{key}}` placeholders replaced.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * substituteVariables('Hello {{name}}!', { name: 'Alice' }); // 'Hello Alice!'
45
+ * substituteVariables('Hello {{name}}!', {}); // 'Hello {{name}}!'
46
+ * ```
47
+ */
48
+ export function substituteVariables(template: string, context: VariableContext): string {
49
+ return template.replace(/\{\{([^}]+)\}\}/g, (match, key: string) => {
50
+ const value = context[key.trim()];
51
+ return value !== undefined ? value : match;
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Recursively substitute variables in all string values within an object or array.
57
+ *
58
+ * Traverses the entire data structure and applies {@link substituteVariables} to
59
+ * every string value. Non-string values (numbers, booleans, null) are left unchanged.
60
+ * Useful for substituting variables throughout an entire CMS content JSON file.
61
+ *
62
+ * @param data - The data structure to process (object, array, or primitive).
63
+ * @param context - Key-value map of variable names to substitution values.
64
+ * @returns A new data structure with all string values substituted.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * substituteInObject(
69
+ * { title: 'Welcome to {{businessName}}', count: 5 },
70
+ * { businessName: 'Wicks & Whiskers' }
71
+ * );
72
+ * // { title: 'Welcome to Wicks & Whiskers', count: 5 }
73
+ * ```
74
+ */
75
+ export function substituteInObject(data: unknown, context: VariableContext): unknown {
76
+ if (typeof data === 'string') {
77
+ return substituteVariables(data, context);
78
+ }
79
+ if (Array.isArray(data)) {
80
+ return data.map((item) => substituteInObject(item, context));
81
+ }
82
+ if (data !== null && typeof data === 'object') {
83
+ const result: Record<string, unknown> = {};
84
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
85
+ result[key] = substituteInObject(value, context);
86
+ }
87
+ return result;
88
+ }
89
+ return data;
90
+ }
91
+
92
+ /**
93
+ * Find all `{{key}}` placeholders used in a template string.
94
+ *
95
+ * Returns a deduplicated array of variable keys found in the template.
96
+ * Used to validate that all required variables have been provided before
97
+ * substitution, and to show the user which fields they need to fill in.
98
+ *
99
+ * @param template - The template string to inspect.
100
+ * @returns Deduplicated array of variable key names found in the template.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * findTemplateVariables('Hello {{name}}, welcome to {{place}}!');
105
+ * // ['name', 'place']
106
+ *
107
+ * findTemplateVariables('No variables here');
108
+ * // []
109
+ * ```
110
+ */
111
+ export function findTemplateVariables(template: string): string[] {
112
+ const matches = template.matchAll(/\{\{([^}]+)\}\}/g);
113
+ const keys = new Set<string>();
114
+ for (const match of matches) {
115
+ const key = match[1]?.trim();
116
+ if (key) keys.add(key);
117
+ }
118
+ return Array.from(keys);
119
+ }
120
+
121
+ /**
122
+ * Validate that all required variable keys from a kit's variable definitions
123
+ * are present in the provided context.
124
+ *
125
+ * @param requiredKeys - Array of variable keys that must be provided.
126
+ * @param context - The user-supplied variable context to validate.
127
+ * @returns Array of missing variable keys. Empty array means all required variables are present.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * validateVariableContext(
132
+ * ['businessName', 'city'],
133
+ * { businessName: 'Wicks & Whiskers' }
134
+ * );
135
+ * // ['city'] ← 'city' is missing
136
+ * ```
137
+ */
138
+ export function validateVariableContext(
139
+ requiredKeys: string[],
140
+ context: VariableContext
141
+ ): string[] {
142
+ return requiredKeys.filter((key) => !context[key]);
143
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "noUncheckedIndexedAccess": true,
6
+ "noImplicitOverride": true,
7
+ "noFallthroughCasesInSwitch": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "moduleResolution": "bundler",
10
+ "module": "ESNext",
11
+ "target": "ES2024",
12
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
13
+ "customConditions": ["svelte"],
14
+ "noEmit": true,
15
+ "isolatedModules": true,
16
+ "verbatimModuleSyntax": true,
17
+ "allowImportingTsExtensions": true,
18
+ "skipLibCheck": true,
19
+ "resolveJsonModule": true
20
+ }
21
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "allowImportingTsExtensions": false,
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist"
10
+ },
11
+ "include": ["src/**/*"]
12
+ }