Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@syncagent/angular

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@syncagent/angular

SyncAgent Angular SDK — service and component for AI database chat

latest
Source
npmnpm
Version
0.4.0
Version published
Weekly downloads
170
3300%
Maintainers
1
Weekly downloads
 
Created
Source

@syncagent/angular

Angular SDK for SyncAgent — injectable service with Angular Signals and RxJS observables for AI database chat.

Works with MongoDB, PostgreSQL, MySQL, SQLite, SQL Server, and Supabase.

npm version License: MIT

Get Your API Key

  • Sign up for a free account
  • Go to your DashboardNew Project → choose your database type
  • Copy your API key (starts with sa_)

Every new project gets a 14-day trial with 500 free requests — no credit card required. After the trial, you get 100 free requests/month on the Free plan.

Install

npm install @syncagent/angular @syncagent/js

Quick Start

// app.component.ts
import { Component, OnInit } from "@angular/core";
import { SyncAgentService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-root",
  providers: [SyncAgentService],
  template: `
    <div class="chat">
      <div *ngIf="agent.status() as s" class="status">⏳ {{ s.label }}</div>

      <div class="messages">
        <div *ngFor="let msg of agent.messages()" [class]="'message ' + msg.role">
          {{ msg.content }}
        </div>
      </div>

      <div *ngIf="agent.error() as err" class="error">⚠️ {{ err.message }}</div>

      <div class="input-row">
        <input [(ngModel)]="input" (keydown.enter)="send()" placeholder="Ask about your data..." [disabled]="agent.isLoading()" />
        <button (click)="send()" [disabled]="agent.isLoading() || !input.trim()">Send</button>
        <button *ngIf="agent.isLoading()" (click)="agent.stop()">Stop</button>
        <button (click)="agent.reset()">Clear</button>
      </div>
    </div>
  `,
})
export class AppComponent implements OnInit {
  input = "";

  constructor(public agent: SyncAgentService) {}

  ngOnInit() {
    this.agent.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
    });
  }

  send() {
    if (!this.input.trim()) return;
    this.agent.sendMessage(this.input);
    this.input = "";
  }
}

Environment Setup

// environments/environment.ts
export const environment = {
  production: false,
  syncagentKey: "sa_your_api_key",
  databaseUrl: "mongodb+srv://user:pass@cluster/db",
};

⚠️ Security note: In production, pass the connection string from your backend API instead of bundling it in the client.

Multi-tenant SaaS

@Component({ providers: [SyncAgentService] })
export class DashboardComponent implements OnInit {
  constructor(
    public agent: SyncAgentService,
    private authService: AuthService,
  ) {}

  ngOnInit() {
    const user = this.authService.currentUser;

    this.agent.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      filter: { organizationId: user.orgId },
      operations: user.isAdmin
        ? ["read", "create", "update", "delete"]
        : ["read"],
      context: { userId: user.id, userRole: user.role },
    });
  }
}

Using RxJS Observables

import { Subject, takeUntil, filter } from "rxjs";

@Component({ providers: [SyncAgentService] })
export class ChatComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(public agent: SyncAgentService) {}

  ngOnInit() {
    this.agent.configure({ apiKey: "...", connectionString: "..." });

    // Subscribe to messages stream
    this.agent.messages$
      .pipe(takeUntil(this.destroy$))
      .subscribe(messages => console.log("Messages:", messages.length));

    // Subscribe to each new assistant message
    this.agent.message$
      .pipe(takeUntil(this.destroy$))
      .subscribe(msg => console.log("New:", msg.content.slice(0, 50)));

    // Subscribe to status updates
    this.agent.status$
      .pipe(takeUntil(this.destroy$), filter(Boolean))
      .subscribe(({ step, label }) => console.log(`[${step}] ${label}`));
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Custom Tools

this.agent.configure({
  apiKey: environment.syncagentKey,
  connectionString: environment.databaseUrl,
  tools: {
    sendEmail: {
      description: "Send an email to a user",
      inputSchema: {
        to:      { type: "string", description: "Recipient email" },
        subject: { type: "string", description: "Subject line" },
        body:    { type: "string", description: "Email body" },
      },
      execute: async ({ to, subject, body }) => {
        await this.emailService.send({ to, subject, body });
        return { sent: true };
      },
    },
  },
  onData: (data) => {
    if (data.collection === "orders") {
      this.orders = data.data;
    }
  },
});

Tools-only Mode

Use toolsOnly: true when you want the agent to only call your custom tools — no database access:

this.agent.configure({
  apiKey: environment.syncagentKey,
  toolsOnly: true,
  tools: {
    searchProducts: {
      description: "Search products by name",
      inputSchema: { query: { type: "string", description: "Search query" } },
      execute: async ({ query }) => {
        const res = await fetch(`/api/products?q=${query}`);
        return res.json();
      },
    },
    createTicket: {
      description: "Create a support ticket",
      inputSchema: {
        title:   { type: "string" },
        message: { type: "string" },
      },
      execute: async ({ title, message }) => {
        return await this.ticketService.create({ title, message });
      },
    },
  },
});

Customer Agent Mode

The CustomerChatService is a dedicated Angular service for building customer-facing support interfaces. It routes messages through SyncAgent's customer support pipeline — using your configured persona, flows, knowledge base, and escalation rules — instead of direct database access.

Provide it at the component or module level and call configure() before sending messages.

import { Component, OnInit } from "@angular/core";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-support-widget",
  providers: [CustomerChatService],
  template: `
    <div class="chat">
      @if (chat.welcomeMessage()) {
        <p class="welcome">{{ chat.welcomeMessage() }}</p>
      }

      <div class="messages">
        @for (msg of chat.messages(); track msg.content) {
          <div [class]="'message ' + msg.role">{{ msg.content }}</div>
        }
      </div>

      @if (chat.isEscalated()) {
        <p class="notice">You've been connected to a human agent.</p>
      }

      @if (chat.error() as err) {
        <p class="error">{{ err.message }}</p>
      }

      <div class="input-row">
        <input
          [(ngModel)]="input"
          (keydown.enter)="send()"
          placeholder="How can we help?"
          [disabled]="chat.isLoading()"
        />
        <button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button>
      </div>

      @if (chat.isResolved()) {
        <div class="rating">
          <p>How was your experience?</p>
          <button *ngFor="let r of [1,2,3,4,5]" (click)="rate(r)">{{ r }}⭐</button>
        </div>
      }
    </div>
  `,
})
export class SupportWidgetComponent implements OnInit {
  input = "";

  constructor(public chat: CustomerChatService) {}

  ngOnInit() {
    this.chat.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      externalUserId: "user_abc123",
      onEscalated: () => console.log("Escalated to human agent"),
      onResolved: (id) => console.log("Conversation resolved:", id),
    });
  }

  send() {
    if (!this.input.trim()) return;
    this.chat.sendMessage(this.input);
    this.input = "";
  }

  rate(rating: number) {
    this.chat.rateConversation(rating);
  }
}

Angular Signals

SignalTypeDescription
messages()Message[]Conversation message history
conversationId()string | nullCurrent conversation ID
isLoading()booleanTrue while waiting for a response
isEscalated()booleanTrue when escalated to a human agent
isResolved()booleanTrue when the conversation is resolved
error()Error | nullLast error, if any
welcomeMessage()string | nullWelcome message (first interaction only)
isConfigured()booleanTrue after configure() has been called

RxJS Observables

ObservableTypeDescription
messages$Observable<Message[]>Conversation history stream
isLoading$Observable<boolean>Loading state stream
error$Observable<Error | null>Error stream
escalated$Observable<void>Emits when escalated to a human agent
resolved$Observable<string>Emits the conversation ID when resolved

CustomerChatServiceConfig

Extends SyncAgentConfig with customer-mode-specific options:

PropertyTypeDescription
apiKeystringYour SyncAgent API key
connectionStringstringDatabase connection string
customerModetrueEnables customer agent mode
externalUserIdstringUnique identifier for the end-user
onEscalated() => voidOptional callback when escalated to a human agent
onResolved(conversationId: string) => voidOptional callback when the conversation is resolved

Methods

MethodSignatureDescription
configure(config)configure(config: CustomerChatServiceConfig): voidInitialize the service with customer mode config. Must be called before other methods.
sendMessage(content, metadata?)sendMessage(content: string, metadata?: Record<string, any>): Promise<void>Send a message and receive an AI response. Ignored if content is empty or already loading.
rateConversation(rating)rateConversation(rating: number): Promise<void>Rate the current conversation (1-5). Throws if no active conversation.
reset()reset(): voidClear all state (messages, conversation ID, errors) and start fresh.

Guest Identification

When no externalUserId is provided, the CustomerChatService activates the guest identification flow. Guests must identify themselves before sending messages. The service exposes Angular signals and RxJS observables for reactive state management.

Signals & Observables

Signal / ObservableTypeDescription
isIdentified()Signal<boolean>true when the guest has been identified (via form or stored identity)
guestIdentity()Signal<GuestIdentity | null>The current guest identity, or null if not yet identified
isIdentified$Observable<boolean>Emits the current identification state (BehaviorSubject)
guestIdentified$Observable<GuestIdentity>Emits the GuestIdentity when identification completes

identifyGuest(data)

ParameterTypeRequiredDescription
namestringYesGuest's display name
emailstringYesGuest's email address
phonestringNoGuest's phone number

Validates input, generates a deterministic guest identifier from the email, persists the identity to localStorage, updates signals/observables, and invokes the onGuestIdentified callback if configured.

Template Usage with Signals

import { Component, OnInit } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-support-widget",
  providers: [CustomerChatService],
  imports: [FormsModule],
  template: `
    @if (!chat.isIdentified()) {
      <form (ngSubmit)="identify()">
        <h2>Welcome</h2>
        <p>Please introduce yourself to get started</p>

        <label for="name">Name</label>
        <input id="name" [(ngModel)]="name" name="name" required />

        <label for="email">Email</label>
        <input id="email" [(ngModel)]="email" name="email" type="email" required />

        <label for="phone">Phone (optional)</label>
        <input id="phone" [(ngModel)]="phone" name="phone" type="tel" />

        @if (chat.error() as err) {
          <p class="error">{{ err.message }}</p>
        }

        <button type="submit">Start Chat</button>
      </form>
    } @else {
      <div class="chat">
        <div class="messages">
          @for (msg of chat.messages(); track msg.content) {
            <div [class]="'message ' + msg.role">{{ msg.content }}</div>
          }
        </div>
        <div class="input-row">
          <input
            [(ngModel)]="input"
            (keydown.enter)="send()"
            placeholder="How can we help?"
            [disabled]="chat.isLoading()"
          />
          <button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button>
        </div>
      </div>
    }
  `,
})
export class SupportWidgetComponent implements OnInit {
  name = "";
  email = "";
  phone = "";
  input = "";

  constructor(public chat: CustomerChatService) {}

  ngOnInit() {
    this.chat.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      onGuestIdentified: (identity) => {
        console.log("Guest identified:", identity.guestId);
      },
    });
  }

  identify() {
    this.chat.identifyGuest({
      name: this.name,
      email: this.email,
      phone: this.phone || undefined,
    });
  }

  send() {
    if (!this.input.trim()) return;
    this.chat.sendMessage(this.input);
    this.input = "";
  }
}

Reactive Handling with guestIdentified$

import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-guest-chat",
  providers: [CustomerChatService],
  template: `<!-- ... -->`,
})
export class GuestChatComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(public chat: CustomerChatService) {}

  ngOnInit() {
    this.chat.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
    });

    // React to guest identification
    this.chat.guestIdentified$
      .pipe(takeUntil(this.destroy$))
      .subscribe((identity) => {
        console.log(`Guest ${identity.name} identified as ${identity.guestId}`);
        // e.g., send analytics event, update UI state
      });

    // Track identification state changes
    this.chat.isIdentified$
      .pipe(takeUntil(this.destroy$))
      .subscribe((identified) => {
        console.log("Identification state:", identified);
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Dual Mode (Database + Customer Agent)

⚠️ Deprecated: createDual() will be removed in a future major version. Use DualChatService instead — pass externalUserId directly to enable both modes on a single instance.

Legacy pattern (deprecated):

import { Component, OnInit } from "@angular/core";
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentService, CustomerChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-dual-chat",
  providers: [SyncAgentService, CustomerChatService],
  template: `<!-- admin + customer UIs -->`,
})
export class DualChatComponent implements OnInit {
  constructor(
    public admin: SyncAgentService,
    public support: CustomerChatService,
  ) {}

  ngOnInit() {
    const { db, support } = SyncAgentClient.createDual({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      externalUserId: "customer_123",
    });

    // Admin: direct database queries
    this.admin.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
    });

    // Customer: support pipeline
    this.support.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      customerMode: true,
      externalUserId: "customer_123",
    });
  }
}

Before (deprecated):

const { db, support } = SyncAgentClient.createDual({
  apiKey: environment.syncagentKey,
  connectionString: environment.databaseUrl,
  externalUserId: "customer_123",
});

After (recommended):

import { DualChatService } from "@syncagent/angular";

@Component({
  providers: [DualChatService],
})
export class DualChatComponent implements OnInit {
  constructor(public dual: DualChatService) {}

  ngOnInit() {
    this.dual.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      externalUserId: "customer_123",
    });
  }
}

Tip: In Angular, you can also use createDual() to get both client instances and pass them to your services' internal logic, or simply configure each service independently as shown above.

Unified Dual Mode

The DualChatService provides a single injectable service that manages both database agent and customer support chat from one component. Instead of wiring up SyncAgentService and CustomerChatService separately, inject DualChatService and call configure() with your config including externalUserId — both modes become available immediately.

import { Component, OnInit } from "@angular/core";
import { DualChatService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-dual-chat",
  providers: [DualChatService],
  template: `
    <div class="dual-chat">
      <section class="db-panel">
        <h3>Database Agent</h3>
        <div class="messages">
          @for (msg of dual.dbMessages(); track msg.content) {
            <div [class]="'message ' + msg.role">{{ msg.content }}</div>
          }
        </div>
        @if (dual.dbError() as err) {
          <p class="error">{{ err.message }}</p>
        }
        <div class="input-row">
          <input
            [(ngModel)]="dbInput"
            (keydown.enter)="sendDb()"
            placeholder="Ask about your data..."
            [disabled]="dual.dbIsLoading()"
          />
          <button (click)="sendDb()" [disabled]="dual.dbIsLoading() || !dbInput.trim()">Send</button>
        </div>
      </section>

      <section class="support-panel">
        <h3>Customer Support</h3>
        <div class="messages">
          @for (msg of dual.supportMessages(); track msg.content) {
            <div [class]="'message ' + msg.role">{{ msg.content }}</div>
          }
        </div>
        @if (dual.supportIsEscalated()) {
          <p class="notice">Connected to a human agent.</p>
        }
        @if (dual.supportError() as err) {
          <p class="error">{{ err.message }}</p>
        }
        <div class="input-row">
          <input
            [(ngModel)]="supportInput"
            (keydown.enter)="sendSupport()"
            placeholder="How can we help?"
            [disabled]="dual.supportIsLoading()"
          />
          <button (click)="sendSupport()" [disabled]="dual.supportIsLoading() || !supportInput.trim()">Send</button>
        </div>
      </section>
    </div>
  `,
})
export class DualChatComponent implements OnInit {
  dbInput = "";
  supportInput = "";

  constructor(public dual: DualChatService) {}

  ngOnInit() {
    this.dual.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      externalUserId: "user_abc123",
      onEscalated: () => console.log("Escalated to human agent"),
      onResolved: (id) => console.log("Resolved:", id),
    });
  }

  sendDb() {
    if (!this.dbInput.trim()) return;
    this.dual.sendDbMessage(this.dbInput);
    this.dbInput = "";
  }

  sendSupport() {
    if (!this.supportInput.trim()) return;
    this.dual.sendSupportMessage(this.supportInput);
    this.supportInput = "";
  }
}

Error Handling

If sendDbMessage(), sendSupportMessage(), stop(), or reset() is called before configure(), the service throws:

DualChatService: call configure() first

If sendSupportMessage() is called without externalUserId in the config, the service throws:

DualChatService: externalUserId is required for customer chat

Angular Signals

SignalTypeDescription
dbMessages()Message[]Database agent conversation message history
dbIsLoading()booleanTrue while the database agent is processing
dbError()Error | nullLast error from the database agent, if any
supportMessages()Message[]Customer support conversation message history
supportIsLoading()booleanTrue while the support agent is processing
supportError()Error | nullLast error from the support agent, if any
supportConversationId()string | nullCurrent support conversation ID
supportIsEscalated()booleanTrue when escalated to a human agent
supportIsResolved()booleanTrue when the support conversation is resolved

RxJS Observables

ObservableTypeDescription
dbMessages$Observable<Message[]>Database agent message history stream
dbIsLoading$Observable<boolean>Database agent loading state stream
dbError$Observable<Error | null>Database agent error stream
supportMessages$Observable<Message[]>Support agent message history stream
supportIsLoading$Observable<boolean>Support agent loading state stream
supportError$Observable<Error | null>Support agent error stream

DualChatServiceConfig

PropertyTypeDescription
apiKeystringYour SyncAgent API key
connectionStringstringDatabase connection string
externalUserIdstringUnique identifier for the end-user (enables customer mode)
contextRecord<string, any>Optional context passed to both agents
onData(data: ToolData) => voidOptional callback when the database agent returns query results
onEscalated() => voidOptional callback when the support conversation is escalated to a human agent
onResolved(conversationId: string) => voidOptional callback when the support conversation is resolved

Methods

MethodSignatureDescription
configure(config)configure(config: DualChatServiceConfig): voidInitialize the service with dual-mode config. Must be called before other methods.
sendDbMessage(content)sendDbMessage(content: string): Promise<void>Send a message to the database agent and receive a streamed response.
sendSupportMessage(content, metadata?)sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>Send a message to the customer support agent. Requires externalUserId in config.
stop()stop(): voidAbort any in-progress streams for both agents.
reset()reset(): voidClear all state (messages, errors, conversation) for both agents and start fresh.

API Reference

SyncAgentService

Methods:

MethodDescription
configure(config)Initialize with API key, connection string, and options
sendMessage(content)Send a message and start streaming
stop()Abort the current stream
reset()Clear all messages

Angular Signals:

SignalTypeDescription
messages()Message[]Conversation history
isLoading()booleanTrue while streaming
error()Error | nullLast error
status(){step,label} | nullLive status (connecting, querying, thinking, done)
lastData()ToolData | nullLast DB query result

RxJS Observables:

ObservableTypeDescription
messages$Observable<Message[]>Conversation history stream
isLoading$Observable<boolean>Loading state stream
error$Observable<Error|null>Error stream
status$Observable<{step,label}|null>Status stream
message$Observable<Message>Emits each new assistant message

Supported Databases

DatabaseConnection String Format
MongoDBmongodb+srv://user:pass@cluster.mongodb.net/mydb
PostgreSQLpostgresql://user:pass@host:5432/mydb
MySQLmysql://user:pass@host:3306/mydb
SQLite/absolute/path/to/database.sqlite
SQL ServerServer=host,1433;Database=mydb;User Id=user;Password=pass;Encrypt=true;
Supabasehttps://xxx.supabase.co|your-anon-key

Security

  • Your database connection string is never stored on SyncAgent servers
  • Passed at runtime, used once, immediately discarded
  • API keys are hashed with bcrypt — raw keys are never stored

Plans & Pricing

PlanRequests/moCollectionsPrice
Free (+ 14-day trial)100 (500 during trial)5GH₵0
Starter5,00020GH₵150/mo
Pro50,000UnlimitedGH₵500/mo
EnterpriseUnlimitedUnlimitedCustom

View full pricing →

Resources

License

MIT

Keywords

syncagent

FAQs

Package last updated on 21 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts