create-mercato-app 0.5.1-develop.2691.d8a0934b37 → 0.5.1-develop.2699.f8b50c8046

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
@@ -3,9 +3,9 @@
3
3
 
4
4
  // src/index.ts
5
5
  import { createInterface } from "node:readline";
6
- import { basename as basename2, dirname as dirname5, join as join6, resolve } from "node:path";
7
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync6, copyFileSync as copyFileSync5 } from "node:fs";
8
- import { fileURLToPath as fileURLToPath5 } from "node:url";
6
+ import { basename as basename2, dirname as dirname6, join as join7, resolve } from "node:path";
7
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync7, copyFileSync as copyFileSync5 } from "node:fs";
8
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
9
9
  import pc from "picocolors";
10
10
 
11
11
  // src/lib/ready-apps.ts
@@ -308,138 +308,262 @@ async function extractTarballSnapshot(archive, targetDir) {
308
308
  }
309
309
  }
310
310
 
311
+ // src/lib/apply-starter-preset.ts
312
+ import { readFileSync, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "node:fs";
313
+ import { join as join2, dirname } from "node:path";
314
+ import { fileURLToPath } from "node:url";
315
+
316
+ // src/lib/starter-presets.ts
317
+ var CORE = "@open-mercato/core";
318
+ var EMPTY_MODULES = [
319
+ { id: "auth", from: CORE },
320
+ { id: "directory", from: CORE },
321
+ { id: "configs", from: CORE },
322
+ { id: "entities", from: CORE },
323
+ { id: "query_index", from: CORE },
324
+ { id: "api_docs", from: CORE }
325
+ ];
326
+ var STARTER_PRESETS = {
327
+ classic: {
328
+ id: "classic",
329
+ label: "Classic",
330
+ description: "Current full starter behavior",
331
+ modules: { mode: "replace", enabled: [] },
332
+ ui: { startPageVariant: "classic", hideDemoLinks: false },
333
+ constraints: { rejectWithReadyApps: false }
334
+ },
335
+ empty: {
336
+ id: "empty",
337
+ label: "Empty",
338
+ description: "Minimal builder-ready baseline",
339
+ modules: { mode: "replace", enabled: EMPTY_MODULES },
340
+ ui: { startPageVariant: "minimal", hideDemoLinks: true },
341
+ files: { remove: ["src/modules/example", "src/modules/example_customers_sync"] },
342
+ constraints: { rejectWithReadyApps: true }
343
+ },
344
+ crm: {
345
+ id: "crm",
346
+ label: "CRM",
347
+ description: "Empty preset plus CRM capabilities",
348
+ extends: "empty",
349
+ modules: {
350
+ mode: "patch",
351
+ add: [
352
+ { id: "customers", from: CORE },
353
+ { id: "dictionaries", from: CORE },
354
+ { id: "feature_toggles", from: CORE }
355
+ ]
356
+ },
357
+ ui: { startPageVariant: "crm", hideDemoLinks: true },
358
+ constraints: { rejectWithReadyApps: true }
359
+ }
360
+ };
361
+ var DEFAULT_PRESET_ID = "classic";
362
+ var VALID_PRESET_IDS = Object.keys(STARTER_PRESETS);
363
+
364
+ // src/lib/apply-starter-preset.ts
365
+ var __dirname = dirname(fileURLToPath(import.meta.url));
366
+ function resolvePreset(presetId) {
367
+ const preset = STARTER_PRESETS[presetId];
368
+ if (!preset) {
369
+ throw new Error(`Unknown preset "${presetId}". Valid presets: ${VALID_PRESET_IDS.join(", ")}`);
370
+ }
371
+ let modules;
372
+ let parentFilesToRemove = [];
373
+ if (preset.modules.mode === "replace") {
374
+ modules = [...preset.modules.enabled];
375
+ } else {
376
+ if (!preset.extends) {
377
+ throw new Error(`Preset "${presetId}" uses mode "patch" but has no "extends" parent`);
378
+ }
379
+ const parent = STARTER_PRESETS[preset.extends];
380
+ if (!parent) {
381
+ throw new Error(`Preset "${presetId}" extends unknown preset "${preset.extends}"`);
382
+ }
383
+ if (parent.modules.mode !== "replace") {
384
+ throw new Error(`Preset "${presetId}" parent "${preset.extends}" must use mode "replace"`);
385
+ }
386
+ parentFilesToRemove = parent.files?.remove ?? [];
387
+ let base = [...parent.modules.enabled];
388
+ if (preset.modules.add) {
389
+ base = [...base, ...preset.modules.add];
390
+ }
391
+ if (preset.modules.remove) {
392
+ const toRemove = new Set(preset.modules.remove);
393
+ base = base.filter((m) => !toRemove.has(m.id));
394
+ }
395
+ modules = base;
396
+ }
397
+ const ids = modules.map((m) => m.id);
398
+ const seen = /* @__PURE__ */ new Set();
399
+ const duplicates = ids.filter((id) => {
400
+ if (seen.has(id)) return true;
401
+ seen.add(id);
402
+ return false;
403
+ });
404
+ if (duplicates.length > 0) {
405
+ throw new Error(`Preset "${presetId}" has duplicate module IDs: ${duplicates.join(", ")}`);
406
+ }
407
+ const ownFilesToRemove = preset.files?.remove ?? [];
408
+ const filesToRemove = [.../* @__PURE__ */ new Set([...parentFilesToRemove, ...ownFilesToRemove])];
409
+ return {
410
+ id: preset.id,
411
+ modules,
412
+ filesToRemove,
413
+ ui: preset.ui,
414
+ isClassic: presetId === DEFAULT_PRESET_ID
415
+ };
416
+ }
417
+ function generateModulesTs(modules) {
418
+ const moduleLines = modules.map((m) => ` { id: '${m.id}', from: '${m.from}' },`).join("\n");
419
+ const template = readFileSync(join2(__dirname, "templates", "modules-ts.template"), "utf8");
420
+ return template.replace("{{MODULES}}", moduleLines);
421
+ }
422
+ function applyStarterPreset(presetId, targetDir) {
423
+ const resolved = resolvePreset(presetId);
424
+ if (resolved.isClassic) return;
425
+ writeFileSync2(join2(targetDir, "src", "modules.ts"), generateModulesTs(resolved.modules));
426
+ for (const relativePath of resolved.filesToRemove) {
427
+ rmSync2(join2(targetDir, relativePath), { recursive: true, force: true });
428
+ }
429
+ writeFileSync2(
430
+ join2(targetDir, ".mercato", "starter-preset.json"),
431
+ JSON.stringify({ preset: presetId, generatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
432
+ );
433
+ }
434
+
311
435
  // src/setup/wizard.ts
312
436
  import { basename } from "node:path";
313
437
 
314
438
  // src/setup/tools/shared.ts
315
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2, copyFileSync, readdirSync as readdirSync2 } from "node:fs";
316
- import { join as join2, dirname } from "node:path";
317
- import { fileURLToPath } from "node:url";
318
- var __dirname = dirname(fileURLToPath(import.meta.url));
319
- var AGENTIC_DIR = join2(__dirname, "agentic", "shared");
320
- var GUIDES_DIR = join2(__dirname, "agentic", "guides");
439
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3, copyFileSync, readdirSync as readdirSync2 } from "node:fs";
440
+ import { join as join3, dirname as dirname2 } from "node:path";
441
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
442
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
443
+ var AGENTIC_DIR = join3(__dirname2, "agentic", "shared");
444
+ var GUIDES_DIR = join3(__dirname2, "agentic", "guides");
321
445
  function resolvePlaceholders(content, config) {
322
446
  return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
323
447
  }
324
448
  function ensureDir(filePath) {
325
- const dir = dirname(filePath);
449
+ const dir = dirname2(filePath);
326
450
  if (!existsSync2(dir)) {
327
451
  mkdirSync2(dir, { recursive: true });
328
452
  }
329
453
  }
330
454
  function writeTemplate(srcRelative, destPath, config) {
331
- const srcPath = join2(AGENTIC_DIR, srcRelative);
332
- const content = readFileSync(srcPath, "utf-8");
455
+ const srcPath = join3(AGENTIC_DIR, srcRelative);
456
+ const content = readFileSync2(srcPath, "utf-8");
333
457
  ensureDir(destPath);
334
- writeFileSync2(destPath, resolvePlaceholders(content, config));
458
+ writeFileSync3(destPath, resolvePlaceholders(content, config));
335
459
  }
336
460
  function copyFile(srcRelative, destPath) {
337
- const srcPath = join2(AGENTIC_DIR, srcRelative);
461
+ const srcPath = join3(AGENTIC_DIR, srcRelative);
338
462
  ensureDir(destPath);
339
463
  copyFileSync(srcPath, destPath);
340
464
  }
341
465
  function generateShared(config) {
342
466
  const { targetDir } = config;
343
- writeTemplate("AGENTS.md.template", join2(targetDir, "AGENTS.md"), config);
344
- writeTemplate("ai/specs/README.md", join2(targetDir, ".ai", "specs", "README.md"), config);
345
- copyFile("ai/specs/SPEC-000-template.md", join2(targetDir, ".ai", "specs", "SPEC-000-template.md"));
346
- copyFile("ai/lessons.md", join2(targetDir, ".ai", "lessons.md"));
467
+ writeTemplate("AGENTS.md.template", join3(targetDir, "AGENTS.md"), config);
468
+ writeTemplate("ai/specs/README.md", join3(targetDir, ".ai", "specs", "README.md"), config);
469
+ copyFile("ai/specs/SPEC-000-template.md", join3(targetDir, ".ai", "specs", "SPEC-000-template.md"));
470
+ copyFile("ai/lessons.md", join3(targetDir, ".ai", "lessons.md"));
347
471
  writeTemplate(
348
472
  "ai/skills/spec-writing/SKILL.md",
349
- join2(targetDir, ".ai", "skills", "spec-writing", "SKILL.md"),
473
+ join3(targetDir, ".ai", "skills", "spec-writing", "SKILL.md"),
350
474
  config
351
475
  );
352
476
  copyFile(
353
477
  "ai/skills/spec-writing/references/spec-template.md",
354
- join2(targetDir, ".ai", "skills", "spec-writing", "references", "spec-template.md")
478
+ join3(targetDir, ".ai", "skills", "spec-writing", "references", "spec-template.md")
355
479
  );
356
480
  copyFile(
357
481
  "ai/skills/spec-writing/references/spec-checklist.md",
358
- join2(targetDir, ".ai", "skills", "spec-writing", "references", "spec-checklist.md")
482
+ join3(targetDir, ".ai", "skills", "spec-writing", "references", "spec-checklist.md")
359
483
  );
360
484
  copyFile(
361
485
  "ai/skills/backend-ui-design/SKILL.md",
362
- join2(targetDir, ".ai", "skills", "backend-ui-design", "SKILL.md")
486
+ join3(targetDir, ".ai", "skills", "backend-ui-design", "SKILL.md")
363
487
  );
364
488
  copyFile(
365
489
  "ai/skills/backend-ui-design/references/ui-components.md",
366
- join2(targetDir, ".ai", "skills", "backend-ui-design", "references", "ui-components.md")
490
+ join3(targetDir, ".ai", "skills", "backend-ui-design", "references", "ui-components.md")
367
491
  );
368
492
  copyFile(
369
493
  "ai/skills/code-review/SKILL.md",
370
- join2(targetDir, ".ai", "skills", "code-review", "SKILL.md")
494
+ join3(targetDir, ".ai", "skills", "code-review", "SKILL.md")
371
495
  );
372
496
  copyFile(
373
497
  "ai/skills/code-review/references/review-checklist.md",
374
- join2(targetDir, ".ai", "skills", "code-review", "references", "review-checklist.md")
498
+ join3(targetDir, ".ai", "skills", "code-review", "references", "review-checklist.md")
375
499
  );
376
500
  copyFile(
377
501
  "ai/skills/integration-builder/SKILL.md",
378
- join2(targetDir, ".ai", "skills", "integration-builder", "SKILL.md")
502
+ join3(targetDir, ".ai", "skills", "integration-builder", "SKILL.md")
379
503
  );
380
504
  copyFile(
381
505
  "ai/skills/integration-builder/references/adapter-contracts.md",
382
- join2(targetDir, ".ai", "skills", "integration-builder", "references", "adapter-contracts.md")
506
+ join3(targetDir, ".ai", "skills", "integration-builder", "references", "adapter-contracts.md")
383
507
  );
384
508
  copyFile(
385
509
  "ai/skills/system-extension/SKILL.md",
386
- join2(targetDir, ".ai", "skills", "system-extension", "SKILL.md")
510
+ join3(targetDir, ".ai", "skills", "system-extension", "SKILL.md")
387
511
  );
388
512
  copyFile(
389
513
  "ai/skills/system-extension/references/extension-contracts.md",
390
- join2(targetDir, ".ai", "skills", "system-extension", "references", "extension-contracts.md")
514
+ join3(targetDir, ".ai", "skills", "system-extension", "references", "extension-contracts.md")
391
515
  );
392
516
  copyFile(
393
517
  "ai/skills/module-scaffold/SKILL.md",
394
- join2(targetDir, ".ai", "skills", "module-scaffold", "SKILL.md")
518
+ join3(targetDir, ".ai", "skills", "module-scaffold", "SKILL.md")
395
519
  );
396
520
  copyFile(
397
521
  "ai/skills/module-scaffold/references/naming-conventions.md",
398
- join2(targetDir, ".ai", "skills", "module-scaffold", "references", "naming-conventions.md")
522
+ join3(targetDir, ".ai", "skills", "module-scaffold", "references", "naming-conventions.md")
399
523
  );
400
524
  copyFile(
401
525
  "ai/skills/module-scaffold/references/navigation-patterns.md",
402
- join2(targetDir, ".ai", "skills", "module-scaffold", "references", "navigation-patterns.md")
526
+ join3(targetDir, ".ai", "skills", "module-scaffold", "references", "navigation-patterns.md")
403
527
  );
404
528
  copyFile(
405
529
  "ai/skills/troubleshooter/SKILL.md",
406
- join2(targetDir, ".ai", "skills", "troubleshooter", "SKILL.md")
530
+ join3(targetDir, ".ai", "skills", "troubleshooter", "SKILL.md")
407
531
  );
408
532
  copyFile(
409
533
  "ai/skills/troubleshooter/references/diagnostic-commands.md",
410
- join2(targetDir, ".ai", "skills", "troubleshooter", "references", "diagnostic-commands.md")
534
+ join3(targetDir, ".ai", "skills", "troubleshooter", "references", "diagnostic-commands.md")
411
535
  );
412
536
  copyFile(
413
537
  "ai/skills/eject-and-customize/SKILL.md",
414
- join2(targetDir, ".ai", "skills", "eject-and-customize", "SKILL.md")
538
+ join3(targetDir, ".ai", "skills", "eject-and-customize", "SKILL.md")
415
539
  );
416
540
  copyFile(
417
541
  "ai/skills/data-model-design/SKILL.md",
418
- join2(targetDir, ".ai", "skills", "data-model-design", "SKILL.md")
542
+ join3(targetDir, ".ai", "skills", "data-model-design", "SKILL.md")
419
543
  );
420
544
  copyFile(
421
545
  "ai/skills/data-model-design/references/mikro-orm-cheatsheet.md",
422
- join2(targetDir, ".ai", "skills", "data-model-design", "references", "mikro-orm-cheatsheet.md")
546
+ join3(targetDir, ".ai", "skills", "data-model-design", "references", "mikro-orm-cheatsheet.md")
423
547
  );
424
548
  copyFile(
425
549
  "ai/skills/implement-spec/SKILL.md",
426
- join2(targetDir, ".ai", "skills", "implement-spec", "SKILL.md")
550
+ join3(targetDir, ".ai", "skills", "implement-spec", "SKILL.md")
427
551
  );
428
552
  copyFile(
429
553
  "ai/skills/integration-tests/SKILL.md",
430
- join2(targetDir, ".ai", "skills", "integration-tests", "SKILL.md")
554
+ join3(targetDir, ".ai", "skills", "integration-tests", "SKILL.md")
431
555
  );
432
556
  copyFile(
433
557
  "ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md",
434
- join2(targetDir, ".ai", "skills", "auto-upgrade-0.4.10-to-0.5.0", "SKILL.md")
558
+ join3(targetDir, ".ai", "skills", "auto-upgrade-0.4.10-to-0.5.0", "SKILL.md")
435
559
  );
436
- copyFile("ai/qa/tests/playwright.config.ts", join2(targetDir, ".ai", "qa", "tests", "playwright.config.ts"));
560
+ copyFile("ai/qa/tests/playwright.config.ts", join3(targetDir, ".ai", "qa", "tests", "playwright.config.ts"));
437
561
  if (existsSync2(GUIDES_DIR)) {
438
- const guidesDestDir = join2(targetDir, ".ai", "guides");
562
+ const guidesDestDir = join3(targetDir, ".ai", "guides");
439
563
  for (const file of readdirSync2(GUIDES_DIR)) {
440
564
  if (file.endsWith(".md")) {
441
- const srcPath = join2(GUIDES_DIR, file);
442
- const destPath = join2(guidesDestDir, file);
565
+ const srcPath = join3(GUIDES_DIR, file);
566
+ const destPath = join3(guidesDestDir, file);
443
567
  ensureDir(destPath);
444
568
  copyFileSync(srcPath, destPath);
445
569
  }
@@ -448,38 +572,38 @@ function generateShared(config) {
448
572
  }
449
573
 
450
574
  // src/setup/tools/claude-code.ts
451
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, copyFileSync as copyFileSync2, symlinkSync, lstatSync, unlinkSync } from "node:fs";
452
- import { join as join3, dirname as dirname2 } from "node:path";
453
- import { fileURLToPath as fileURLToPath2 } from "node:url";
454
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
455
- var AGENTIC_DIR2 = join3(__dirname2, "agentic", "claude-code");
575
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4, copyFileSync as copyFileSync2, symlinkSync, lstatSync, unlinkSync } from "node:fs";
576
+ import { join as join4, dirname as dirname3 } from "node:path";
577
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
578
+ var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
579
+ var AGENTIC_DIR2 = join4(__dirname3, "agentic", "claude-code");
456
580
  function resolvePlaceholders2(content, config) {
457
581
  return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
458
582
  }
459
583
  function ensureDir2(filePath) {
460
- const dir = dirname2(filePath);
584
+ const dir = dirname3(filePath);
461
585
  if (!existsSync3(dir)) {
462
586
  mkdirSync3(dir, { recursive: true });
463
587
  }
464
588
  }
465
589
  function writeTemplate2(srcRelative, destPath, config) {
466
- const srcPath = join3(AGENTIC_DIR2, srcRelative);
467
- const content = readFileSync2(srcPath, "utf-8");
590
+ const srcPath = join4(AGENTIC_DIR2, srcRelative);
591
+ const content = readFileSync3(srcPath, "utf-8");
468
592
  ensureDir2(destPath);
469
- writeFileSync3(destPath, resolvePlaceholders2(content, config));
593
+ writeFileSync4(destPath, resolvePlaceholders2(content, config));
470
594
  }
471
595
  function copyFile2(srcRelative, destPath) {
472
- const srcPath = join3(AGENTIC_DIR2, srcRelative);
596
+ const srcPath = join4(AGENTIC_DIR2, srcRelative);
473
597
  ensureDir2(destPath);
474
598
  copyFileSync2(srcPath, destPath);
475
599
  }
476
600
  function generateClaudeCode(config) {
477
601
  const { targetDir } = config;
478
- writeTemplate2("CLAUDE.md.template", join3(targetDir, "CLAUDE.md"), config);
479
- copyFile2("settings.json", join3(targetDir, ".claude", "settings.json"));
480
- copyFile2("hooks/entity-migration-check.ts", join3(targetDir, ".claude", "hooks", "entity-migration-check.ts"));
481
- copyFile2("mcp.json.example", join3(targetDir, ".mcp.json.example"));
482
- ensureSkillsLink(join3(targetDir, ".claude", "skills"), join3("..", ".ai", "skills"));
602
+ writeTemplate2("CLAUDE.md.template", join4(targetDir, "CLAUDE.md"), config);
603
+ copyFile2("settings.json", join4(targetDir, ".claude", "settings.json"));
604
+ copyFile2("hooks/entity-migration-check.ts", join4(targetDir, ".claude", "hooks", "entity-migration-check.ts"));
605
+ copyFile2("mcp.json.example", join4(targetDir, ".mcp.json.example"));
606
+ ensureSkillsLink(join4(targetDir, ".claude", "skills"), join4("..", ".ai", "skills"));
483
607
  }
484
608
  function ensureSkillsLink(linkPath, target) {
485
609
  ensureDir2(linkPath);
@@ -493,31 +617,31 @@ function ensureSkillsLink(linkPath, target) {
493
617
  }
494
618
 
495
619
  // src/setup/tools/codex.ts
496
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4, copyFileSync as copyFileSync3, symlinkSync as symlinkSync2, lstatSync as lstatSync2, unlinkSync as unlinkSync2 } from "node:fs";
497
- import { join as join4, dirname as dirname3 } from "node:path";
498
- import { fileURLToPath as fileURLToPath3 } from "node:url";
499
- var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
500
- var AGENTIC_DIR3 = join4(__dirname3, "agentic", "codex");
620
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5, copyFileSync as copyFileSync3, symlinkSync as symlinkSync2, lstatSync as lstatSync2, unlinkSync as unlinkSync2 } from "node:fs";
621
+ import { join as join5, dirname as dirname4 } from "node:path";
622
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
623
+ var __dirname4 = dirname4(fileURLToPath4(import.meta.url));
624
+ var AGENTIC_DIR3 = join5(__dirname4, "agentic", "codex");
501
625
  var MARKER_START = "<!-- CODEX_ENFORCEMENT_RULES_START -->";
502
626
  var MARKER_END = "<!-- CODEX_ENFORCEMENT_RULES_END -->";
503
627
  function ensureDir3(filePath) {
504
- const dir = dirname3(filePath);
628
+ const dir = dirname4(filePath);
505
629
  if (!existsSync4(dir)) {
506
630
  mkdirSync4(dir, { recursive: true });
507
631
  }
508
632
  }
509
633
  function copyFile3(srcRelative, destPath) {
510
- const srcPath = join4(AGENTIC_DIR3, srcRelative);
634
+ const srcPath = join5(AGENTIC_DIR3, srcRelative);
511
635
  ensureDir3(destPath);
512
636
  copyFileSync3(srcPath, destPath);
513
637
  }
514
638
  function generateCodex(config) {
515
639
  const { targetDir } = config;
516
- const agentsPath = join4(targetDir, "AGENTS.md");
517
- const rulesPath = join4(AGENTIC_DIR3, "enforcement-rules.md");
640
+ const agentsPath = join5(targetDir, "AGENTS.md");
641
+ const rulesPath = join5(AGENTIC_DIR3, "enforcement-rules.md");
518
642
  if (existsSync4(agentsPath)) {
519
- let agents = readFileSync3(agentsPath, "utf-8");
520
- const rules = readFileSync3(rulesPath, "utf-8");
643
+ let agents = readFileSync4(agentsPath, "utf-8");
644
+ const rules = readFileSync4(rulesPath, "utf-8");
521
645
  if (agents.includes(MARKER_START)) {
522
646
  const startIdx = agents.indexOf(MARKER_START);
523
647
  const endIdx = agents.indexOf(MARKER_END);
@@ -532,10 +656,10 @@ function generateCodex(config) {
532
656
  agents = agents + "\n\n" + rules;
533
657
  }
534
658
  }
535
- writeFileSync4(agentsPath, agents);
659
+ writeFileSync5(agentsPath, agents);
536
660
  }
537
- copyFile3("mcp.json.example", join4(targetDir, ".codex", "mcp.json.example"));
538
- ensureSkillsLink2(join4(targetDir, ".codex", "skills"), join4("..", ".ai", "skills"));
661
+ copyFile3("mcp.json.example", join5(targetDir, ".codex", "mcp.json.example"));
662
+ ensureSkillsLink2(join5(targetDir, ".codex", "skills"), join5("..", ".ai", "skills"));
539
663
  }
540
664
  function ensureSkillsLink2(linkPath, target) {
541
665
  ensureDir3(linkPath);
@@ -549,40 +673,40 @@ function ensureSkillsLink2(linkPath, target) {
549
673
  }
550
674
 
551
675
  // src/setup/tools/cursor.ts
552
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5, copyFileSync as copyFileSync4, symlinkSync as symlinkSync3, lstatSync as lstatSync3, unlinkSync as unlinkSync3 } from "node:fs";
553
- import { join as join5, dirname as dirname4 } from "node:path";
554
- import { fileURLToPath as fileURLToPath4 } from "node:url";
555
- var __dirname4 = dirname4(fileURLToPath4(import.meta.url));
556
- var AGENTIC_DIR4 = join5(__dirname4, "agentic", "cursor");
676
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6, copyFileSync as copyFileSync4, symlinkSync as symlinkSync3, lstatSync as lstatSync3, unlinkSync as unlinkSync3 } from "node:fs";
677
+ import { join as join6, dirname as dirname5 } from "node:path";
678
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
679
+ var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
680
+ var AGENTIC_DIR4 = join6(__dirname5, "agentic", "cursor");
557
681
  function resolvePlaceholders3(content, config) {
558
682
  return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
559
683
  }
560
684
  function ensureDir4(filePath) {
561
- const dir = dirname4(filePath);
685
+ const dir = dirname5(filePath);
562
686
  if (!existsSync5(dir)) {
563
687
  mkdirSync5(dir, { recursive: true });
564
688
  }
565
689
  }
566
690
  function writeTemplate3(srcRelative, destPath, config) {
567
- const srcPath = join5(AGENTIC_DIR4, srcRelative);
568
- const content = readFileSync4(srcPath, "utf-8");
691
+ const srcPath = join6(AGENTIC_DIR4, srcRelative);
692
+ const content = readFileSync5(srcPath, "utf-8");
569
693
  ensureDir4(destPath);
570
- writeFileSync5(destPath, resolvePlaceholders3(content, config));
694
+ writeFileSync6(destPath, resolvePlaceholders3(content, config));
571
695
  }
572
696
  function copyFile4(srcRelative, destPath) {
573
- const srcPath = join5(AGENTIC_DIR4, srcRelative);
697
+ const srcPath = join6(AGENTIC_DIR4, srcRelative);
574
698
  ensureDir4(destPath);
575
699
  copyFileSync4(srcPath, destPath);
576
700
  }
577
701
  function generateCursor(config) {
578
702
  const { targetDir } = config;
579
- writeTemplate3("rules/open-mercato.mdc", join5(targetDir, ".cursor", "rules", "open-mercato.mdc"), config);
580
- copyFile4("rules/entity-guard.mdc", join5(targetDir, ".cursor", "rules", "entity-guard.mdc"));
581
- copyFile4("rules/generated-guard.mdc", join5(targetDir, ".cursor", "rules", "generated-guard.mdc"));
582
- copyFile4("hooks.json", join5(targetDir, ".cursor", "hooks.json"));
583
- copyFile4("hooks/entity-migration-check.mjs", join5(targetDir, ".cursor", "hooks", "entity-migration-check.mjs"));
584
- copyFile4("mcp.json.example", join5(targetDir, ".cursor", "mcp.json.example"));
585
- ensureSkillsLink3(join5(targetDir, ".cursor", "skills"), join5("..", ".ai", "skills"));
703
+ writeTemplate3("rules/open-mercato.mdc", join6(targetDir, ".cursor", "rules", "open-mercato.mdc"), config);
704
+ copyFile4("rules/entity-guard.mdc", join6(targetDir, ".cursor", "rules", "entity-guard.mdc"));
705
+ copyFile4("rules/generated-guard.mdc", join6(targetDir, ".cursor", "rules", "generated-guard.mdc"));
706
+ copyFile4("hooks.json", join6(targetDir, ".cursor", "hooks.json"));
707
+ copyFile4("hooks/entity-migration-check.mjs", join6(targetDir, ".cursor", "hooks", "entity-migration-check.mjs"));
708
+ copyFile4("mcp.json.example", join6(targetDir, ".cursor", "mcp.json.example"));
709
+ ensureSkillsLink3(join6(targetDir, ".cursor", "skills"), join6("..", ".ai", "skills"));
586
710
  }
587
711
  function ensureSkillsLink3(linkPath, target) {
588
712
  ensureDir4(linkPath);
@@ -675,10 +799,10 @@ function printSummary(selectedIds) {
675
799
  }
676
800
 
677
801
  // src/index.ts
678
- var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
679
- var packageJson = JSON.parse(readFileSync5(join6(__dirname5, "..", "package.json"), "utf-8"));
802
+ var __dirname6 = dirname6(fileURLToPath6(import.meta.url));
803
+ var packageJson = JSON.parse(readFileSync6(join7(__dirname6, "..", "package.json"), "utf-8"));
680
804
  var PACKAGE_VERSION = packageJson.version;
681
- var TEMPLATE_DIR = join6(__dirname5, "..", "template");
805
+ var TEMPLATE_DIR = join7(__dirname6, "..", "template");
682
806
  function showHelp() {
683
807
  console.log(`
684
808
  ${pc.bold("create-mercato-app")} - Create a new Open Mercato application
@@ -692,6 +816,7 @@ ${pc.bold("Arguments:")}
692
816
  ${pc.bold("Options:")}
693
817
  --app <name> Bootstrap an official ready app from open-mercato/ready-app-<name>
694
818
  --app-url <url> Bootstrap a ready app from a GitHub repository URL
819
+ --preset <id> Starter preset: classic (default), empty, or crm
695
820
  --skip-agentic-setup Skip the interactive agentic setup wizard
696
821
  --registry <url> Custom npm registry URL
697
822
  --verdaccio Use local Verdaccio registry (http://localhost:4873)
@@ -700,6 +825,8 @@ ${pc.bold("Options:")}
700
825
 
701
826
  ${pc.bold("Examples:")}
702
827
  npx create-mercato-app my-store
828
+ npx create-mercato-app my-store --preset empty
829
+ npx create-mercato-app my-store --preset crm
703
830
  npx create-mercato-app my-prm --app prm
704
831
  npx create-mercato-app my-marketplace --app-url https://github.com/some-agency/ready-app-marketplace
705
832
  npx create-mercato-app my-store --verdaccio
@@ -720,6 +847,7 @@ function parseArgs(args) {
720
847
  const options = {
721
848
  app: void 0,
722
849
  appUrl: void 0,
850
+ preset: void 0,
723
851
  registry: void 0,
724
852
  skipAgenticSetup: false,
725
853
  verdaccio: false,
@@ -746,6 +874,9 @@ function parseArgs(args) {
746
874
  } else if (arg === "--app-url") {
747
875
  options.appUrl = requireOptionValue(args, index, arg);
748
876
  index += 1;
877
+ } else if (arg === "--preset") {
878
+ options.preset = requireOptionValue(args, index, arg);
879
+ index += 1;
749
880
  } else if (!arg.startsWith("-")) {
750
881
  appName = arg;
751
882
  }
@@ -785,32 +916,32 @@ function copyDirRecursive(src, dest, placeholders) {
785
916
  }
786
917
  const entries = readdirSync3(src);
787
918
  for (const entry of entries) {
788
- const srcPath = join6(src, entry);
919
+ const srcPath = join7(src, entry);
789
920
  const destName = FILE_RENAMES[entry] ?? entry;
790
- let destPath = join6(dest, destName);
921
+ let destPath = join7(dest, destName);
791
922
  const stat = statSync2(srcPath);
792
923
  if (stat.isDirectory()) {
793
924
  if (SKIP_DIRS.has(entry)) continue;
794
925
  copyDirRecursive(srcPath, destPath, placeholders);
795
926
  } else if (entry.endsWith(".template")) {
796
927
  const finalName = entry.replace(".template", "");
797
- destPath = join6(dest, finalName);
798
- let content = readFileSync5(srcPath, "utf-8");
928
+ destPath = join7(dest, finalName);
929
+ let content = readFileSync6(srcPath, "utf-8");
799
930
  for (const [key, value] of Object.entries(placeholders)) {
800
931
  content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
801
932
  }
802
- writeFileSync6(destPath, content);
933
+ writeFileSync7(destPath, content);
803
934
  } else {
804
935
  copyFileSync5(srcPath, destPath);
805
936
  }
806
937
  }
807
938
  }
808
939
  function ensureGeneratedCssPlaceholder(targetDir) {
809
- const generatedDir = join6(targetDir, ".mercato", "generated");
810
- const placeholderPath = join6(generatedDir, "module-package-sources.css");
940
+ const generatedDir = join7(targetDir, ".mercato", "generated");
941
+ const placeholderPath = join7(generatedDir, "module-package-sources.css");
811
942
  mkdirSync6(generatedDir, { recursive: true });
812
943
  if (!existsSync6(placeholderPath)) {
813
- writeFileSync6(placeholderPath, "");
944
+ writeFileSync7(placeholderPath, "");
814
945
  }
815
946
  }
816
947
  async function scaffoldTemplateApp(targetDir, placeholders) {
@@ -910,6 +1041,15 @@ async function main(argv = process.argv.slice(2)) {
910
1041
  throw new Error(`Directory "${appName}" already exists`);
911
1042
  }
912
1043
  const readyAppSource = resolveReadyAppSource(options, PACKAGE_VERSION);
1044
+ const presetId = options.preset ?? DEFAULT_PRESET_ID;
1045
+ if (!VALID_PRESET_IDS.includes(presetId)) {
1046
+ throw new Error(`Unknown preset "${presetId}". Valid presets: ${VALID_PRESET_IDS.join(", ")}`);
1047
+ }
1048
+ if (presetId !== DEFAULT_PRESET_ID && readyAppSource) {
1049
+ throw new Error(
1050
+ `--preset ${presetId} cannot be combined with --app or --app-url. Presets apply only to the built-in template.`
1051
+ );
1052
+ }
913
1053
  const registryConfig = options.verdaccio ? buildRegistryConfig("http://localhost:4873") : options.registry ? buildRegistryConfig(options.registry) : "";
914
1054
  console.log("");
915
1055
  console.log(pc.bold(`Creating a new Open Mercato app in ${pc.cyan(targetDir)}`));
@@ -925,6 +1065,7 @@ async function main(argv = process.argv.slice(2)) {
925
1065
  await scaffoldImportedReadyApp(targetDir, readyAppSource);
926
1066
  } else {
927
1067
  await scaffoldTemplateApp(targetDir, placeholders);
1068
+ applyStarterPreset(presetId, targetDir);
928
1069
  }
929
1070
  console.log(pc.green("Success!") + ` Created ${pc.bold(appName)}`);
930
1071
  console.log("");
@@ -937,7 +1078,7 @@ async function main(argv = process.argv.slice(2)) {
937
1078
  console.log(pc.dim("For more information, visit https://github.com/open-mercato/open-mercato"));
938
1079
  console.log("");
939
1080
  }
940
- var isEntrypoint = process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath5(import.meta.url);
1081
+ var isEntrypoint = process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath6(import.meta.url);
941
1082
  if (isEntrypoint) {
942
1083
  main().catch((error) => {
943
1084
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,29 @@
1
+ // Central place to enable modules and their source.
2
+ // - id: module id (plural snake_case; special cases: 'auth')
3
+ // - from: '@open-mercato/core' | '@app' | custom alias/path in future
4
+ import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
5
+
6
+ export type ModuleEntry = { id: string; from?: '@open-mercato/core' | '@app' | string }
7
+
8
+ export const enabledModules: ModuleEntry[] = [
9
+ {{MODULES}}
10
+ ]
11
+
12
+ const enterpriseModulesEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES, false)
13
+ const enterpriseSsoEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO, false)
14
+ const enterpriseSecurityEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY, false)
15
+
16
+ if (enterpriseModulesEnabled) {
17
+ enabledModules.push(
18
+ { id: 'record_locks', from: '@open-mercato/enterprise' },
19
+ { id: 'system_status_overlays', from: '@open-mercato/enterprise' },
20
+ )
21
+ }
22
+
23
+ if (enterpriseModulesEnabled && enterpriseSsoEnabled) {
24
+ enabledModules.push({ id: 'sso', from: '@open-mercato/enterprise' })
25
+ }
26
+
27
+ if (enterpriseModulesEnabled && enterpriseSecurityEnabled) {
28
+ enabledModules.push({ id: 'security', from: '@open-mercato/enterprise' })
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.5.1-develop.2691.d8a0934b37",
3
+ "version": "0.5.1-develop.2699.f8b50c8046",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,8 @@ import Link from 'next/link'
11
11
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
12
12
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
13
13
  import type { EntityManager } from '@mikro-orm/postgresql'
14
+ import { User } from '@open-mercato/core/modules/auth/data/entities'
15
+ import { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'
14
16
 
15
17
  function FeatureBadge({ label }: { label: string }) {
16
18
  return (
@@ -60,9 +62,9 @@ export default async function Home() {
60
62
  try {
61
63
  const container = await createRequestContainer()
62
64
  const em = container.resolve<EntityManager>('em')
63
- usersCount = await em.count('User', {})
64
- tenantsCount = await em.count('Tenant', {})
65
- orgsCount = await em.count('Organization', {})
65
+ usersCount = await em.count(User, {})
66
+ tenantsCount = await em.count(Tenant, {})
67
+ orgsCount = await em.count(Organization, {})
66
68
  dbStatus = t('app.page.dbStatus.connected', 'Connected')
67
69
  } catch (error: unknown) {
68
70
  const message = error instanceof Error ? error.message : t('app.page.dbStatus.noConnection', 'no connection')
@@ -191,25 +191,32 @@ export const { metadata, GET, POST, PUT, DELETE } = makeCrudRoute({
191
191
  .map((d: any) => d.key)
192
192
  // Fallback discovery: keys that have values even if no definition exists
193
193
  try {
194
- const knex = (em as any).getConnection().getKnex()
195
- const rows = await knex('custom_field_values')
196
- .distinct('field_key')
197
- .where({ entity_id: ENTITY_ID as any })
198
- .modify((qb: any) => {
199
- if (scopedOrgIds === null) {
200
- // no organization restriction
201
- } else if (scopedOrgIds.length > 0) {
202
- qb.andWhere((b: any) => {
203
- b.whereIn('organization_id', scopedOrgIds as any)
204
- b.orWhereNull('organization_id')
205
- })
206
- } else {
207
- qb.whereNull('organization_id')
208
- }
209
- if (ctx.auth!.tenantId != null) qb.andWhere((b: any) => b.where({ tenant_id: ctx.auth!.tenantId }).orWhereNull('tenant_id'))
210
- else qb.whereNull('tenant_id')
211
- })
212
- .whereNull('deleted_at')
194
+ const db = (em as any).getKysely()
195
+ let cfvQuery = db
196
+ .selectFrom('custom_field_values')
197
+ .select('field_key')
198
+ .distinct()
199
+ .where('entity_id', '=', ENTITY_ID as any)
200
+ .where('deleted_at', 'is', null)
201
+ if (scopedOrgIds === null) {
202
+ // no organization restriction
203
+ } else if (scopedOrgIds.length > 0) {
204
+ cfvQuery = cfvQuery.where((eb: any) => eb.or([
205
+ eb('organization_id', 'in', scopedOrgIds),
206
+ eb('organization_id', 'is', null),
207
+ ]))
208
+ } else {
209
+ cfvQuery = cfvQuery.where('organization_id', 'is', null)
210
+ }
211
+ if (ctx.auth!.tenantId != null) {
212
+ cfvQuery = cfvQuery.where((eb: any) => eb.or([
213
+ eb('tenant_id', '=', ctx.auth!.tenantId),
214
+ eb('tenant_id', 'is', null),
215
+ ]))
216
+ } else {
217
+ cfvQuery = cfvQuery.where('tenant_id', 'is', null)
218
+ }
219
+ const rows = await cfvQuery.execute()
213
220
  const keysFromValues = (rows || []).map((r: any) => String(r.field_key))
214
221
  // Merge with code-declared keys and de-dupe
215
222
  dynamicCfKeys = Array.from(new Set([ ...cfSel.keys, ...keysFromDefs, ...keysFromValues ]))
@@ -407,7 +407,7 @@ const deleteTodoCommand: CommandHandler<{ body?: Record<string, unknown>; query?
407
407
  restored.isDone = before.is_done
408
408
  restored.tenantId = before.tenantId ?? scope.tenantId
409
409
  restored.organizationId = before.organizationId ?? scope.organizationId
410
- await em.persistAndFlush(restored)
410
+ await em.persist(restored).flush()
411
411
  } else {
412
412
  restored = await de.createOrmEntity({
413
413
  entity: Todo,
@@ -1,4 +1,4 @@
1
- import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
1
+ import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
2
2
 
3
3
  @Entity({ tableName: 'example_items' })
4
4
  export class ExampleItem {
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { type Kysely } from 'kysely'
4
5
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
6
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
7
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -82,9 +83,10 @@ export async function GET(request: Request) {
82
83
  : []
83
84
 
84
85
  const em = (container.resolve('em') as EntityManager).fork()
85
- const knex = em.getKnex()
86
- const rowsQuery = knex('example_customer_interaction_mappings')
87
- .select<MappingRow[]>([
86
+ const db = (em as any).getKysely() as Kysely<any>
87
+ let rowsQuery = db
88
+ .selectFrom('example_customer_interaction_mappings')
89
+ .select([
88
90
  'id',
89
91
  'interaction_id',
90
92
  'todo_id',
@@ -97,29 +99,32 @@ export async function GET(request: Request) {
97
99
  'organization_id',
98
100
  'tenant_id',
99
101
  ])
100
- .where('tenant_id', auth.tenantId)
102
+ .where('tenant_id', '=', auth.tenantId)
101
103
  .orderBy('updated_at', 'desc')
102
104
  .orderBy('id', 'desc')
103
105
  .limit(query.limit + 1)
104
106
 
105
107
  if (organizationIds.length > 0) {
106
- rowsQuery.whereIn('organization_id', organizationIds)
108
+ rowsQuery = rowsQuery.where('organization_id', 'in', organizationIds)
107
109
  }
108
110
  if (query.interactionId) {
109
- rowsQuery.andWhere('interaction_id', query.interactionId)
111
+ rowsQuery = rowsQuery.where('interaction_id', '=', query.interactionId)
110
112
  }
111
113
  if (query.todoId) {
112
- rowsQuery.andWhere('todo_id', query.todoId)
114
+ rowsQuery = rowsQuery.where('todo_id', '=', query.todoId)
113
115
  }
114
116
  if (cursor) {
115
- rowsQuery.andWhere(function applyCursor() {
116
- this.where('updated_at', '<', new Date(cursor.updatedAt)).orWhere(function applyTieBreaker() {
117
- this.where('updated_at', new Date(cursor.updatedAt)).andWhere('id', '<', cursor.id)
118
- })
119
- })
117
+ const cursorDate = new Date(cursor.updatedAt)
118
+ rowsQuery = rowsQuery.where(eb => eb.or([
119
+ eb('updated_at', '<', cursorDate),
120
+ eb.and([
121
+ eb('updated_at', '=', cursorDate),
122
+ eb('id', '<', cursor.id),
123
+ ]),
124
+ ]))
120
125
  }
121
126
 
122
- const rows = await rowsQuery
127
+ const rows = (await rowsQuery.execute()) as MappingRow[]
123
128
  const pageRows = rows.slice(0, query.limit)
124
129
  const interactionIds = Array.from(new Set(pageRows.map((row) => row.interaction_id)))
125
130
  const interactions = interactionIds.length > 0
@@ -1,4 +1,4 @@
1
- import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/core'
1
+ import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
2
2
 
3
3
  @Entity({ tableName: 'example_customer_interaction_mappings' })
4
4
  @Unique({
@@ -247,6 +247,6 @@ export async function deleteExampleCustomerInteractionMapping(
247
247
  mapping: ExampleCustomerInteractionMapping | null | undefined,
248
248
  ): Promise<boolean> {
249
249
  if (!mapping) return false
250
- await em.removeAndFlush(mapping)
250
+ await em.remove(mapping).flush()
251
251
  return true
252
252
  }
@@ -1,4 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { type Kysely } from 'kysely'
2
3
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
3
4
  import { loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
4
5
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
@@ -720,34 +721,36 @@ async function loadLegacyExampleTodoLinks(
720
721
  cursor?: string,
721
722
  ): Promise<{ rows: LegacyExampleTodoLinkRow[]; nextCursor?: string }> {
722
723
  const em = (container.resolve('em') as EntityManager).fork()
723
- const knex = em.getKnex()
724
+ const db = (em as any).getKysely() as Kysely<any>
724
725
  const parsedCursor = decodeCursor(cursor)
725
- const query = knex('customer_todo_links')
726
- .select<LegacyExampleTodoLinkRow[]>([
726
+ let query = db
727
+ .selectFrom('customer_todo_links')
728
+ .select([
727
729
  'id',
728
730
  'entity_id as entityId',
729
731
  'todo_id as todoId',
730
732
  'created_by_user_id as createdByUserId',
731
733
  'created_at as createdAt',
732
734
  ])
733
- .where({
734
- tenant_id: scope.tenantId,
735
- organization_id: scope.organizationId,
736
- todo_source: 'example:todo',
737
- })
735
+ .where('tenant_id', '=', scope.tenantId)
736
+ .where('organization_id', '=', scope.organizationId)
737
+ .where('todo_source', '=', 'example:todo')
738
738
  .orderBy('created_at', 'asc')
739
739
  .orderBy('id', 'asc')
740
740
  .limit(limit + 1)
741
741
 
742
742
  if (parsedCursor) {
743
- query.andWhere(function applyCursor() {
744
- this.where('created_at', '>', new Date(parsedCursor.createdAt)).orWhere(function applyTieBreaker() {
745
- this.where('created_at', new Date(parsedCursor.createdAt)).andWhere('id', '>', parsedCursor.id)
746
- })
747
- })
743
+ const cursorDate = new Date(parsedCursor.createdAt)
744
+ query = query.where(eb => eb.or([
745
+ eb('created_at', '>', cursorDate),
746
+ eb.and([
747
+ eb('created_at', '=', cursorDate),
748
+ eb('id', '>', parsedCursor.id),
749
+ ]),
750
+ ]))
748
751
  }
749
752
 
750
- const rows = await query
753
+ const rows = (await query.execute()) as LegacyExampleTodoLinkRow[]
751
754
  const pageRows = rows.slice(0, limit)
752
755
  const next = rows.length > limit ? pageRows[pageRows.length - 1] : null
753
756
  return {