@happyvertical/smrt-social 0.51.38 → 0.53.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.
package/AGENTS.md CHANGED
@@ -5,7 +5,7 @@ Social media account management with OAuth and post scheduling. Supports YouTube
5
5
  ## Models
6
6
 
7
7
  - **SocialAccount** (STI): `platform`, `accessToken`/`refreshToken`, `tokenExpiresAt`, `status` (connected/expired/error), `linkBehavior` (description/reply/none). `isTokenExpired` checks with 5-min buffer. `isReady` gate checks active + connected + token present + not expired.
8
- - **SocialPost**: `scheduledAt`, `publishedAt`, `status` (draft/scheduled/publishing/published/failed), `analytics` JSON (views/likes/comments/shares/clicks).
8
+ - **SocialPost**: `scheduledAt`, `publishedAt`, `status` (draft/scheduled/publishing/published/failed), `analytics` JSON (views/likes/comments/shares/clicks). `createdByUserId` (nullable `@crossPackageRef` to `smrt-users:User`, uuid on PostgreSQL) records the poster; pass it to `createDraft()` so a failed publish can be reported to that person. Agent/schedule posts leave it null; `recordPublishSuccess`/`Failure` never change it. Adding the column is additive: consumers run `db:migrate`.
9
9
  - **OAuthState** (STI): CSRF token + PKCE `codeVerifier` with 10-min TTL.
10
10
 
11
11
  ## Gotchas
package/README.md CHANGED
@@ -59,7 +59,7 @@ await state.save();
59
59
  | Export | Description |
60
60
  |--------|------------|
61
61
  | `SocialAccount` | Connected platform account with OAuth credentials and publishing settings |
62
- | `SocialPost` | Scheduled or published post with analytics tracking |
62
+ | `SocialPost` | Scheduled or published post with analytics tracking; `createdByUserId` records the poster |
63
63
  | `OAuthState` | Temporary OAuth flow state with CSRF protection and PKCE support |
64
64
 
65
65
  ### Types
@@ -11,6 +11,11 @@ export interface CreateSocialPostDraftOptions {
11
11
  linkUrl?: string | null;
12
12
  mediaUrl?: string | null;
13
13
  scheduledAt?: Date | null;
14
+ /**
15
+ * User creating the post (the poster). Stored as `createdByUserId` so a
16
+ * failed publish can be reported to them; omit for agent/schedule posts.
17
+ */
18
+ createdByUserId?: string | null;
14
19
  tenantId?: string | null;
15
20
  }
16
21
  export interface PublishSuccessData {
@@ -1 +1 @@
1
- {"version":3,"file":"social-post-collection.d.ts","sourceRoot":"","sources":["../../src/collections/social-post-collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,UAAU,EACV,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,qBAAa,oBAAqB,SAAQ,cAAc,CAAC,UAAU,CAAC;IAClE,MAAM,CAAC,QAAQ,CAAC,UAAU,oBAAc;IAElC,WAAW,CACf,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,UAAU,CAAC;IAchB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAIvD,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAO9D,iBAAiB,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAqBhE,oBAAoB,CACxB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,UAAU,CAAC;IAahB,oBAAoB,CACxB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,GAAG,MAAM,GACpB,OAAO,CAAC,UAAU,CAAC;IAOhB,qBAAqB,CACzB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,aAAa,EACxB,QAAQ,GAAE,IAAiB,GAC1B,OAAO,CAAC,UAAU,CAAC;CASvB"}
1
+ {"version":3,"file":"social-post-collection.d.ts","sourceRoot":"","sources":["../../src/collections/social-post-collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,UAAU,EACV,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,qBAAa,oBAAqB,SAAQ,cAAc,CAAC,UAAU,CAAC;IAClE,MAAM,CAAC,QAAQ,CAAC,UAAU,oBAAc;IAElC,WAAW,CACf,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,UAAU,CAAC;IAchB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAIvD,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAO9D,iBAAiB,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAqBhE,oBAAoB,CACxB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,UAAU,CAAC;IAahB,oBAAoB,CACxB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,GAAG,MAAM,GACpB,OAAO,CAAC,UAAU,CAAC;IAOhB,qBAAqB,CACzB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,aAAa,EACxB,QAAQ,GAAE,IAAiB,GAC1B,OAAO,CAAC,UAAU,CAAC;CASvB"}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { TenantScoped, getCurrentTenant, tenantId, withTenant } from "@happyvert
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { SecretService } from "@happyvertical/smrt-secrets";
6
6
  //#region src/__smrt-register__.ts
7
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-social\",\"packageVersion\":\"0.51.38\",\"objects\":{\"@happyvertical/smrt-social:OAuthState\":{\"name\":\"oauthstate\",\"className\":\"OAuthState\",\"qualifiedName\":\"@happyvertical/smrt-social:OAuthState\",\"collection\":\"oauthstates\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/oauth-state.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"platform\":{\"type\":\"text\",\"required\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"codeVerifier\":{\"type\":\"text\",\"required\":false},\"redirectUri\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"scopes\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"expiresAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"verifyState\":{\"name\":\"verifyState\",\"async\":false,\"parameters\":[{\"name\":\"callbackState\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"generateState\":{\"name\":\"generateState\",\"async\":false,\"parameters\":[],\"returnType\":\"string\",\"isStatic\":true,\"isPublic\":true},\"generateCodeVerifier\":{\"name\":\"generateCodeVerifier\",\"async\":false,\"parameters\":[],\"returnType\":\"string\",\"isStatic\":true,\"isPublic\":true},\"generateCodeChallenge\":{\"name\":\"generateCodeChallenge\",\"async\":true,\"parameters\":[{\"name\":\"verifier\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<string>\",\"isStatic\":true,\"isPublic\":true}},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"delete\"]},\"mcp\":false,\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"OAuthState\",\"collectionExportName\":\"OAuthStateCollection\",\"schema\":{\"tableName\":\"oauth_states\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"oauth_states\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"state\\\" TEXT DEFAULT '',\\n \\\"code_verifier\\\" TEXT,\\n \\\"redirect_uri\\\" TEXT DEFAULT '',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"expires_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"code_verifier\":{\"type\":\"TEXT\",\"notNull\":false},\"redirect_uri\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"oauth_states_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"oauth_states_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"oauth_states_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"709e111f\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialAccount\":{\"name\":\"socialaccount\",\"className\":\"SocialAccount\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialAccount\",\"collection\":\"socialaccounts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-account.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"platform\":{\"type\":\"text\",\"required\":false,\"default\":\"youtube\"},\"platformUserId\":{\"type\":\"text\",\"required\":false},\"platformUsername\":{\"type\":\"text\",\"required\":false},\"platformUrl\":{\"type\":\"text\",\"required\":false},\"accessToken\":{\"type\":\"text\",\"required\":false},\"refreshToken\":{\"type\":\"text\",\"required\":false},\"credentialSecretId\":{\"type\":\"text\",\"required\":false},\"accessTokenSecretName\":{\"type\":\"text\",\"required\":false},\"refreshTokenSecretName\":{\"type\":\"text\",\"required\":false},\"tokenExpiresAt\":{\"type\":\"datetime\",\"required\":false},\"isActive\":{\"type\":\"boolean\",\"required\":false,\"default\":true},\"defaultHashtags\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"scopes\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"missingPermissions\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"linkBehavior\":{\"type\":\"text\",\"required\":false,\"default\":\"description\"},\"publishMode\":{\"type\":\"text\",\"required\":false,\"default\":\"dry_run\"},\"publicPublishingAllowed\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"connected\"},\"errorMessage\":{\"type\":\"text\",\"required\":false}},\"methods\":{\"getSlug\":{\"name\":\"getSlug\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<string | null | undefined>\",\"isStatic\":false,\"isPublic\":true},\"setCredentials\":{\"name\":\"setCredentials\",\"async\":true,\"parameters\":[{\"name\":\"credentials\",\"type\":\"Record<string, unknown>\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getCredentials\":{\"name\":\"getCredentials\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<Record<string, unknown> | null>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialAccount\",\"collectionExportName\":\"SocialAccountCollection\",\"schema\":{\"tableName\":\"social_accounts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_accounts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"platform_user_id\\\" TEXT,\\n \\\"platform_username\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"access_token\\\" TEXT,\\n \\\"refresh_token\\\" TEXT,\\n \\\"credential_secret_id\\\" TEXT,\\n \\\"access_token_secret_name\\\" TEXT,\\n \\\"refresh_token_secret_name\\\" TEXT,\\n \\\"token_expires_at\\\" TIMESTAMP,\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"default_hashtags\\\" JSON DEFAULT '[]',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"missing_permissions\\\" JSON DEFAULT '[]',\\n \\\"link_behavior\\\" TEXT DEFAULT 'description',\\n \\\"publish_mode\\\" TEXT DEFAULT 'dry_run',\\n \\\"public_publishing_allowed\\\" BOOLEAN DEFAULT FALSE,\\n \\\"status\\\" TEXT DEFAULT 'connected',\\n \\\"error_message\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"platform_user_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_username\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token\":{\"type\":\"TEXT\",\"notNull\":false},\"credential_secret_id\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"token_expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":true},\"default_hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"missing_permissions\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_behavior\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"description\"},\"publish_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"dry_run\"},\"public_publishing_allowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"connected\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_accounts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_accounts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_accounts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"0c175da6\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshot\":{\"name\":\"socialpostanalyticssnapshot\",\"className\":\"SocialPostAnalyticsSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshot\",\"collection\":\"socialpostanalyticssnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-post-analytics-snapshot.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"socialPostId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"SocialPost\"},\"platform\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"analytics\":{\"type\":\"json\",\"required\":false},\"raw\":{\"type\":\"json\",\"required\":false,\"_meta\":{\"nullable\":true}},\"capturedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialPostAnalyticsSnapshot\",\"collectionExportName\":\"SocialPostAnalyticsSnapshotCollection\",\"validationRules\":[{\"field\":\"platform\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"social_post_analytics_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_post_analytics_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_post_id\\\" UUID,\\n \\\"platform\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"raw\\\" JSON,\\n \\\"captured_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_post_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_posts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"platform\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"raw\":{\"type\":\"JSON\",\"notNull\":false},\"captured_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_post_analytics_snapshots_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_post_analytics_snapshots_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_post_analytics_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_post_analytics_snapshots_social_post_id_idx\",\"columns\":[\"social_post_id\"]}],\"version\":\"bd17983d\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPost\":{\"name\":\"socialpost\",\"className\":\"SocialPost\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPost\",\"collection\":\"socialposts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-post.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"socialAccountId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"SocialAccount\"},\"videoContentId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-video:VideoContent\"},\"contentId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-content:Content\"},\"postType\":{\"type\":\"text\",\"required\":false,\"default\":\"text\"},\"mediaUrl\":{\"type\":\"text\",\"required\":false},\"title\":{\"type\":\"text\",\"required\":false},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"linkUrl\":{\"type\":\"text\",\"required\":false},\"platformPostId\":{\"type\":\"text\",\"required\":false},\"platformUrl\":{\"type\":\"text\",\"required\":false},\"scheduledAt\":{\"type\":\"datetime\",\"required\":false},\"publishedAt\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"draft\"},\"errorMessage\":{\"type\":\"text\",\"required\":false},\"analytics\":{\"type\":\"json\",\"required\":false},\"analyticsLastSyncedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialPost\",\"collectionExportName\":\"SocialPostCollection\",\"schema\":{\"tableName\":\"social_posts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_posts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_account_id\\\" UUID,\\n \\\"video_content_id\\\" UUID,\\n \\\"content_id\\\" UUID,\\n \\\"post_type\\\" TEXT DEFAULT 'text',\\n \\\"media_url\\\" TEXT,\\n \\\"title\\\" TEXT,\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"hashtags\\\" JSON DEFAULT '[]',\\n \\\"link_url\\\" TEXT,\\n \\\"platform_post_id\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"scheduled_at\\\" TIMESTAMP,\\n \\\"published_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"error_message\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"analytics_last_synced_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_account_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_accounts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"video_content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"post_type\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"text\"},\"media_url\":{\"type\":\"TEXT\",\"notNull\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_url\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_post_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"scheduled_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"published_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"draft\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"analytics_last_synced_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_posts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_posts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_posts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_posts_social_account_id_idx\",\"columns\":[\"social_account_id\"]},{\"name\":\"social_posts_video_content_id_idx\",\"columns\":[\"video_content_id\"]},{\"name\":\"social_posts_content_id_idx\",\"columns\":[\"content_id\"]}],\"version\":\"49573fcf\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:OAuthStateCollection\":{\"name\":\"oauthstatecollection\",\"className\":\"OAuthStateCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:OAuthStateCollection\",\"collection\":\"oauthstates\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/oauth-state-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"findByState\":{\"name\":\"findByState\",\"async\":true,\"parameters\":[{\"name\":\"state\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<OAuthState | null>\",\"isStatic\":false,\"isPublic\":true},\"findExpired\":{\"name\":\"findExpired\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<OAuthState[]>\",\"isStatic\":false,\"isPublic\":true},\"deleteExpired\":{\"name\":\"deleteExpired\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"mcp\":false},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"OAuthState\",\"exportName\":\"OAuthStateCollection\",\"collectionExportName\":\"OAuthStateCollectionCollection\",\"schema\":{\"tableName\":\"oauth_states\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"oauth_states\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"state\\\" TEXT DEFAULT '',\\n \\\"code_verifier\\\" TEXT,\\n \\\"redirect_uri\\\" TEXT DEFAULT '',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"expires_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"code_verifier\":{\"type\":\"TEXT\",\"notNull\":false},\"redirect_uri\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"oauth_states_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"oauth_states_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"oauth_states_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"709e111f\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialAccountCollection\":{\"name\":\"socialaccountcollection\",\"className\":\"SocialAccountCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialAccountCollection\",\"collection\":\"socialaccounts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-account-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"findActive\":{\"name\":\"findActive\",\"async\":true,\"parameters\":[{\"name\":\"platform\",\"type\":\"SocialPlatformType\",\"optional\":true}],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true},\"findReady\":{\"name\":\"findReady\",\"async\":true,\"parameters\":[{\"name\":\"platform\",\"type\":\"SocialPlatformType\",\"optional\":true}],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true},\"findNeedsAttention\":{\"name\":\"findNeedsAttention\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialAccount\",\"exportName\":\"SocialAccountCollection\",\"collectionExportName\":\"SocialAccountCollectionCollection\",\"schema\":{\"tableName\":\"social_accounts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_accounts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"platform_user_id\\\" TEXT,\\n \\\"platform_username\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"access_token\\\" TEXT,\\n \\\"refresh_token\\\" TEXT,\\n \\\"credential_secret_id\\\" TEXT,\\n \\\"access_token_secret_name\\\" TEXT,\\n \\\"refresh_token_secret_name\\\" TEXT,\\n \\\"token_expires_at\\\" TIMESTAMP,\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"default_hashtags\\\" JSON DEFAULT '[]',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"missing_permissions\\\" JSON DEFAULT '[]',\\n \\\"link_behavior\\\" TEXT DEFAULT 'description',\\n \\\"publish_mode\\\" TEXT DEFAULT 'dry_run',\\n \\\"public_publishing_allowed\\\" BOOLEAN DEFAULT FALSE,\\n \\\"status\\\" TEXT DEFAULT 'connected',\\n \\\"error_message\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"platform_user_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_username\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token\":{\"type\":\"TEXT\",\"notNull\":false},\"credential_secret_id\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"token_expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":true},\"default_hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"missing_permissions\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_behavior\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"description\"},\"publish_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"dry_run\"},\"public_publishing_allowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"connected\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_accounts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_accounts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_accounts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"0c175da6\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshotCollection\":{\"name\":\"socialpostanalyticssnapshotcollection\",\"className\":\"SocialPostAnalyticsSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshotCollection\",\"collection\":\"socialpostanalyticssnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-post-analytics-snapshot-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"recordSnapshot\":{\"name\":\"recordSnapshot\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":false,\"memberTypes\":[\"string\",\"SocialPlatformType\",\"PostAnalytics\",\"RawAnalyticsPayload\",\"Date\",\"string | null\"]}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot>\",\"isStatic\":false,\"isPublic\":true},\"findForPost\":{\"name\":\"findForPost\",\"async\":true,\"parameters\":[{\"name\":\"socialPostId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot[]>\",\"isStatic\":false,\"isPublic\":true},\"findLatestForPost\":{\"name\":\"findLatestForPost\",\"async\":true,\"parameters\":[{\"name\":\"socialPostId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot | null>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialPostAnalyticsSnapshot\",\"exportName\":\"SocialPostAnalyticsSnapshotCollection\",\"collectionExportName\":\"SocialPostAnalyticsSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"social_post_analytics_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_post_analytics_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_post_id\\\" UUID,\\n \\\"platform\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"raw\\\" JSON,\\n \\\"captured_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_post_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_posts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"platform\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"raw\":{\"type\":\"JSON\",\"notNull\":false},\"captured_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_post_analytics_snapshots_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_post_analytics_snapshots_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_post_analytics_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_post_analytics_snapshots_social_post_id_idx\",\"columns\":[\"social_post_id\"]}],\"version\":\"bd17983d\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostCollection\":{\"name\":\"socialpostcollection\",\"className\":\"SocialPostCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostCollection\",\"collection\":\"socialposts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-post-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"createDraft\":{\"name\":\"createDraft\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"CreateSocialPostDraftOptions\",\"optional\":false}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"PostStatus\",\"optional\":false}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"findForAccount\":{\"name\":\"findForAccount\",\"async\":true,\"parameters\":[{\"name\":\"socialAccountId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"findDueForPublish\":{\"name\":\"findDueForPublish\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"recordPublishSuccess\":{\"name\":\"recordPublishSuccess\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"data\",\"type\":\"PublishSuccessData\",\"optional\":false}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"recordPublishFailure\":{\"name\":\"recordPublishFailure\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"error\",\"type\":\"Error | string\",\"optional\":false,\"unionBranches\":[{\"type\":\"Error\"},{\"type\":\"string\"}]}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"updateLatestAnalytics\":{\"name\":\"updateLatestAnalytics\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"analytics\",\"type\":\"PostAnalytics\",\"optional\":false},{\"name\":\"syncedAt\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialPost\",\"exportName\":\"SocialPostCollection\",\"collectionExportName\":\"SocialPostCollectionCollection\",\"schema\":{\"tableName\":\"social_posts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_posts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_account_id\\\" UUID,\\n \\\"video_content_id\\\" UUID,\\n \\\"content_id\\\" UUID,\\n \\\"post_type\\\" TEXT DEFAULT 'text',\\n \\\"media_url\\\" TEXT,\\n \\\"title\\\" TEXT,\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"hashtags\\\" JSON DEFAULT '[]',\\n \\\"link_url\\\" TEXT,\\n \\\"platform_post_id\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"scheduled_at\\\" TIMESTAMP,\\n \\\"published_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"error_message\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"analytics_last_synced_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_account_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_accounts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"video_content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"post_type\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"text\"},\"media_url\":{\"type\":\"TEXT\",\"notNull\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_url\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_post_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"scheduled_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"published_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"draft\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"analytics_last_synced_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_posts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_posts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_posts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_posts_social_account_id_idx\",\"columns\":[\"social_account_id\"]},{\"name\":\"social_posts_video_content_id_idx\",\"columns\":[\"video_content_id\"]},{\"name\":\"social_posts_content_id_idx\",\"columns\":[\"content_id\"]}],\"version\":\"49573fcf\"},\"importPath\":\"@happyvertical/smrt-social\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-content\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-secrets\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-video\"]}"));
7
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-social\",\"packageVersion\":\"0.53.0\",\"objects\":{\"@happyvertical/smrt-social:OAuthState\":{\"name\":\"oauthstate\",\"className\":\"OAuthState\",\"qualifiedName\":\"@happyvertical/smrt-social:OAuthState\",\"collection\":\"oauthstates\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/oauth-state.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"platform\":{\"type\":\"text\",\"required\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"codeVerifier\":{\"type\":\"text\",\"required\":false},\"redirectUri\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"scopes\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"expiresAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{\"verifyState\":{\"name\":\"verifyState\",\"async\":false,\"parameters\":[{\"name\":\"callbackState\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"boolean\",\"isStatic\":false,\"isPublic\":true},\"generateState\":{\"name\":\"generateState\",\"async\":false,\"parameters\":[],\"returnType\":\"string\",\"isStatic\":true,\"isPublic\":true},\"generateCodeVerifier\":{\"name\":\"generateCodeVerifier\",\"async\":false,\"parameters\":[],\"returnType\":\"string\",\"isStatic\":true,\"isPublic\":true},\"generateCodeChallenge\":{\"name\":\"generateCodeChallenge\",\"async\":true,\"parameters\":[{\"name\":\"verifier\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<string>\",\"isStatic\":true,\"isPublic\":true}},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"delete\"]},\"mcp\":false,\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"OAuthState\",\"collectionExportName\":\"OAuthStateCollection\",\"schema\":{\"tableName\":\"oauth_states\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"oauth_states\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"state\\\" TEXT DEFAULT '',\\n \\\"code_verifier\\\" TEXT,\\n \\\"redirect_uri\\\" TEXT DEFAULT '',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"expires_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"code_verifier\":{\"type\":\"TEXT\",\"notNull\":false},\"redirect_uri\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"oauth_states_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"oauth_states_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"oauth_states_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"709e111f\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialAccount\":{\"name\":\"socialaccount\",\"className\":\"SocialAccount\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialAccount\",\"collection\":\"socialaccounts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-account.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"platform\":{\"type\":\"text\",\"required\":false,\"default\":\"youtube\"},\"platformUserId\":{\"type\":\"text\",\"required\":false},\"platformUsername\":{\"type\":\"text\",\"required\":false},\"platformUrl\":{\"type\":\"text\",\"required\":false},\"accessToken\":{\"type\":\"text\",\"required\":false},\"refreshToken\":{\"type\":\"text\",\"required\":false},\"credentialSecretId\":{\"type\":\"text\",\"required\":false},\"accessTokenSecretName\":{\"type\":\"text\",\"required\":false},\"refreshTokenSecretName\":{\"type\":\"text\",\"required\":false},\"tokenExpiresAt\":{\"type\":\"datetime\",\"required\":false},\"isActive\":{\"type\":\"boolean\",\"required\":false,\"default\":true},\"defaultHashtags\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"scopes\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"missingPermissions\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"linkBehavior\":{\"type\":\"text\",\"required\":false,\"default\":\"description\"},\"publishMode\":{\"type\":\"text\",\"required\":false,\"default\":\"dry_run\"},\"publicPublishingAllowed\":{\"type\":\"boolean\",\"required\":false,\"default\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"connected\"},\"errorMessage\":{\"type\":\"text\",\"required\":false}},\"methods\":{\"getSlug\":{\"name\":\"getSlug\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<string | null | undefined>\",\"isStatic\":false,\"isPublic\":true},\"setCredentials\":{\"name\":\"setCredentials\",\"async\":true,\"parameters\":[{\"name\":\"credentials\",\"type\":\"Record<string, unknown>\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getCredentials\":{\"name\":\"getCredentials\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<Record<string, unknown> | null>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialAccount\",\"collectionExportName\":\"SocialAccountCollection\",\"schema\":{\"tableName\":\"social_accounts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_accounts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"platform_user_id\\\" TEXT,\\n \\\"platform_username\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"access_token\\\" TEXT,\\n \\\"refresh_token\\\" TEXT,\\n \\\"credential_secret_id\\\" TEXT,\\n \\\"access_token_secret_name\\\" TEXT,\\n \\\"refresh_token_secret_name\\\" TEXT,\\n \\\"token_expires_at\\\" TIMESTAMP,\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"default_hashtags\\\" JSON DEFAULT '[]',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"missing_permissions\\\" JSON DEFAULT '[]',\\n \\\"link_behavior\\\" TEXT DEFAULT 'description',\\n \\\"publish_mode\\\" TEXT DEFAULT 'dry_run',\\n \\\"public_publishing_allowed\\\" BOOLEAN DEFAULT FALSE,\\n \\\"status\\\" TEXT DEFAULT 'connected',\\n \\\"error_message\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"platform_user_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_username\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token\":{\"type\":\"TEXT\",\"notNull\":false},\"credential_secret_id\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"token_expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":true},\"default_hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"missing_permissions\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_behavior\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"description\"},\"publish_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"dry_run\"},\"public_publishing_allowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"connected\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_accounts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_accounts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_accounts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"0c175da6\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshot\":{\"name\":\"socialpostanalyticssnapshot\",\"className\":\"SocialPostAnalyticsSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshot\",\"collection\":\"socialpostanalyticssnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-post-analytics-snapshot.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"socialPostId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"SocialPost\"},\"platform\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"analytics\":{\"type\":\"json\",\"required\":false},\"raw\":{\"type\":\"json\",\"required\":false,\"_meta\":{\"nullable\":true}},\"capturedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialPostAnalyticsSnapshot\",\"collectionExportName\":\"SocialPostAnalyticsSnapshotCollection\",\"validationRules\":[{\"field\":\"platform\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"social_post_analytics_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_post_analytics_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_post_id\\\" UUID,\\n \\\"platform\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"raw\\\" JSON,\\n \\\"captured_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_post_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_posts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"platform\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"raw\":{\"type\":\"JSON\",\"notNull\":false},\"captured_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_post_analytics_snapshots_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_post_analytics_snapshots_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_post_analytics_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_post_analytics_snapshots_social_post_id_idx\",\"columns\":[\"social_post_id\"]}],\"version\":\"bd17983d\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPost\":{\"name\":\"socialpost\",\"className\":\"SocialPost\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPost\",\"collection\":\"socialposts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/social-post.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"socialAccountId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"SocialAccount\"},\"createdByUserId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-users:User\"},\"videoContentId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-video:VideoContent\"},\"contentId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-content:Content\"},\"postType\":{\"type\":\"text\",\"required\":false,\"default\":\"text\"},\"mediaUrl\":{\"type\":\"text\",\"required\":false},\"title\":{\"type\":\"text\",\"required\":false},\"description\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"linkUrl\":{\"type\":\"text\",\"required\":false},\"platformPostId\":{\"type\":\"text\",\"required\":false},\"platformUrl\":{\"type\":\"text\",\"required\":false},\"scheduledAt\":{\"type\":\"datetime\",\"required\":false},\"publishedAt\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"draft\"},\"errorMessage\":{\"type\":\"text\",\"required\":false},\"analytics\":{\"type\":\"json\",\"required\":false},\"analyticsLastSyncedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{},\"decoratorConfig\":{\"tableStrategy\":\"sti\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"]},\"extends\":\"SmrtObject\",\"exportName\":\"SocialPost\",\"collectionExportName\":\"SocialPostCollection\",\"schema\":{\"tableName\":\"social_posts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_posts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_account_id\\\" UUID,\\n \\\"created_by_user_id\\\" UUID,\\n \\\"video_content_id\\\" UUID,\\n \\\"content_id\\\" UUID,\\n \\\"post_type\\\" TEXT DEFAULT 'text',\\n \\\"media_url\\\" TEXT,\\n \\\"title\\\" TEXT,\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"hashtags\\\" JSON DEFAULT '[]',\\n \\\"link_url\\\" TEXT,\\n \\\"platform_post_id\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"scheduled_at\\\" TIMESTAMP,\\n \\\"published_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"error_message\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"analytics_last_synced_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_account_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_accounts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"created_by_user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"video_content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"post_type\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"text\"},\"media_url\":{\"type\":\"TEXT\",\"notNull\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_url\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_post_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"scheduled_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"published_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"draft\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"analytics_last_synced_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_posts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_posts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_posts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_posts_social_account_id_idx\",\"columns\":[\"social_account_id\"]},{\"name\":\"social_posts_created_by_user_id_idx\",\"columns\":[\"created_by_user_id\"]},{\"name\":\"social_posts_video_content_id_idx\",\"columns\":[\"video_content_id\"]},{\"name\":\"social_posts_content_id_idx\",\"columns\":[\"content_id\"]}],\"version\":\"cfb50b15\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:OAuthStateCollection\":{\"name\":\"oauthstatecollection\",\"className\":\"OAuthStateCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:OAuthStateCollection\",\"collection\":\"oauthstates\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/oauth-state-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"findByState\":{\"name\":\"findByState\",\"async\":true,\"parameters\":[{\"name\":\"state\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<OAuthState | null>\",\"isStatic\":false,\"isPublic\":true},\"findExpired\":{\"name\":\"findExpired\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<OAuthState[]>\",\"isStatic\":false,\"isPublic\":true},\"deleteExpired\":{\"name\":\"deleteExpired\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<number>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"mcp\":false},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"OAuthState\",\"exportName\":\"OAuthStateCollection\",\"collectionExportName\":\"OAuthStateCollectionCollection\",\"schema\":{\"tableName\":\"oauth_states\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"oauth_states\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"state\\\" TEXT DEFAULT '',\\n \\\"code_verifier\\\" TEXT,\\n \\\"redirect_uri\\\" TEXT DEFAULT '',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"expires_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"state\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"code_verifier\":{\"type\":\"TEXT\",\"notNull\":false},\"redirect_uri\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"oauth_states_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"oauth_states_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"oauth_states_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"709e111f\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialAccountCollection\":{\"name\":\"socialaccountcollection\",\"className\":\"SocialAccountCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialAccountCollection\",\"collection\":\"socialaccounts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-account-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"findActive\":{\"name\":\"findActive\",\"async\":true,\"parameters\":[{\"name\":\"platform\",\"type\":\"SocialPlatformType\",\"optional\":true}],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true},\"findReady\":{\"name\":\"findReady\",\"async\":true,\"parameters\":[{\"name\":\"platform\",\"type\":\"SocialPlatformType\",\"optional\":true}],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true},\"findNeedsAttention\":{\"name\":\"findNeedsAttention\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<SocialAccount[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialAccount\",\"exportName\":\"SocialAccountCollection\",\"collectionExportName\":\"SocialAccountCollectionCollection\",\"schema\":{\"tableName\":\"social_accounts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_accounts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"platform\\\" TEXT DEFAULT 'youtube',\\n \\\"platform_user_id\\\" TEXT,\\n \\\"platform_username\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"access_token\\\" TEXT,\\n \\\"refresh_token\\\" TEXT,\\n \\\"credential_secret_id\\\" TEXT,\\n \\\"access_token_secret_name\\\" TEXT,\\n \\\"refresh_token_secret_name\\\" TEXT,\\n \\\"token_expires_at\\\" TIMESTAMP,\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"default_hashtags\\\" JSON DEFAULT '[]',\\n \\\"scopes\\\" JSON DEFAULT '[]',\\n \\\"missing_permissions\\\" JSON DEFAULT '[]',\\n \\\"link_behavior\\\" TEXT DEFAULT 'description',\\n \\\"publish_mode\\\" TEXT DEFAULT 'dry_run',\\n \\\"public_publishing_allowed\\\" BOOLEAN DEFAULT FALSE,\\n \\\"status\\\" TEXT DEFAULT 'connected',\\n \\\"error_message\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"platform\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"youtube\"},\"platform_user_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_username\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token\":{\"type\":\"TEXT\",\"notNull\":false},\"credential_secret_id\":{\"type\":\"TEXT\",\"notNull\":false},\"access_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"refresh_token_secret_name\":{\"type\":\"TEXT\",\"notNull\":false},\"token_expires_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":true},\"default_hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"scopes\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"missing_permissions\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_behavior\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"description\"},\"publish_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"dry_run\"},\"public_publishing_allowed\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"connected\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_accounts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_accounts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_accounts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"0c175da6\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshotCollection\":{\"name\":\"socialpostanalyticssnapshotcollection\",\"className\":\"SocialPostAnalyticsSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostAnalyticsSnapshotCollection\",\"collection\":\"socialpostanalyticssnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-post-analytics-snapshot-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"recordSnapshot\":{\"name\":\"recordSnapshot\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":false,\"memberTypes\":[\"string\",\"SocialPlatformType\",\"PostAnalytics\",\"RawAnalyticsPayload\",\"Date\",\"string | null\"]}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot>\",\"isStatic\":false,\"isPublic\":true},\"findForPost\":{\"name\":\"findForPost\",\"async\":true,\"parameters\":[{\"name\":\"socialPostId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot[]>\",\"isStatic\":false,\"isPublic\":true},\"findLatestForPost\":{\"name\":\"findLatestForPost\",\"async\":true,\"parameters\":[{\"name\":\"socialPostId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPostAnalyticsSnapshot | null>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialPostAnalyticsSnapshot\",\"exportName\":\"SocialPostAnalyticsSnapshotCollection\",\"collectionExportName\":\"SocialPostAnalyticsSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"social_post_analytics_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_post_analytics_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_post_id\\\" UUID,\\n \\\"platform\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"raw\\\" JSON,\\n \\\"captured_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_post_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_posts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"platform\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"raw\":{\"type\":\"JSON\",\"notNull\":false},\"captured_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_post_analytics_snapshots_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_post_analytics_snapshots_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_post_analytics_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_post_analytics_snapshots_social_post_id_idx\",\"columns\":[\"social_post_id\"]}],\"version\":\"bd17983d\"},\"importPath\":\"@happyvertical/smrt-social\"},\"@happyvertical/smrt-social:SocialPostCollection\":{\"name\":\"socialpostcollection\",\"className\":\"SocialPostCollection\",\"qualifiedName\":\"@happyvertical/smrt-social:SocialPostCollection\",\"collection\":\"socialposts\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/social/src/collections/social-post-collection.ts\",\"packageName\":\"@happyvertical/smrt-social\",\"fields\":{},\"methods\":{\"createDraft\":{\"name\":\"createDraft\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"CreateSocialPostDraftOptions\",\"optional\":false}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"PostStatus\",\"optional\":false}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"findForAccount\":{\"name\":\"findForAccount\",\"async\":true,\"parameters\":[{\"name\":\"socialAccountId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"findDueForPublish\":{\"name\":\"findDueForPublish\",\"async\":true,\"parameters\":[{\"name\":\"now\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<SocialPost[]>\",\"isStatic\":false,\"isPublic\":true},\"recordPublishSuccess\":{\"name\":\"recordPublishSuccess\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"data\",\"type\":\"PublishSuccessData\",\"optional\":false}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"recordPublishFailure\":{\"name\":\"recordPublishFailure\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"error\",\"type\":\"Error | string\",\"optional\":false,\"unionBranches\":[{\"type\":\"Error\"},{\"type\":\"string\"}]}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true},\"updateLatestAnalytics\":{\"name\":\"updateLatestAnalytics\",\"async\":true,\"parameters\":[{\"name\":\"post\",\"type\":\"SocialPost\",\"optional\":false},{\"name\":\"analytics\",\"type\":\"PostAnalytics\",\"optional\":false},{\"name\":\"syncedAt\",\"type\":\"Date\",\"optional\":true}],\"returnType\":\"Promise<SocialPost>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"SocialPost\",\"exportName\":\"SocialPostCollection\",\"collectionExportName\":\"SocialPostCollectionCollection\",\"schema\":{\"tableName\":\"social_posts\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"social_posts\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"social_account_id\\\" UUID,\\n \\\"created_by_user_id\\\" UUID,\\n \\\"video_content_id\\\" UUID,\\n \\\"content_id\\\" UUID,\\n \\\"post_type\\\" TEXT DEFAULT 'text',\\n \\\"media_url\\\" TEXT,\\n \\\"title\\\" TEXT,\\n \\\"description\\\" TEXT DEFAULT '',\\n \\\"hashtags\\\" JSON DEFAULT '[]',\\n \\\"link_url\\\" TEXT,\\n \\\"platform_post_id\\\" TEXT,\\n \\\"platform_url\\\" TEXT,\\n \\\"scheduled_at\\\" TIMESTAMP,\\n \\\"published_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"error_message\\\" TEXT,\\n \\\"analytics\\\" JSON,\\n \\\"analytics_last_synced_at\\\" TIMESTAMP\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"social_account_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"social_accounts\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"created_by_user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"video_content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"content_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false},\"post_type\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"text\"},\"media_url\":{\"type\":\"TEXT\",\"notNull\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"},\"hashtags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"link_url\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_post_id\":{\"type\":\"TEXT\",\"notNull\":false},\"platform_url\":{\"type\":\"TEXT\",\"notNull\":false},\"scheduled_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"published_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"draft\"},\"error_message\":{\"type\":\"TEXT\",\"notNull\":false},\"analytics\":{\"type\":\"JSON\",\"notNull\":false},\"analytics_last_synced_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false}},\"indexes\":[{\"name\":\"social_posts_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"social_posts_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"social_posts_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"social_posts_social_account_id_idx\",\"columns\":[\"social_account_id\"]},{\"name\":\"social_posts_created_by_user_id_idx\",\"columns\":[\"created_by_user_id\"]},{\"name\":\"social_posts_video_content_id_idx\",\"columns\":[\"video_content_id\"]},{\"name\":\"social_posts_content_id_idx\",\"columns\":[\"content_id\"]}],\"version\":\"cfb50b15\"},\"importPath\":\"@happyvertical/smrt-social\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-content\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-secrets\",\"@happyvertical/smrt-tenancy\",\"@happyvertical/smrt-video\"]}"));
8
8
  //#endregion
9
9
  //#region src/oauth-state.ts
10
10
  var __defProp$3 = Object.defineProperty;
@@ -411,6 +411,7 @@ var __decorateClass$1 = (decorators, target, key, kind) => {
411
411
  var SocialPost = class extends SmrtObject {
412
412
  tenantId = null;
413
413
  socialAccountId = null;
414
+ createdByUserId = null;
414
415
  videoContentId = null;
415
416
  contentId = null;
416
417
  /**
@@ -499,6 +500,7 @@ var SocialPost = class extends SmrtObject {
499
500
  if (options.errorMessage !== void 0) this.errorMessage = options.errorMessage;
500
501
  if (options.analytics !== void 0) this.analytics = options.analytics;
501
502
  if (options.analyticsLastSyncedAt !== void 0) this.analyticsLastSyncedAt = options.analyticsLastSyncedAt;
503
+ if (options.createdByUserId !== void 0) this.createdByUserId = options.createdByUserId;
502
504
  if (options.tenantId !== void 0) this.tenantId = options.tenantId;
503
505
  }
504
506
  /**
@@ -543,6 +545,7 @@ var SocialPost = class extends SmrtObject {
543
545
  };
544
546
  __decorateClass$1([tenantId({ nullable: true })], SocialPost.prototype, "tenantId", 2);
545
547
  __decorateClass$1([foreignKey(SocialAccount)], SocialPost.prototype, "socialAccountId", 2);
548
+ __decorateClass$1([crossPackageRef("@happyvertical/smrt-users:User")], SocialPost.prototype, "createdByUserId", 2);
546
549
  __decorateClass$1([crossPackageRef("@happyvertical/smrt-video:VideoContent")], SocialPost.prototype, "videoContentId", 2);
547
550
  __decorateClass$1([crossPackageRef("@happyvertical/smrt-content:Content")], SocialPost.prototype, "contentId", 2);
548
551
  SocialPost = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["tenantId","secretService","secret"],"sources":["../src/__smrt-register__.ts","../src/oauth-state.ts","../src/collections/oauth-state-collection.ts","../src/social-account.ts","../src/collections/social-account-collection.ts","../src/social-post.ts","../src/social-post-analytics-snapshot.ts","../src/collections/social-post-analytics-snapshot-collection.ts","../src/collections/social-post-collection.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * OAuth State Model\n *\n * Manages temporary OAuth state for connecting social accounts.\n * Used to verify callback requests and prevent CSRF attacks.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { SocialPlatformType } from './social-account.js';\n\n/**\n * OAuth state creation options\n */\nexport interface OAuthStateOptions extends SmrtObjectOptions {\n /**\n * Platform being connected\n */\n platform?: SocialPlatformType;\n\n /**\n * CSRF state token\n */\n state?: string;\n\n /**\n * PKCE code verifier (for platforms that require it)\n */\n codeVerifier?: string | null;\n\n /**\n * Redirect URI used in the OAuth request\n */\n redirectUri?: string;\n\n /**\n * Requested OAuth scopes\n */\n scopes?: string[];\n\n /**\n * When this state expires\n */\n expiresAt?: Date;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Temporary OAuth state for social account connection\n *\n * OAuthState stores temporary data during the OAuth flow to:\n * - Verify callback requests match initiated requests (CSRF protection)\n * - Store PKCE code verifier for code exchange\n * - Track redirect URI and scopes for verification\n *\n * These records should be cleaned up after successful connection\n * or after expiration.\n *\n * @example\n * ```typescript\n * import { OAuthState } from '@happyvertical/smrt-social';\n *\n * // Create state when initiating OAuth\n * const state = new OAuthState({\n * platform: 'youtube',\n * state: crypto.randomUUID(),\n * codeVerifier: generatePKCEVerifier(),\n * redirectUri: 'https://app.example.com/oauth/callback',\n * scopes: ['youtube.upload', 'youtube.readonly'],\n * expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes\n * });\n * await state.save();\n *\n * // After successful callback, delete the state\n * await state.delete();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'delete'],\n },\n mcp: false,\n cli: { skipApiCheck: true },\n})\nexport class OAuthState extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Platform being connected\n */\n platform: SocialPlatformType = 'youtube';\n\n /**\n * CSRF state token\n * This is sent to the OAuth provider and verified on callback\n */\n state: string = '';\n\n /**\n * PKCE code verifier\n * Required for platforms using PKCE (YouTube, etc.)\n */\n codeVerifier: string | null = null;\n\n /**\n * Redirect URI used in the OAuth request\n * Must match on callback for verification\n */\n redirectUri: string = '';\n\n /**\n * Requested OAuth scopes\n */\n scopes: string[] = [];\n\n /**\n * When this state expires\n * States should be short-lived (10 minutes typical)\n */\n expiresAt: Date = new Date(Date.now() + 10 * 60 * 1000);\n\n constructor(options: OAuthStateOptions = {}) {\n super(options);\n\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.state !== undefined) this.state = options.state;\n if (options.codeVerifier !== undefined)\n this.codeVerifier = options.codeVerifier;\n if (options.redirectUri !== undefined)\n this.redirectUri = options.redirectUri;\n if (options.scopes !== undefined) this.scopes = options.scopes;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n\n /**\n * Check if the state has expired\n */\n get isExpired(): boolean {\n return Date.now() >= this.expiresAt.getTime();\n }\n\n /**\n * Check if the state is still valid\n */\n get isValid(): boolean {\n return !this.isExpired && this.state.length > 0;\n }\n\n /**\n * Verify a callback state matches this record\n */\n verifyState(callbackState: string): boolean {\n return this.isValid && this.state === callbackState;\n }\n\n /**\n * Generate a new random state token\n */\n static generateState(): string {\n return crypto.randomUUID();\n }\n\n /**\n * Generate a PKCE code verifier\n * Returns a 43-128 character random string\n */\n static generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n );\n }\n\n /**\n * Generate PKCE code challenge from verifier (S256 method)\n * Note: This requires async crypto operations\n */\n static async generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { OAuthState } from '../oauth-state.js';\n\nexport class OAuthStateCollection extends SmrtCollection<OAuthState> {\n static readonly _itemClass = OAuthState;\n\n async findByState(state: string): Promise<OAuthState | null> {\n return this.get({ state });\n }\n\n async findExpired(now: Date = new Date()): Promise<OAuthState[]> {\n return this.list({\n where: { 'expiresAt <=': now },\n orderBy: 'expiresAt ASC',\n });\n }\n\n async deleteExpired(now: Date = new Date()): Promise<number> {\n const expired = await this.findExpired(now);\n await Promise.all(expired.map((state) => state.delete()));\n return expired.length;\n }\n}\n","/**\n * Social Account Model\n *\n * Manages connected social media platform accounts for publishing.\n * Stores OAuth credentials and account settings.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { getSocialPlatformSetup } from './server.js';\n\n/**\n * Supported social platforms\n */\nexport type SocialPlatformType =\n | 'youtube'\n | 'threads'\n | 'x'\n | 'bluesky'\n | 'facebook';\n\n/**\n * Account connection status\n */\nexport type AccountStatus =\n | 'connected'\n | 'disconnected'\n | 'expired'\n | 'missing_permissions'\n | 'error';\n\n/**\n * Link behavior for posts with links\n */\nexport type LinkBehavior =\n | 'description'\n | 'inline'\n | 'attachment'\n | 'reply'\n | 'none';\n\n/**\n * Controls how far publish operations are allowed to go.\n */\nexport type PublishMode =\n | 'dry_run'\n | 'stage_remote'\n | 'private_or_scheduled'\n | 'public';\n\n/**\n * Social account creation options\n */\nexport interface SocialAccountOptions extends SmrtObjectOptions {\n /**\n * Human-readable name for the account\n */\n name?: string;\n\n /**\n * Social platform type\n */\n platform?: SocialPlatformType;\n\n /**\n * Platform-specific user ID\n */\n platformUserId?: string | null;\n\n /**\n * Platform username/handle\n */\n platformUsername?: string | null;\n\n /**\n * Profile URL on the platform\n */\n platformUrl?: string | null;\n\n /**\n * Deprecated raw OAuth access token\n */\n accessToken?: string | null;\n\n /**\n * Deprecated raw OAuth refresh token\n */\n refreshToken?: string | null;\n\n /**\n * Secret name containing the full platform credential payload\n */\n credentialSecretId?: string | null;\n\n /**\n * Secret name containing the OAuth access token\n */\n accessTokenSecretName?: string | null;\n\n /**\n * Secret name containing the OAuth refresh token\n */\n refreshTokenSecretName?: string | null;\n\n /**\n * Token expiration time\n */\n tokenExpiresAt?: Date | null;\n\n /**\n * Whether the account is active\n * @default true\n */\n isActive?: boolean;\n\n /**\n * Default hashtags to add to posts\n */\n defaultHashtags?: string[];\n\n /**\n * Granted OAuth scopes or platform permissions\n */\n scopes?: string[];\n\n /**\n * Required permissions that still need approval/granting\n */\n missingPermissions?: string[];\n\n /**\n * How to handle links in posts\n * @default 'description'\n */\n linkBehavior?: LinkBehavior;\n\n /**\n * Safety mode for publish operations.\n * @default 'dry_run'\n */\n publishMode?: PublishMode;\n\n /**\n * Separate latch required before public publishing can happen.\n * @default false\n */\n publicPublishingAllowed?: boolean;\n\n /**\n * Account connection status\n * @default 'connected'\n */\n status?: AccountStatus;\n\n /**\n * Error message if status is 'error'\n */\n errorMessage?: string | null;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Connected social media account for publishing\n *\n * SocialAccount represents a connected social platform account\n * with OAuth credentials and publishing settings. Accounts can\n * be used to publish content to multiple platforms.\n *\n * @example\n * ```typescript\n * import { SocialAccount } from '@happyvertical/smrt-social';\n *\n * const account = new SocialAccount({\n * name: 'Bentley News YouTube',\n * platform: 'youtube',\n * platformUserId: 'UC...',\n * platformUsername: 'Bentley News',\n * accessToken: '...encrypted...',\n * refreshToken: '...encrypted...',\n * tokenExpiresAt: new Date('2026-02-25'),\n * defaultHashtags: ['news', 'local', 'bentley'],\n * linkBehavior: 'description',\n * });\n * await account.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'update', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: { skipApiCheck: true },\n})\nexport class SocialAccount extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Human-readable name for the account\n */\n name: string = '';\n\n /**\n * Social platform type\n */\n platform: SocialPlatformType = 'youtube';\n\n /**\n * Platform-specific user ID\n */\n platformUserId: string | null = null;\n\n /**\n * Platform username/handle\n */\n platformUsername: string | null = null;\n\n /**\n * Profile URL on the platform\n */\n platformUrl: string | null = null;\n\n /**\n * Deprecated raw OAuth access token.\n * Prefer credentialSecretId/accessTokenSecretName.\n */\n accessToken: string | null = null;\n\n /**\n * Deprecated raw OAuth refresh token.\n * Prefer credentialSecretId/refreshTokenSecretName.\n */\n refreshToken: string | null = null;\n\n /**\n * Secret name containing the complete platform credential payload.\n */\n credentialSecretId: string | null = null;\n\n /**\n * Secret name containing only the access token.\n */\n accessTokenSecretName: string | null = null;\n\n /**\n * Secret name containing only the refresh token.\n */\n refreshTokenSecretName: string | null = null;\n\n /**\n * Token expiration time\n */\n tokenExpiresAt: Date | null = null;\n\n /**\n * Whether the account is active\n */\n isActive: boolean = true;\n\n /**\n * Default hashtags to add to posts\n */\n defaultHashtags: string[] = [];\n\n /**\n * Granted OAuth scopes or platform permissions.\n */\n scopes: string[] = [];\n\n /**\n * Required permissions that still need app review or user grant.\n */\n missingPermissions: string[] = [];\n\n /**\n * How to handle links in posts\n * - description: Include link in post body/description\n * - reply: Post link as a reply (better for X algorithm)\n * - none: Don't include link\n */\n linkBehavior: LinkBehavior = 'description';\n\n /**\n * Safety mode for publish operations.\n */\n publishMode: PublishMode = 'dry_run';\n\n /**\n * Separate latch required before public publishing is allowed.\n */\n publicPublishingAllowed: boolean = false;\n\n /**\n * Account connection status\n */\n status: AccountStatus = 'connected';\n\n /**\n * Error message if status is 'error'\n */\n errorMessage: string | null = null;\n\n constructor(options: SocialAccountOptions = {}) {\n super(options);\n\n if (options.name !== undefined) this.name = options.name;\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.platformUserId !== undefined)\n this.platformUserId = options.platformUserId;\n if (options.platformUsername !== undefined)\n this.platformUsername = options.platformUsername;\n if (options.platformUrl !== undefined)\n this.platformUrl = options.platformUrl;\n if (options.accessToken !== undefined)\n this.accessToken = options.accessToken;\n if (options.refreshToken !== undefined)\n this.refreshToken = options.refreshToken;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId;\n if (options.accessTokenSecretName !== undefined)\n this.accessTokenSecretName = options.accessTokenSecretName;\n if (options.refreshTokenSecretName !== undefined)\n this.refreshTokenSecretName = options.refreshTokenSecretName;\n if (options.tokenExpiresAt !== undefined)\n this.tokenExpiresAt = options.tokenExpiresAt;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.defaultHashtags !== undefined)\n this.defaultHashtags = options.defaultHashtags;\n if (options.scopes !== undefined) this.scopes = options.scopes;\n if (options.missingPermissions !== undefined)\n this.missingPermissions = options.missingPermissions;\n if (options.linkBehavior !== undefined)\n this.linkBehavior = options.linkBehavior;\n if (options.publishMode !== undefined)\n this.publishMode = options.publishMode;\n if (options.publicPublishingAllowed !== undefined)\n this.publicPublishingAllowed = options.publicPublishingAllowed;\n if (options.status !== undefined) this.status = options.status;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n\n if (options.platform !== undefined) {\n const setup = getSocialPlatformSetup(this.platform);\n if (options.linkBehavior === undefined) {\n this.linkBehavior = setup.defaultLinkBehavior;\n }\n if (options.publishMode === undefined) {\n this.publishMode = setup.defaultPublishMode;\n }\n }\n }\n\n /**\n * Social accounts need a slug identity that is scoped by tenant and platform.\n * A newsroom may connect `@localnews` on X, YouTube, Threads, and Facebook;\n * the generic name-derived slug would make those accounts overwrite each\n * other through SMRT's slug/context upsert identity.\n */\n async getSlug(): Promise<string | null | undefined> {\n if (!this.slug) {\n const identity =\n this.platformUserId || this.platformUsername || this.name || this.id;\n if (!identity) {\n return this.slug;\n }\n\n const source = [\n this.tenantId ? `tenant-${this.tenantId}` : 'no-tenant',\n this.platform,\n identity,\n ]\n .filter(Boolean)\n .join('-');\n\n this.slug = source\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n }\n\n return this.slug;\n }\n\n /**\n * Check if the token is expired or will expire soon\n */\n get isTokenExpired(): boolean {\n if (!this.tokenExpiresAt) return false;\n // Expire 5 minutes early to allow for refresh\n const expiryBuffer = 5 * 60 * 1000;\n return Date.now() >= this.tokenExpiresAt.getTime() - expiryBuffer;\n }\n\n /**\n * Check if the account needs attention (expired or error)\n */\n get needsAttention(): boolean {\n return (\n this.status !== 'connected' ||\n this.isTokenExpired ||\n this.missingPermissions.length > 0 ||\n (this.publishMode === 'public' && !this.publicPublishingAllowed)\n );\n }\n\n /**\n * Check if the account is ready for publishing\n */\n get isReady(): boolean {\n return (\n this.isActive &&\n this.status === 'connected' &&\n this.hasCredentials &&\n this.missingPermissions.length === 0 &&\n !this.isTokenExpired &&\n (this.publishMode !== 'public' || this.publicPublishingAllowed)\n );\n }\n\n /**\n * Effective publish mode after applying the public-publishing latch.\n */\n get effectivePublishMode(): PublishMode {\n if (this.publishMode === 'public' && !this.publicPublishingAllowed) {\n return 'dry_run';\n }\n return this.publishMode;\n }\n\n /**\n * Check whether any usable credential reference exists.\n */\n get hasCredentials(): boolean {\n return Boolean(\n this.credentialSecretId || this.accessTokenSecretName || this.accessToken,\n );\n }\n\n /**\n * Store all platform credentials in smrt-secrets as a single JSON payload.\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n this.id = randomUUID();\n }\n\n const secretName = this.credentialSecretId ?? `social-account-${this.id}`;\n const tenantId = this.requireCredentialTenantId(\n 'store social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n\n await secretService.storeForTenant(\n tenantId,\n secretName,\n JSON.stringify(credentials),\n {\n description: options.description ?? `Credentials for ${this.name}`,\n category: options.category ?? 'social',\n },\n );\n\n this.credentialSecretId = secretName;\n\n await this.withCredentialTenantContext(async () => {\n await this.save();\n });\n }\n\n /**\n * Retrieve platform credentials from smrt-secrets, falling back to deprecated fields.\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n if (this.accessTokenSecretName || this.refreshTokenSecretName) {\n const tenantId = this.requireCredentialTenantId(\n 'retrieve social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n const credentials: Record<string, unknown> = {};\n\n if (this.accessTokenSecretName) {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.accessTokenSecretName,\n );\n credentials.accessToken = secret.value;\n }\n\n if (this.refreshTokenSecretName) {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.refreshTokenSecretName,\n );\n credentials.refreshToken = secret.value;\n }\n\n return credentials;\n }\n\n if (!this.accessToken && !this.refreshToken) return null;\n return {\n accessToken: this.accessToken,\n refreshToken: this.refreshToken,\n };\n }\n\n const credentialSecretId = this.credentialSecretId;\n const tenantId = this.requireCredentialTenantId(\n 'retrieve social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n const secret = await secretService.retrieveForTenant(\n tenantId,\n credentialSecretId,\n );\n return JSON.parse(secret.value);\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n const currentTenantId = getCurrentTenant()?.tenantId;\n\n if (!tenantId || currentTenantId === tenantId) {\n return fn();\n }\n\n return withTenant({ tenantId }, fn);\n }\n\n private getCredentialTenantId(): string | null {\n if (this.tenantId) {\n return this.tenantId;\n }\n\n return getCurrentTenant()?.tenantId ?? null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { SocialAccount, type SocialPlatformType } from '../social-account.js';\n\nexport class SocialAccountCollection extends SmrtCollection<SocialAccount> {\n static readonly _itemClass = SocialAccount;\n\n async findActive(platform?: SocialPlatformType): Promise<SocialAccount[]> {\n const accounts = await this.list({\n where: {\n isActive: true,\n ...(platform ? { platform } : {}),\n },\n orderBy: 'name ASC',\n });\n\n return accounts;\n }\n\n async findReady(platform?: SocialPlatformType): Promise<SocialAccount[]> {\n const accounts = await this.findActive(platform);\n return accounts.filter((account) => account.isReady);\n }\n\n async findNeedsAttention(): Promise<SocialAccount[]> {\n const accounts = await this.list({\n where: { isActive: true },\n orderBy: 'name ASC',\n });\n return accounts.filter((account) => account.needsAttention);\n }\n}\n","/**\n * Social Post Model\n *\n * Manages posts published or scheduled to social media platforms.\n * Tracks publishing status and engagement analytics.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { SocialAccount } from './social-account.js';\n\n/**\n * Post status\n */\nexport type PostStatus =\n | 'draft'\n | 'pending_approval'\n | 'approved'\n | 'scheduled'\n | 'publishing'\n | 'dry_run'\n | 'staged'\n | 'published'\n | 'failed'\n | 'cancelled';\n\n/**\n * Social post type\n */\nexport type SocialPostType = 'text' | 'link' | 'image' | 'video';\n\n/**\n * Post analytics\n */\nexport interface PostAnalytics {\n /**\n * View/impression count\n */\n views?: number;\n\n /**\n * Impression count when the platform distinguishes it from views\n */\n impressions?: number;\n\n /**\n * Like/favorite count\n */\n likes?: number;\n\n /**\n * Comment count\n */\n comments?: number;\n\n /**\n * Share/repost count\n */\n shares?: number;\n\n /**\n * Link click count\n */\n clicks?: number;\n\n /**\n * Raw platform analytics payload\n */\n raw?: unknown;\n\n /**\n * When analytics were last updated\n */\n lastUpdated?: Date;\n}\n\n/**\n * Social post creation options\n */\nexport interface SocialPostOptions extends SmrtObjectOptions {\n /**\n * Social account to publish to\n */\n socialAccountId?: string | null;\n\n /**\n * Video content to publish\n */\n videoContentId?: string | null;\n\n /**\n * Generic content ID (for non-video content)\n */\n contentId?: string | null;\n\n /**\n * High-level post type\n */\n postType?: SocialPostType;\n\n /**\n * Public media URL for platforms that cannot upload buffers\n */\n mediaUrl?: string | null;\n\n /**\n * Post title (for platforms that support it)\n */\n title?: string | null;\n\n /**\n * Post description/caption\n */\n description?: string;\n\n /**\n * Hashtags to include\n */\n hashtags?: string[];\n\n /**\n * Link URL to include\n */\n linkUrl?: string | null;\n\n /**\n * Platform-specific post ID (after publishing)\n */\n platformPostId?: string | null;\n\n /**\n * Public post URL (after publishing)\n */\n platformUrl?: string | null;\n\n /**\n * Scheduled publish time\n */\n scheduledAt?: Date | null;\n\n /**\n * Actual publish time\n */\n publishedAt?: Date | null;\n\n /**\n * Post status\n * @default 'draft'\n */\n status?: PostStatus;\n\n /**\n * Error message if status is 'failed'\n */\n errorMessage?: string | null;\n\n /**\n * Engagement analytics\n */\n analytics?: PostAnalytics;\n\n /**\n * When analytics were last synced\n */\n analyticsLastSyncedAt?: Date | null;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Social media post for multi-platform publishing\n *\n * SocialPost represents a post published or scheduled to a social\n * platform. It tracks publishing status and engagement analytics,\n * and can reference VideoContent for video posts.\n *\n * @example\n * ```typescript\n * import { SocialPost } from '@happyvertical/smrt-social';\n *\n * const post = new SocialPost({\n * socialAccountId: 'account-123',\n * videoContentId: 'video-456',\n * title: 'Breaking News from Bentley',\n * description: 'Latest updates from the town council meeting.',\n * hashtags: ['news', 'local', 'bentley'],\n * linkUrl: 'https://example.com/article',\n * scheduledAt: new Date('2026-01-26T18:00:00Z'),\n * });\n * await post.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'update', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: true,\n})\nexport class SocialPost extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Social account to publish to\n */\n @foreignKey(SocialAccount)\n socialAccountId: string | null = null;\n\n /**\n * Video content to publish\n *\n * Cross-package target (`@happyvertical/smrt-video`), so it is a\n * `@crossPackageRef` rather than a `@foreignKey`: no DDL FK constraint and\n * no value import back into this package.\n */\n @crossPackageRef('@happyvertical/smrt-video:VideoContent')\n videoContentId: string | null = null;\n\n /**\n * Generic content ID (for non-video content)\n */\n @crossPackageRef('@happyvertical/smrt-content:Content')\n contentId: string | null = null;\n\n /**\n * High-level post type\n */\n postType: SocialPostType = 'text';\n\n /**\n * Public media URL for platforms that require URL media publishing\n */\n mediaUrl: string | null = null;\n\n /**\n * Post title (for platforms that support it)\n */\n title: string | null = null;\n\n /**\n * Post description/caption\n */\n description: string = '';\n\n /**\n * Hashtags to include\n */\n hashtags: string[] = [];\n\n /**\n * Link URL to include in the post\n */\n linkUrl: string | null = null;\n\n /**\n * Platform-specific post ID (set after publishing)\n */\n platformPostId: string | null = null;\n\n /**\n * Public post URL (set after publishing)\n */\n platformUrl: string | null = null;\n\n /**\n * Scheduled publish time\n * If set, post will be scheduled instead of published immediately\n */\n scheduledAt: Date | null = null;\n\n /**\n * Actual publish time\n */\n publishedAt: Date | null = null;\n\n /**\n * Post status\n * - draft: Not yet submitted for publishing\n * - pending_approval: Awaiting editorial approval\n * - approved: Approved and ready to publish\n * - scheduled: Queued for future publishing\n * - publishing: Currently being published\n * - dry_run: Payload was validated without remote write\n * - staged: Non-public platform object/container was created\n * - published: Successfully published\n * - failed: Publishing failed\n * - cancelled: Publishing was cancelled\n */\n status: PostStatus = 'draft';\n\n /**\n * Error message if status is 'failed'\n */\n errorMessage: string | null = null;\n\n /**\n * Engagement analytics\n */\n analytics: PostAnalytics = {};\n\n /**\n * When analytics were last synced\n */\n analyticsLastSyncedAt: Date | null = null;\n\n constructor(options: SocialPostOptions = {}) {\n super(options);\n\n if (options.socialAccountId !== undefined)\n this.socialAccountId = options.socialAccountId;\n if (options.videoContentId !== undefined)\n this.videoContentId = options.videoContentId;\n if (options.contentId !== undefined) this.contentId = options.contentId;\n if (options.postType !== undefined) this.postType = options.postType;\n if (options.mediaUrl !== undefined) this.mediaUrl = options.mediaUrl;\n if (options.title !== undefined) this.title = options.title;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.hashtags !== undefined) this.hashtags = options.hashtags;\n if (options.linkUrl !== undefined) this.linkUrl = options.linkUrl;\n if (options.platformPostId !== undefined)\n this.platformPostId = options.platformPostId;\n if (options.platformUrl !== undefined)\n this.platformUrl = options.platformUrl;\n if (options.scheduledAt !== undefined)\n this.scheduledAt = options.scheduledAt;\n if (options.publishedAt !== undefined)\n this.publishedAt = options.publishedAt;\n if (options.status !== undefined) this.status = options.status;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.analytics !== undefined) this.analytics = options.analytics;\n if (options.analyticsLastSyncedAt !== undefined)\n this.analyticsLastSyncedAt = options.analyticsLastSyncedAt;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n\n /**\n * Check if the post is scheduled for future publishing\n */\n get isScheduled(): boolean {\n return this.status === 'scheduled' && this.scheduledAt !== null;\n }\n\n /**\n * Check if the post has been published\n */\n get isPublished(): boolean {\n return this.status === 'published';\n }\n\n /**\n * Check if the post can be edited (draft or failed)\n */\n get isEditable(): boolean {\n return (\n this.status === 'draft' ||\n this.status === 'pending_approval' ||\n this.status === 'failed'\n );\n }\n\n /**\n * Check if the post is due for publishing now.\n */\n get isDueForPublish(): boolean {\n if (this.status !== 'approved' && this.status !== 'scheduled') {\n return false;\n }\n return !this.scheduledAt || this.scheduledAt.getTime() <= Date.now();\n }\n\n /**\n * Get formatted hashtag string\n */\n get hashtagString(): string {\n return this.hashtags\n .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`))\n .join(' ');\n }\n\n /**\n * Get the full post text with hashtags\n */\n get fullText(): string {\n const parts = [this.description];\n if (this.hashtags.length > 0) {\n parts.push(this.hashtagString);\n }\n return parts.join('\\n\\n');\n }\n}\n","/**\n * Social Post Analytics Snapshot Model\n *\n * Stores timestamped normalized and raw platform analytics payloads.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { SocialPlatformType } from './social-account.js';\nimport { type PostAnalytics, SocialPost } from './social-post.js';\n\nexport type RawAnalyticsPayload = Record<string, unknown> | unknown[] | null;\n\nexport interface SocialPostAnalyticsSnapshotOptions extends SmrtObjectOptions {\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n\n /**\n * Social post being measured\n */\n socialPostId?: string | null;\n\n /**\n * Platform that produced this snapshot\n */\n platform?: SocialPlatformType | null;\n\n /**\n * Normalized platform analytics.\n */\n analytics?: PostAnalytics;\n\n /**\n * Raw platform payload\n */\n raw?: RawAnalyticsPayload;\n\n /**\n * When the platform data was captured\n */\n capturedAt?: Date;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: true,\n})\nexport class SocialPostAnalyticsSnapshot extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey(SocialPost)\n socialPostId: string | null = null;\n\n @field({ required: true })\n platform: SocialPlatformType | null = null;\n\n analytics: PostAnalytics = {};\n\n @field({ type: 'json', nullable: true })\n raw: RawAnalyticsPayload = null;\n\n capturedAt: Date = new Date();\n\n constructor(options: SocialPostAnalyticsSnapshotOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.socialPostId !== undefined)\n this.socialPostId = options.socialPostId;\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.analytics !== undefined) this.analytics = options.analytics;\n if (options.raw !== undefined) this.raw = options.raw;\n if (options.capturedAt !== undefined) this.capturedAt = options.capturedAt;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport type { SocialPlatformType } from '../social-account.js';\nimport type { PostAnalytics } from '../social-post.js';\nimport {\n type RawAnalyticsPayload,\n SocialPostAnalyticsSnapshot,\n} from '../social-post-analytics-snapshot.js';\n\nfunction isRawAnalyticsPayload(value: unknown): value is RawAnalyticsPayload {\n return (\n value === null ||\n Array.isArray(value) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\nexport class SocialPostAnalyticsSnapshotCollection extends SmrtCollection<SocialPostAnalyticsSnapshot> {\n static readonly _itemClass = SocialPostAnalyticsSnapshot;\n\n async recordSnapshot(options: {\n socialPostId: string;\n platform: SocialPlatformType;\n metrics: PostAnalytics;\n raw?: RawAnalyticsPayload;\n capturedAt?: Date;\n tenantId?: string | null;\n }): Promise<SocialPostAnalyticsSnapshot> {\n const snapshot = await this.create({\n socialPostId: options.socialPostId,\n platform: options.platform,\n analytics: options.metrics,\n raw:\n options.raw ??\n (isRawAnalyticsPayload(options.metrics.raw)\n ? options.metrics.raw\n : null),\n capturedAt: options.capturedAt ?? new Date(),\n tenantId: options.tenantId,\n });\n return snapshot;\n }\n\n async findForPost(\n socialPostId: string,\n ): Promise<SocialPostAnalyticsSnapshot[]> {\n return this.list({\n where: { socialPostId },\n orderBy: 'capturedAt DESC',\n });\n }\n\n async findLatestForPost(\n socialPostId: string,\n ): Promise<SocialPostAnalyticsSnapshot | null> {\n const snapshots = await this.findForPost(socialPostId);\n return snapshots[0] ?? null;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n type PostAnalytics,\n type PostStatus,\n SocialPost,\n type SocialPostType,\n} from '../social-post.js';\n\nexport interface CreateSocialPostDraftOptions {\n socialAccountId: string;\n contentId?: string | null;\n videoContentId?: string | null;\n postType?: SocialPostType;\n title?: string | null;\n description?: string;\n hashtags?: string[];\n linkUrl?: string | null;\n mediaUrl?: string | null;\n scheduledAt?: Date | null;\n tenantId?: string | null;\n}\n\nexport interface PublishSuccessData {\n platformPostId: string;\n platformUrl: string;\n publishedAt?: Date;\n status?: PostStatus;\n}\n\nexport class SocialPostCollection extends SmrtCollection<SocialPost> {\n static readonly _itemClass = SocialPost;\n\n async createDraft(\n options: CreateSocialPostDraftOptions,\n ): Promise<SocialPost> {\n return this.create({\n ...options,\n postType:\n options.postType ??\n (options.videoContentId || options.mediaUrl\n ? 'video'\n : options.linkUrl\n ? 'link'\n : 'text'),\n status: options.scheduledAt ? 'scheduled' : 'draft',\n });\n }\n\n async findByStatus(status: PostStatus): Promise<SocialPost[]> {\n return this.list({ where: { status }, orderBy: 'scheduledAt ASC' });\n }\n\n async findForAccount(socialAccountId: string): Promise<SocialPost[]> {\n return this.list({\n where: { socialAccountId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findDueForPublish(now: Date = new Date()): Promise<SocialPost[]> {\n const [approved, scheduled] = await Promise.all([\n this.list({\n where: { status: 'approved' },\n orderBy: 'scheduledAt ASC',\n }),\n this.list({\n where: { status: 'scheduled', 'scheduledAt <=': now },\n orderBy: 'scheduledAt ASC',\n }),\n ]);\n\n return [\n ...approved.filter((post) => post.isDueForPublish),\n ...scheduled,\n ].sort(\n (a, b) =>\n (a.scheduledAt?.getTime() ?? 0) - (b.scheduledAt?.getTime() ?? 0),\n );\n }\n\n async recordPublishSuccess(\n post: SocialPost,\n data: PublishSuccessData,\n ): Promise<SocialPost> {\n const status = data.status ?? 'published';\n post.platformPostId = data.platformPostId;\n post.platformUrl = data.platformUrl;\n if (status === 'published') {\n post.publishedAt = data.publishedAt ?? post.publishedAt ?? new Date();\n }\n post.status = status;\n post.errorMessage = null;\n await post.save();\n return post;\n }\n\n async recordPublishFailure(\n post: SocialPost,\n error: Error | string,\n ): Promise<SocialPost> {\n post.status = 'failed';\n post.errorMessage = formatPublishError(error);\n await post.save();\n return post;\n }\n\n async updateLatestAnalytics(\n post: SocialPost,\n analytics: PostAnalytics,\n syncedAt: Date = new Date(),\n ): Promise<SocialPost> {\n post.analytics = {\n ...analytics,\n lastUpdated: analytics.lastUpdated ?? syncedAt,\n };\n post.analyticsLastSyncedAt = syncedAt;\n await post.save();\n return post;\n }\n}\n\nfunction formatPublishError(error: Error | string): string {\n if (typeof error === 'string') {\n return error;\n }\n\n const details = [error.message];\n const code = (error as Error & { code?: unknown }).code;\n if (code !== undefined) {\n details.push(`code=${String(code)}`);\n }\n if (error.stack) {\n details.push(error.stack);\n }\n return details.join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["tenantId","secretService","secret"],"sources":["../src/__smrt-register__.ts","../src/oauth-state.ts","../src/collections/oauth-state-collection.ts","../src/social-account.ts","../src/collections/social-account-collection.ts","../src/social-post.ts","../src/social-post-analytics-snapshot.ts","../src/collections/social-post-analytics-snapshot-collection.ts","../src/collections/social-post-collection.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * OAuth State Model\n *\n * Manages temporary OAuth state for connecting social accounts.\n * Used to verify callback requests and prevent CSRF attacks.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { SocialPlatformType } from './social-account.js';\n\n/**\n * OAuth state creation options\n */\nexport interface OAuthStateOptions extends SmrtObjectOptions {\n /**\n * Platform being connected\n */\n platform?: SocialPlatformType;\n\n /**\n * CSRF state token\n */\n state?: string;\n\n /**\n * PKCE code verifier (for platforms that require it)\n */\n codeVerifier?: string | null;\n\n /**\n * Redirect URI used in the OAuth request\n */\n redirectUri?: string;\n\n /**\n * Requested OAuth scopes\n */\n scopes?: string[];\n\n /**\n * When this state expires\n */\n expiresAt?: Date;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Temporary OAuth state for social account connection\n *\n * OAuthState stores temporary data during the OAuth flow to:\n * - Verify callback requests match initiated requests (CSRF protection)\n * - Store PKCE code verifier for code exchange\n * - Track redirect URI and scopes for verification\n *\n * These records should be cleaned up after successful connection\n * or after expiration.\n *\n * @example\n * ```typescript\n * import { OAuthState } from '@happyvertical/smrt-social';\n *\n * // Create state when initiating OAuth\n * const state = new OAuthState({\n * platform: 'youtube',\n * state: crypto.randomUUID(),\n * codeVerifier: generatePKCEVerifier(),\n * redirectUri: 'https://app.example.com/oauth/callback',\n * scopes: ['youtube.upload', 'youtube.readonly'],\n * expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes\n * });\n * await state.save();\n *\n * // After successful callback, delete the state\n * await state.delete();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'delete'],\n },\n mcp: false,\n cli: { skipApiCheck: true },\n})\nexport class OAuthState extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Platform being connected\n */\n platform: SocialPlatformType = 'youtube';\n\n /**\n * CSRF state token\n * This is sent to the OAuth provider and verified on callback\n */\n state: string = '';\n\n /**\n * PKCE code verifier\n * Required for platforms using PKCE (YouTube, etc.)\n */\n codeVerifier: string | null = null;\n\n /**\n * Redirect URI used in the OAuth request\n * Must match on callback for verification\n */\n redirectUri: string = '';\n\n /**\n * Requested OAuth scopes\n */\n scopes: string[] = [];\n\n /**\n * When this state expires\n * States should be short-lived (10 minutes typical)\n */\n expiresAt: Date = new Date(Date.now() + 10 * 60 * 1000);\n\n constructor(options: OAuthStateOptions = {}) {\n super(options);\n\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.state !== undefined) this.state = options.state;\n if (options.codeVerifier !== undefined)\n this.codeVerifier = options.codeVerifier;\n if (options.redirectUri !== undefined)\n this.redirectUri = options.redirectUri;\n if (options.scopes !== undefined) this.scopes = options.scopes;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n\n /**\n * Check if the state has expired\n */\n get isExpired(): boolean {\n return Date.now() >= this.expiresAt.getTime();\n }\n\n /**\n * Check if the state is still valid\n */\n get isValid(): boolean {\n return !this.isExpired && this.state.length > 0;\n }\n\n /**\n * Verify a callback state matches this record\n */\n verifyState(callbackState: string): boolean {\n return this.isValid && this.state === callbackState;\n }\n\n /**\n * Generate a new random state token\n */\n static generateState(): string {\n return crypto.randomUUID();\n }\n\n /**\n * Generate a PKCE code verifier\n * Returns a 43-128 character random string\n */\n static generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n );\n }\n\n /**\n * Generate PKCE code challenge from verifier (S256 method)\n * Note: This requires async crypto operations\n */\n static async generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { OAuthState } from '../oauth-state.js';\n\nexport class OAuthStateCollection extends SmrtCollection<OAuthState> {\n static readonly _itemClass = OAuthState;\n\n async findByState(state: string): Promise<OAuthState | null> {\n return this.get({ state });\n }\n\n async findExpired(now: Date = new Date()): Promise<OAuthState[]> {\n return this.list({\n where: { 'expiresAt <=': now },\n orderBy: 'expiresAt ASC',\n });\n }\n\n async deleteExpired(now: Date = new Date()): Promise<number> {\n const expired = await this.findExpired(now);\n await Promise.all(expired.map((state) => state.delete()));\n return expired.length;\n }\n}\n","/**\n * Social Account Model\n *\n * Manages connected social media platform accounts for publishing.\n * Stores OAuth credentials and account settings.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport {\n getCurrentTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport { getSocialPlatformSetup } from './server.js';\n\n/**\n * Supported social platforms\n */\nexport type SocialPlatformType =\n | 'youtube'\n | 'threads'\n | 'x'\n | 'bluesky'\n | 'facebook';\n\n/**\n * Account connection status\n */\nexport type AccountStatus =\n | 'connected'\n | 'disconnected'\n | 'expired'\n | 'missing_permissions'\n | 'error';\n\n/**\n * Link behavior for posts with links\n */\nexport type LinkBehavior =\n | 'description'\n | 'inline'\n | 'attachment'\n | 'reply'\n | 'none';\n\n/**\n * Controls how far publish operations are allowed to go.\n */\nexport type PublishMode =\n | 'dry_run'\n | 'stage_remote'\n | 'private_or_scheduled'\n | 'public';\n\n/**\n * Social account creation options\n */\nexport interface SocialAccountOptions extends SmrtObjectOptions {\n /**\n * Human-readable name for the account\n */\n name?: string;\n\n /**\n * Social platform type\n */\n platform?: SocialPlatformType;\n\n /**\n * Platform-specific user ID\n */\n platformUserId?: string | null;\n\n /**\n * Platform username/handle\n */\n platformUsername?: string | null;\n\n /**\n * Profile URL on the platform\n */\n platformUrl?: string | null;\n\n /**\n * Deprecated raw OAuth access token\n */\n accessToken?: string | null;\n\n /**\n * Deprecated raw OAuth refresh token\n */\n refreshToken?: string | null;\n\n /**\n * Secret name containing the full platform credential payload\n */\n credentialSecretId?: string | null;\n\n /**\n * Secret name containing the OAuth access token\n */\n accessTokenSecretName?: string | null;\n\n /**\n * Secret name containing the OAuth refresh token\n */\n refreshTokenSecretName?: string | null;\n\n /**\n * Token expiration time\n */\n tokenExpiresAt?: Date | null;\n\n /**\n * Whether the account is active\n * @default true\n */\n isActive?: boolean;\n\n /**\n * Default hashtags to add to posts\n */\n defaultHashtags?: string[];\n\n /**\n * Granted OAuth scopes or platform permissions\n */\n scopes?: string[];\n\n /**\n * Required permissions that still need approval/granting\n */\n missingPermissions?: string[];\n\n /**\n * How to handle links in posts\n * @default 'description'\n */\n linkBehavior?: LinkBehavior;\n\n /**\n * Safety mode for publish operations.\n * @default 'dry_run'\n */\n publishMode?: PublishMode;\n\n /**\n * Separate latch required before public publishing can happen.\n * @default false\n */\n publicPublishingAllowed?: boolean;\n\n /**\n * Account connection status\n * @default 'connected'\n */\n status?: AccountStatus;\n\n /**\n * Error message if status is 'error'\n */\n errorMessage?: string | null;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Connected social media account for publishing\n *\n * SocialAccount represents a connected social platform account\n * with OAuth credentials and publishing settings. Accounts can\n * be used to publish content to multiple platforms.\n *\n * @example\n * ```typescript\n * import { SocialAccount } from '@happyvertical/smrt-social';\n *\n * const account = new SocialAccount({\n * name: 'Bentley News YouTube',\n * platform: 'youtube',\n * platformUserId: 'UC...',\n * platformUsername: 'Bentley News',\n * accessToken: '...encrypted...',\n * refreshToken: '...encrypted...',\n * tokenExpiresAt: new Date('2026-02-25'),\n * defaultHashtags: ['news', 'local', 'bentley'],\n * linkBehavior: 'description',\n * });\n * await account.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'update', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: { skipApiCheck: true },\n})\nexport class SocialAccount extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Human-readable name for the account\n */\n name: string = '';\n\n /**\n * Social platform type\n */\n platform: SocialPlatformType = 'youtube';\n\n /**\n * Platform-specific user ID\n */\n platformUserId: string | null = null;\n\n /**\n * Platform username/handle\n */\n platformUsername: string | null = null;\n\n /**\n * Profile URL on the platform\n */\n platformUrl: string | null = null;\n\n /**\n * Deprecated raw OAuth access token.\n * Prefer credentialSecretId/accessTokenSecretName.\n */\n accessToken: string | null = null;\n\n /**\n * Deprecated raw OAuth refresh token.\n * Prefer credentialSecretId/refreshTokenSecretName.\n */\n refreshToken: string | null = null;\n\n /**\n * Secret name containing the complete platform credential payload.\n */\n credentialSecretId: string | null = null;\n\n /**\n * Secret name containing only the access token.\n */\n accessTokenSecretName: string | null = null;\n\n /**\n * Secret name containing only the refresh token.\n */\n refreshTokenSecretName: string | null = null;\n\n /**\n * Token expiration time\n */\n tokenExpiresAt: Date | null = null;\n\n /**\n * Whether the account is active\n */\n isActive: boolean = true;\n\n /**\n * Default hashtags to add to posts\n */\n defaultHashtags: string[] = [];\n\n /**\n * Granted OAuth scopes or platform permissions.\n */\n scopes: string[] = [];\n\n /**\n * Required permissions that still need app review or user grant.\n */\n missingPermissions: string[] = [];\n\n /**\n * How to handle links in posts\n * - description: Include link in post body/description\n * - reply: Post link as a reply (better for X algorithm)\n * - none: Don't include link\n */\n linkBehavior: LinkBehavior = 'description';\n\n /**\n * Safety mode for publish operations.\n */\n publishMode: PublishMode = 'dry_run';\n\n /**\n * Separate latch required before public publishing is allowed.\n */\n publicPublishingAllowed: boolean = false;\n\n /**\n * Account connection status\n */\n status: AccountStatus = 'connected';\n\n /**\n * Error message if status is 'error'\n */\n errorMessage: string | null = null;\n\n constructor(options: SocialAccountOptions = {}) {\n super(options);\n\n if (options.name !== undefined) this.name = options.name;\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.platformUserId !== undefined)\n this.platformUserId = options.platformUserId;\n if (options.platformUsername !== undefined)\n this.platformUsername = options.platformUsername;\n if (options.platformUrl !== undefined)\n this.platformUrl = options.platformUrl;\n if (options.accessToken !== undefined)\n this.accessToken = options.accessToken;\n if (options.refreshToken !== undefined)\n this.refreshToken = options.refreshToken;\n if (options.credentialSecretId !== undefined)\n this.credentialSecretId = options.credentialSecretId;\n if (options.accessTokenSecretName !== undefined)\n this.accessTokenSecretName = options.accessTokenSecretName;\n if (options.refreshTokenSecretName !== undefined)\n this.refreshTokenSecretName = options.refreshTokenSecretName;\n if (options.tokenExpiresAt !== undefined)\n this.tokenExpiresAt = options.tokenExpiresAt;\n if (options.isActive !== undefined) this.isActive = options.isActive;\n if (options.defaultHashtags !== undefined)\n this.defaultHashtags = options.defaultHashtags;\n if (options.scopes !== undefined) this.scopes = options.scopes;\n if (options.missingPermissions !== undefined)\n this.missingPermissions = options.missingPermissions;\n if (options.linkBehavior !== undefined)\n this.linkBehavior = options.linkBehavior;\n if (options.publishMode !== undefined)\n this.publishMode = options.publishMode;\n if (options.publicPublishingAllowed !== undefined)\n this.publicPublishingAllowed = options.publicPublishingAllowed;\n if (options.status !== undefined) this.status = options.status;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n\n if (options.platform !== undefined) {\n const setup = getSocialPlatformSetup(this.platform);\n if (options.linkBehavior === undefined) {\n this.linkBehavior = setup.defaultLinkBehavior;\n }\n if (options.publishMode === undefined) {\n this.publishMode = setup.defaultPublishMode;\n }\n }\n }\n\n /**\n * Social accounts need a slug identity that is scoped by tenant and platform.\n * A newsroom may connect `@localnews` on X, YouTube, Threads, and Facebook;\n * the generic name-derived slug would make those accounts overwrite each\n * other through SMRT's slug/context upsert identity.\n */\n async getSlug(): Promise<string | null | undefined> {\n if (!this.slug) {\n const identity =\n this.platformUserId || this.platformUsername || this.name || this.id;\n if (!identity) {\n return this.slug;\n }\n\n const source = [\n this.tenantId ? `tenant-${this.tenantId}` : 'no-tenant',\n this.platform,\n identity,\n ]\n .filter(Boolean)\n .join('-');\n\n this.slug = source\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n }\n\n return this.slug;\n }\n\n /**\n * Check if the token is expired or will expire soon\n */\n get isTokenExpired(): boolean {\n if (!this.tokenExpiresAt) return false;\n // Expire 5 minutes early to allow for refresh\n const expiryBuffer = 5 * 60 * 1000;\n return Date.now() >= this.tokenExpiresAt.getTime() - expiryBuffer;\n }\n\n /**\n * Check if the account needs attention (expired or error)\n */\n get needsAttention(): boolean {\n return (\n this.status !== 'connected' ||\n this.isTokenExpired ||\n this.missingPermissions.length > 0 ||\n (this.publishMode === 'public' && !this.publicPublishingAllowed)\n );\n }\n\n /**\n * Check if the account is ready for publishing\n */\n get isReady(): boolean {\n return (\n this.isActive &&\n this.status === 'connected' &&\n this.hasCredentials &&\n this.missingPermissions.length === 0 &&\n !this.isTokenExpired &&\n (this.publishMode !== 'public' || this.publicPublishingAllowed)\n );\n }\n\n /**\n * Effective publish mode after applying the public-publishing latch.\n */\n get effectivePublishMode(): PublishMode {\n if (this.publishMode === 'public' && !this.publicPublishingAllowed) {\n return 'dry_run';\n }\n return this.publishMode;\n }\n\n /**\n * Check whether any usable credential reference exists.\n */\n get hasCredentials(): boolean {\n return Boolean(\n this.credentialSecretId || this.accessTokenSecretName || this.accessToken,\n );\n }\n\n /**\n * Store all platform credentials in smrt-secrets as a single JSON payload.\n */\n async setCredentials(\n credentials: Record<string, unknown>,\n options: {\n description?: string;\n category?: string;\n } = {},\n ): Promise<void> {\n if (!this.id) {\n this.id = randomUUID();\n }\n\n const secretName = this.credentialSecretId ?? `social-account-${this.id}`;\n const tenantId = this.requireCredentialTenantId(\n 'store social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n\n await secretService.storeForTenant(\n tenantId,\n secretName,\n JSON.stringify(credentials),\n {\n description: options.description ?? `Credentials for ${this.name}`,\n category: options.category ?? 'social',\n },\n );\n\n this.credentialSecretId = secretName;\n\n await this.withCredentialTenantContext(async () => {\n await this.save();\n });\n }\n\n /**\n * Retrieve platform credentials from smrt-secrets, falling back to deprecated fields.\n */\n async getCredentials(): Promise<Record<string, unknown> | null> {\n if (!this.credentialSecretId) {\n if (this.accessTokenSecretName || this.refreshTokenSecretName) {\n const tenantId = this.requireCredentialTenantId(\n 'retrieve social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n const credentials: Record<string, unknown> = {};\n\n if (this.accessTokenSecretName) {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.accessTokenSecretName,\n );\n credentials.accessToken = secret.value;\n }\n\n if (this.refreshTokenSecretName) {\n const secret = await secretService.retrieveForTenant(\n tenantId,\n this.refreshTokenSecretName,\n );\n credentials.refreshToken = secret.value;\n }\n\n return credentials;\n }\n\n if (!this.accessToken && !this.refreshToken) return null;\n return {\n accessToken: this.accessToken,\n refreshToken: this.refreshToken,\n };\n }\n\n const credentialSecretId = this.credentialSecretId;\n const tenantId = this.requireCredentialTenantId(\n 'retrieve social account credentials',\n );\n const secretService = await SecretService.create({ db: this.db });\n const secret = await secretService.retrieveForTenant(\n tenantId,\n credentialSecretId,\n );\n return JSON.parse(secret.value);\n }\n\n private async withCredentialTenantContext<T>(\n fn: () => Promise<T>,\n ): Promise<T> {\n const tenantId = this.getCredentialTenantId();\n const currentTenantId = getCurrentTenant()?.tenantId;\n\n if (!tenantId || currentTenantId === tenantId) {\n return fn();\n }\n\n return withTenant({ tenantId }, fn);\n }\n\n private getCredentialTenantId(): string | null {\n if (this.tenantId) {\n return this.tenantId;\n }\n\n return getCurrentTenant()?.tenantId ?? null;\n }\n\n private requireCredentialTenantId(action: string): string {\n const tenantId = this.getCredentialTenantId();\n if (!tenantId) {\n throw new Error(`Tenant context required to ${action}.`);\n }\n return tenantId;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { SocialAccount, type SocialPlatformType } from '../social-account.js';\n\nexport class SocialAccountCollection extends SmrtCollection<SocialAccount> {\n static readonly _itemClass = SocialAccount;\n\n async findActive(platform?: SocialPlatformType): Promise<SocialAccount[]> {\n const accounts = await this.list({\n where: {\n isActive: true,\n ...(platform ? { platform } : {}),\n },\n orderBy: 'name ASC',\n });\n\n return accounts;\n }\n\n async findReady(platform?: SocialPlatformType): Promise<SocialAccount[]> {\n const accounts = await this.findActive(platform);\n return accounts.filter((account) => account.isReady);\n }\n\n async findNeedsAttention(): Promise<SocialAccount[]> {\n const accounts = await this.list({\n where: { isActive: true },\n orderBy: 'name ASC',\n });\n return accounts.filter((account) => account.needsAttention);\n }\n}\n","/**\n * Social Post Model\n *\n * Manages posts published or scheduled to social media platforms.\n * Tracks publishing status and engagement analytics.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { SocialAccount } from './social-account.js';\n\n/**\n * Post status\n */\nexport type PostStatus =\n | 'draft'\n | 'pending_approval'\n | 'approved'\n | 'scheduled'\n | 'publishing'\n | 'dry_run'\n | 'staged'\n | 'published'\n | 'failed'\n | 'cancelled';\n\n/**\n * Social post type\n */\nexport type SocialPostType = 'text' | 'link' | 'image' | 'video';\n\n/**\n * Post analytics\n */\nexport interface PostAnalytics {\n /**\n * View/impression count\n */\n views?: number;\n\n /**\n * Impression count when the platform distinguishes it from views\n */\n impressions?: number;\n\n /**\n * Like/favorite count\n */\n likes?: number;\n\n /**\n * Comment count\n */\n comments?: number;\n\n /**\n * Share/repost count\n */\n shares?: number;\n\n /**\n * Link click count\n */\n clicks?: number;\n\n /**\n * Raw platform analytics payload\n */\n raw?: unknown;\n\n /**\n * When analytics were last updated\n */\n lastUpdated?: Date;\n}\n\n/**\n * Social post creation options\n */\nexport interface SocialPostOptions extends SmrtObjectOptions {\n /**\n * Social account to publish to\n */\n socialAccountId?: string | null;\n\n /**\n * Video content to publish\n */\n videoContentId?: string | null;\n\n /**\n * Generic content ID (for non-video content)\n */\n contentId?: string | null;\n\n /**\n * High-level post type\n */\n postType?: SocialPostType;\n\n /**\n * Public media URL for platforms that cannot upload buffers\n */\n mediaUrl?: string | null;\n\n /**\n * Post title (for platforms that support it)\n */\n title?: string | null;\n\n /**\n * Post description/caption\n */\n description?: string;\n\n /**\n * Hashtags to include\n */\n hashtags?: string[];\n\n /**\n * Link URL to include\n */\n linkUrl?: string | null;\n\n /**\n * Platform-specific post ID (after publishing)\n */\n platformPostId?: string | null;\n\n /**\n * Public post URL (after publishing)\n */\n platformUrl?: string | null;\n\n /**\n * Scheduled publish time\n */\n scheduledAt?: Date | null;\n\n /**\n * Actual publish time\n */\n publishedAt?: Date | null;\n\n /**\n * Post status\n * @default 'draft'\n */\n status?: PostStatus;\n\n /**\n * Error message if status is 'failed'\n */\n errorMessage?: string | null;\n\n /**\n * Engagement analytics\n */\n analytics?: PostAnalytics;\n\n /**\n * When analytics were last synced\n */\n analyticsLastSyncedAt?: Date | null;\n\n /**\n * User who created (drafted, scheduled, or published) the post\n */\n createdByUserId?: string | null;\n\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n}\n\n/**\n * Social media post for multi-platform publishing\n *\n * SocialPost represents a post published or scheduled to a social\n * platform. It tracks publishing status and engagement analytics,\n * and can reference VideoContent for video posts.\n *\n * @example\n * ```typescript\n * import { SocialPost } from '@happyvertical/smrt-social';\n *\n * const post = new SocialPost({\n * socialAccountId: 'account-123',\n * videoContentId: 'video-456',\n * title: 'Breaking News from Bentley',\n * description: 'Latest updates from the town council meeting.',\n * hashtags: ['news', 'local', 'bentley'],\n * linkUrl: 'https://example.com/article',\n * scheduledAt: new Date('2026-01-26T18:00:00Z'),\n * });\n * await post.save();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'update', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: true,\n})\nexport class SocialPost extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Social account to publish to\n */\n @foreignKey(SocialAccount)\n socialAccountId: string | null = null;\n\n /**\n * User who created the post: drafted it, scheduled it, or published it\n * directly. Hosts use it to tell the poster when publishing fails.\n *\n * A `@crossPackageRef` to `@happyvertical/smrt-users:User`, so a native uuid\n * column on PostgreSQL with no DDL FK. Null for posts created before the\n * column existed and for posts an agent or schedule created with no person\n * behind them. Set through `SocialPostCollection.createDraft()`; the\n * publish-outcome helpers (`recordPublishSuccess`/`Failure`) never change it.\n */\n @crossPackageRef('@happyvertical/smrt-users:User')\n createdByUserId: string | null = null;\n\n /**\n * Video content to publish\n *\n * Cross-package target (`@happyvertical/smrt-video`), so it is a\n * `@crossPackageRef` rather than a `@foreignKey`: no DDL FK constraint and\n * no value import back into this package.\n */\n @crossPackageRef('@happyvertical/smrt-video:VideoContent')\n videoContentId: string | null = null;\n\n /**\n * Generic content ID (for non-video content)\n */\n @crossPackageRef('@happyvertical/smrt-content:Content')\n contentId: string | null = null;\n\n /**\n * High-level post type\n */\n postType: SocialPostType = 'text';\n\n /**\n * Public media URL for platforms that require URL media publishing\n */\n mediaUrl: string | null = null;\n\n /**\n * Post title (for platforms that support it)\n */\n title: string | null = null;\n\n /**\n * Post description/caption\n */\n description: string = '';\n\n /**\n * Hashtags to include\n */\n hashtags: string[] = [];\n\n /**\n * Link URL to include in the post\n */\n linkUrl: string | null = null;\n\n /**\n * Platform-specific post ID (set after publishing)\n */\n platformPostId: string | null = null;\n\n /**\n * Public post URL (set after publishing)\n */\n platformUrl: string | null = null;\n\n /**\n * Scheduled publish time\n * If set, post will be scheduled instead of published immediately\n */\n scheduledAt: Date | null = null;\n\n /**\n * Actual publish time\n */\n publishedAt: Date | null = null;\n\n /**\n * Post status\n * - draft: Not yet submitted for publishing\n * - pending_approval: Awaiting editorial approval\n * - approved: Approved and ready to publish\n * - scheduled: Queued for future publishing\n * - publishing: Currently being published\n * - dry_run: Payload was validated without remote write\n * - staged: Non-public platform object/container was created\n * - published: Successfully published\n * - failed: Publishing failed\n * - cancelled: Publishing was cancelled\n */\n status: PostStatus = 'draft';\n\n /**\n * Error message if status is 'failed'\n */\n errorMessage: string | null = null;\n\n /**\n * Engagement analytics\n */\n analytics: PostAnalytics = {};\n\n /**\n * When analytics were last synced\n */\n analyticsLastSyncedAt: Date | null = null;\n\n constructor(options: SocialPostOptions = {}) {\n super(options);\n\n if (options.socialAccountId !== undefined)\n this.socialAccountId = options.socialAccountId;\n if (options.videoContentId !== undefined)\n this.videoContentId = options.videoContentId;\n if (options.contentId !== undefined) this.contentId = options.contentId;\n if (options.postType !== undefined) this.postType = options.postType;\n if (options.mediaUrl !== undefined) this.mediaUrl = options.mediaUrl;\n if (options.title !== undefined) this.title = options.title;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.hashtags !== undefined) this.hashtags = options.hashtags;\n if (options.linkUrl !== undefined) this.linkUrl = options.linkUrl;\n if (options.platformPostId !== undefined)\n this.platformPostId = options.platformPostId;\n if (options.platformUrl !== undefined)\n this.platformUrl = options.platformUrl;\n if (options.scheduledAt !== undefined)\n this.scheduledAt = options.scheduledAt;\n if (options.publishedAt !== undefined)\n this.publishedAt = options.publishedAt;\n if (options.status !== undefined) this.status = options.status;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.analytics !== undefined) this.analytics = options.analytics;\n if (options.analyticsLastSyncedAt !== undefined)\n this.analyticsLastSyncedAt = options.analyticsLastSyncedAt;\n if (options.createdByUserId !== undefined)\n this.createdByUserId = options.createdByUserId;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n\n /**\n * Check if the post is scheduled for future publishing\n */\n get isScheduled(): boolean {\n return this.status === 'scheduled' && this.scheduledAt !== null;\n }\n\n /**\n * Check if the post has been published\n */\n get isPublished(): boolean {\n return this.status === 'published';\n }\n\n /**\n * Check if the post can be edited (draft or failed)\n */\n get isEditable(): boolean {\n return (\n this.status === 'draft' ||\n this.status === 'pending_approval' ||\n this.status === 'failed'\n );\n }\n\n /**\n * Check if the post is due for publishing now.\n */\n get isDueForPublish(): boolean {\n if (this.status !== 'approved' && this.status !== 'scheduled') {\n return false;\n }\n return !this.scheduledAt || this.scheduledAt.getTime() <= Date.now();\n }\n\n /**\n * Get formatted hashtag string\n */\n get hashtagString(): string {\n return this.hashtags\n .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`))\n .join(' ');\n }\n\n /**\n * Get the full post text with hashtags\n */\n get fullText(): string {\n const parts = [this.description];\n if (this.hashtags.length > 0) {\n parts.push(this.hashtagString);\n }\n return parts.join('\\n\\n');\n }\n}\n","/**\n * Social Post Analytics Snapshot Model\n *\n * Stores timestamped normalized and raw platform analytics payloads.\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { SocialPlatformType } from './social-account.js';\nimport { type PostAnalytics, SocialPost } from './social-post.js';\n\nexport type RawAnalyticsPayload = Record<string, unknown> | unknown[] | null;\n\nexport interface SocialPostAnalyticsSnapshotOptions extends SmrtObjectOptions {\n /**\n * Tenant ID for multi-tenant isolation\n */\n tenantId?: string | null;\n\n /**\n * Social post being measured\n */\n socialPostId?: string | null;\n\n /**\n * Platform that produced this snapshot\n */\n platform?: SocialPlatformType | null;\n\n /**\n * Normalized platform analytics.\n */\n analytics?: PostAnalytics;\n\n /**\n * Raw platform payload\n */\n raw?: RawAnalyticsPayload;\n\n /**\n * When the platform data was captured\n */\n capturedAt?: Date;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: {\n include: ['list', 'get', 'create', 'delete'],\n },\n mcp: {\n include: ['list', 'get'],\n },\n cli: true,\n})\nexport class SocialPostAnalyticsSnapshot extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey(SocialPost)\n socialPostId: string | null = null;\n\n @field({ required: true })\n platform: SocialPlatformType | null = null;\n\n analytics: PostAnalytics = {};\n\n @field({ type: 'json', nullable: true })\n raw: RawAnalyticsPayload = null;\n\n capturedAt: Date = new Date();\n\n constructor(options: SocialPostAnalyticsSnapshotOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.socialPostId !== undefined)\n this.socialPostId = options.socialPostId;\n if (options.platform !== undefined) this.platform = options.platform;\n if (options.analytics !== undefined) this.analytics = options.analytics;\n if (options.raw !== undefined) this.raw = options.raw;\n if (options.capturedAt !== undefined) this.capturedAt = options.capturedAt;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport type { SocialPlatformType } from '../social-account.js';\nimport type { PostAnalytics } from '../social-post.js';\nimport {\n type RawAnalyticsPayload,\n SocialPostAnalyticsSnapshot,\n} from '../social-post-analytics-snapshot.js';\n\nfunction isRawAnalyticsPayload(value: unknown): value is RawAnalyticsPayload {\n return (\n value === null ||\n Array.isArray(value) ||\n (typeof value === 'object' && value !== null)\n );\n}\n\nexport class SocialPostAnalyticsSnapshotCollection extends SmrtCollection<SocialPostAnalyticsSnapshot> {\n static readonly _itemClass = SocialPostAnalyticsSnapshot;\n\n async recordSnapshot(options: {\n socialPostId: string;\n platform: SocialPlatformType;\n metrics: PostAnalytics;\n raw?: RawAnalyticsPayload;\n capturedAt?: Date;\n tenantId?: string | null;\n }): Promise<SocialPostAnalyticsSnapshot> {\n const snapshot = await this.create({\n socialPostId: options.socialPostId,\n platform: options.platform,\n analytics: options.metrics,\n raw:\n options.raw ??\n (isRawAnalyticsPayload(options.metrics.raw)\n ? options.metrics.raw\n : null),\n capturedAt: options.capturedAt ?? new Date(),\n tenantId: options.tenantId,\n });\n return snapshot;\n }\n\n async findForPost(\n socialPostId: string,\n ): Promise<SocialPostAnalyticsSnapshot[]> {\n return this.list({\n where: { socialPostId },\n orderBy: 'capturedAt DESC',\n });\n }\n\n async findLatestForPost(\n socialPostId: string,\n ): Promise<SocialPostAnalyticsSnapshot | null> {\n const snapshots = await this.findForPost(socialPostId);\n return snapshots[0] ?? null;\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n type PostAnalytics,\n type PostStatus,\n SocialPost,\n type SocialPostType,\n} from '../social-post.js';\n\nexport interface CreateSocialPostDraftOptions {\n socialAccountId: string;\n contentId?: string | null;\n videoContentId?: string | null;\n postType?: SocialPostType;\n title?: string | null;\n description?: string;\n hashtags?: string[];\n linkUrl?: string | null;\n mediaUrl?: string | null;\n scheduledAt?: Date | null;\n /**\n * User creating the post (the poster). Stored as `createdByUserId` so a\n * failed publish can be reported to them; omit for agent/schedule posts.\n */\n createdByUserId?: string | null;\n tenantId?: string | null;\n}\n\nexport interface PublishSuccessData {\n platformPostId: string;\n platformUrl: string;\n publishedAt?: Date;\n status?: PostStatus;\n}\n\nexport class SocialPostCollection extends SmrtCollection<SocialPost> {\n static readonly _itemClass = SocialPost;\n\n async createDraft(\n options: CreateSocialPostDraftOptions,\n ): Promise<SocialPost> {\n return this.create({\n ...options,\n postType:\n options.postType ??\n (options.videoContentId || options.mediaUrl\n ? 'video'\n : options.linkUrl\n ? 'link'\n : 'text'),\n status: options.scheduledAt ? 'scheduled' : 'draft',\n });\n }\n\n async findByStatus(status: PostStatus): Promise<SocialPost[]> {\n return this.list({ where: { status }, orderBy: 'scheduledAt ASC' });\n }\n\n async findForAccount(socialAccountId: string): Promise<SocialPost[]> {\n return this.list({\n where: { socialAccountId },\n orderBy: 'created_at DESC',\n });\n }\n\n async findDueForPublish(now: Date = new Date()): Promise<SocialPost[]> {\n const [approved, scheduled] = await Promise.all([\n this.list({\n where: { status: 'approved' },\n orderBy: 'scheduledAt ASC',\n }),\n this.list({\n where: { status: 'scheduled', 'scheduledAt <=': now },\n orderBy: 'scheduledAt ASC',\n }),\n ]);\n\n return [\n ...approved.filter((post) => post.isDueForPublish),\n ...scheduled,\n ].sort(\n (a, b) =>\n (a.scheduledAt?.getTime() ?? 0) - (b.scheduledAt?.getTime() ?? 0),\n );\n }\n\n async recordPublishSuccess(\n post: SocialPost,\n data: PublishSuccessData,\n ): Promise<SocialPost> {\n const status = data.status ?? 'published';\n post.platformPostId = data.platformPostId;\n post.platformUrl = data.platformUrl;\n if (status === 'published') {\n post.publishedAt = data.publishedAt ?? post.publishedAt ?? new Date();\n }\n post.status = status;\n post.errorMessage = null;\n await post.save();\n return post;\n }\n\n async recordPublishFailure(\n post: SocialPost,\n error: Error | string,\n ): Promise<SocialPost> {\n post.status = 'failed';\n post.errorMessage = formatPublishError(error);\n await post.save();\n return post;\n }\n\n async updateLatestAnalytics(\n post: SocialPost,\n analytics: PostAnalytics,\n syncedAt: Date = new Date(),\n ): Promise<SocialPost> {\n post.analytics = {\n ...analytics,\n lastUpdated: analytics.lastUpdated ?? syncedAt,\n };\n post.analyticsLastSyncedAt = syncedAt;\n await post.save();\n return post;\n }\n}\n\nfunction formatPublishError(error: Error | string): string {\n if (typeof error === 'string') {\n return error;\n }\n\n const details = [error.message];\n const code = (error as Error & { code?: unknown }).code;\n if (code !== undefined) {\n details.push(`code=${String(code)}`);\n }\n if (error.stack) {\n details.push(error.stack);\n }\n return details.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AC2FO,IAAM,aAAN,cAAyB,WAAW;CAKzC,WAA0B;;;;CAK1B,WAA+B;;;;;CAM/B,QAAgB;;;;;CAMhB,eAA8B;;;;;CAM9B,cAAsB;;;;CAKtB,SAAmB,CAAC;;;;;CAMpB,YAAkB,IAAI,KAAK,KAAK,IAAI,IAAI,MAAU,GAAI;CAEtD,YAAY,UAA6B,CAAC,GAAG;EAC3C,MAAM,OAAO;EAEb,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,UAAU,KAAA,GAAW,KAAK,QAAQ,QAAQ;EACtD,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;CAC9D;;;;CAKA,IAAI,YAAqB;EACvB,OAAO,KAAK,IAAI,KAAK,KAAK,UAAU,QAAQ;CAC9C;;;;CAKA,IAAI,UAAmB;EACrB,OAAO,CAAC,KAAK,aAAa,KAAK,MAAM,SAAS;CAChD;;;;CAKA,YAAY,eAAgC;EAC1C,OAAO,KAAK,WAAW,KAAK,UAAU;CACxC;;;;CAKA,OAAO,gBAAwB;EAC7B,OAAO,OAAO,WAAW;CAC3B;;;;;CAMA,OAAO,uBAA+B;EACpC,MAAM,wBAAQ,IAAI,WAAW,EAAE;EAC/B,OAAO,gBAAgB,KAAK;EAC5B,OAAO,MAAM,KAAK,QAAQ,SAAS,KAAK,SAAS,EAAE,CAAA,CAAE,SAAS,GAAG,GAAG,CAAC,CAAA,CAAE,KACrE,EACF;CACF;;;;;CAMA,aAAa,sBAAsB,UAAmC;EAEpE,MAAM,OAAO,IADO,YACP,CAAA,CAAQ,OAAO,QAAQ;EACpC,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;EAEzD,OADe,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAAC,CAC1D,CAAA,CAAO,QAAQ,OAAO,GAAG,CAAA,CAAE,QAAQ,OAAO,GAAG,CAAA,CAAE,QAAQ,MAAM,EAAE;CACxE;AACF;AArGE,kBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GAJjB,WAKX,WAAA,YAAA,CAAA;AALW,aAAN,kBAAA,CATN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,eAAe;CACf,KAAK,EACH,SAAS;EAAC;EAAQ;EAAO;EAAU;CAAQ,EAC7C;CACA,KAAK;CACL,KAAK,EAAE,cAAc,KAAK;AAC5B,CAAC,CAAA,GACY,UAAA;;;ACxFN,IAAM,uBAAN,cAAmC,eAA2B;CACnE,OAAgB,aAAa;CAE7B,MAAM,YAAY,OAA2C;EAC3D,OAAO,KAAK,IAAI,EAAE,MAAM,CAAC;CAC3B;CAEA,MAAM,YAAY,sBAAY,IAAI,KAAK,GAA0B;EAC/D,OAAO,KAAK,KAAK;GACf,OAAO,EAAE,gBAAgB,IAAI;GAC7B,SAAS;EACX,CAAC;CACH;CAEA,MAAM,cAAc,sBAAY,IAAI,KAAK,GAAoB;EAC3D,MAAM,UAAU,MAAM,KAAK,YAAY,GAAG;EAC1C,MAAM,QAAQ,IAAI,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC,CAAC;EACxD,OAAO,QAAQ;CACjB;AACF;;;;;;;;;;;AC2LO,IAAM,gBAAN,cAA4B,WAAW;CAK5C,WAA0B;;;;CAK1B,OAAe;;;;CAKf,WAA+B;;;;CAK/B,iBAAgC;;;;CAKhC,mBAAkC;;;;CAKlC,cAA6B;;;;;CAM7B,cAA6B;;;;;CAM7B,eAA8B;;;;CAK9B,qBAAoC;;;;CAKpC,wBAAuC;;;;CAKvC,yBAAwC;;;;CAKxC,iBAA8B;;;;CAK9B,WAAoB;;;;CAKpB,kBAA4B,CAAC;;;;CAK7B,SAAmB,CAAC;;;;CAKpB,qBAA+B,CAAC;;;;;;;CAQhC,eAA6B;;;;CAK7B,cAA2B;;;;CAK3B,0BAAmC;;;;CAKnC,SAAwB;;;;CAKxB,eAA8B;CAE9B,YAAY,UAAgC,CAAC,GAAG;EAC9C,MAAM,OAAO;EAEb,IAAI,QAAQ,SAAS,KAAA,GAAW,KAAK,OAAO,QAAQ;EACpD,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBAAiB,QAAQ;EAChC,IAAI,QAAQ,qBAAqB,KAAA,GAC/B,KAAK,mBAAmB,QAAQ;EAClC,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,uBAAuB,KAAA,GACjC,KAAK,qBAAqB,QAAQ;EACpC,IAAI,QAAQ,0BAA0B,KAAA,GACpC,KAAK,wBAAwB,QAAQ;EACvC,IAAI,QAAQ,2BAA2B,KAAA,GACrC,KAAK,yBAAyB,QAAQ;EACxC,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBAAiB,QAAQ;EAChC,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,oBAAoB,KAAA,GAC9B,KAAK,kBAAkB,QAAQ;EACjC,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,uBAAuB,KAAA,GACjC,KAAK,qBAAqB,QAAQ;EACpC,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,4BAA4B,KAAA,GACtC,KAAK,0BAA0B,QAAQ;EACzC,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAE5D,IAAI,QAAQ,aAAa,KAAA,GAAW;GAClC,MAAM,QAAQ,uBAAuB,KAAK,QAAQ;GAClD,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,MAAM;GAE5B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,MAAM;EAE7B;CACF;;;;;;;CAQA,MAAM,UAA8C;EAClD,IAAI,CAAC,KAAK,MAAM;GACd,MAAM,WACJ,KAAK,kBAAkB,KAAK,oBAAoB,KAAK,QAAQ,KAAK;GACpE,IAAI,CAAC,UACH,OAAO,KAAK;GAGd,MAAM,SAAS;IACb,KAAK,WAAW,UAAU,KAAK,aAAa;IAC5C,KAAK;IACL;GACF,CAAA,CACG,OAAO,OAAO,CAAA,CACd,KAAK,GAAG;GAEX,KAAK,OAAO,OACT,YAAY,CAAA,CACZ,QAAQ,eAAe,GAAG,CAAA,CAC1B,QAAQ,YAAY,EAAE;EAC3B;EAEA,OAAO,KAAK;CACd;;;;CAKA,IAAI,iBAA0B;EAC5B,IAAI,CAAC,KAAK,gBAAgB,OAAO;EAGjC,OAAO,KAAK,IAAI,KAAK,KAAK,eAAe,QAAQ,IAD5B,MAAS;CAEhC;;;;CAKA,IAAI,iBAA0B;EAC5B,OACE,KAAK,WAAW,eAChB,KAAK,kBACL,KAAK,mBAAmB,SAAS,KAChC,KAAK,gBAAgB,YAAY,CAAC,KAAK;CAE5C;;;;CAKA,IAAI,UAAmB;EACrB,OACE,KAAK,YACL,KAAK,WAAW,eAChB,KAAK,kBACL,KAAK,mBAAmB,WAAW,KACnC,CAAC,KAAK,mBACL,KAAK,gBAAgB,YAAY,KAAK;CAE3C;;;;CAKA,IAAI,uBAAoC;EACtC,IAAI,KAAK,gBAAgB,YAAY,CAAC,KAAK,yBACzC,OAAO;EAET,OAAO,KAAK;CACd;;;;CAKA,IAAI,iBAA0B;EAC5B,OAAO,QACL,KAAK,sBAAsB,KAAK,yBAAyB,KAAK,WAChE;CACF;;;;CAKA,MAAM,eACJ,aACA,UAGI,CAAC,GACU;EACf,IAAI,CAAC,KAAK,IACR,KAAK,KAAK,WAAW;EAGvB,MAAM,aAAa,KAAK,sBAAsB,kBAAkB,KAAK;EACrE,MAAMA,YAAW,KAAK,0BACpB,kCACF;EAGA,OAAM,MAFsB,cAAc,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,EAAA,CAE5C,eAClBA,WACA,YACA,KAAK,UAAU,WAAW,GAC1B;GACE,aAAa,QAAQ,eAAe,mBAAmB,KAAK;GAC5D,UAAU,QAAQ,YAAY;EAChC,CACF;EAEA,KAAK,qBAAqB;EAE1B,MAAM,KAAK,4BAA4B,YAAY;GACjD,MAAM,KAAK,KAAK;EAClB,CAAC;CACH;;;;CAKA,MAAM,iBAA0D;EAC9D,IAAI,CAAC,KAAK,oBAAoB;GAC5B,IAAI,KAAK,yBAAyB,KAAK,wBAAwB;IAC7D,MAAMA,YAAW,KAAK,0BACpB,qCACF;IACA,MAAMC,iBAAgB,MAAM,cAAc,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC;IAChE,MAAM,cAAuC,CAAC;IAE9C,IAAI,KAAK,uBAKP,YAAY,eAAcC,MAJLD,eAAc,kBACjCD,WACA,KAAK,qBACP,EAAA,CACiC;IAGnC,IAAI,KAAK,wBAKP,YAAY,gBAAeE,MAJND,eAAc,kBACjCD,WACA,KAAK,sBACP,EAAA,CACkC;IAGpC,OAAO;GACT;GAEA,IAAI,CAAC,KAAK,eAAe,CAAC,KAAK,cAAc,OAAO;GACpD,OAAO;IACL,aAAa,KAAK;IAClB,cAAc,KAAK;GACrB;EACF;EAEA,MAAM,qBAAqB,KAAK;EAChC,MAAMA,YAAW,KAAK,0BACpB,qCACF;EAEA,MAAM,SAAS,OAAM,MADO,cAAc,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,EAAA,CAC7B,kBACjCA,WACA,kBACF;EACA,OAAO,KAAK,MAAM,OAAO,KAAK;CAChC;CAEA,MAAc,4BACZ,IACY;EACZ,MAAMA,YAAW,KAAK,sBAAsB;EAC5C,MAAM,kBAAkB,iBAAiB,CAAA,EAAG;EAE5C,IAAI,CAACA,aAAY,oBAAoBA,WACnC,OAAO,GAAG;EAGZ,OAAO,WAAW,EAAE,UAAAA,UAAS,GAAG,EAAE;CACpC;CAEQ,wBAAuC;EAC7C,IAAI,KAAK,UACP,OAAO,KAAK;EAGd,OAAO,iBAAiB,CAAA,EAAG,YAAY;CACzC;CAEQ,0BAA0B,QAAwB;EACxD,MAAMA,YAAW,KAAK,sBAAsB;EAC5C,IAAI,CAACA,WACH,MAAM,IAAI,MAAM,8BAA8B,OAAM,EAAG;EAEzD,OAAOA;CACT;AACF;AAvWE,kBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GAJjB,cAKX,WAAA,YAAA,CAAA;AALW,gBAAN,kBAAA,CAXN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,eAAe;CACf,KAAK,EACH,SAAS;EAAC;EAAQ;EAAO;EAAU;EAAU;CAAQ,EACvD;CACA,KAAK,EACH,SAAS,CAAC,QAAQ,KAAK,EACzB;CACA,KAAK,EAAE,cAAc,KAAK;AAC5B,CAAC,CAAA,GACY,aAAA;;;AC9MN,IAAM,0BAAN,cAAsC,eAA8B;CACzE,OAAgB,aAAa;CAE7B,MAAM,WAAW,UAAyD;EASxE,OAAO,MARgB,KAAK,KAAK;GAC/B,OAAO;IACL,UAAU;IACV,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;GACjC;GACA,SAAS;EACX,CAAC;CAGH;CAEA,MAAM,UAAU,UAAyD;EAEvE,QAAO,MADgB,KAAK,WAAW,QAAQ,EAAA,CAC/B,QAAQ,YAAY,QAAQ,OAAO;CACrD;CAEA,MAAM,qBAA+C;EAKnD,QAAO,MAJgB,KAAK,KAAK;GAC/B,OAAO,EAAE,UAAU,KAAK;GACxB,SAAS;EACX,CAAC,EAAA,CACe,QAAQ,YAAY,QAAQ,cAAc;CAC5D;AACF;;;;;;;;;;;AC2LO,IAAM,aAAN,cAAyB,WAAW;CAKzC,WAA0B;CAM1B,kBAAiC;CAajC,kBAAiC;CAUjC,iBAAgC;CAMhC,YAA2B;;;;CAK3B,WAA2B;;;;CAK3B,WAA0B;;;;CAK1B,QAAuB;;;;CAKvB,cAAsB;;;;CAKtB,WAAqB,CAAC;;;;CAKtB,UAAyB;;;;CAKzB,iBAAgC;;;;CAKhC,cAA6B;;;;;CAM7B,cAA2B;;;;CAK3B,cAA2B;;;;;;;;;;;;;;CAe3B,SAAqB;;;;CAKrB,eAA8B;;;;CAK9B,YAA2B,CAAC;;;;CAK5B,wBAAqC;CAErC,YAAY,UAA6B,CAAC,GAAG;EAC3C,MAAM,OAAO;EAEb,IAAI,QAAQ,oBAAoB,KAAA,GAC9B,KAAK,kBAAkB,QAAQ;EACjC,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBAAiB,QAAQ;EAChC,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,UAAU,KAAA,GAAW,KAAK,QAAQ,QAAQ;EACtD,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBAAiB,QAAQ;EAChC,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,0BAA0B,KAAA,GACpC,KAAK,wBAAwB,QAAQ;EACvC,IAAI,QAAQ,oBAAoB,KAAA,GAC9B,KAAK,kBAAkB,QAAQ;EACjC,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;CAC9D;;;;CAKA,IAAI,cAAuB;EACzB,OAAO,KAAK,WAAW,eAAe,KAAK,gBAAgB;CAC7D;;;;CAKA,IAAI,cAAuB;EACzB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,IAAI,aAAsB;EACxB,OACE,KAAK,WAAW,WAChB,KAAK,WAAW,sBAChB,KAAK,WAAW;CAEpB;;;;CAKA,IAAI,kBAA2B;EAC7B,IAAI,KAAK,WAAW,cAAc,KAAK,WAAW,aAChD,OAAO;EAET,OAAO,CAAC,KAAK,eAAe,KAAK,YAAY,QAAQ,KAAK,KAAK,IAAI;CACrE;;;;CAKA,IAAI,gBAAwB;EAC1B,OAAO,KAAK,SACT,KAAK,QAAS,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,KAAM,CAAA,CACpD,KAAK,GAAG;CACb;;;;CAKA,IAAI,WAAmB;EACrB,MAAM,QAAQ,CAAC,KAAK,WAAW;EAC/B,IAAI,KAAK,SAAS,SAAS,GACzB,MAAM,KAAK,KAAK,aAAa;EAE/B,OAAO,MAAM,KAAK,MAAM;CAC1B;AACF;AA9ME,kBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GAJjB,WAKX,WAAA,YAAA,CAAA;AAMA,kBAAA,CADC,WAAW,aAAa,CAAA,GAVd,WAWX,WAAA,mBAAA,CAAA;AAaA,kBAAA,CADC,gBAAgB,gCAAgC,CAAA,GAvBtC,WAwBX,WAAA,mBAAA,CAAA;AAUA,kBAAA,CADC,gBAAgB,wCAAwC,CAAA,GAjC9C,WAkCX,WAAA,kBAAA,CAAA;AAMA,kBAAA,CADC,gBAAgB,qCAAqC,CAAA,GAvC3C,WAwCX,WAAA,aAAA,CAAA;AAxCW,aAAN,kBAAA,CAXN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,eAAe;CACf,KAAK,EACH,SAAS;EAAC;EAAQ;EAAO;EAAU;EAAU;CAAQ,EACvD;CACA,KAAK,EACH,SAAS,CAAC,QAAQ,KAAK,EACzB;CACA,KAAK;AACP,CAAC,CAAA,GACY,UAAA;;;;;;;;;;;AChKN,IAAM,8BAAN,cAA0C,WAAW;CAE1D,WAA0B;CAG1B,eAA8B;CAG9B,WAAsC;CAEtC,YAA2B,CAAC;CAG5B,MAA2B;CAE3B,6BAAmB,IAAI,KAAK;CAE5B,YAAY,UAA8C,CAAC,GAAG;EAC5D,MAAM,OAAO;EAEb,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,QAAQ,KAAA,GAAW,KAAK,MAAM,QAAQ;EAClD,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;CAClE;AACF;AA1BE,gBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GADjB,4BAEX,WAAA,YAAA,CAAA;AAGA,gBAAA,CADC,WAAW,UAAU,CAAA,GAJX,4BAKX,WAAA,gBAAA,CAAA;AAGA,gBAAA,CADC,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GAPd,4BAQX,WAAA,YAAA,CAAA;AAKA,gBAAA,CADC,MAAM;CAAE,MAAM;CAAQ,UAAU;AAAK,CAAC,CAAA,GAZ5B,4BAaX,WAAA,OAAA,CAAA;AAbW,8BAAN,gBAAA,CAXN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,eAAe;CACf,KAAK,EACH,SAAS;EAAC;EAAQ;EAAO;EAAU;CAAQ,EAC7C;CACA,KAAK,EACH,SAAS,CAAC,QAAQ,KAAK,EACzB;CACA,KAAK;AACP,CAAC,CAAA,GACY,2BAAA;;;ACjDb,SAAS,sBAAsB,OAA8C;CAC3E,OACE,UAAU,QACV,MAAM,QAAQ,KAAK,KAClB,OAAO,UAAU,YAAY,UAAU;AAE5C;AAEO,IAAM,wCAAN,cAAoD,eAA4C;CACrG,OAAgB,aAAa;CAE7B,MAAM,eAAe,SAOoB;EAavC,OAAO,MAZgB,KAAK,OAAO;GACjC,cAAc,QAAQ;GACtB,UAAU,QAAQ;GAClB,WAAW,QAAQ;GACnB,KACE,QAAQ,QACP,sBAAsB,QAAQ,QAAQ,GAAG,IACtC,QAAQ,QAAQ,MAChB;GACN,YAAY,QAAQ,8BAAc,IAAI,KAAK;GAC3C,UAAU,QAAQ;EACpB,CAAC;CAEH;CAEA,MAAM,YACJ,cACwC;EACxC,OAAO,KAAK,KAAK;GACf,OAAO,EAAE,aAAa;GACtB,SAAS;EACX,CAAC;CACH;CAEA,MAAM,kBACJ,cAC6C;EAE7C,QAAO,MADiB,KAAK,YAAY,YAAY,EAAA,CACpC,MAAM;CACzB;AACF;;;ACvBO,IAAM,uBAAN,cAAmC,eAA2B;CACnE,OAAgB,aAAa;CAE7B,MAAM,YACJ,SACqB;EACrB,OAAO,KAAK,OAAO;GACjB,GAAG;GACH,UACE,QAAQ,aACP,QAAQ,kBAAkB,QAAQ,WAC/B,UACA,QAAQ,UACN,SACA;GACR,QAAQ,QAAQ,cAAc,cAAc;EAC9C,CAAC;CACH;CAEA,MAAM,aAAa,QAA2C;EAC5D,OAAO,KAAK,KAAK;GAAE,OAAO,EAAE,OAAO;GAAG,SAAS;EAAkB,CAAC;CACpE;CAEA,MAAM,eAAe,iBAAgD;EACnE,OAAO,KAAK,KAAK;GACf,OAAO,EAAE,gBAAgB;GACzB,SAAS;EACX,CAAC;CACH;CAEA,MAAM,kBAAkB,sBAAY,IAAI,KAAK,GAA0B;EACrE,MAAM,CAAC,UAAU,aAAa,MAAM,QAAQ,IAAI,CAC9C,KAAK,KAAK;GACR,OAAO,EAAE,QAAQ,WAAW;GAC5B,SAAS;EACX,CAAC,GACD,KAAK,KAAK;GACR,OAAO;IAAE,QAAQ;IAAa,kBAAkB;GAAI;GACpD,SAAS;EACX,CAAC,CACH,CAAC;EAED,OAAO,CACL,GAAG,SAAS,QAAQ,SAAS,KAAK,eAAe,GACjD,GAAG,SACL,CAAA,CAAE,MACC,GAAG,OACD,EAAE,aAAa,QAAQ,KAAK,MAAM,EAAE,aAAa,QAAQ,KAAK,EACnE;CACF;CAEA,MAAM,qBACJ,MACA,MACqB;EACrB,MAAM,SAAS,KAAK,UAAU;EAC9B,KAAK,iBAAiB,KAAK;EAC3B,KAAK,cAAc,KAAK;EACxB,IAAI,WAAW,aACb,KAAK,cAAc,KAAK,eAAe,KAAK,+BAAe,IAAI,KAAK;EAEtE,KAAK,SAAS;EACd,KAAK,eAAe;EACpB,MAAM,KAAK,KAAK;EAChB,OAAO;CACT;CAEA,MAAM,qBACJ,MACA,OACqB;EACrB,KAAK,SAAS;EACd,KAAK,eAAe,mBAAmB,KAAK;EAC5C,MAAM,KAAK,KAAK;EAChB,OAAO;CACT;CAEA,MAAM,sBACJ,MACA,WACA,2BAAiB,IAAI,KAAK,GACL;EACrB,KAAK,YAAY;GACf,GAAG;GACH,aAAa,UAAU,eAAe;EACxC;EACA,KAAK,wBAAwB;EAC7B,MAAM,KAAK,KAAK;EAChB,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,OAA+B;CACzD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,MAAM,UAAU,CAAC,MAAM,OAAO;CAC9B,MAAM,OAAQ,MAAqC;CACnD,IAAI,SAAS,KAAA,GACX,QAAQ,KAAK,QAAQ,OAAO,IAAI,GAAG;CAErC,IAAI,MAAM,OACR,QAAQ,KAAK,MAAM,KAAK;CAE1B,OAAO,QAAQ,KAAK,IAAI;AAC1B"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-social",
5
- "packageVersion": "0.51.38",
5
+ "packageVersion": "0.53.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-social:OAuthState": {
8
8
  "name": "oauthstate",
@@ -847,6 +847,11 @@
847
847
  "required": false,
848
848
  "related": "SocialAccount"
849
849
  },
850
+ "createdByUserId": {
851
+ "type": "crossPackageRef",
852
+ "required": false,
853
+ "related": "@happyvertical/smrt-users:User"
854
+ },
850
855
  "videoContentId": {
851
856
  "type": "crossPackageRef",
852
857
  "required": false,
@@ -952,7 +957,7 @@
952
957
  "collectionExportName": "SocialPostCollection",
953
958
  "schema": {
954
959
  "tableName": "social_posts",
955
- "ddl": "CREATE TABLE IF NOT EXISTS \"social_posts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"_meta_type\" TEXT NOT NULL,\n \"_meta_data\" JSON,\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"social_account_id\" UUID,\n \"video_content_id\" UUID,\n \"content_id\" UUID,\n \"post_type\" TEXT DEFAULT 'text',\n \"media_url\" TEXT,\n \"title\" TEXT,\n \"description\" TEXT DEFAULT '',\n \"hashtags\" JSON DEFAULT '[]',\n \"link_url\" TEXT,\n \"platform_post_id\" TEXT,\n \"platform_url\" TEXT,\n \"scheduled_at\" TIMESTAMP,\n \"published_at\" TIMESTAMP,\n \"status\" TEXT DEFAULT 'draft',\n \"error_message\" TEXT,\n \"analytics\" JSON,\n \"analytics_last_synced_at\" TIMESTAMP\n);",
960
+ "ddl": "CREATE TABLE IF NOT EXISTS \"social_posts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"_meta_type\" TEXT NOT NULL,\n \"_meta_data\" JSON,\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"social_account_id\" UUID,\n \"created_by_user_id\" UUID,\n \"video_content_id\" UUID,\n \"content_id\" UUID,\n \"post_type\" TEXT DEFAULT 'text',\n \"media_url\" TEXT,\n \"title\" TEXT,\n \"description\" TEXT DEFAULT '',\n \"hashtags\" JSON DEFAULT '[]',\n \"link_url\" TEXT,\n \"platform_post_id\" TEXT,\n \"platform_url\" TEXT,\n \"scheduled_at\" TIMESTAMP,\n \"published_at\" TIMESTAMP,\n \"status\" TEXT DEFAULT 'draft',\n \"error_message\" TEXT,\n \"analytics\" JSON,\n \"analytics_last_synced_at\" TIMESTAMP\n);",
956
961
  "columns": {
957
962
  "id": {
958
963
  "type": "UUID",
@@ -1003,6 +1008,11 @@
1003
1008
  "onUpdate": "CASCADE"
1004
1009
  }
1005
1010
  },
1011
+ "created_by_user_id": {
1012
+ "type": "UUID",
1013
+ "referenceKind": "crossPackageRef",
1014
+ "notNull": false
1015
+ },
1006
1016
  "video_content_id": {
1007
1017
  "type": "UUID",
1008
1018
  "referenceKind": "crossPackageRef",
@@ -1105,6 +1115,12 @@
1105
1115
  "social_account_id"
1106
1116
  ]
1107
1117
  },
1118
+ {
1119
+ "name": "social_posts_created_by_user_id_idx",
1120
+ "columns": [
1121
+ "created_by_user_id"
1122
+ ]
1123
+ },
1108
1124
  {
1109
1125
  "name": "social_posts_video_content_id_idx",
1110
1126
  "columns": [
@@ -1118,7 +1134,7 @@
1118
1134
  ]
1119
1135
  }
1120
1136
  ],
1121
- "version": "49573fcf"
1137
+ "version": "cfb50b15"
1122
1138
  },
1123
1139
  "importPath": "@happyvertical/smrt-social"
1124
1140
  },
@@ -1810,7 +1826,7 @@
1810
1826
  "collectionExportName": "SocialPostCollectionCollection",
1811
1827
  "schema": {
1812
1828
  "tableName": "social_posts",
1813
- "ddl": "CREATE TABLE IF NOT EXISTS \"social_posts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"_meta_type\" TEXT NOT NULL,\n \"_meta_data\" JSON,\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"social_account_id\" UUID,\n \"video_content_id\" UUID,\n \"content_id\" UUID,\n \"post_type\" TEXT DEFAULT 'text',\n \"media_url\" TEXT,\n \"title\" TEXT,\n \"description\" TEXT DEFAULT '',\n \"hashtags\" JSON DEFAULT '[]',\n \"link_url\" TEXT,\n \"platform_post_id\" TEXT,\n \"platform_url\" TEXT,\n \"scheduled_at\" TIMESTAMP,\n \"published_at\" TIMESTAMP,\n \"status\" TEXT DEFAULT 'draft',\n \"error_message\" TEXT,\n \"analytics\" JSON,\n \"analytics_last_synced_at\" TIMESTAMP\n);",
1829
+ "ddl": "CREATE TABLE IF NOT EXISTS \"social_posts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"_meta_type\" TEXT NOT NULL,\n \"_meta_data\" JSON,\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"social_account_id\" UUID,\n \"created_by_user_id\" UUID,\n \"video_content_id\" UUID,\n \"content_id\" UUID,\n \"post_type\" TEXT DEFAULT 'text',\n \"media_url\" TEXT,\n \"title\" TEXT,\n \"description\" TEXT DEFAULT '',\n \"hashtags\" JSON DEFAULT '[]',\n \"link_url\" TEXT,\n \"platform_post_id\" TEXT,\n \"platform_url\" TEXT,\n \"scheduled_at\" TIMESTAMP,\n \"published_at\" TIMESTAMP,\n \"status\" TEXT DEFAULT 'draft',\n \"error_message\" TEXT,\n \"analytics\" JSON,\n \"analytics_last_synced_at\" TIMESTAMP\n);",
1814
1830
  "columns": {
1815
1831
  "id": {
1816
1832
  "type": "UUID",
@@ -1861,6 +1877,11 @@
1861
1877
  "onUpdate": "CASCADE"
1862
1878
  }
1863
1879
  },
1880
+ "created_by_user_id": {
1881
+ "type": "UUID",
1882
+ "referenceKind": "crossPackageRef",
1883
+ "notNull": false
1884
+ },
1864
1885
  "video_content_id": {
1865
1886
  "type": "UUID",
1866
1887
  "referenceKind": "crossPackageRef",
@@ -1963,6 +1984,12 @@
1963
1984
  "social_account_id"
1964
1985
  ]
1965
1986
  },
1987
+ {
1988
+ "name": "social_posts_created_by_user_id_idx",
1989
+ "columns": [
1990
+ "created_by_user_id"
1991
+ ]
1992
+ },
1966
1993
  {
1967
1994
  "name": "social_posts_video_content_id_idx",
1968
1995
  "columns": [
@@ -1976,7 +2003,7 @@
1976
2003
  ]
1977
2004
  }
1978
2005
  ],
1979
- "version": "49573fcf"
2006
+ "version": "cfb50b15"
1980
2007
  },
1981
2008
  "importPath": "@happyvertical/smrt-social"
1982
2009
  }
@@ -3,13 +3,13 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-social",
6
- "packageVersion": "0.51.38",
6
+ "packageVersion": "0.53.0",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "4f67a913933e2e2905a189923afe6d962c85db80dd1f246d205e5b1f79d0ed67",
11
- "packageJson": "d3c7110edbe42667bd42b8466862581d4061d969fdf1527120fb22643e16db99",
12
- "agents": "f31b84b31431fcb43ab480fafd9684d91f0e55a935e95aa31fd19ea1f31b0ad4"
10
+ "manifest": "b63e90d6833c557557c223a10572646313b6ebaefd56fd916b377a0e47d9d7df",
11
+ "packageJson": "6503dd2bae4aeeaeacc81921ed5e5988edb1e1d57621e862974cb137e8e7f813",
12
+ "agents": "8649047a57ee16eb46557ab5a9504315421f6f78b434ee19ed0d4df24b45f20d"
13
13
  },
14
14
  "exports": [
15
15
  ".",
@@ -32,7 +32,7 @@
32
32
  "@happyvertical/sql": "catalog:",
33
33
  "@sveltejs/package": "^2.5.8",
34
34
  "@types/node": "24.13.6",
35
- "svelte": "^5.57.1",
35
+ "svelte": "^5.57.0",
36
36
  "svelte-check": "^4.7.6",
37
37
  "typescript": "6.0.3",
38
38
  "vite": "8.1.4",
@@ -64,7 +64,7 @@
64
64
  "youtube"
65
65
  ],
66
66
  "risks": [
67
- "cross-package-refs:2",
67
+ "cross-package-refs:3",
68
68
  "sensitive-fields-excluded",
69
69
  "sti-inheritance"
70
70
  ],
@@ -824,6 +824,13 @@
824
824
  "related": "SocialAccount",
825
825
  "columnType": "UUID"
826
826
  },
827
+ {
828
+ "name": "createdByUserId",
829
+ "type": "crossPackageRef",
830
+ "required": false,
831
+ "related": "@happyvertical/smrt-users:User",
832
+ "columnType": "UUID"
833
+ },
827
834
  {
828
835
  "name": "videoContentId",
829
836
  "type": "crossPackageRef",
@@ -935,6 +942,13 @@
935
942
  "related": "SocialAccount",
936
943
  "columnType": "UUID"
937
944
  },
945
+ {
946
+ "name": "createdByUserId",
947
+ "type": "crossPackageRef",
948
+ "required": false,
949
+ "related": "@happyvertical/smrt-users:User",
950
+ "columnType": "UUID"
951
+ },
938
952
  {
939
953
  "name": "videoContentId",
940
954
  "type": "crossPackageRef",
@@ -2198,11 +2212,11 @@
2198
2212
  "prompts": [],
2199
2213
  "relationshipsV2": {
2200
2214
  "foreignKeyFields": 2,
2201
- "crossPackageRefFields": 2,
2215
+ "crossPackageRefFields": 3,
2202
2216
  "junctionCollections": 0,
2203
2217
  "hierarchicalObjects": 0,
2204
2218
  "polymorphicAssociations": 0,
2205
- "uuidColumns": 24
2219
+ "uuidColumns": 26
2206
2220
  },
2207
- "agentDoc": "# @happyvertical/smrt-social\n\nSocial media account management with OAuth and post scheduling. Supports YouTube, Threads, X (Twitter), Bluesky.\n\n## Models\n\n- **SocialAccount** (STI): `platform`, `accessToken`/`refreshToken`, `tokenExpiresAt`, `status` (connected/expired/error), `linkBehavior` (description/reply/none). `isTokenExpired` checks with 5-min buffer. `isReady` gate checks active + connected + token present + not expired.\n- **SocialPost**: `scheduledAt`, `publishedAt`, `status` (draft/scheduled/publishing/published/failed), `analytics` JSON (views/likes/comments/shares/clicks).\n- **OAuthState** (STI): CSRF token + PKCE `codeVerifier` with 10-min TTL.\n\n## Gotchas\n\n- **Tokens not encrypted**: OAuth tokens stored as plaintext — TODO for smrt-secrets integration\n- **No auto-publishing**: `scheduledAt` is metadata only — app must implement job runner to trigger publishing\n- **Analytics manual**: `analytics` field must be updated by platform sync, not auto-populated\n- **Platform enum hardcoded**: youtube/threads/x/bluesky — extending requires code changes\n- **OAuthState TTL**: 10 minutes, app must clean up expired states\n- **Optional tenancy** on all models\n- **Relationship targets**: `SocialPost.socialAccountId` and\n `SocialPostAnalyticsSnapshot.socialPostId` are same-package `@foreignKey`\n columns; `SocialPost.videoContentId` is a `@crossPackageRef` to\n `@happyvertical/smrt-video:VideoContent` (no value import back into this\n package). They previously used `@foreignKey(() => X)`, whose target resolved\n to `''`, so none of them carried a relationship edge (#2379).\n"
2221
+ "agentDoc": "# @happyvertical/smrt-social\n\nSocial media account management with OAuth and post scheduling. Supports YouTube, Threads, X (Twitter), Bluesky.\n\n## Models\n\n- **SocialAccount** (STI): `platform`, `accessToken`/`refreshToken`, `tokenExpiresAt`, `status` (connected/expired/error), `linkBehavior` (description/reply/none). `isTokenExpired` checks with 5-min buffer. `isReady` gate checks active + connected + token present + not expired.\n- **SocialPost**: `scheduledAt`, `publishedAt`, `status` (draft/scheduled/publishing/published/failed), `analytics` JSON (views/likes/comments/shares/clicks). `createdByUserId` (nullable `@crossPackageRef` to `smrt-users:User`, uuid on PostgreSQL) records the poster; pass it to `createDraft()` so a failed publish can be reported to that person. Agent/schedule posts leave it null; `recordPublishSuccess`/`Failure` never change it. Adding the column is additive: consumers run `db:migrate`.\n- **OAuthState** (STI): CSRF token + PKCE `codeVerifier` with 10-min TTL.\n\n## Gotchas\n\n- **Tokens not encrypted**: OAuth tokens stored as plaintext — TODO for smrt-secrets integration\n- **No auto-publishing**: `scheduledAt` is metadata only — app must implement job runner to trigger publishing\n- **Analytics manual**: `analytics` field must be updated by platform sync, not auto-populated\n- **Platform enum hardcoded**: youtube/threads/x/bluesky — extending requires code changes\n- **OAuthState TTL**: 10 minutes, app must clean up expired states\n- **Optional tenancy** on all models\n- **Relationship targets**: `SocialPost.socialAccountId` and\n `SocialPostAnalyticsSnapshot.socialPostId` are same-package `@foreignKey`\n columns; `SocialPost.videoContentId` is a `@crossPackageRef` to\n `@happyvertical/smrt-video:VideoContent` (no value import back into this\n package). They previously used `@foreignKey(() => X)`, whose target resolved\n to `''`, so none of them carried a relationship edge (#2379).\n"
2208
2222
  }
@@ -124,6 +124,10 @@ export interface SocialPostOptions extends SmrtObjectOptions {
124
124
  * When analytics were last synced
125
125
  */
126
126
  analyticsLastSyncedAt?: Date | null;
127
+ /**
128
+ * User who created (drafted, scheduled, or published) the post
129
+ */
130
+ createdByUserId?: string | null;
127
131
  /**
128
132
  * Tenant ID for multi-tenant isolation
129
133
  */
@@ -161,6 +165,17 @@ export declare class SocialPost extends SmrtObject {
161
165
  * Social account to publish to
162
166
  */
163
167
  socialAccountId: string | null;
168
+ /**
169
+ * User who created the post: drafted it, scheduled it, or published it
170
+ * directly. Hosts use it to tell the poster when publishing fails.
171
+ *
172
+ * A `@crossPackageRef` to `@happyvertical/smrt-users:User`, so a native uuid
173
+ * column on PostgreSQL with no DDL FK. Null for posts created before the
174
+ * column existed and for posts an agent or schedule created with no person
175
+ * behind them. Set through `SocialPostCollection.createDraft()`; the
176
+ * publish-outcome helpers (`recordPublishSuccess`/`Failure`) never change it.
177
+ */
178
+ createdByUserId: string | null;
164
179
  /**
165
180
  * Video content to publish
166
181
  *
@@ -1 +1 @@
1
- {"version":3,"file":"social-post.d.ts","sourceRoot":"","sources":["../src/social-post.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"social-post.d.ts","sourceRoot":"","sources":["../src/social-post.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-social",
3
- "version": "0.51.38",
3
+ "version": "0.53.0",
4
4
  "type": "module",
5
5
  "smrtRawPrimitives": "strict",
6
6
  "description": "Social media account management for multi-platform publishing in the SMRT ecosystem",
@@ -26,7 +26,7 @@
26
26
  "./manifest.json": "./dist/manifest.json"
27
27
  },
28
28
  "peerDependencies": {
29
- "svelte": "^5.57.1"
29
+ "svelte": "^5.57.0"
30
30
  },
31
31
  "peerDependenciesMeta": {
32
32
  "svelte": {
@@ -39,19 +39,19 @@
39
39
  "AGENTS.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@happyvertical/smrt-config": "0.51.38",
43
- "@happyvertical/smrt-content": "0.51.38",
44
- "@happyvertical/smrt-core": "0.51.38",
45
- "@happyvertical/smrt-secrets": "0.51.38",
46
- "@happyvertical/smrt-tenancy": "0.51.38",
47
- "@happyvertical/smrt-ui": "0.51.38",
48
- "@happyvertical/smrt-video": "0.51.38",
49
- "@happyvertical/utils": "^0.96.0"
42
+ "@happyvertical/smrt-config": "0.53.0",
43
+ "@happyvertical/smrt-content": "0.53.0",
44
+ "@happyvertical/smrt-core": "0.53.0",
45
+ "@happyvertical/smrt-secrets": "0.53.0",
46
+ "@happyvertical/smrt-tenancy": "0.53.0",
47
+ "@happyvertical/smrt-ui": "0.53.0",
48
+ "@happyvertical/smrt-video": "0.53.0",
49
+ "@happyvertical/utils": "^0.98.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@happyvertical/logger": "^0.96.0",
53
- "@happyvertical/smrt-vitest": "0.51.38",
54
- "@happyvertical/sql": "^0.96.0",
52
+ "@happyvertical/logger": "^0.98.0",
53
+ "@happyvertical/smrt-vitest": "0.53.0",
54
+ "@happyvertical/sql": "^0.98.0",
55
55
  "@sveltejs/package": "^2.5.8",
56
56
  "@types/node": "24.13.6",
57
57
  "svelte": "^5.57.1",