@colixsystems/datastore-client 0.4.0 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,27 +1,60 @@
1
1
  // Hand-written ambient types for @colixsystems/datastore-client.
2
2
  // The package is plain ESM JavaScript; this file is shipped for IDE
3
3
  // IntelliSense. Keep in sync with src/index.js when adding exports.
4
+ //
5
+ // CONTRACT: snake_case in BOTH directions. Wire fields are snake_case
6
+ // verbatim (`table_id`, `created_at`, `can_read`, …) and the client does NO
7
+ // case transform. The only camelCase here is JS method names and the factory
8
+ // option names. This is the DATA PLANE only — tables, records, aggregates,
9
+ // and record-level permissions. Users / groups / files / payments live in
10
+ // sibling packages.
11
+
12
+ // A column on a virtual table (snake_case wire shape).
13
+ export interface Column {
14
+ id: string;
15
+ table_id: string;
16
+ name: string;
17
+ data_type: string;
18
+ required?: boolean;
19
+ target_table_id?: string | null;
20
+ relation_type?: string | null;
21
+ is_identification_column?: boolean;
22
+ encrypted?: boolean;
23
+ inherit_acl?: boolean;
24
+ default_value?: unknown;
25
+ created_at?: string;
26
+ updated_at?: string;
27
+ [key: string]: unknown;
28
+ }
4
29
 
5
- export interface TableMeta {
30
+ // The full table schema returned by `tables.get(idOrName)` / `schema(t)`.
31
+ export interface Table {
6
32
  id: string;
33
+ tenant_id?: string;
7
34
  name: string;
8
- displayName?: string;
9
- columns: Array<{
10
- name: string;
11
- type: string;
12
- required?: boolean;
13
- }>;
35
+ grant_creator_permissions?: boolean;
36
+ allow_anonymous_create?: boolean;
37
+ html_template?: string | null;
38
+ columns?: Column[];
39
+ created_at?: string;
40
+ updated_at?: string;
41
+ deleted_at?: string | null;
42
+ [key: string]: unknown;
14
43
  }
15
44
 
45
+ // A free-form record. Keys correspond to the table's column `name`s; the
46
+ // host-managed fields are snake_case (`id`, `created_at`, `updated_at`).
16
47
  export interface Record_ {
17
48
  id: string;
49
+ created_at?: string;
50
+ updated_at?: string;
18
51
  [key: string]: unknown;
19
52
  }
20
53
  export { Record_ as Record };
21
54
 
22
55
  // The uniform list envelope every collection endpoint returns
23
- // (`{ data, meta }`). `meta.total` is the ACL-aware row count; use it
24
- // with `limit`/`offset` to page.
56
+ // (`{ data, meta }`), returned VERBATIM. `meta.total` is the ACL-aware row
57
+ // count; use it with `limit` / `offset` to page.
25
58
  export interface Page<T> {
26
59
  data: T[];
27
60
  meta: {
@@ -34,93 +67,78 @@ export interface Page<T> {
34
67
  // Record-list query. A `filter` maps a column name to an `op:value`
35
68
  // expression — e.g. `{ status: "eq:Open", priority: "gte:3" }`. Supported
36
69
  // operators: eq, neq, lt, gt, gte, lte, contains, empty, nempty (and the
37
- // `me` sentinel for USER columns). `filterMode` ANDs conditions by
38
- // default, or ORs them. `q` is a free-text search across the table's
39
- // string/text columns. Pagination is `limit` + `offset`.
70
+ // `me` sentinel for USER / USER_GROUP columns). `filterMode` ANDs conditions
71
+ // by default, or ORs them (sent as `filter_mode`). `q` is a free-text search
72
+ // across the table's string/text columns. `sort` is a column name optionally
73
+ // prefixed with `-` for descending (e.g. `-created_at`). Pagination is
74
+ // `limit` + `offset`.
40
75
  export interface Query {
41
- filter?: Record<string, string>;
76
+ filter?: { [column: string]: string };
42
77
  filterMode?: "and" | "or";
43
78
  q?: string;
79
+ sort?: string;
44
80
  limit?: number;
45
81
  offset?: number;
46
82
  }
47
83
 
48
84
  // Aggregate request: group rows by a single column and (optionally) sum a
49
- // single numeric field, with the same `filter` map the list accepts.
85
+ // single numeric field, with the same `filter` map the list accepts. Sent as
86
+ // `group_by` / `sum_field` on the wire.
50
87
  export interface AggregateSpec {
51
88
  groupBy?: string;
52
89
  sumField?: string;
53
- filter?: Record<string, string>;
90
+ filter?: { [column: string]: string };
54
91
  }
55
92
 
56
93
  // Aggregate response: one row per group with the row count and the sum of
57
- // `sumField` (0 when no `sumField` was requested).
94
+ // `sum_field` (0 when no `sum_field` was requested).
58
95
  export type AggregateResult = Array<{
59
96
  group: string;
60
97
  count: number;
61
98
  sum: number;
62
99
  }>;
63
100
 
64
- // The principal returned by `users.me()` (`GET /auth/app/me`). `email` is
65
- // present for the logged-in user; the directory projection from
66
- // `users.get(id)` returns `{ id, name, role }` for non-Studio callers.
67
- export interface AppUser {
68
- id: string;
69
- name: string | null;
70
- email?: string | null;
71
- role?: string;
72
- groupIds?: string[];
73
- }
74
-
75
- export interface Group {
101
+ // A single row-level grant on a record (REQ-ACL-06), snake_case verbatim.
102
+ // Exactly one of `user_id` / `group_id` is set; the four `can_*` flags are
103
+ // the capability bits. `source_kind` / `source_id` are non-null only for
104
+ // grants cascaded from a parent record through an inheriting relation —
105
+ // directly-authored grants have both null.
106
+ export interface RecordPermission {
76
107
  id: string;
77
- name: string;
78
- tenantId?: string;
79
- createdAt?: string;
80
- updatedAt?: string;
108
+ table_id: string;
109
+ record_id: string | null;
110
+ user_id: string | null;
111
+ group_id: string | null;
112
+ can_read: boolean;
113
+ can_write: boolean;
114
+ can_delete: boolean;
115
+ can_grant: boolean;
116
+ source_kind: string | null;
117
+ source_id: string | null;
118
+ created_at: string;
119
+ updated_at: string;
81
120
  }
82
121
 
83
- // A single row-level grant on a record (REQ-ACL-06). Exactly one of
84
- // `userId` / `groupId` is set; the four `can*` flags are the capability
85
- // bits. `sourceKind` / `sourceId` are non-null only for grants cascaded
86
- // from a parent record through an inheriting relation — directly-authored
87
- // grants have both null.
88
- export interface RecordPermission {
89
- id: string;
90
- tableId: string;
91
- recordId: string | null;
92
- userId: string | null;
93
- groupId: string | null;
94
- canRead: boolean;
95
- canWrite: boolean;
96
- canDelete: boolean;
97
- canGrant: boolean;
98
- sourceKind: string | null;
99
- sourceId: string | null;
100
- createdAt: string;
101
- updatedAt: string;
102
- }
103
-
104
- // Grant body for `permissions(recordId).grant(...)`. Provide exactly ONE of
105
- // `userId` or `groupId`. Flags default server-side to `canRead: true` and
106
- // the rest `false` when omitted.
122
+ // Grant body for `permissions(recordId).grant(...)`, sent snake_case
123
+ // verbatim. Provide exactly ONE of `user_id` or `group_id`. Flags default
124
+ // server-side to `can_read: true` and the rest `false` when omitted.
107
125
  export interface RecordPermissionGrant {
108
- userId?: string | null;
109
- groupId?: string | null;
110
- canRead?: boolean;
111
- canWrite?: boolean;
112
- canDelete?: boolean;
113
- canGrant?: boolean;
126
+ user_id?: string | null;
127
+ group_id?: string | null;
128
+ can_read?: boolean;
129
+ can_write?: boolean;
130
+ can_delete?: boolean;
131
+ can_grant?: boolean;
114
132
  }
115
133
 
116
- // Flag patch for `permissions(recordId).update(...)`. Only the four
117
- // capability bits are mutable; the grantee is fixed at grant time. Omitted
118
- // flags are left unchanged.
134
+ // Flag patch for `permissions(recordId).update(...)`, sent snake_case
135
+ // verbatim. Only the four capability bits are mutable; the grantee is fixed
136
+ // at grant time. Omitted flags are left unchanged.
119
137
  export interface RecordPermissionPatch {
120
- canRead?: boolean;
121
- canWrite?: boolean;
122
- canDelete?: boolean;
123
- canGrant?: boolean;
138
+ can_read?: boolean;
139
+ can_write?: boolean;
140
+ can_delete?: boolean;
141
+ can_grant?: boolean;
124
142
  }
125
143
 
126
144
  export interface RecordPermissionsNamespace {
@@ -133,6 +151,29 @@ export interface RecordPermissionsNamespace {
133
151
  revoke(permissionId: string): Promise<void>;
134
152
  }
135
153
 
154
+ // REQ-RT-07 realtime subscription transport state, reported via
155
+ // `subscribe({ onStatus })`. "live" = socket open + subscribe acked;
156
+ // "fallback" = the caller should poll (no WebSocket impl, subscribe rejected
157
+ // by ACL, or the first connect timed out).
158
+ export type SubscriptionStatus =
159
+ | "connecting"
160
+ | "live"
161
+ | "reconnecting"
162
+ | "fallback";
163
+
164
+ export interface SubscriptionHandlers {
165
+ onCreated?: (record: Record_) => void;
166
+ onUpdated?: (record: Record_) => void;
167
+ onDeleted?: (record: Record_) => void;
168
+ onStatus?: (status: SubscriptionStatus) => void;
169
+ }
170
+
171
+ export interface SubscriptionOptions {
172
+ // Emit "fallback" if the first subscribe doesn't land within this many ms
173
+ // (default 3000) so the caller can start polling without waiting forever.
174
+ fallbackAfterMs?: number;
175
+ }
176
+
136
177
  export interface RecordsNamespace {
137
178
  list(query?: Query): Promise<Page<Record_>>;
138
179
  get(id: string): Promise<Record_>;
@@ -141,28 +182,42 @@ export interface RecordsNamespace {
141
182
  delete(id: string): Promise<void>;
142
183
  aggregate(spec: AggregateSpec): Promise<AggregateResult>;
143
184
  permissions(recordId: string): RecordPermissionsNamespace;
185
+ // REQ-RT-07: subscribe to this table's realtime change stream. Returns a
186
+ // synchronous unsubscribe function.
187
+ subscribe(
188
+ handlers: SubscriptionHandlers,
189
+ options?: SubscriptionOptions,
190
+ ): () => void;
144
191
  }
145
192
 
146
193
  export interface DatastoreClient {
147
194
  tables: {
148
- list(): Promise<TableMeta[]>;
149
- get(idOrName: string): Promise<TableMeta>;
195
+ list(): Promise<Page<Table>>;
196
+ get(idOrName: string): Promise<Table>;
150
197
  };
198
+ // Alias of tables.get for the table's column structure (the host calls
199
+ // ctx.datastore.schema(t)).
200
+ schema(tableId: string): Promise<Table>;
151
201
  records(table: string): RecordsNamespace;
152
- users: {
153
- me(): Promise<AppUser>;
154
- get(id: string): Promise<AppUser>;
155
- };
156
- groups: {
157
- listMine(): Promise<Group[]>;
158
- };
202
+ }
203
+
204
+ export interface RequestHeadersContext {
205
+ namespace: string;
206
+ operation: string;
159
207
  }
160
208
 
161
209
  export interface CreateDatastoreClientOptions {
162
210
  baseUrl: string;
163
211
  getToken: () => string | Promise<string>;
164
212
  getTenantId: () => string | Promise<string>;
213
+ getRequestHeaders?: (
214
+ ctx: RequestHeadersContext,
215
+ ) => Record<string, string> | Promise<Record<string, string>>;
165
216
  fetchImpl?: typeof fetch;
217
+ // REQ-RT-07: WebSocket constructor for records(t).subscribe(). Defaults to
218
+ // globalThis.WebSocket (browser + React Native). Omit in environments
219
+ // without one — subscribe() then reports "fallback".
220
+ webSocketImpl?: typeof WebSocket;
166
221
  }
167
222
 
168
223
  export function createDatastoreClient(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@colixsystems/datastore-client",
3
- "version": "0.4.0",
4
- "description": "Typed, scoped client for the AppStudio datastore API. Used by widgets through the injected WidgetContext.",
3
+ "version": "0.6.0",
4
+ "description": "Typed, scoped data-plane client for the AppStudio datastore API (tables, records, aggregates, record-level permissions, realtime subscribe). snake_case wire contract, no transform.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",