# knack-api

View-based API wrapper for the [Knack](https://www.knack.com/) platform. Provides CRUD operations (GET, POST, PUT, DELETE) with automatic pagination, retry logic, and built-in session management.

For object-based requests, see [knack-object-api](https://www.npmjs.com/package/knack-object-api).

## Installation

```bash
npm install knack-api
```

## Next-Gen Knack support (v3.1.0+)

v3.1.0 works on both Classic and Next-Gen Knack apps. It detects the platform at runtime: Classic
reads the synchronous globals (`Knack.getUserToken()`, `Knack.application_id`), while Next-Gen
uses the promise-based object API (`Knack.getUser()`, `Knack.getApplicationDetails()`). Network
requests use the native `fetch` API instead of jQuery (`Knack.$.ajax`), which Next-Gen removes.
No code changes are required in your app — the imports and method signatures are unchanged.

## Migrating from v2 to v3

### Imports

```js
// v2
const { knack_api } = require("knack-api");
import { knack_api } from "knack-api/ts";

// v3
import { get, post, put, deletion } from "knack-api";
```

### Calling methods

```js
// v2
await knack_api.get({...});
await knack_api.post({...});
await knack_api.put({...});
await knack_api.deletion({...});

// v3
await get({...});
await post({...});
await put({...});
await deletion({...});
```

### Types (TypeScript)

```ts
// v2 - lowercase
import {
  getSettings,
  putSettings,
  postSettings,
  deleteSettings,
} from "knack-api/ts";

// v3 - PascalCase, imported from main entry
import {
  GetSettings,
  PutSettings,
  PostSettings,
  DeleteSettings,
} from "knack-api";
```

### Filter rules `value` type

```ts
// v2 - used to accept string only
value?: string;

// v3 - now accepts any type
value?: any;
```

### Package output

v2 shipped raw JS files (`knack_api.js`, `helpers/`).
v3 ships compiled output only, the package contains `dist/` with CJS, ESM, and type declarations.

## Import

```javascript
// JavaScript
import { get, post, put, deletion } from "knack-api";

// TypeScript
import {
  get,
  post,
  put,
  deletion,
  GetSettings,
  PutSettings,
  PostSettings,
  DeleteSettings,
} from "knack-api";
```

## API

All methods accept a settings object and return a Promise.

`host` and `subdomain` are optional on all request types. By default, requests go to `https://api.knack.com`. Setting `host: 'knackly'` and `subdomain: 'api'` would send requests to `https://api.knackly.com`.

### `get(settings)`

Retrieves records from a view. Automatically paginates through all available pages.

```javascript
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  host: "knackly",
  subdomain: "api",
});
```

### `post(settings)`

Creates a new record.

```javascript
const result = await post({
  sceneKey: "scene_1",
  viewKey: "view_1",
  payload: { field_1: "value" },
});
```

### `put(settings)`

Updates an existing record.

```javascript
const result = await put({
  sceneKey: "scene_1",
  viewKey: "view_1",
  recordId: "6123abc456def",
  payload: { field_1: "updated value" },
});
```

### `deletion(settings)`

Deletes a record.

```javascript
const result = await deletion({
  sceneKey: "scene_1",
  viewKey: "view_1",
  recordId: "6123abc456def",
});
```

## Settings Reference

### GET Settings

| Parameter     | Type                 | Required | Default   | Description                                                                 |
| ------------- | -------------------- | -------- | --------- | --------------------------------------------------------------------------- |
| `sceneKey`    | `string`             | Yes      | —         | Scene identifier (e.g. `'scene_1'`)                                         |
| `viewKey`     | `string`             | Yes      | —         | View identifier (e.g. `'view_1'`)                                           |
| `recordId`    | `string`             | No       | —         | Fetch a single record by ID                                                 |
| `filters`     | `object`             | No       | —         | Filter rules (see [Filters](#filters))                                      |
| `sort`        | `object \| object[]` | No       | —         | Sort configuration (see [Sorting](#sorting))                                |
| `page`        | `number`             | No       | `1`       | Starting page number                                                        |
| `rowsPerPage` | `number`             | No       | `1000`    | Records per page (max `1000`)                                               |
| `recordLimit` | `number`             | No       | —         | Maximum total records to retrieve                                           |
| `cb`          | `function`           | No       | —         | Callback invoked after each page request                                    |
| `isPublic`    | `boolean`            | No       | `false`   | Skip authentication for public views                                        |
| `context`     | `object`             | No       | —         | Context for views that require a parent record ID (see [Context](#context)) |
| `host`        | `string`             | No       | `'knack'` | Host used in the request URL                                                |
| `subdomain`   | `string`             | No       | `'api'`   | Subdomain used in the request URL                                           |

### POST Settings

| Parameter   | Type      | Required | Description                          |
| ----------- | --------- | -------- | ------------------------------------ |
| `sceneKey`  | `string`  | Yes      | Scene identifier                     |
| `viewKey`   | `string`  | Yes      | View identifier                      |
| `payload`   | `object`  | Yes      | Field values for the new record      |
| `isPublic`  | `boolean` | No       | Skip authentication for public views |
| `host`      | `string`  | No       | Host used in the request URL         |
| `subdomain` | `string`  | No       | Subdomain used in the request URL    |

### PUT Settings

| Parameter   | Type      | Required | Description                          |
| ----------- | --------- | -------- | ------------------------------------ |
| `sceneKey`  | `string`  | Yes      | Scene identifier                     |
| `viewKey`   | `string`  | Yes      | View identifier                      |
| `recordId`  | `string`  | Yes      | ID of the record to update           |
| `payload`   | `object`  | Yes      | Field values to update               |
| `isPublic`  | `boolean` | No       | Skip authentication for public views |
| `host`      | `string`  | No       | Host used in the request URL         |
| `subdomain` | `string`  | No       | Subdomain used in the request URL    |

### DELETE Settings

| Parameter   | Type      | Required | Description                          |
| ----------- | --------- | -------- | ------------------------------------ |
| `sceneKey`  | `string`  | Yes      | Scene identifier                     |
| `viewKey`   | `string`  | Yes      | View identifier                      |
| `recordId`  | `string`  | Yes      | ID of the record to delete           |
| `isPublic`  | `boolean` | No       | Skip authentication for public views |
| `host`      | `string`  | No       | Host used in the request URL         |
| `subdomain` | `string`  | No       | Subdomain used in the request URL    |

## Filters

Pass a `filters` object to narrow GET results.

```javascript
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  filters: {
    match: "and",
    rules: [
      { field: "field_1", operator: "contains", value: "search term" },
      { field: "field_2", operator: "is not blank" },
    ],
  },
});
```

### Available Operators

| Operator                 | Description                                   |
| ------------------------ | --------------------------------------------- |
| `is`                     | Exact match                                   |
| `is not`                 | Not equal                                     |
| `is blank`               | Field is empty                                |
| `is not blank`           | Field is not empty                            |
| `contains`               | String contains value                         |
| `does not contain`       | String does not contain value                 |
| `starts with`            | String starts with value                      |
| `ends with`              | String ends with value                        |
| `is any`                 | Value is in a list                            |
| `higher than`            | Numeric greater than                          |
| `lower than`             | Numeric less than                             |
| `near`                   | Location proximity (use `range` for distance) |
| `is today`               | Date is today                                 |
| `is today or before`     | Date is today or earlier                      |
| `is today or after`      | Date is today or later                        |
| `is before today`        | Date is before today                          |
| `is after today`         | Date is after today                           |
| `is before`              | Date is before value                          |
| `is after`               | Date is after value                           |
| `is during the current`  | Date during current period                    |
| `is during the previous` | Date during previous period                   |
| `is during the next`     | Date during next period                       |
| `is before the previous` | Date before previous period                   |
| `is after the next`      | Date after next period                        |
| `is before current time` | Time is before now                            |
| `is after current time`  | Time is after now                             |

## Sorting

Sort accepts a single object or an array for multi-field sorting.

```javascript
// Single sort
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  sort: { field: "field_1", order: "asc" },
});

// Multi-field sort
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  sort: [
    { field: "field_1", order: "asc" },
    { field: "field_2", order: "desc" },
  ],
});
```

## Include / Exclude Fields

Filter which fields are returned from a GET request using `.include()` or `.exclude()`.

```javascript
// Only return specific fields
const results = await get(settings).include(["field_1", "field_2"]);

// Return all fields except those specified
const results = await get(settings).exclude(["field_3"]);
```

## Pagination

GET requests automatically paginate through all available pages and return the combined results. You can control this behavior with:

- **`rowsPerPage`** — Records per request (max `1000`, default `1000`).
- **`recordLimit`** — Stop after retrieving this many total records.
- **`page`** — Start from a specific page (default `1`).

```javascript
// Fetch up to 200 records, 50 per request
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  rowsPerPage: 50,
  recordLimit: 200,
});
```

## Callback

The `cb` parameter is called after each individual page request. This is useful for tracking progress when fetching large datasets.

```javascript
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  cb: (data) => {
    console.log(`Received ${data.records.length} records`);
  },
});
```

All records are still accumulated in memory and returned in the final resolved promise.

## Public Views

Set `isPublic: true` to skip user token verification. Use this for views that are accessible without authentication.

```javascript
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  isPublic: true,
});
```

Available on `get`, `post`, `put` and `deletion` methods.

## Context

Some views require a parent record ID to fetch related data. Pass a `context` object where the key follows the pattern `{page-slug}_id` and the value is the record ID.

```javascript
const results = await get({
  sceneKey: "scene_1",
  viewKey: "view_1",
  context: {
    "my-page-slug_id": "6123abc456def",
  },
});
```

## Retry Logic

Failed requests are automatically retried up to 3 times before rejecting the promise. This behavior is built in and not configurable.

## Session Management

For authenticated requests, the library verifies the user's session token before making API calls. If the session has expired, a modal is displayed prompting the user to log back in, and the promise is rejected with `"User token is invalid."`.

## License

ISC
