@iwo-szapar/data-mcp 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,16 @@
3
3
  *
4
4
  * Apply migrations — additive only, skips existing collections.
5
5
  * Creates all expected collections in the database.
6
+ *
7
+ * RC-3 fix (incident 2026-05-11): Replaced hardcoded project-relative
8
+ * "migrations/supabase/" string with runtime-resolved absolute path to
9
+ * the bundled SQL files inside this package.
6
10
  */
11
+ import { dirname, join } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
7
13
  import { makeToolResponse, makeErrorResponse } from '../shared.js';
14
+ const PACKAGE_MIGRATIONS_SUPABASE = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'migrations', 'supabase');
15
+ const PACKAGE_MIGRATIONS_POCKETBASE = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'migrations', 'pocketbase');
8
16
  /** Collection schemas for PocketBase creation */
9
17
  const COLLECTION_SCHEMAS = [
10
18
  { name: 'knowledge', description: 'Knowledge items (facts, patterns, insights, lessons, references)' },
@@ -23,10 +31,11 @@ const COLLECTION_SCHEMAS = [
23
31
  { name: 'affiliates', description: 'Affiliate partners and commissions' },
24
32
  ];
25
33
  export function registerSetupMigrate(server, adapter) {
26
- server.tool('setup_migrate', 'Create missing database collections/tables. Additive only — existing collections are skipped. For PocketBase, creates collections via API. For Supabase, reports SQL migrations to run manually.', {}, async () => {
34
+ server.tool('setup_migrate', 'Create missing database collections/tables. Additive only — existing collections are skipped. For PocketBase, creates collections via API. For Supabase, reports SQL migrations and recommends running setup_bootstrap for paste-ready SQL.', {}, async () => {
27
35
  try {
28
36
  const skipped = [];
29
37
  const needsMigration = [];
38
+ const migrationsPath = adapter.backend === 'supabase' ? PACKAGE_MIGRATIONS_SUPABASE : PACKAGE_MIGRATIONS_POCKETBASE;
30
39
  for (const schema of COLLECTION_SCHEMAS) {
31
40
  try {
32
41
  const exists = await adapter.collectionExists(schema.name);
@@ -34,18 +43,12 @@ export function registerSetupMigrate(server, adapter) {
34
43
  skipped.push(schema.name);
35
44
  continue;
36
45
  }
37
- if (adapter.backend === 'supabase') {
38
- needsMigration.push({
39
- name: schema.name,
40
- instruction: 'Run SQL migrations from migrations/supabase/ directory.',
41
- });
42
- }
43
- else {
44
- needsMigration.push({
45
- name: schema.name,
46
- instruction: 'Run PocketBase migrations from migrations/pocketbase/ directory.',
47
- });
48
- }
46
+ needsMigration.push({
47
+ name: schema.name,
48
+ instruction: adapter.backend === 'supabase'
49
+ ? `Apply SQL from ${migrationsPath}/. Easiest path: call setup_bootstrap for a paste-ready SQL block.`
50
+ : `Apply migration from ${migrationsPath}/.`,
51
+ });
49
52
  }
50
53
  catch (err) {
51
54
  const msg = err instanceof Error ? err.message : 'Unknown error';
@@ -55,6 +58,7 @@ export function registerSetupMigrate(server, adapter) {
55
58
  }
56
59
  return makeToolResponse({
57
60
  backend: adapter.backend,
61
+ migrations_path: migrationsPath,
58
62
  existing: skipped.length,
59
63
  needs_migration: needsMigration.length,
60
64
  details: {
@@ -62,8 +66,8 @@ export function registerSetupMigrate(server, adapter) {
62
66
  needs_migration: needsMigration.length > 0 ? needsMigration : undefined,
63
67
  },
64
68
  message: adapter.backend === 'pocketbase'
65
- ? `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need migration files. Run PocketBase migrations from migrations/pocketbase/ directory.`
66
- : `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need SQL migrations. Apply migrations from migrations/supabase/ directory.`,
69
+ ? `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need migration files. Apply from ${migrationsPath}/.`
70
+ : `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need SQL. Run setup_bootstrap for a paste-ready block, or apply from ${migrationsPath}/ manually.`,
67
71
  });
68
72
  }
69
73
  catch (error) {
@@ -72,4 +76,4 @@ export function registerSetupMigrate(server, adapter) {
72
76
  }
73
77
  });
74
78
  }
75
- //# sourceMappingURL=setup-migrate.js.map
79
+ //# sourceMappingURL=setup-migrate.js.map
@@ -2,6 +2,13 @@
2
2
  * Tool: setup_status
3
3
  *
4
4
  * Check backend connection, list existing/missing collections, report status.
5
+ *
6
+ * RC-1 fix (incident 2026-05-11): Previously called adapter.listCollections()
7
+ * which uses rpc('get_public_tables') — a custom Postgres function absent from
8
+ * fresh Supabase projects. Fallback to information_schema also failed because
9
+ * PostgREST only exposes the public schema. Now uses the same code path as
10
+ * setup_migrate (adapter.collectionExists per expected collection) so the
11
+ * diagnostic agrees with the data tools.
5
12
  */
6
13
  import { makeToolResponse, makeErrorResponse } from '../shared.js';
7
14
  const EXPECTED_COLLECTIONS = [
@@ -23,20 +30,18 @@ const EXPECTED_COLLECTIONS = [
23
30
  export function registerSetupStatus(server, adapter) {
24
31
  server.tool('setup_status', 'Check database connection status, list existing and missing collections, and report schema readiness.', {}, { readOnlyHint: true }, async () => {
25
32
  try {
26
- // Test connection by listing collections
27
- const existing = await adapter.listCollections();
28
- const existingSet = new Set(existing);
29
33
  const present = [];
30
34
  const missing = [];
31
35
  for (const collection of EXPECTED_COLLECTIONS) {
32
- if (existingSet.has(collection)) {
36
+ const exists = await adapter.collectionExists(collection);
37
+ if (exists) {
33
38
  present.push(collection);
34
39
  }
35
40
  else {
36
41
  missing.push(collection);
37
42
  }
38
43
  }
39
- // Check for settings table and schema version
44
+ const existingSet = new Set(present);
40
45
  let schemaVersion = null;
41
46
  if (existingSet.has('settings')) {
42
47
  try {
@@ -75,4 +80,4 @@ export function registerSetupStatus(server, adapter) {
75
80
  }
76
81
  });
77
82
  }
78
- //# sourceMappingURL=setup-status.js.map
83
+ //# sourceMappingURL=setup-status.js.map
@@ -2,26 +2,24 @@
2
2
  * PocketBase Migration 001: Core schema
3
3
  *
4
4
  * Creates knowledge, decisions, sessions collections.
5
- * PocketBase v0.23+ field format.
6
5
  */
7
6
 
8
7
  /// <reference path="../pb_data/types.d.ts" />
9
8
 
10
9
  migrate((app) => {
10
+ // knowledge collection
11
11
  const knowledge = new Collection({
12
12
  name: 'knowledge',
13
13
  type: 'base',
14
- fields: [
15
- { name: 'type', type: 'select', required: true, maxSelect: 1, values: ['fact', 'knowledge', 'pattern', 'insight', 'lesson', 'reference'] },
16
- { name: 'title', type: 'text', required: true, max: 500 },
17
- { name: 'content', type: 'editor', required: true, maxSize: 50000 },
18
- { name: 'summary', type: 'text', max: 2000 },
14
+ schema: [
15
+ { name: 'type', type: 'select', required: true, options: { values: ['fact', 'knowledge', 'pattern', 'insight', 'lesson', 'reference'] } },
16
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
17
+ { name: 'content', type: 'editor', required: true, options: { maxSize: 50000 } },
18
+ { name: 'summary', type: 'text', options: { maxSize: 2000 } },
19
19
  { name: 'tags', type: 'json' },
20
- { name: 'source', type: 'text', max: 500 },
21
- { name: 'confidence', type: 'number', min: 0, max: 1 },
20
+ { name: 'source', type: 'text', options: { maxSize: 500 } },
21
+ { name: 'confidence', type: 'number', options: { min: 0, max: 1 } },
22
22
  { name: 'last_validated_at', type: 'date' },
23
- { name: 'created', type: 'autodate', onCreate: true },
24
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
25
23
  ],
26
24
  indexes: [
27
25
  'CREATE INDEX idx_knowledge_type ON knowledge (type)',
@@ -30,47 +28,45 @@ migrate((app) => {
30
28
  });
31
29
  app.save(knowledge);
32
30
 
31
+ // decisions collection
33
32
  const decisions = new Collection({
34
33
  name: 'decisions',
35
34
  type: 'base',
36
- fields: [
37
- { name: 'title', type: 'text', required: true, max: 500 },
38
- { name: 'context', type: 'editor', maxSize: 5000 },
35
+ schema: [
36
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
37
+ { name: 'context', type: 'editor', options: { maxSize: 5000 } },
39
38
  { name: 'options_considered', type: 'json', required: true },
40
- { name: 'chosen_option', type: 'text', required: true, max: 500 },
41
- { name: 'rationale', type: 'editor', maxSize: 5000 },
42
- { name: 'outcome', type: 'editor', maxSize: 5000 },
39
+ { name: 'chosen_option', type: 'text', required: true, options: { maxSize: 500 } },
40
+ { name: 'rationale', type: 'editor', options: { maxSize: 5000 } },
41
+ { name: 'outcome', type: 'editor', options: { maxSize: 5000 } },
43
42
  { name: 'tags', type: 'json' },
44
- { name: 'created', type: 'autodate', onCreate: true },
45
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
46
43
  ],
47
44
  });
48
45
  app.save(decisions);
49
46
 
47
+ // sessions collection
50
48
  const sessions = new Collection({
51
49
  name: 'sessions',
52
50
  type: 'base',
53
- fields: [
54
- { name: 'title', type: 'text', required: true, max: 500 },
55
- { name: 'summary', type: 'editor', required: true, maxSize: 10000 },
51
+ schema: [
52
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
53
+ { name: 'summary', type: 'editor', required: true, options: { maxSize: 10000 } },
56
54
  { name: 'session_date', type: 'date' },
57
55
  { name: 'skills_used', type: 'json' },
58
56
  { name: 'files_changed', type: 'json' },
59
57
  { name: 'decisions_made', type: 'json' },
60
58
  { name: 'duration_minutes', type: 'number' },
61
- { name: 'task_id', type: 'text', max: 100 },
62
- { name: 'branch', type: 'text', max: 200 },
59
+ { name: 'task_id', type: 'text', options: { maxSize: 100 } },
60
+ { name: 'branch', type: 'text', options: { maxSize: 200 } },
63
61
  { name: 'patterns_learned', type: 'json' },
64
62
  { name: 'knowledge_created', type: 'number' },
65
63
  { name: 'knowledge_updated', type: 'number' },
66
64
  { name: 'metadata', type: 'json' },
67
- { name: 'created', type: 'autodate', onCreate: true },
68
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
69
65
  ],
70
66
  });
71
67
  app.save(sessions);
72
68
  }, (app) => {
73
- const c1 = app.findCollectionByNameOrId('sessions'); if (c1) app.delete(c1);
74
- const c2 = app.findCollectionByNameOrId('decisions'); if (c2) app.delete(c2);
75
- const c3 = app.findCollectionByNameOrId('knowledge'); if (c3) app.delete(c3);
69
+ app.delete(app.findCollectionByNameOrId('sessions'));
70
+ app.delete(app.findCollectionByNameOrId('decisions'));
71
+ app.delete(app.findCollectionByNameOrId('knowledge'));
76
72
  });
@@ -1,23 +1,21 @@
1
1
  /**
2
2
  * PocketBase Migration 002: Goals and tasks
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
7
6
 
8
7
  migrate((app) => {
8
+ // goals collection
9
9
  const goals = new Collection({
10
10
  name: 'goals',
11
11
  type: 'base',
12
- fields: [
13
- { name: 'title', type: 'text', required: true, max: 500 },
14
- { name: 'description', type: 'editor', maxSize: 5000 },
15
- { name: 'timeframe', type: 'select', required: true, maxSelect: 1, values: ['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] },
16
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['active', 'completed', 'paused', 'abandoned'] },
12
+ schema: [
13
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
14
+ { name: 'description', type: 'editor', options: { maxSize: 5000 } },
15
+ { name: 'timeframe', type: 'select', required: true, options: { values: ['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] } },
16
+ { name: 'status', type: 'select', required: true, options: { values: ['active', 'completed', 'paused', 'abandoned'] } },
17
17
  { name: 'key_results', type: 'json' },
18
18
  { name: 'tags', type: 'json' },
19
- { name: 'created', type: 'autodate', onCreate: true },
20
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
21
19
  ],
22
20
  indexes: [
23
21
  'CREATE INDEX idx_goals_status ON goals (status)',
@@ -26,19 +24,18 @@ migrate((app) => {
26
24
  });
27
25
  app.save(goals);
28
26
 
27
+ // tasks collection
29
28
  const tasks = new Collection({
30
29
  name: 'tasks',
31
30
  type: 'base',
32
- fields: [
33
- { name: 'title', type: 'text', required: true, max: 500 },
34
- { name: 'description', type: 'editor', maxSize: 5000 },
35
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['todo', 'in_progress', 'done', 'cancelled'] },
36
- { name: 'priority', type: 'select', required: true, maxSelect: 1, values: ['low', 'medium', 'high', 'urgent'] },
31
+ schema: [
32
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
33
+ { name: 'description', type: 'editor', options: { maxSize: 5000 } },
34
+ { name: 'status', type: 'select', required: true, options: { values: ['todo', 'in_progress', 'done', 'cancelled'] } },
35
+ { name: 'priority', type: 'select', required: true, options: { values: ['low', 'medium', 'high', 'urgent'] } },
37
36
  { name: 'due_date', type: 'date' },
38
37
  { name: 'tags', type: 'json' },
39
- { name: 'goal_id', type: 'text', max: 100 },
40
- { name: 'created', type: 'autodate', onCreate: true },
41
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
38
+ { name: 'goal_id', type: 'text', options: { maxSize: 100 } },
42
39
  ],
43
40
  indexes: [
44
41
  'CREATE INDEX idx_tasks_status ON tasks (status)',
@@ -47,6 +44,6 @@ migrate((app) => {
47
44
  });
48
45
  app.save(tasks);
49
46
  }, (app) => {
50
- const t = app.findCollectionByNameOrId('tasks'); if (t) app.delete(t);
51
- const g = app.findCollectionByNameOrId('goals'); if (g) app.delete(g);
47
+ app.delete(app.findCollectionByNameOrId('tasks'));
48
+ app.delete(app.findCollectionByNameOrId('goals'));
52
49
  });
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * PocketBase Migration 003: Contacts
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
@@ -9,18 +8,16 @@ migrate((app) => {
9
8
  const contacts = new Collection({
10
9
  name: 'contacts',
11
10
  type: 'base',
12
- fields: [
13
- { name: 'name', type: 'text', required: true, max: 200 },
14
- { name: 'company', type: 'text', max: 200 },
15
- { name: 'role', type: 'text', max: 200 },
16
- { name: 'email', type: 'email' },
17
- { name: 'phone', type: 'text', max: 50 },
18
- { name: 'relationship', type: 'select', maxSelect: 1, values: ['colleague', 'client', 'prospect', 'partner', 'other'] },
19
- { name: 'notes', type: 'editor', maxSize: 5000 },
11
+ schema: [
12
+ { name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
13
+ { name: 'company', type: 'text', options: { maxSize: 200 } },
14
+ { name: 'role', type: 'text', options: { maxSize: 200 } },
15
+ { name: 'email', type: 'email', options: { maxSize: 200 } },
16
+ { name: 'phone', type: 'text', options: { maxSize: 50 } },
17
+ { name: 'relationship', type: 'select', options: { values: ['colleague', 'client', 'prospect', 'partner', 'other'] } },
18
+ { name: 'notes', type: 'editor', options: { maxSize: 5000 } },
20
19
  { name: 'tags', type: 'json' },
21
20
  { name: 'last_contact_date', type: 'date' },
22
- { name: 'created', type: 'autodate', onCreate: true },
23
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
24
21
  ],
25
22
  indexes: [
26
23
  'CREATE INDEX idx_contacts_name ON contacts (name)',
@@ -28,5 +25,5 @@ migrate((app) => {
28
25
  });
29
26
  app.save(contacts);
30
27
  }, (app) => {
31
- const c = app.findCollectionByNameOrId('contacts'); if (c) app.delete(c);
28
+ app.delete(app.findCollectionByNameOrId('contacts'));
32
29
  });
@@ -1,19 +1,17 @@
1
1
  /**
2
2
  * PocketBase Migration 004: Entity aliases + settings
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
7
6
 
8
7
  migrate((app) => {
8
+ // entity_aliases collection
9
9
  const aliases = new Collection({
10
10
  name: 'entity_aliases',
11
11
  type: 'base',
12
- fields: [
13
- { name: 'canonical', type: 'text', required: true, max: 100 },
14
- { name: 'alias', type: 'text', required: true, max: 200 },
15
- { name: 'created', type: 'autodate', onCreate: true },
16
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
12
+ schema: [
13
+ { name: 'canonical', type: 'text', required: true, options: { maxSize: 100 } },
14
+ { name: 'alias', type: 'text', required: true, options: { maxSize: 200 } },
17
15
  ],
18
16
  indexes: [
19
17
  'CREATE UNIQUE INDEX idx_entity_aliases_unique ON entity_aliases (canonical, alias)',
@@ -23,14 +21,13 @@ migrate((app) => {
23
21
  });
24
22
  app.save(aliases);
25
23
 
24
+ // settings collection
26
25
  const settings = new Collection({
27
26
  name: 'settings',
28
27
  type: 'base',
29
- fields: [
30
- { name: 'key', type: 'text', required: true, max: 100 },
28
+ schema: [
29
+ { name: 'key', type: 'text', required: true, options: { maxSize: 100 } },
31
30
  { name: 'value', type: 'editor' },
32
- { name: 'created', type: 'autodate', onCreate: true },
33
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
34
31
  ],
35
32
  indexes: [
36
33
  'CREATE UNIQUE INDEX idx_settings_key ON settings (key)',
@@ -38,6 +35,6 @@ migrate((app) => {
38
35
  });
39
36
  app.save(settings);
40
37
  }, (app) => {
41
- const s = app.findCollectionByNameOrId('settings'); if (s) app.delete(s);
42
- const a = app.findCollectionByNameOrId('entity_aliases'); if (a) app.delete(a);
38
+ app.delete(app.findCollectionByNameOrId('settings'));
39
+ app.delete(app.findCollectionByNameOrId('entity_aliases'));
43
40
  });
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * PocketBase Migration 005: Prospects (CRM)
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
@@ -9,22 +8,20 @@ migrate((app) => {
9
8
  const prospects = new Collection({
10
9
  name: 'prospects',
11
10
  type: 'base',
12
- fields: [
13
- { name: 'name', type: 'text', required: true, max: 200 },
14
- { name: 'email', type: 'email' },
15
- { name: 'company', type: 'text', max: 200 },
16
- { name: 'role', type: 'text', max: 200 },
17
- { name: 'stage', type: 'select', required: true, maxSelect: 1, values: ['new', 'contacted', 'responded', 'interested', 'ready_to_buy', 'proposal_sent', 'negotiating', 'closed_won', 'closed_lost', 'nurturing'] },
18
- { name: 'source', type: 'text', max: 200 },
11
+ schema: [
12
+ { name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
13
+ { name: 'email', type: 'email', options: { maxSize: 200 } },
14
+ { name: 'company', type: 'text', options: { maxSize: 200 } },
15
+ { name: 'role', type: 'text', options: { maxSize: 200 } },
16
+ { name: 'stage', type: 'select', required: true, options: { values: ['new', 'contacted', 'responded', 'interested', 'ready_to_buy', 'proposal_sent', 'negotiating', 'closed_won', 'closed_lost', 'nurturing'] } },
17
+ { name: 'source', type: 'text', options: { maxSize: 200 } },
19
18
  { name: 'estimated_value', type: 'number' },
20
- { name: 'next_action_type', type: 'text', max: 100 },
19
+ { name: 'next_action_type', type: 'text', options: { maxSize: 100 } },
21
20
  { name: 'next_followup_date', type: 'date' },
22
21
  { name: 'last_contact_date', type: 'date' },
23
- { name: 'notes', type: 'editor', maxSize: 10000 },
22
+ { name: 'notes', type: 'editor', options: { maxSize: 10000 } },
24
23
  { name: 'tags', type: 'json' },
25
- { name: 'linkedin_url', type: 'url' },
26
- { name: 'created', type: 'autodate', onCreate: true },
27
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
24
+ { name: 'linkedin_url', type: 'url', options: { maxSize: 500 } },
28
25
  ],
29
26
  indexes: [
30
27
  'CREATE INDEX idx_prospects_stage ON prospects (stage)',
@@ -32,5 +29,5 @@ migrate((app) => {
32
29
  });
33
30
  app.save(prospects);
34
31
  }, (app) => {
35
- const p = app.findCollectionByNameOrId('prospects'); if (p) app.delete(p);
32
+ app.delete(app.findCollectionByNameOrId('prospects'));
36
33
  });
@@ -1,27 +1,25 @@
1
1
  /**
2
2
  * PocketBase Migration 006: Business collections (blog, email, content)
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
7
6
 
8
7
  migrate((app) => {
8
+ // blog_posts collection
9
9
  const blogPosts = new Collection({
10
10
  name: 'blog_posts',
11
11
  type: 'base',
12
- fields: [
13
- { name: 'title', type: 'text', required: true, max: 500 },
14
- { name: 'slug', type: 'text', required: true, max: 200 },
12
+ schema: [
13
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
14
+ { name: 'slug', type: 'text', required: true, options: { maxSize: 200 } },
15
15
  { name: 'content', type: 'editor', required: true },
16
- { name: 'excerpt', type: 'text', max: 500 },
17
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['draft', 'published', 'archived'] },
16
+ { name: 'excerpt', type: 'text', options: { maxSize: 500 } },
17
+ { name: 'status', type: 'select', required: true, options: { values: ['draft', 'published', 'archived'] } },
18
18
  { name: 'published_at', type: 'date' },
19
19
  { name: 'tags', type: 'json' },
20
- { name: 'seo_title', type: 'text', max: 200 },
21
- { name: 'seo_description', type: 'text', max: 300 },
22
- { name: 'og_image_url', type: 'url' },
23
- { name: 'created', type: 'autodate', onCreate: true },
24
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
20
+ { name: 'seo_title', type: 'text', options: { maxSize: 200 } },
21
+ { name: 'seo_description', type: 'text', options: { maxSize: 300 } },
22
+ { name: 'og_image_url', type: 'url', options: { maxSize: 500 } },
25
23
  ],
26
24
  indexes: [
27
25
  'CREATE UNIQUE INDEX idx_blog_posts_slug ON blog_posts (slug)',
@@ -30,25 +28,24 @@ migrate((app) => {
30
28
  });
31
29
  app.save(blogPosts);
32
30
 
31
+ // email_queue collection
33
32
  const emailQueue = new Collection({
34
33
  name: 'email_queue',
35
34
  type: 'base',
36
- fields: [
37
- { name: 'to_email', type: 'email', required: true },
38
- { name: 'to_name', type: 'text', max: 200 },
39
- { name: 'subject', type: 'text', required: true, max: 500 },
35
+ schema: [
36
+ { name: 'to_email', type: 'email', required: true, options: { maxSize: 200 } },
37
+ { name: 'to_name', type: 'text', options: { maxSize: 200 } },
38
+ { name: 'subject', type: 'text', required: true, options: { maxSize: 500 } },
40
39
  { name: 'body_html', type: 'editor', required: true },
41
40
  { name: 'body_text', type: 'editor' },
42
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['queued', 'sent', 'failed', 'bounced'] },
43
- { name: 'sequence_id', type: 'text', max: 100 },
41
+ { name: 'status', type: 'select', required: true, options: { values: ['queued', 'sent', 'failed', 'bounced'] } },
42
+ { name: 'sequence_id', type: 'text', options: { maxSize: 100 } },
44
43
  { name: 'sequence_step', type: 'number' },
45
- { name: 'prospect_id', type: 'text', max: 100 },
44
+ { name: 'prospect_id', type: 'text', options: { maxSize: 100 } },
46
45
  { name: 'scheduled_at', type: 'date' },
47
46
  { name: 'sent_at', type: 'date' },
48
47
  { name: 'error', type: 'editor' },
49
- { name: 'resend_id', type: 'text', max: 200 },
50
- { name: 'created', type: 'autodate', onCreate: true },
51
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
48
+ { name: 'resend_id', type: 'text', options: { maxSize: 200 } },
52
49
  ],
53
50
  indexes: [
54
51
  'CREATE INDEX idx_email_queue_status ON email_queue (status)',
@@ -56,20 +53,19 @@ migrate((app) => {
56
53
  });
57
54
  app.save(emailQueue);
58
55
 
56
+ // content_calendar collection
59
57
  const contentCalendar = new Collection({
60
58
  name: 'content_calendar',
61
59
  type: 'base',
62
- fields: [
63
- { name: 'title', type: 'text', required: true, max: 500 },
60
+ schema: [
61
+ { name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
64
62
  { name: 'content', type: 'editor' },
65
- { name: 'platform', type: 'select', required: true, maxSelect: 1, values: ['linkedin', 'newsletter', 'blog', 'twitter', 'other'] },
66
- { name: 'pillar', type: 'text', max: 100 },
67
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['idea', 'drafting', 'ready', 'published'] },
63
+ { name: 'platform', type: 'select', required: true, options: { values: ['linkedin', 'newsletter', 'blog', 'twitter', 'other'] } },
64
+ { name: 'pillar', type: 'text', options: { maxSize: 100 } },
65
+ { name: 'status', type: 'select', required: true, options: { values: ['idea', 'drafting', 'ready', 'published'] } },
68
66
  { name: 'scheduled_date', type: 'date' },
69
- { name: 'published_url', type: 'url' },
70
- { name: 'persona', type: 'text', max: 100 },
71
- { name: 'created', type: 'autodate', onCreate: true },
72
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
67
+ { name: 'published_url', type: 'url', options: { maxSize: 500 } },
68
+ { name: 'persona', type: 'text', options: { maxSize: 100 } },
73
69
  ],
74
70
  indexes: [
75
71
  'CREATE INDEX idx_content_calendar_platform ON content_calendar (platform)',
@@ -78,7 +74,7 @@ migrate((app) => {
78
74
  });
79
75
  app.save(contentCalendar);
80
76
  }, (app) => {
81
- const cc = app.findCollectionByNameOrId('content_calendar'); if (cc) app.delete(cc);
82
- const eq = app.findCollectionByNameOrId('email_queue'); if (eq) app.delete(eq);
83
- const bp = app.findCollectionByNameOrId('blog_posts'); if (bp) app.delete(bp);
77
+ app.delete(app.findCollectionByNameOrId('content_calendar'));
78
+ app.delete(app.findCollectionByNameOrId('email_queue'));
79
+ app.delete(app.findCollectionByNameOrId('blog_posts'));
84
80
  });
@@ -1,24 +1,22 @@
1
1
  /**
2
2
  * PocketBase Migration 007: Newsletter subscribers and affiliates
3
- * PocketBase v0.23+ field format.
4
3
  */
5
4
 
6
5
  /// <reference path="../pb_data/types.d.ts" />
7
6
 
8
7
  migrate((app) => {
8
+ // newsletter_subscribers collection
9
9
  const subscribers = new Collection({
10
10
  name: 'newsletter_subscribers',
11
11
  type: 'base',
12
- fields: [
13
- { name: 'email', type: 'email', required: true },
14
- { name: 'name', type: 'text', max: 200 },
15
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['active', 'unsubscribed', 'bounced'] },
16
- { name: 'source', type: 'text', max: 200 },
12
+ schema: [
13
+ { name: 'email', type: 'email', required: true, options: { maxSize: 200 } },
14
+ { name: 'name', type: 'text', options: { maxSize: 200 } },
15
+ { name: 'status', type: 'select', required: true, options: { values: ['active', 'unsubscribed', 'bounced'] } },
16
+ { name: 'source', type: 'text', options: { maxSize: 200 } },
17
17
  { name: 'tags', type: 'json' },
18
18
  { name: 'subscribed_at', type: 'date' },
19
19
  { name: 'unsubscribed_at', type: 'date' },
20
- { name: 'created', type: 'autodate', onCreate: true },
21
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
22
20
  ],
23
21
  indexes: [
24
22
  'CREATE UNIQUE INDEX idx_newsletter_subscribers_email ON newsletter_subscribers (email)',
@@ -26,20 +24,19 @@ migrate((app) => {
26
24
  });
27
25
  app.save(subscribers);
28
26
 
27
+ // affiliates collection
29
28
  const affiliates = new Collection({
30
29
  name: 'affiliates',
31
30
  type: 'base',
32
- fields: [
33
- { name: 'name', type: 'text', required: true, max: 200 },
34
- { name: 'email', type: 'email', required: true },
35
- { name: 'code', type: 'text', required: true, max: 100 },
36
- { name: 'commission_rate', type: 'number', min: 0, max: 1 },
37
- { name: 'status', type: 'select', required: true, maxSelect: 1, values: ['pending', 'active', 'paused', 'terminated'] },
31
+ schema: [
32
+ { name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
33
+ { name: 'email', type: 'email', required: true, options: { maxSize: 200 } },
34
+ { name: 'code', type: 'text', required: true, options: { maxSize: 100 } },
35
+ { name: 'commission_rate', type: 'number', options: { min: 0, max: 1 } },
36
+ { name: 'status', type: 'select', required: true, options: { values: ['pending', 'active', 'paused', 'terminated'] } },
38
37
  { name: 'total_earned_cents', type: 'number' },
39
38
  { name: 'total_paid_cents', type: 'number' },
40
- { name: 'stripe_account_id', type: 'text', max: 200 },
41
- { name: 'created', type: 'autodate', onCreate: true },
42
- { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
39
+ { name: 'stripe_account_id', type: 'text', options: { maxSize: 200 } },
43
40
  ],
44
41
  indexes: [
45
42
  'CREATE UNIQUE INDEX idx_affiliates_email ON affiliates (email)',
@@ -48,6 +45,6 @@ migrate((app) => {
48
45
  });
49
46
  app.save(affiliates);
50
47
  }, (app) => {
51
- const a = app.findCollectionByNameOrId('affiliates'); if (a) app.delete(a);
52
- const s = app.findCollectionByNameOrId('newsletter_subscribers'); if (s) app.delete(s);
48
+ app.delete(app.findCollectionByNameOrId('affiliates'));
49
+ app.delete(app.findCollectionByNameOrId('newsletter_subscribers'));
53
50
  });