Can-Connect Data Middleware

raw JSON →
4.0.6 verified Thu Apr 23 auth: no javascript

Can-Connect is a JavaScript library providing a flexible, framework-agnostic middleware layer for managing persisted data. It allows developers to compose various behaviors to create powerful model layers, abstracting data access from underlying sources like REST APIs, WebSockets, or local storage. The current stable version is 4.0.6, with recent releases including maintenance patches for both the v3 and v4 series, indicating active development. Its core differentiator lies in its highly modular, behavior-driven architecture, enabling custom data handling logic to be assembled and reused, making it suitable for complex data interaction patterns across any JavaScript environment, not just the CanJS ecosystem.

error Error: Cannot find module 'can-connect/tag/'
cause Attempting to import from `can-connect/tag/` after upgrading to v4.0.0, where this module was removed.
fix
Install can-connect-tag separately via npm install can-connect-tag and update your import statements to point to the new package.
error TypeError: (0 , can_connect__WEBPACK_IMPORTED_MODULE_0__.connect) is not a function
cause This error often occurs in bundlers (like Webpack) when mixing CommonJS `require()` with ESM `import`, or when attempting to destructure a named export that doesn't exist or isn't properly aliased.
fix
Ensure you are using import { connect } from 'can-connect'; for ESM environments and const { connect } = require('can-connect'); if using CommonJS (though ESM is preferred for v4+).
error TypeError: Cannot read properties of undefined (reading 'id') during data hydration
cause A data object being processed by `can-connect` (e.g., in a `constructor-hydrate` behavior) is missing the property designated as the ID, or the `idProp` option is misconfigured, especially for Map types.
fix
Verify that your data consistently includes the ID property defined by idProp (defaulting to 'id'). Consider upgrading to can-connect@4.0.5 or newer for improved handling of missing props objects during hydration.
breaking The `can-connect/tag/` module has been removed in v4.0.0 and moved to its own repository, `can-connect-tag`. Direct imports from `can-connect/tag/` will now fail.
fix Migrate usage to `can-connect-tag` by installing it separately (`npm install can-connect-tag`) and updating import paths.
gotcha Prior to v4.0.5, hydrating Map types without a `props` object could lead to errors related to `connection.id`. Ensure objects passed for hydration have expected properties, especially for ID retrieval.
fix Upgrade to v4.0.5 or newer. Ensure data objects consistently include properties expected by the connection's `idProp`.
gotcha Versions prior to v3.2.6 and v4.0.1 experienced issues with production builds when using non-StealJS bundlers, potentially causing build failures or incorrect output.
fix Upgrade to v3.2.6 (for 3.x series) or v4.0.1 (for 4.x series) or newer to ensure compatibility with various bundlers.
gotcha The `can-util` dependency was removed in v3.2.3. If your application indirectly relied on `can-util` exports through `can-connect`, this change might break existing functionality.
fix Directly import `can-util` if your application explicitly uses its utilities; otherwise, ensure your code no longer implicitly relies on its presence via `can-connect`.
npm install can-connect
yarn add can-connect
pnpm add can-connect

This quickstart demonstrates how to define a data connection using `can-connect` by composing various behaviors like `dataUrl` for RESTful interaction and `constructor` for instance management. It showcases fetching a list and a single item from a mock API.

import { connect, dataUrl, constructor } from 'can-connect';

// Mock global fetch for demonstration purposes
global.fetch = async (url, options) => {
  console.log(`Mock fetch: ${options?.method || 'GET'} ${url}`);
  if (url === '/api/todos' && options?.method === 'GET') {
    return {
      json: async () => ([
        { _id: '1', title: 'Buy milk' },
        { _id: '2', title: 'Walk dog' }
      ]),
      status: 200
    };
  }
  if (url.startsWith('/api/todos/') && options?.method === 'GET') {
    const id = url.split('/').pop();
    return {
      json: async () => ({ _id: id, title: `Todo ${id}` }),
      status: 200
    };
  }
  return { json: async () => ({}), status: 200 };
};

const TodoConnection = connect([
  dataUrl('/api/todos'), // Defines how to fetch data from a URL
  constructor(),         // Provides methods for creating, updating, deleting instances
  {
    name: 'todos',
    // Custom behaviors or overrides can be added here
    getListData() {
      console.log('Custom getListData called...');
      return this._super(); // Call the base dataUrl behavior's getListData
    },
    getData(id) {
      console.log(`Custom getData for ID: ${id} called...`);
      return this._super(id); // Call the base dataUrl behavior's getData
    }
  }
], {
  // Connection options
  idProp: '_id' // Specify the ID property, default is 'id'
});

async function run() {
  try {
    console.log('Fetching all todos...');
    const todos = await TodoConnection.getList({});
    console.log('Retrieved Todos:', todos);

    console.log('\nFetching todo with ID 1...');
    const todo1 = await TodoConnection.get('1');
    console.log('Retrieved Todo 1:', todo1);

  } catch (error) {
    console.error('Operation failed:', error.message);
  }
}

run();