@spfn/cms 0.1.0-alpha.9 → 0.2.0-beta.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.
- package/README.md +320 -359
- package/dist/actions.d.ts +12 -6
- package/dist/actions.js +25 -10
- package/dist/actions.js.map +1 -1
- package/dist/config.d.ts +39 -0
- package/dist/config.js +39 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +149 -0
- package/dist/errors.js +164 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +138 -20
- package/dist/index.js +212 -23
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +44 -81
- package/dist/server.js +610 -256
- package/dist/server.js.map +1 -1
- package/migrations/0000_medical_ozymandias.sql +54 -0
- package/migrations/meta/0000_snapshot.json +336 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +54 -44
- package/dist/actions.d.ts.map +0 -1
- package/dist/client.d.ts +0 -138
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -62
- package/dist/client.js.map +0 -1
- package/dist/cms.config.d.ts +0 -77
- package/dist/cms.config.d.ts.map +0 -1
- package/dist/cms.config.js +0 -111
- package/dist/cms.config.js.map +0 -1
- package/dist/entities/cms-audit-logs.d.ts +0 -213
- package/dist/entities/cms-audit-logs.d.ts.map +0 -1
- package/dist/entities/cms-audit-logs.js +0 -103
- package/dist/entities/cms-audit-logs.js.map +0 -1
- package/dist/entities/cms-draft-cache.d.ts +0 -188
- package/dist/entities/cms-draft-cache.d.ts.map +0 -1
- package/dist/entities/cms-draft-cache.js +0 -112
- package/dist/entities/cms-draft-cache.js.map +0 -1
- package/dist/entities/cms-label-values.d.ts +0 -192
- package/dist/entities/cms-label-values.d.ts.map +0 -1
- package/dist/entities/cms-label-values.js +0 -105
- package/dist/entities/cms-label-values.js.map +0 -1
- package/dist/entities/cms-label-versions.d.ts +0 -207
- package/dist/entities/cms-label-versions.d.ts.map +0 -1
- package/dist/entities/cms-label-versions.js +0 -80
- package/dist/entities/cms-label-versions.js.map +0 -1
- package/dist/entities/cms-labels.d.ts +0 -189
- package/dist/entities/cms-labels.d.ts.map +0 -1
- package/dist/entities/cms-labels.js +0 -48
- package/dist/entities/cms-labels.js.map +0 -1
- package/dist/entities/cms-published-cache.d.ts +0 -199
- package/dist/entities/cms-published-cache.d.ts.map +0 -1
- package/dist/entities/cms-published-cache.js +0 -103
- package/dist/entities/cms-published-cache.js.map +0 -1
- package/dist/entities/index.d.ts +0 -10
- package/dist/entities/index.d.ts.map +0 -1
- package/dist/entities/index.js +0 -10
- package/dist/entities/index.js.map +0 -1
- package/dist/generators/index.d.ts +0 -19
- package/dist/generators/index.d.ts.map +0 -1
- package/dist/generators/index.js +0 -19
- package/dist/generators/index.js.map +0 -1
- package/dist/generators/label-sync-generator.d.ts +0 -33
- package/dist/generators/label-sync-generator.d.ts.map +0 -1
- package/dist/generators/label-sync-generator.js +0 -86
- package/dist/generators/label-sync-generator.js.map +0 -1
- package/dist/helpers/locale.actions.d.ts +0 -132
- package/dist/helpers/locale.actions.d.ts.map +0 -1
- package/dist/helpers/locale.actions.js +0 -210
- package/dist/helpers/locale.actions.js.map +0 -1
- package/dist/helpers/locale.constants.d.ts +0 -10
- package/dist/helpers/locale.constants.d.ts.map +0 -1
- package/dist/helpers/locale.constants.js +0 -10
- package/dist/helpers/locale.constants.js.map +0 -1
- package/dist/helpers/locale.d.ts +0 -17
- package/dist/helpers/locale.d.ts.map +0 -1
- package/dist/helpers/locale.js +0 -20
- package/dist/helpers/locale.js.map +0 -1
- package/dist/helpers/sync.d.ts +0 -41
- package/dist/helpers/sync.d.ts.map +0 -1
- package/dist/helpers/sync.js +0 -309
- package/dist/helpers/sync.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/init.d.ts +0 -31
- package/dist/init.d.ts.map +0 -1
- package/dist/init.js +0 -36
- package/dist/init.js.map +0 -1
- package/dist/labels/helpers.d.ts +0 -31
- package/dist/labels/helpers.d.ts.map +0 -1
- package/dist/labels/helpers.js +0 -60
- package/dist/labels/helpers.js.map +0 -1
- package/dist/labels/index.d.ts +0 -7
- package/dist/labels/index.d.ts.map +0 -1
- package/dist/labels/index.js +0 -7
- package/dist/labels/index.js.map +0 -1
- package/dist/repositories/cms-draft-cache.repository.d.ts +0 -62
- package/dist/repositories/cms-draft-cache.repository.d.ts.map +0 -1
- package/dist/repositories/cms-draft-cache.repository.js +0 -56
- package/dist/repositories/cms-draft-cache.repository.js.map +0 -1
- package/dist/repositories/cms-label-values.repository.d.ts +0 -32
- package/dist/repositories/cms-label-values.repository.d.ts.map +0 -1
- package/dist/repositories/cms-label-values.repository.js +0 -72
- package/dist/repositories/cms-label-values.repository.js.map +0 -1
- package/dist/repositories/cms-labels.repository.d.ts +0 -53
- package/dist/repositories/cms-labels.repository.d.ts.map +0 -1
- package/dist/repositories/cms-labels.repository.js +0 -77
- package/dist/repositories/cms-labels.repository.js.map +0 -1
- package/dist/repositories/cms-published-cache.repository.d.ts +0 -53
- package/dist/repositories/cms-published-cache.repository.d.ts.map +0 -1
- package/dist/repositories/cms-published-cache.repository.js +0 -54
- package/dist/repositories/cms-published-cache.repository.js.map +0 -1
- package/dist/repositories/index.d.ts +0 -8
- package/dist/repositories/index.d.ts.map +0 -1
- package/dist/repositories/index.js +0 -9
- package/dist/repositories/index.js.map +0 -1
- package/dist/routes/labels/[id]/contract.d.ts +0 -68
- package/dist/routes/labels/[id]/contract.d.ts.map +0 -1
- package/dist/routes/labels/[id]/contract.js +0 -84
- package/dist/routes/labels/[id]/contract.js.map +0 -1
- package/dist/routes/labels/[id]/index.d.ts +0 -10
- package/dist/routes/labels/[id]/index.d.ts.map +0 -1
- package/dist/routes/labels/[id]/index.js +0 -96
- package/dist/routes/labels/[id]/index.js.map +0 -1
- package/dist/routes/labels/by-key/[key]/contract.d.ts +0 -24
- package/dist/routes/labels/by-key/[key]/contract.d.ts.map +0 -1
- package/dist/routes/labels/by-key/[key]/contract.js +0 -28
- package/dist/routes/labels/by-key/[key]/contract.js.map +0 -1
- package/dist/routes/labels/by-key/[key]/index.d.ts +0 -8
- package/dist/routes/labels/by-key/[key]/index.d.ts.map +0 -1
- package/dist/routes/labels/by-key/[key]/index.js +0 -32
- package/dist/routes/labels/by-key/[key]/index.js.map +0 -1
- package/dist/routes/labels/contract.d.ts +0 -59
- package/dist/routes/labels/contract.d.ts.map +0 -1
- package/dist/routes/labels/contract.js +0 -75
- package/dist/routes/labels/contract.js.map +0 -1
- package/dist/routes/labels/index.d.ts +0 -10
- package/dist/routes/labels/index.d.ts.map +0 -1
- package/dist/routes/labels/index.js +0 -73
- package/dist/routes/labels/index.js.map +0 -1
- package/dist/routes/published-cache/contract.d.ts +0 -25
- package/dist/routes/published-cache/contract.d.ts.map +0 -1
- package/dist/routes/published-cache/contract.js +0 -35
- package/dist/routes/published-cache/contract.js.map +0 -1
- package/dist/routes/published-cache/index.d.ts +0 -8
- package/dist/routes/published-cache/index.d.ts.map +0 -1
- package/dist/routes/published-cache/index.js +0 -33
- package/dist/routes/published-cache/index.js.map +0 -1
- package/dist/routes/values/[labelId]/[version]/contract.d.ts +0 -29
- package/dist/routes/values/[labelId]/[version]/contract.d.ts.map +0 -1
- package/dist/routes/values/[labelId]/[version]/contract.js +0 -33
- package/dist/routes/values/[labelId]/[version]/contract.js.map +0 -1
- package/dist/routes/values/[labelId]/[version]/index.d.ts +0 -8
- package/dist/routes/values/[labelId]/[version]/index.d.ts.map +0 -1
- package/dist/routes/values/[labelId]/[version]/index.js +0 -45
- package/dist/routes/values/[labelId]/[version]/index.js.map +0 -1
- package/dist/routes/values/[labelId]/contract.d.ts +0 -38
- package/dist/routes/values/[labelId]/contract.d.ts.map +0 -1
- package/dist/routes/values/[labelId]/contract.js +0 -59
- package/dist/routes/values/[labelId]/contract.js.map +0 -1
- package/dist/routes/values/[labelId]/index.d.ts +0 -8
- package/dist/routes/values/[labelId]/index.d.ts.map +0 -1
- package/dist/routes/values/[labelId]/index.js +0 -42
- package/dist/routes/values/[labelId]/index.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/store.d.ts +0 -87
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -205
- package/dist/store.js.map +0 -1
- package/dist/types.d.ts +0 -74
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -7
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,24 +1,213 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createApi } from "@spfn/core/nextjs";
|
|
3
|
+
import { logger } from "@spfn/core/logger";
|
|
4
|
+
|
|
5
|
+
// src/lib/bind-locale.ts
|
|
6
|
+
function isLocaleRecord(obj) {
|
|
7
|
+
if (!obj || typeof obj !== "object") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const values = Object.values(obj);
|
|
11
|
+
if (values.length === 0) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return values.every((v) => typeof v === "string");
|
|
15
|
+
}
|
|
16
|
+
function bindLocale(labels, locale, fallbackLocale) {
|
|
17
|
+
return createProxy(labels, locale, fallbackLocale);
|
|
18
|
+
}
|
|
19
|
+
function createProxy(obj, locale, fallbackLocale) {
|
|
20
|
+
return new Proxy(obj, {
|
|
21
|
+
get(target, prop) {
|
|
22
|
+
const value = target[prop];
|
|
23
|
+
if (value === void 0) {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
if (isLocaleRecord(value)) {
|
|
27
|
+
if (value[locale] !== void 0) {
|
|
28
|
+
return value[locale];
|
|
29
|
+
}
|
|
30
|
+
if (fallbackLocale && value[fallbackLocale] !== void 0) {
|
|
31
|
+
return value[fallbackLocale];
|
|
32
|
+
}
|
|
33
|
+
const firstLocale = Object.keys(value)[0];
|
|
34
|
+
return value[firstLocale];
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === "object" && value !== null) {
|
|
37
|
+
return createProxy(value, locale, fallbackLocale);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/actions.ts
|
|
45
|
+
import { cookies } from "next/headers";
|
|
46
|
+
var LOCALE_COOKIE_NAME = "cms-locale";
|
|
47
|
+
var LOCALE_MAX_AGE = 365 * 24 * 60 * 60;
|
|
48
|
+
async function getLocale(defaultLocale) {
|
|
49
|
+
const cookieStore = await cookies();
|
|
50
|
+
const localeCookie = cookieStore.get(LOCALE_COOKIE_NAME);
|
|
51
|
+
return localeCookie?.value ?? defaultLocale ?? "en";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/lib/helpers.ts
|
|
55
|
+
function setNestedValue(target, path, value) {
|
|
56
|
+
const parts = path.split(".");
|
|
57
|
+
let current = target;
|
|
58
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
59
|
+
const part = parts[i];
|
|
60
|
+
if (!current[part]) {
|
|
61
|
+
current[part] = {};
|
|
62
|
+
}
|
|
63
|
+
current = current[part];
|
|
64
|
+
}
|
|
65
|
+
const lastPart = parts[parts.length - 1];
|
|
66
|
+
current[lastPart] = value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/lib/define-labels.ts
|
|
70
|
+
function defineLabelConfig(config) {
|
|
71
|
+
return config;
|
|
72
|
+
}
|
|
73
|
+
function defineLabels(labels) {
|
|
74
|
+
return labels;
|
|
75
|
+
}
|
|
76
|
+
function format(template, vars) {
|
|
77
|
+
return template.replace(/\{(\w+)}/g, (match, key) => {
|
|
78
|
+
const value = vars[key];
|
|
79
|
+
return value !== void 0 ? String(value) : match;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/index.ts
|
|
84
|
+
var cmsLogger = logger.child("@spfn/cms");
|
|
85
|
+
var api = createApi();
|
|
86
|
+
function createCmsClient(labelsDefinition, config) {
|
|
87
|
+
async function getLabel(section) {
|
|
88
|
+
const locale = await getLocale(config.defaultLocale);
|
|
89
|
+
cmsLogger.debug("getLabel called", {
|
|
90
|
+
section,
|
|
91
|
+
locale,
|
|
92
|
+
defaultLocale: config.defaultLocale,
|
|
93
|
+
fallbackLocale: config.fallbackLocale
|
|
94
|
+
});
|
|
95
|
+
const cache = await api.getLabelCache.call({
|
|
96
|
+
query: {
|
|
97
|
+
sections: [section],
|
|
98
|
+
locale
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const filteredLabels = {};
|
|
102
|
+
if (section in labelsDefinition) {
|
|
103
|
+
filteredLabels[section] = labelsDefinition[section];
|
|
104
|
+
}
|
|
105
|
+
const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);
|
|
106
|
+
const merged = deepMergeCache(defaults, cache, locale);
|
|
107
|
+
return merged[section];
|
|
108
|
+
}
|
|
109
|
+
async function getLabels(sections) {
|
|
110
|
+
const locale = await getLocale(config.defaultLocale);
|
|
111
|
+
cmsLogger.debug("getLabels called", {
|
|
112
|
+
sections,
|
|
113
|
+
locale,
|
|
114
|
+
defaultLocale: config.defaultLocale,
|
|
115
|
+
fallbackLocale: config.fallbackLocale,
|
|
116
|
+
availableDefinitionKeys: Object.keys(labelsDefinition)
|
|
117
|
+
});
|
|
118
|
+
const cache = await api.getLabelCache.call({
|
|
119
|
+
query: {
|
|
120
|
+
sections: [...sections],
|
|
121
|
+
locale
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
cmsLogger.debug("Fetched from cache", {
|
|
125
|
+
cacheKeys: Object.keys(cache),
|
|
126
|
+
cacheEntryCount: Object.keys(cache).length,
|
|
127
|
+
cacheStructure: Object.entries(cache).map(([key, value]) => ({
|
|
128
|
+
section: key,
|
|
129
|
+
isObject: typeof value === "object",
|
|
130
|
+
isNull: value === null,
|
|
131
|
+
contentKeys: value && typeof value === "object" ? Object.keys(value) : []
|
|
132
|
+
}))
|
|
133
|
+
});
|
|
134
|
+
const filteredLabels = {};
|
|
135
|
+
for (const section of sections) {
|
|
136
|
+
if (section in labelsDefinition) {
|
|
137
|
+
filteredLabels[section] = labelsDefinition[section];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
cmsLogger.debug("Filtered sections", {
|
|
141
|
+
requestedSections: sections,
|
|
142
|
+
filteredSections: Object.keys(filteredLabels),
|
|
143
|
+
filteredLabelsStructure: Object.entries(filteredLabels).map(([key, value]) => ({
|
|
144
|
+
section: key,
|
|
145
|
+
hasValue: !!value,
|
|
146
|
+
isObject: typeof value === "object",
|
|
147
|
+
nestedKeys: value && typeof value === "object" ? Object.keys(value) : []
|
|
148
|
+
}))
|
|
149
|
+
});
|
|
150
|
+
const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);
|
|
151
|
+
cmsLogger.debug("Generated defaults with locale binding", {
|
|
152
|
+
defaultsKeys: Object.keys(defaults)
|
|
153
|
+
});
|
|
154
|
+
const merged = deepMergeCache(defaults, cache, locale);
|
|
155
|
+
cmsLogger.debug("Merged cache and defaults", {
|
|
156
|
+
mergedKeys: Object.keys(merged)
|
|
157
|
+
});
|
|
158
|
+
return merged;
|
|
159
|
+
}
|
|
160
|
+
return { api, getLabel, getLabels, format };
|
|
161
|
+
}
|
|
162
|
+
function deepMergeCache(defaults, cache, locale) {
|
|
163
|
+
const result = { ...defaults };
|
|
164
|
+
cmsLogger.debug("deepMergeCache: Starting merge", {
|
|
165
|
+
cacheEntries: Object.keys(cache).length,
|
|
166
|
+
locale
|
|
167
|
+
});
|
|
168
|
+
for (const [section, content] of Object.entries(cache)) {
|
|
169
|
+
if (!content || typeof content !== "object") {
|
|
170
|
+
cmsLogger.debug("deepMergeCache: Skipping invalid content", { section });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const contentKeys = Object.keys(content);
|
|
174
|
+
cmsLogger.debug("deepMergeCache: Processing section", {
|
|
175
|
+
section,
|
|
176
|
+
labelCount: contentKeys.length
|
|
177
|
+
});
|
|
178
|
+
for (const [flatKey, value] of Object.entries(content)) {
|
|
179
|
+
let extractedValue;
|
|
180
|
+
if (value && typeof value === "object" && "content" in value) {
|
|
181
|
+
extractedValue = value.content;
|
|
182
|
+
cmsLogger.debug("deepMergeCache: Extracted from content field", {
|
|
183
|
+
flatKey,
|
|
184
|
+
hasContent: true
|
|
185
|
+
});
|
|
186
|
+
} else if (value && typeof value === "object" && locale in value) {
|
|
187
|
+
extractedValue = value[locale];
|
|
188
|
+
cmsLogger.debug("deepMergeCache: Extracted from locale field", {
|
|
189
|
+
flatKey,
|
|
190
|
+
locale
|
|
191
|
+
});
|
|
192
|
+
} else {
|
|
193
|
+
extractedValue = value;
|
|
194
|
+
cmsLogger.debug("deepMergeCache: Using raw value", {
|
|
195
|
+
flatKey,
|
|
196
|
+
valueType: typeof value
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
setNestedValue(result, flatKey, extractedValue);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
cmsLogger.debug("deepMergeCache: Merge completed", {
|
|
203
|
+
resultKeys: Object.keys(result)
|
|
204
|
+
});
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
export {
|
|
208
|
+
createCmsClient,
|
|
209
|
+
defineLabelConfig,
|
|
210
|
+
defineLabels,
|
|
211
|
+
format
|
|
212
|
+
};
|
|
24
213
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;;;;GAOG;AAEH,oBAAoB;AACpB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7E,kDAAkD;AAClD,cAAc,aAAa,CAAC;AAG5B,oCAAoC;AACpC,cAAc,yBAAyB,CAAC;AAExC,iCAAiC;AACjC,cAAc,qBAAqB,CAAC;AAEpC,wDAAwD;AACxD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5F,sDAAsD;AACtD,cAAc,mBAAmB,CAAC;AAElC,gDAAgD;AAChD,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/bind-locale.ts","../src/actions.ts","../src/lib/helpers.ts","../src/lib/define-labels.ts"],"sourcesContent":["import { createApi } from \"@spfn/core/nextjs\";\nimport { logger } from \"@spfn/core/logger\";\nimport { type AppRouter } from './server/routes/index';\nimport { bindLocale, type SectionKeys, type BoundLabelSection, type BoundLabelsSections } from './lib/bind-locale';\nimport { getLocale } from './actions';\nimport { setNestedValue } from './lib/helpers';\nimport { format, defineLabelConfig, defineLabels } from './lib/define-labels';\n\nconst cmsLogger = logger.child('@spfn/cms');\n\n/**\n * Default API client (for backward compatibility or when not using labels)\n */\nconst api = createApi<AppRouter>();\n\n/**\n * Create CMS client with API, label getters, and format utility\n *\n * @param labelsDefinition - Labels defined using defineLabels()\n * @param config - Label config from defineLabelConfig()\n * @returns API client, getLabel (single), getLabels (multiple), and format utility\n *\n * @example\n * ```typescript\n * // labels.ts - Setup once\n * export const { api, getLabel, getLabels, format } = createCmsClient(labelsDefinition, labelConfig);\n *\n * // Single section - direct access\n * const label = await getLabel('home');\n * label.hero.title // \"Hello\" (no section name!)\n *\n * // Multiple sections - with section names\n * const labels = await getLabels(['home', 'about']);\n * labels.home.hero.title // \"Hello\"\n * labels.about.title // \"About Us\"\n *\n * // With template variables\n * const greeting = label.hero.greeting; // \"Hello {name}\"\n * format(greeting, { name: \"John\" }); // \"Hello John\"\n * ```\n */\nexport function createCmsClient<T>(\n labelsDefinition: T,\n config: { defaultLocale: string; fallbackLocale?: string }\n)\n{\n /**\n * Get a single section's labels (without section name wrapper)\n *\n * @param section - Section name to fetch\n * @returns Labels for the section, directly accessible\n *\n * @example\n * ```typescript\n * const label = await getLabel('signup');\n * label.title // Direct access\n * label.userName\n * ```\n */\n async function getLabel<K extends SectionKeys<T>>(section: K): Promise<BoundLabelSection<T, K>>\n {\n // Auto-detect locale from cookie, fallback to config.defaultLocale\n const locale = await getLocale(config.defaultLocale);\n\n cmsLogger.debug('getLabel called', {\n section,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [section as string],\n locale\n }\n });\n\n // 2. Filter only requested section\n const filteredLabels: any = {};\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n\n // 3. Generate defaults with locale binding\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n // 5. Return only the section content (without section name)\n return merged[section] as BoundLabelSection<T, K>;\n }\n\n /**\n * Get multiple sections' labels (with section names as keys)\n *\n * @param sections - Array of section names to fetch\n * @returns Object with section names as keys\n *\n * @example\n * ```typescript\n * const labels = await getLabels(['home', 'about']);\n * labels.home.title\n * labels.about.description\n * ```\n */\n async function getLabels<K extends SectionKeys<T>>(sections: readonly K[]): Promise<BoundLabelsSections<T, K>>\n {\n // Auto-detect locale from cookie, fallback to config.defaultLocale\n const locale = await getLocale(config.defaultLocale);\n\n cmsLogger.debug('getLabels called', {\n sections,\n locale,\n defaultLocale: config.defaultLocale,\n fallbackLocale: config.fallbackLocale,\n availableDefinitionKeys: Object.keys(labelsDefinition as any),\n });\n\n // 1. Fetch from published_cache\n const cache = await api.getLabelCache.call({\n query: {\n sections: [...sections] as unknown as string[],\n locale\n }\n });\n\n cmsLogger.debug('Fetched from cache', {\n cacheKeys: Object.keys(cache),\n cacheEntryCount: Object.keys(cache).length,\n cacheStructure: Object.entries(cache).map(([key, value]) => ({\n section: key,\n isObject: typeof value === 'object',\n isNull: value === null,\n contentKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 2. Filter only requested sections (performance optimization)\n const filteredLabels: any = {};\n for (const section of sections)\n {\n if (section in (labelsDefinition as any))\n {\n filteredLabels[section] = (labelsDefinition as any)[section];\n }\n }\n\n cmsLogger.debug('Filtered sections', {\n requestedSections: sections,\n filteredSections: Object.keys(filteredLabels),\n filteredLabelsStructure: Object.entries(filteredLabels).map(([key, value]) => ({\n section: key,\n hasValue: !!value,\n isObject: typeof value === 'object',\n nestedKeys: value && typeof value === 'object' ? Object.keys(value) : [],\n })),\n });\n\n // 3. Generate defaults with locale binding (only for requested sections)\n const defaults = bindLocale(filteredLabels, locale, config.fallbackLocale);\n\n cmsLogger.debug('Generated defaults with locale binding', {\n defaultsKeys: Object.keys(defaults),\n });\n\n // 4. Merge: cache takes priority, fallback to defaults\n const merged = deepMergeCache(defaults, cache, locale);\n\n cmsLogger.debug('Merged cache and defaults', {\n mergedKeys: Object.keys(merged),\n });\n\n return merged as BoundLabelsSections<T, K>;\n }\n\n return { api, getLabel, getLabels, format };\n}\n\n/**\n * Deep merge cache into defaults\n */\nfunction deepMergeCache(defaults: any, cache: Record<string, any>, locale: string): any\n{\n const result = { ...defaults };\n\n cmsLogger.debug('deepMergeCache: Starting merge', {\n cacheEntries: Object.keys(cache).length,\n locale,\n });\n\n for (const [section, content] of Object.entries(cache))\n {\n if (!content || typeof content !== 'object')\n {\n cmsLogger.debug('deepMergeCache: Skipping invalid content', { section });\n continue;\n }\n\n const contentKeys = Object.keys(content);\n cmsLogger.debug('deepMergeCache: Processing section', {\n section,\n labelCount: contentKeys.length,\n });\n\n for (const [flatKey, value] of Object.entries(content))\n {\n // Extract locale-specific value from LabelValue format\n let extractedValue: any;\n\n if (value && typeof value === 'object' && 'content' in value)\n {\n extractedValue = (value as any).content;\n cmsLogger.debug('deepMergeCache: Extracted from content field', {\n flatKey,\n hasContent: true,\n });\n }\n else if (value && typeof value === 'object' && locale in value)\n {\n extractedValue = (value as any)[locale];\n cmsLogger.debug('deepMergeCache: Extracted from locale field', {\n flatKey,\n locale,\n });\n }\n else\n {\n extractedValue = value;\n cmsLogger.debug('deepMergeCache: Using raw value', {\n flatKey,\n valueType: typeof value,\n });\n }\n\n // Set value using helper function\n setNestedValue(result, flatKey, extractedValue);\n }\n }\n\n cmsLogger.debug('deepMergeCache: Merge completed', {\n resultKeys: Object.keys(result),\n });\n\n return result;\n}\n\n/**\n * Re-export format utility for standalone use\n *\n * @example\n * ```typescript\n * import { format } from '@spfn/cms/api-client';\n *\n * const text = \"Hello {name}, you have {count} messages\";\n * format(text, { name: \"John\", count: 5 });\n * // \"Hello John, you have 5 messages\"\n * ```\n */\nexport { format, defineLabelConfig, defineLabels };\n\n/**\n * Re-export types for external use\n */\nexport type { BoundLabels, SectionKeys, BoundLabelSection, BoundLabelsSections } from './lib/bind-locale';","/**\n * Bind locale to labels, returning locale-specific values\n *\n * @example\n * ```ts\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * ```\n */\n\n/**\n * Type that converts locale records to strings\n */\nexport type BoundLabels<T> = {\n [K in keyof T]: T[K] extends Record<string, any>\n ? IsLocaleRecord<T[K]> extends true\n ? string\n : BoundLabels<T[K]>\n : T[K];\n};\n\n/**\n * Extract section keys from label definition\n */\nexport type SectionKeys<T> = Extract<keyof T, string>;\n\n/**\n * Get content of a single section (without section name wrapper)\n */\nexport type BoundLabelSection<T, K extends SectionKeys<T>> = BoundLabels<T>[K];\n\n/**\n * Pick specific sections from bound labels (for multiple sections)\n */\nexport type BoundLabelsSections<T, K extends SectionKeys<T>> = Pick<BoundLabels<T>, K>;\n\n/**\n * Check if object is a locale record (has string values only)\n */\ntype IsLocaleRecord<T> = T extends Record<string, string> ? true : false;\n\n/**\n * Check if an object is a locale record at runtime\n */\nfunction isLocaleRecord(obj: any): boolean\n{\n if (!obj || typeof obj !== 'object')\n {\n return false;\n }\n\n const values = Object.values(obj);\n\n // Empty object is not a locale record\n if (values.length === 0)\n {\n return false;\n }\n\n // All values must be strings\n return values.every(v => typeof v === 'string');\n}\n\n/**\n * Bind a locale to label definitions, returning locale-specific values\n *\n * @param labels - Label definitions with locale records\n * @param locale - Locale to bind (e.g., 'en', 'ko')\n * @param fallbackLocale - Optional fallback locale if value not found\n * @returns Labels with locale-specific string values\n *\n * @example\n * ```typescript\n * const labelsDefinition = defineLabels({\n * home: {\n * title: { en: \"Home\", ko: \"홈\" },\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * });\n *\n * const labels = bindLocale(labelsDefinition, 'ko');\n * labels.home.title // \"홈\"\n * labels.home.hero.title // \"환영합니다\"\n *\n * // With fallback\n * const labelsEn = bindLocale(labelsDefinition, 'en', 'ko');\n * ```\n */\nexport function bindLocale<T>(\n labels: T,\n locale: string,\n fallbackLocale?: string\n): BoundLabels<T>\n{\n return createProxy(labels, locale, fallbackLocale) as BoundLabels<T>;\n}\n\n/**\n * Create a proxy that intercepts property access and returns locale-specific values\n */\nfunction createProxy(obj: any, locale: string, fallbackLocale?: string): any\n{\n return new Proxy(obj, {\n get(target, prop)\n {\n const value = target[prop];\n\n // If value doesn't exist, return undefined\n if (value === undefined)\n {\n return undefined;\n }\n\n // If this is a locale record, return the locale value\n if (isLocaleRecord(value))\n {\n // Try to get the requested locale\n if (value[locale] !== undefined)\n {\n return value[locale];\n }\n\n // Fallback to fallbackLocale if specified\n if (fallbackLocale && value[fallbackLocale] !== undefined)\n {\n return value[fallbackLocale];\n }\n\n // If locale not found, return first available locale\n const firstLocale = Object.keys(value)[0];\n return value[firstLocale];\n }\n\n // If this is a nested object, wrap it in a proxy\n if (typeof value === 'object' && value !== null)\n {\n return createProxy(value, locale, fallbackLocale);\n }\n\n // Otherwise return as-is\n return value;\n },\n });\n}","\"use server\"\n\nimport { cookies } from 'next/headers';\n\nconst LOCALE_COOKIE_NAME = 'cms-locale';\nconst LOCALE_MAX_AGE = 365 * 24 * 60 * 60; // 1 year\n\n/**\n * Set user's preferred locale in cookie\n *\n * @param locale - Language code (e.g., 'ko', 'en', 'ja')\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_NAME, locale, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: LOCALE_MAX_AGE,\n path: '/',\n });\n}\n\n/**\n * Get user's preferred locale from cookie\n *\n * @param defaultLocale - Default locale from labelConfig.defaultLocale\n * @returns Language code (from cookie, or defaultLocale, or 'en')\n */\nexport async function getLocale(defaultLocale?: string): Promise<string>\n{\n const cookieStore = await cookies();\n const localeCookie = cookieStore.get(LOCALE_COOKIE_NAME);\n\n return localeCookie?.value ?? defaultLocale ?? 'en';\n}","/**\n * CMS Helper Functions\n */\n\nexport type FlatLabel = Record<string, Record<string, string>>;\n\n/**\n * Flatten nested label structure into dot notation\n *\n * @param labels - Nested label object\n * @param prefix - Key prefix for recursion\n * @returns Flattened label structure\n *\n * @example\n * ```typescript\n * const nested = {\n * home: {\n * hero: {\n * title: { en: \"Welcome\", ko: \"환영합니다\" }\n * }\n * }\n * };\n *\n * const flat = flattenLabels(nested);\n * // { \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" } }\n * ```\n */\nexport function flattenLabels<T extends Record<string, any>>(labels: T, prefix = ''): FlatLabel\n{\n const result: FlatLabel = {};\n\n if (!labels || typeof labels !== 'object')\n {\n return result;\n }\n\n const obj = labels as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(obj))\n {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (!value || typeof value !== 'object')\n {\n continue;\n }\n\n const valueObj = value as Record<string, unknown>;\n\n // Check if this is a leaf node (locale values: { en: \"...\", ko: \"...\" })\n const isLeaf = Object.values(valueObj).every(v => typeof v === 'string');\n\n if (isLeaf)\n {\n result[newKey] = valueObj as Record<string, string>;\n }\n else\n {\n // Recursively flatten nested structure\n Object.assign(result, flattenLabels(value, newKey));\n }\n }\n\n return result;\n}\n\n/**\n * Set a value in nested object using dot notation path\n *\n * @param target - Target object to modify\n * @param path - Dot notation path (e.g., \"home.hero.title\")\n * @param value - Value to set\n *\n * @example\n * ```typescript\n * const obj = {};\n * setNestedValue(obj, \"home.hero.title\", \"Welcome\");\n * // obj = { home: { hero: { title: \"Welcome\" } } }\n * ```\n */\nexport function setNestedValue(target: any, path: string, value: any): void\n{\n const parts = path.split('.');\n let current = target;\n\n for (let i = 0; i < parts.length - 1; i++)\n {\n const part = parts[i];\n if (!current[part])\n {\n current[part] = {};\n }\n current = current[part];\n }\n\n // Set the leaf value\n const lastPart = parts[parts.length - 1];\n current[lastPart] = value;\n}\n\n/**\n * Unflatten dot notation keys back to nested structure\n *\n * @param flat - Flattened label object\n * @returns Nested label structure\n *\n * @example\n * ```typescript\n * const flat = {\n * \"home.hero.title\": { en: \"Welcome\", ko: \"환영합니다\" },\n * \"home.hero.subtitle\": { en: \"Subtitle\", ko: \"부제목\" }\n * };\n *\n * const nested = unflattenLabels(flat);\n * // {\n * // home: {\n * // hero: {\n * // title: { en: \"Welcome\", ko: \"환영합니다\" },\n * // subtitle: { en: \"Subtitle\", ko: \"부제목\" }\n * // }\n * // }\n * // }\n * ```\n */\nexport function unflattenLabels(flat: FlatLabel): Record<string, any>\n{\n const result: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(flat))\n {\n setNestedValue(result, key, value);\n }\n\n return result;\n}","/**\n * Defines a type-safe label configuration.\n *\n * @example\n * ```ts\n * export const labelConfig = defineLabelConfig({\n * locales: ['en', 'ar'] as const,\n * defaultLocale: 'en',\n * fallbackLocale: 'en', // Optional\n * });\n *\n * export type LabelConfig = typeof labelConfig;\n * export type AppLocale = typeof labelConfig.locales[number]; // 'en' | 'ar'\n * ```\n */\nexport function defineLabelConfig<const TLocales extends readonly string[]>(config: {\n locales: TLocales;\n defaultLocale: TLocales[number];\n fallbackLocale?: TLocales[number];\n useBrowserLanguage?: boolean;\n})\n{\n return config;\n}\n\n/**\n * Define nested label structure (tRPC-style)\n *\n * @example\n * ```ts\n * export const labels = defineLabels({\n * home: {\n * slogan: { en: \"Welcome\", ko: \"환영합니다\" },\n * hero: {\n * title: { en: \"Hello\", ko: \"안녕하세요\" }\n * }\n * },\n * about: {\n * title: { en: \"About Us\", ko: \"회사 소개\" }\n * }\n * });\n *\n * // Usage\n * labels.home.slogan;\n * labels.home.hero.title;\n * labels.about.title;\n * ```\n */\nexport function defineLabels<const T>(labels: T)\n{\n return labels;\n}\n\nexport function format(template: string, vars: Record<string, string | number>): string\n{\n return template.replace(/\\{(\\w+)}/g, (match, key) =>\n {\n const value = vars[key];\n return value !== undefined ? String(value) : match;\n });\n}"],"mappings":"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","names":[]}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,99 +1,62 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _spfn_core_route from '@spfn/core/route';
|
|
2
|
+
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
3
|
+
|
|
4
|
+
declare const cmsAppRouter: _spfn_core_route.Router<{
|
|
5
|
+
getLabelCache: _spfn_core_route.RouteDef<{
|
|
6
|
+
query: _sinclair_typebox.TObject<{
|
|
7
|
+
sections: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
|
|
8
|
+
locale: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
9
|
+
}>;
|
|
10
|
+
}, {}, Record<string, any>>;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
2
13
|
/**
|
|
3
|
-
*
|
|
14
|
+
* Result of label synchronization
|
|
4
15
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
16
|
+
interface SyncResult {
|
|
17
|
+
added: string[];
|
|
18
|
+
removed: string[];
|
|
19
|
+
updated: string[];
|
|
20
|
+
unchanged: string[];
|
|
21
|
+
}
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
23
|
+
* Options for label synchronization
|
|
14
24
|
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Section API Return Type
|
|
18
|
-
*/
|
|
19
|
-
export type SectionAPI = {
|
|
25
|
+
interface SyncOptions {
|
|
20
26
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param key - 라벨 키 (섹션 제외, 예: 'hero.title')
|
|
24
|
-
* @param defaultValue - 기본값
|
|
25
|
-
* @param replace - 변수 치환 맵 (예: { name: 'John' })
|
|
26
|
-
* @returns 라벨 값 (문자열인 경우 변수 치환됨)
|
|
27
|
+
* If true, removes labels from DB that don't exist in code
|
|
28
|
+
* @default false
|
|
27
29
|
*/
|
|
28
|
-
|
|
30
|
+
removeOrphaned?: boolean;
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
32
|
+
* If true, runs in dry-run mode without actual DB changes
|
|
33
|
+
* @default false
|
|
31
34
|
*/
|
|
32
|
-
|
|
33
|
-
}
|
|
35
|
+
dryRun?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* 동일한 요청 내에서 같은 섹션을 여러 번 요청해도 한 번만 API 호출
|
|
38
|
-
*
|
|
39
|
-
* @param section - 섹션 이름 (예: 'home', 'why-futureplay')
|
|
40
|
-
* @param locale - 언어 코드 (선택, 미지정시 쿠키에서 자동 조회)
|
|
41
|
-
* @returns Section API ({ t, data })
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```tsx
|
|
45
|
-
* // Server Component
|
|
46
|
-
* import { getSection } from '@spfn/cms/server';
|
|
39
|
+
* CMS Label Synchronization Service
|
|
47
40
|
*
|
|
48
|
-
*
|
|
49
|
-
* {
|
|
50
|
-
* // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴
|
|
51
|
-
* const { t } = await getSection('home');
|
|
52
|
-
*
|
|
53
|
-
* // 또는 명시적으로 locale 지정
|
|
54
|
-
* const { t: tEn } = await getSection('home', 'en');
|
|
55
|
-
*
|
|
56
|
-
* return (
|
|
57
|
-
* <div>
|
|
58
|
-
* <h1>{t('hero.title')}</h1>
|
|
59
|
-
* <p>{t('hero.subtitle', 'Default Subtitle')}</p>
|
|
60
|
-
* <p>{t('hero.greeting', 'Hello {name}!', { name: 'World' })}</p>
|
|
61
|
-
* </div>
|
|
62
|
-
* );
|
|
63
|
-
* }
|
|
64
|
-
* ```
|
|
41
|
+
* Synchronizes labels defined in code with database
|
|
65
42
|
*/
|
|
66
|
-
|
|
43
|
+
|
|
67
44
|
/**
|
|
68
|
-
*
|
|
69
|
-
* 단일 API 호출로 여러 섹션을 효율적으로 가져옵니다
|
|
45
|
+
* Sync labels with database
|
|
70
46
|
*
|
|
71
|
-
* @param
|
|
72
|
-
* @param
|
|
73
|
-
* @returns
|
|
47
|
+
* @param labels - Single label definition or array of label definitions
|
|
48
|
+
* @param options - Sync options
|
|
49
|
+
* @returns Sync result
|
|
74
50
|
*
|
|
75
51
|
* @example
|
|
76
|
-
* ```
|
|
77
|
-
* //
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* export default async function Page()
|
|
81
|
-
* {
|
|
82
|
-
* // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴
|
|
83
|
-
* const sections = await getSections(['home', 'why-futureplay']);
|
|
84
|
-
*
|
|
85
|
-
* // 또는 명시적으로 locale 지정
|
|
86
|
-
* const sectionsEn = await getSections(['home', 'why-futureplay'], 'en');
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Single definition
|
|
54
|
+
* await syncLabels(labelsDefinition);
|
|
87
55
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* <h1>{sections.home.t('hero.title')}</h1>
|
|
91
|
-
* <p>{sections['why-futureplay'].t('intro.text')}</p>
|
|
92
|
-
* </div>
|
|
93
|
-
* );
|
|
94
|
-
* }
|
|
56
|
+
* // Multiple definitions
|
|
57
|
+
* await syncLabels([homeLabels, aboutLabels, commonLabels]);
|
|
95
58
|
* ```
|
|
96
59
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
60
|
+
declare function syncLabels<T extends Record<string, any>>(labels: T | T[], options?: SyncOptions): Promise<SyncResult>;
|
|
61
|
+
|
|
62
|
+
export { cmsAppRouter, syncLabels };
|