@communecter/cocolight-api-client 1.0.150 → 1.0.152

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.
@@ -19,6 +19,7 @@ import { COSTUM_RUNTIME } from "./runtime-map.js";
19
19
 
20
20
  import type { CostumExtensionModule, CostumExtensionField, KnownCostumSlug, Collection } from "./types.js";
21
21
  import type { BaseEntity } from "../api/BaseEntity.js";
22
+ import type { GetElementsKeyResponse } from "../types/api-responses.js";
22
23
 
23
24
  /**
24
25
  * Contexte costum résolu, attaché à une instance d'entité via `config.costumCtx`.
@@ -99,13 +100,13 @@ type ScopeCollection = "poi" | "organizations" | "projects" | "events";
99
100
  * et n'altère jamais l'état partagé — chaque entité reçoit son propre `_costumCtx`.
100
101
  */
101
102
  export class CostumScope {
102
- readonly slug: KnownCostumSlug;
103
+ readonly slug: KnownCostumSlug | (string & {});
103
104
  private readonly user: BaseEntity;
104
105
  private readonly module: CostumExtensionModule;
105
106
  private readonly costumId: string;
106
107
  private readonly costumType: string;
107
108
 
108
- constructor(user: BaseEntity, slug: KnownCostumSlug, module: CostumExtensionModule, costumId: string, costumType: string) {
109
+ constructor(user: BaseEntity, slug: KnownCostumSlug | (string & {}), module: CostumExtensionModule, costumId: string, costumType: string) {
109
110
  this.user = user;
110
111
  this.slug = slug;
111
112
  this.module = module;
@@ -191,8 +192,23 @@ export class CostumScope {
191
192
  export function resolveCostumCtxFromSource(sourceKey: string | undefined | null, collection: Collection): CostumRuntimeContext | null {
192
193
  if (!sourceKey) return null;
193
194
  const entry = COSTUM_RUNTIME[sourceKey]?.[collection];
194
- if (!entry) return null;
195
195
  const meta = COSTUM_META[sourceKey as KnownCostumSlug];
196
+ if (!entry) {
197
+ // Pas d'overlay de champs pour (sourceKey, collection). Si le costum est CONNU (meta présent), on
198
+ // reconnaît quand même le scope (source.key = identité) via un ctx NU (fields:[]) — l'élément est
199
+ // rattaché au costum sans champ spécifique. Costum hors registry → null (résolution live = inc1b).
200
+ if (!meta) return null;
201
+ return {
202
+ slug: sourceKey,
203
+ costumId: meta.costumId,
204
+ costumType: meta.costumType,
205
+ collection,
206
+ schema: { type: "object", additionalProperties: true, properties: {} },
207
+ fields: [],
208
+ presets: {},
209
+ hidden: [],
210
+ };
211
+ }
196
212
  const props = (entry.schema as { properties?: Record<string, unknown> }).properties ?? {};
197
213
  return {
198
214
  slug: sourceKey,
@@ -206,16 +222,53 @@ export function resolveCostumCtxFromSource(sourceKey: string | undefined | null,
206
222
  };
207
223
  }
208
224
 
225
+ /** Module costum « nu » (scope d'IDENTITÉ sans overlay de champs) : `collections` vide → `create()` no-op. */
226
+ function fieldlessModule(slug: string): CostumExtensionModule {
227
+ return { slug, collections: {} };
228
+ }
229
+
230
+ /**
231
+ * Ouvre un scope costum FIELDLESS depuis une entité DÉJÀ CHARGÉE (zéro fetch) : `costumSlug` = slug de
232
+ * l'entité (= `source.key` posé par le backend), `costumId/costumType` = l'entité porteuse. Pendant runtime
233
+ * exact de `_withCostumContext` (BaseEntity), pour la voie « entité tenue » (un site nu sans form). Le
234
+ * backend re-gate `found` sur l'existence d'un costum sur l'entité → un site sans costum n'estampille rien.
235
+ */
236
+ export function costumScopeFromEntity(user: BaseEntity, entity: BaseEntity): CostumScope {
237
+ const sd = entity.serverData as { slug?: string | null; id?: string | null } | undefined;
238
+ const slug = sd?.slug;
239
+ const id = sd?.id;
240
+ if (typeof slug !== "string" || slug.trim() === "") {
241
+ throw new ApiError("me.costum(entity) : l'entité n'a pas de slug (charge-la d'abord).", 400);
242
+ }
243
+ if (typeof id !== "string" || !id) {
244
+ throw new ApiError("me.costum(entity) : l'entité n'a pas d'id.", 400);
245
+ }
246
+ return new CostumScope(user, slug, fieldlessModule(slug), id, entity.getEntityType());
247
+ }
248
+
209
249
  /**
210
- * Résout un `CostumScope` pour un user connecté. Known-only (inc1) : charge le module généré
211
- * (code-split via dynamic import) + le meta du registry. Slug inconnu => erreur explicite.
250
+ * Résout un `CostumScope` par SLUG pour un user connecté.
251
+ * - slug à champs (meta + loader) charge le module généré (code-split via dynamic import).
252
+ * - slug CONNU sans loader (fieldless) → scope d'identité nu (`collections:{}`).
253
+ * - slug HORS registry → résolution LIVE via `slug/getinfo` (contextId/contextType) → scope nu (tier 3b).
212
254
  */
213
- export async function resolveCostumScope(user: BaseEntity, slug: KnownCostumSlug): Promise<CostumScope> {
214
- const meta = COSTUM_META[slug];
215
- const loader = COSTUM_SCHEMAS[slug];
216
- if (!meta || !loader) {
217
- throw new ApiError(`Costum inconnu : "${slug}". (Le live-fetch d'un costum hors registry n'est pas encore supporté.)`, 404);
255
+ export async function resolveCostumScope(user: BaseEntity, slug: KnownCostumSlug | (string & {})): Promise<CostumScope> {
256
+ const meta = COSTUM_META[slug as KnownCostumSlug];
257
+ const loader = COSTUM_SCHEMAS[slug as KnownCostumSlug];
258
+ if (meta && loader) {
259
+ const { default: module } = await loader();
260
+ return new CostumScope(user, slug, module, meta.costumId, meta.costumType);
261
+ }
262
+ if (meta) {
263
+ // Connu mais sans overlay de champs : scope d'identité pur (estampille source.key, aucun champ costum).
264
+ return new CostumScope(user, slug, fieldlessModule(slug), meta.costumId, meta.costumType);
265
+ }
266
+ // Tier 3b — slug HORS registry : résolution LIVE slug → {contextId, contextType} via la route `slug/getinfo`
267
+ // (GET_ELEMENTS_KEY, « none »). Couvre les sites fieldless/cold/futurs sans rien bundler. costumSlug = le slug
268
+ // demandé (slug ENTITÉ) ; le backend re-gate `found` sur l'existence d'un costum sur l'entité résolue.
269
+ const info = await user.endpointApi.getElementsKey({ pathParams: { slug } }) as GetElementsKeyResponse;
270
+ if (info?.contextId && info?.contextType) {
271
+ return new CostumScope(user, slug, fieldlessModule(slug), String(info.contextId), String(info.contextType));
218
272
  }
219
- const { default: module } = await loader();
220
- return new CostumScope(user, slug, module, meta.costumId, meta.costumType);
273
+ throw new ApiError(`Costum inconnu : "${slug}" slug introuvable (résolution live slug/getinfo).`, 404);
221
274
  }