@happyvertical/smrt-core 0.51.6 → 0.51.8

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 (32) hide show
  1. package/README.md +26 -0
  2. package/agents/build-knowledge.md +13 -0
  3. package/dist/consumer-plugin/index.d.ts.map +1 -1
  4. package/dist/consumer-plugin/index.js +141 -49
  5. package/dist/consumer-plugin/index.js.map +1 -1
  6. package/dist/manifest/discover-smrt-packages.d.ts +3 -2
  7. package/dist/manifest/discover-smrt-packages.d.ts.map +1 -1
  8. package/dist/manifest/discover-smrt-packages.js +34 -15
  9. package/dist/manifest/discover-smrt-packages.js.map +1 -1
  10. package/dist/manifest/package-manifest-exports.d.ts +34 -0
  11. package/dist/manifest/package-manifest-exports.d.ts.map +1 -0
  12. package/dist/manifest/package-manifest-exports.js +144 -0
  13. package/dist/manifest/package-manifest-exports.js.map +1 -0
  14. package/dist/manifest/static-manifest.js +1 -1
  15. package/dist/manifest/static-manifest.js.map +1 -1
  16. package/dist/manifest/store.js +1 -1
  17. package/dist/manifest.json +1 -1
  18. package/dist/migrations/differ.d.ts.map +1 -1
  19. package/dist/migrations/differ.js +19 -27
  20. package/dist/migrations/differ.js.map +1 -1
  21. package/dist/schema/column-data-probes.d.ts +71 -0
  22. package/dist/schema/column-data-probes.d.ts.map +1 -1
  23. package/dist/schema/column-data-probes.js +79 -1
  24. package/dist/schema/column-data-probes.js.map +1 -1
  25. package/dist/schema/live-parity.d.ts.map +1 -1
  26. package/dist/schema/live-parity.js +10 -12
  27. package/dist/schema/live-parity.js.map +1 -1
  28. package/dist/smrt-knowledge.json +5 -5
  29. package/dist/vite-plugin/index.d.ts.map +1 -1
  30. package/dist/vite-plugin/index.js +93 -3
  31. package/dist/vite-plugin/index.js.map +1 -1
  32. package/package.json +4 -4
package/README.md CHANGED
@@ -191,6 +191,32 @@ generates routes, types, registration, and virtual modules, but it disables
191
191
  source/package scans, watch rescans, and manifest writes. Omit
192
192
  `generationSnapshot` for normal local development.
193
193
 
194
+ #### Where consumer manifests are resolved
195
+
196
+ `smrtConsumer()` resolves each consumed package's manifest through that
197
+ package's own `package.json#exports` map first — the `./manifest.json` entry
198
+ (or its `./manifest` alias), which every s-m-r-t model package is required
199
+ to publish — and only then falls back to the conventional
200
+ `dist/manifest/static-manifest.js`, `dist/manifest.json`, and `manifest.json`
201
+ paths. A package whose build emits elsewhere (for example a triple-purpose
202
+ package that maps `"./manifest.json": "./dist/lib/manifest.json"`) therefore
203
+ loads normally.
204
+
205
+ Every declared target is a candidate, not a single choice: an export array is
206
+ a fallback list and each recognized condition contributes its own target, so a
207
+ missing first entry does not hide a valid later one. Candidates are probed in
208
+ order and the first one that loads wins, so a candidate that exists but cannot
209
+ be read, parsed, or imported is reported and skipped rather than ending the
210
+ search — the conventional paths stay reachable behind a stale or malformed
211
+ export target. Targets that resolve outside the package directory are dropped,
212
+ both lexically and after following symlinks; the package root is itself
213
+ resolved through symlinks first, so pnpm and workspace installs are unaffected.
214
+
215
+ A package that yields no usable manifest is never skipped silently. An entry in
216
+ an explicit `packages` list is an assertion, so it fails the build by name;
217
+ a package found by dependency-name discovery only warns by name and contributes
218
+ no objects.
219
+
194
220
  #### Consumer web collection definitions
195
221
 
196
222
  `smrtConsumer()` also resolves `@smrt/web` for browser collection consumers:
@@ -36,6 +36,19 @@ owned by each consumer.
36
36
  Keep `manifest.json` runtime-focused. `smrt-knowledge.json` is the deterministic
37
37
  agent contract for downstream review and architecture tools.
38
38
 
39
+ The CLI's schema-command gate reads that same merged file: a pure-consumer
40
+ project (0 local objects) qualifies on the consumed packages its manifest
41
+ records, so a clobbered `objects` map is what made `smrt db:migrate` report
42
+ `missing_local_manifest` (#2925).
43
+
44
+ `.smrt/manifest.json` has two writers and neither owns all of it: `smrtPlugin()`
45
+ contributes the project's scanned objects and `smrtConsumer()` the consumed
46
+ packages' entries. Both writes are merge-preserving, keyed on each entry's
47
+ recorded `packageName` — a plain overwrite in either direction silently drops
48
+ the other's objects, and the last pass of a multi-environment build is not
49
+ reliably the aggregating one (#1760, #2925). `dist/manifest.json` stays
50
+ local-only: a published package must not carry its dependencies' objects.
51
+
39
52
  The schema-version-1 object projection is additive and high-signal: it retains
40
53
  normalized tenant mode/field, explicit `cti`/`sti` strategy, conflict columns,
41
54
  method signatures, and field defaults/constraints/readonly/transient flags.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAa,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAWnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uCAAuC,CAAC;AAoB/C,OAAO,EACL,kCAAkC,EAClC,KAAK,sCAAsC,EAC3C,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAqDnC;;;;GAIG;AACH,MAAM,WAAW,4BACf,SAAQ,OAAO,CACb,IAAI,CACF,gBAAgB,EACd,WAAW,GACX,YAAY,GACZ,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,aAAa,GACb,gBAAgB,CACnB,CACF;IACD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IACnD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,4BAA4B,CAAC;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA0VD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAqVtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAa,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAYnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uCAAuC,CAAC;AAoB/C,OAAO,EACL,kCAAkC,EAClC,KAAK,sCAAsC,EAC3C,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAqDnC;;;;GAIG;AACH,MAAM,WAAW,4BACf,SAAQ,OAAO,CACb,IAAI,CACF,gBAAgB,EACd,WAAW,GACX,YAAY,GACZ,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,aAAa,GACb,gBAAgB,CACnB,CACF;IACD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IACnD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,4BAA4B,CAAC;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA0VD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAqWtE"}
@@ -3,6 +3,7 @@ import { canonicalSvelteKitPath } from "../vite-plugin/sveltekit-path.js";
3
3
  import { clearGeneratedSvelteKitRouteFiles, reconcileSvelteKitRouteGitignore } from "../vite-plugin/sveltekit-generator.js";
4
4
  import { generateWebModule, isCollectionManifestClass, resolveCollectionItemObject } from "../vite-plugin/web-collections.js";
5
5
  import { buildDomainKnowledgeManifest } from "../knowledge.js";
6
+ import { manifestExportCandidates } from "../manifest/package-manifest-exports.js";
6
7
  import { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot } from "../generation-snapshot.js";
7
8
  import { resolveFileKnowledgeConfig } from "../knowledge-config.js";
8
9
  import { generateClientModule } from "../vite-plugin/generated-client.js";
@@ -181,9 +182,29 @@ function smrtConsumer(options = {}) {
181
182
  if (!generationSnapshot) throw new Error("[smrt:consumer] Generation snapshot is not configured");
182
183
  return loadVerifiedSmrtGenerationSnapshot(generationSnapshot, projectRoot, "dependencies");
183
184
  }
185
+ /**
186
+ * Resolve the packages to aggregate, and whether that list was asserted by
187
+ * the consumer's config. An explicit list fails closed when a package
188
+ * yields no manifest; a heuristically discovered one only warns.
189
+ */
190
+ async function resolveConsumerPackages() {
191
+ if (packages.length === 0 && !disableScanning) return {
192
+ names: await discoverSmrtPackages(projectRoot),
193
+ explicit: false
194
+ };
195
+ return {
196
+ names: packages,
197
+ explicit: packages.length > 0
198
+ };
199
+ }
184
200
  async function generateConfigTypes(manifest) {
185
201
  if (!generateTypes || typesGenerated) return;
186
- typeManifest = manifest ?? (generationSnapshot ? loadGenerationSnapshot() : await aggregateTypeManifests(packages.length === 0 && !disableScanning ? await discoverSmrtPackages(projectRoot) : packages, projectRoot));
202
+ if (manifest) typeManifest = manifest;
203
+ else if (generationSnapshot) typeManifest = loadGenerationSnapshot();
204
+ else {
205
+ const resolved = await resolveConsumerPackages();
206
+ typeManifest = await aggregateTypeManifests(resolved.names, projectRoot, { explicit: resolved.explicit });
207
+ }
187
208
  await generateProjectTypes(typeManifest, typesDir, projectRoot);
188
209
  typesGenerated = true;
189
210
  }
@@ -198,8 +219,8 @@ function smrtConsumer(options = {}) {
198
219
  const previousConsumerRouteRoots = loadConsumerSvelteKitRouteRoots(projectRoot);
199
220
  if (consumerSvelteKit) {
200
221
  await assertConsumerSvelteKitFormerRouteRootsAreSafe(userConfig, projectRoot, previousConsumerRouteRoots, env);
201
- const routePackages = packages.length === 0 && !disableScanning ? await discoverSmrtPackages(projectRoot) : packages;
202
- const routeManifest = generationSnapshot ? loadGenerationSnapshot() : await aggregateTypeManifests(routePackages, projectRoot);
222
+ const routePackages = await resolveConsumerPackages();
223
+ const routeManifest = generationSnapshot ? loadGenerationSnapshot() : await aggregateTypeManifests(routePackages.names, projectRoot, { explicit: routePackages.explicit });
203
224
  const hostedManifest = selectConsumerRouteManifest(routeManifest, consumerSvelteKit);
204
225
  const routesDir = canonicalConsumerRouteRoot(projectRoot, consumerSvelteKit.routesDir ?? "src/routes/api");
205
226
  const reservedRoutePaths = await activeProducerKnowledgeRoutePaths(userConfig, projectRoot, env);
@@ -259,11 +280,11 @@ function smrtConsumer(options = {}) {
259
280
  }
260
281
  return;
261
282
  }
262
- if (packages.length === 0 && !disableScanning) smrtPackages = await discoverSmrtPackages(projectRoot);
263
- else smrtPackages = packages;
283
+ const resolvedPackages = await resolveConsumerPackages();
284
+ smrtPackages = resolvedPackages.names;
264
285
  if (smrtPackages.length > 0) {
265
286
  console.log(`[smrt:consumer] Found SMRT packages: ${smrtPackages.join(", ")}`);
266
- typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot);
287
+ typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot, { explicit: resolvedPackages.explicit });
267
288
  const agentSurface = producerApi ? await producerApi.resolveKnowledgeAgentSurface() : void 0;
268
289
  await saveAggregatedManifest(typeManifest, projectRoot, producerApi?.resolveKnowledgeConfig, agentSurface);
269
290
  await generateRegistrationFile(typeManifest, projectRoot);
@@ -343,66 +364,137 @@ async function discoverSmrtPackages(projectRoot) {
343
364
  return packages;
344
365
  }
345
366
  /**
346
- * Check if a package has SMRT manifest
367
+ * Manifest locations probed for a consumed package, in preference order.
368
+ *
369
+ * The package's own `package.json#exports` map comes first: it is the
370
+ * published contract for where the manifest lives, and a package whose build
371
+ * emits outside this framework's conventional layout (for example
372
+ * `"./manifest.json": "./dist/lib/manifest.json"`) is otherwise skipped
373
+ * entirely (issue #2923). The conventional paths remain as a fallback for
374
+ * packages that ship a manifest without exporting a subpath for it.
375
+ */
376
+ function legacyStaticManifestPath(packageDir) {
377
+ return path.join(packageDir, "dist", "manifest", "static-manifest.js");
378
+ }
379
+ function packageManifestCandidates(packageDir, packageJson) {
380
+ return [
381
+ ...manifestExportCandidates(packageDir, packageJson),
382
+ legacyStaticManifestPath(packageDir),
383
+ path.join(packageDir, "dist", "manifest.json"),
384
+ path.join(packageDir, "manifest.json")
385
+ ];
386
+ }
387
+ /**
388
+ * Whether a dependency looks like a SMRT package, for the name-heuristic
389
+ * discovery path only.
390
+ *
391
+ * This decides whether a package this framework knows nothing about is pulled
392
+ * into aggregation at all, so mere existence of a manifest-shaped file is not
393
+ * enough: `manifest.json` at a package root or `dist/manifest.json` is a
394
+ * common artifact of unrelated tooling (Vite build manifests, PWA and
395
+ * extension manifests). A false positive would announce a stranger under
396
+ * "Found SMRT packages", tell the user to publish a manifest for a package
397
+ * that has nothing to do with SMRT, and — for a JS candidate — evaluate that
398
+ * dependency's module inside the build.
399
+ *
400
+ * A JSON candidate is therefore confirmed by parsing it and requiring
401
+ * `moduleType: 'smrt'`, matching what build-time discovery requires. A JS
402
+ * candidate cannot be identified without importing it, which is the thing
403
+ * being avoided, so only the historical `dist/manifest/static-manifest.js`
404
+ * location counts — exactly the probe this function used before #2923.
347
405
  */
348
406
  async function hasSmrtManifest(nodeModulesPath, packageName) {
349
407
  const packagePath = path.join(nodeModulesPath, packageName);
350
- const manifestPath = path.join(packagePath, "dist", "manifest", "static-manifest.js");
351
- return fs.existsSync(manifestPath);
408
+ const legacyStaticManifest = legacyStaticManifestPath(packagePath);
409
+ for (const manifestPath of packageManifestCandidates(packagePath)) {
410
+ if (!fs.existsSync(manifestPath)) continue;
411
+ if (!manifestPath.endsWith(".json")) {
412
+ if (manifestPath === legacyStaticManifest) return true;
413
+ continue;
414
+ }
415
+ try {
416
+ if (JSON.parse(fs.readFileSync(manifestPath, "utf-8"))?.moduleType === "smrt") return true;
417
+ } catch {}
418
+ }
419
+ return false;
352
420
  }
353
421
  /**
354
- * Aggregate type manifests from multiple packages
422
+ * Aggregate type manifests from multiple packages.
423
+ *
424
+ * A package that yields no usable manifest is never dropped silently
425
+ * (issue #2923): an explicitly listed package fails the build, and a
426
+ * heuristically discovered one warns by name. Silence here surfaces much
427
+ * later as missing tables, routes, and generated types with nothing in the
428
+ * build log to connect them to the package that was skipped.
429
+ *
430
+ * @param packages - Package names to aggregate.
431
+ * @param projectRoot - Consumer project root containing `node_modules`.
432
+ * @param options - `explicit` marks a caller-supplied `packages` list, whose
433
+ * entries are assertions rather than guesses and therefore fail closed.
355
434
  */
356
- async function aggregateTypeManifests(packages, projectRoot) {
435
+ async function aggregateTypeManifests(packages, projectRoot, options = {}) {
357
436
  const aggregatedManifest = {
358
437
  version: "1.0.0",
359
438
  timestamp: 0,
360
439
  smrtDependencies: [...packages],
361
440
  objects: {}
362
441
  };
363
- for (const packageName of packages) try {
364
- const packageDir = path.join(projectRoot, "node_modules", packageName);
365
- const packageJsonPath = path.join(packageDir, "package.json");
366
- let packageJson;
442
+ const unresolvedPackages = [];
443
+ for (const packageName of packages) {
444
+ let loadedManifest = false;
367
445
  try {
368
- const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
369
- packageJson = JSON.parse(packageJsonContent);
370
- } catch {
371
- console.warn(`[smrt:consumer] Could not read package.json for ${packageName}`);
372
- continue;
373
- }
374
- const manifestCandidates = [
375
- path.join(packageDir, "dist", "manifest", "static-manifest.js"),
376
- path.join(packageDir, "dist", "manifest.json"),
377
- path.join(packageDir, "manifest.json")
378
- ];
379
- for (const manifestPath of manifestCandidates) if (fs.existsSync(manifestPath)) {
380
- let manifest;
381
- if (manifestPath.endsWith(".js")) {
382
- const manifestModule = await import(manifestPath);
383
- manifest = manifestModule.staticManifest || manifestModule.default;
384
- } else {
385
- const manifestContent = fs.readFileSync(manifestPath, "utf-8");
386
- manifest = JSON.parse(manifestContent);
446
+ const packageDir = path.join(projectRoot, "node_modules", packageName);
447
+ const packageJsonPath = path.join(packageDir, "package.json");
448
+ let packageJson;
449
+ try {
450
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
451
+ packageJson = JSON.parse(packageJsonContent);
452
+ } catch {
453
+ console.warn(`[smrt:consumer] Could not read package.json for ${packageName}`);
454
+ unresolvedPackages.push(packageName);
455
+ continue;
387
456
  }
388
- if (manifest?.objects) {
389
- console.log(`[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`);
390
- for (const [objectName, objectDef] of Object.entries(manifest.objects)) {
391
- const def = objectDef;
392
- aggregatedManifest.objects[objectName] = {
393
- ...def,
394
- packageName: def.packageName || manifest.packageName || packageName,
395
- packageVersion: def.packageVersion || manifest.packageVersion || packageJson.version,
396
- importPath: def.importPath || determineImportPath(packageJson),
397
- exportName: def.exportName || def.className || objectName,
398
- collectionExportName: def.collectionExportName || `${def.className || objectName}Collection`
399
- };
457
+ const manifestCandidates = packageManifestCandidates(packageDir, packageJson);
458
+ for (const manifestPath of manifestCandidates) if (fs.existsSync(manifestPath)) {
459
+ let manifest;
460
+ try {
461
+ if (manifestPath.endsWith(".js")) {
462
+ const manifestModule = await import(manifestPath);
463
+ manifest = manifestModule.staticManifest || manifestModule.default;
464
+ } else {
465
+ const manifestContent = fs.readFileSync(manifestPath, "utf-8");
466
+ manifest = JSON.parse(manifestContent);
467
+ }
468
+ } catch (error) {
469
+ console.warn(`[smrt:consumer] Could not load manifest candidate ${manifestPath} for ${packageName}; trying the next location:`, error);
470
+ continue;
471
+ }
472
+ if (manifest?.objects) {
473
+ console.log(`[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`);
474
+ for (const [objectName, objectDef] of Object.entries(manifest.objects)) {
475
+ const def = objectDef;
476
+ aggregatedManifest.objects[objectName] = {
477
+ ...def,
478
+ packageName: def.packageName || manifest.packageName || packageName,
479
+ packageVersion: def.packageVersion || manifest.packageVersion || packageJson.version,
480
+ importPath: def.importPath || determineImportPath(packageJson),
481
+ exportName: def.exportName || def.className || objectName,
482
+ collectionExportName: def.collectionExportName || `${def.className || objectName}Collection`
483
+ };
484
+ }
485
+ loadedManifest = true;
486
+ break;
400
487
  }
401
- break;
402
488
  }
489
+ } catch (error) {
490
+ console.warn(`[smrt:consumer] Error loading manifest from ${packageName}:`, error);
403
491
  }
404
- } catch (error) {
405
- console.warn(`[smrt:consumer] Error loading manifest from ${packageName}:`, error);
492
+ if (!loadedManifest) unresolvedPackages.push(packageName);
493
+ }
494
+ if (unresolvedPackages.length > 0) {
495
+ const named = unresolvedPackages.join(", ");
496
+ if (options.explicit) throw new Error(`[smrt:consumer] No SMRT manifest could be resolved for ${named}. Listed packages must publish a manifest through package.json#exports ("./manifest.json") or at dist/manifest.json, dist/manifest/static-manifest.js, or manifest.json. Build the package, or remove it from smrtConsumer({ packages }).`);
497
+ console.warn(`[smrt:consumer] No SMRT manifest could be resolved for ${named}; contributing 0 objects. If the package provides SMRT objects, publish its manifest through package.json#exports ("./manifest.json").`);
406
498
  }
407
499
  return aggregatedManifest;
408
500
  }