@qikdev/sdk 1.0.10 → 1.0.13
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/main.js +1294 -1229
- package/package.json +1 -1
- package/src/api/qik.access.js +177 -201
- package/src/api/qik.api.js +55 -28
- package/src/api/qik.auth.js +181 -113
- package/src/api/qik.cache.js +29 -7
- package/src/api/qik.content.js +275 -147
- package/src/api/qik.core.js +19 -28
- package/src/api/qik.files.js +48 -38
- package/src/api/qik.filter.js +148 -207
- package/src/api/qik.geo.js +59 -42
- package/src/api/qik.socket.js +228 -62
- package/src/api/qik.storage.js +17 -8
- package/src/api/qik.system.js +6 -5
- package/src/api/qik.utils.js +244 -199
- package/src/version.js +1 -1
package/src/api/qik.content.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
///////////////////////////////////////////////////
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Reading and writing records (content) through the REST API: list, get, create, update, patch,
|
|
5
|
+
* delete and restore, plus the glossary of content types, filter comparators and field validation helpers.
|
|
6
|
+
* Every call runs as the signed-in user (or the application token) and is subject to their permissions:
|
|
7
|
+
* records they can't see aren't returned, and fields they can't see are removed from the records that are.
|
|
6
8
|
* @alias content
|
|
7
9
|
* @constructor
|
|
8
10
|
* @hideconstructor
|
|
@@ -26,14 +28,16 @@ export default function (qik) {
|
|
|
26
28
|
let inflightVariablesRequest;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* Retrieves the organisation's variables (stored key/value settings such as API keys) that the current user
|
|
32
|
+
* is allowed to see, as one object keyed by variable key (POST /variables, up to 1000 variables).
|
|
33
|
+
* Often used by action code to read credentials. The result is kept in memory after the first call;
|
|
34
|
+
* later calls return that copy unless `options.reload` is set.
|
|
31
35
|
* @alias content.variables
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
34
|
-
* @param
|
|
36
|
+
* @param {Array<String>} [keys] Sent to the server, but currently ignored there: every visible variable is always returned. Pick the ones you need from the result.
|
|
37
|
+
* @param {Object} [options] Request options.
|
|
38
|
+
* @param {Boolean} [options.reload] Fetch again instead of returning the in-memory copy (`options.refresh` works too).
|
|
39
|
+
* @returns {Promise<Object>} An object mapping each variable key to its value, e.g. { OAUTH_CLIENT_ID: 'abc', OAUTH_KEY: 'xyz' }.
|
|
35
40
|
* @example
|
|
36
|
-
*
|
|
37
41
|
* const { OAUTH_CLIENT_ID, OAUTH_KEY } = await sdk.content.variables();
|
|
38
42
|
*/
|
|
39
43
|
|
|
@@ -117,22 +121,23 @@ export default function (qik) {
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
* Retrieves the glossary: every content type the current user can know about, built-in types and the
|
|
125
|
+
* organisation's own definitions alike, each with its key, title, plural, fields (including definedFields
|
|
126
|
+
* for definitions), validation and other configuration. Use it to find the real type key for
|
|
127
|
+
* content.list / content.create; keys can't be inferred from titles.
|
|
128
|
+
* Loaded from GET /glossary/compressed (or GET /glossary with `uncompressed`) and kept in the SDK cache
|
|
129
|
+
* until `options.reload` or sdk.cache.reset() (which logout and organisation switches call).
|
|
130
|
+
* @alias content.glossary
|
|
131
|
+
* @param {Object} [options] Options.
|
|
132
|
+
* @param {Boolean} [options.hash] Return an object keyed by type key ({ profile: {...}, article: {...} }) instead of an array.
|
|
133
|
+
* @param {Boolean} [options.hex] Return an object keyed by each type's two-character id prefix instead of an array.
|
|
134
|
+
* @param {Boolean} [options.reload] Fetch again instead of using the cached copy (`options.refresh` works too).
|
|
135
|
+
* @param {Boolean} [options.uncompressed] Load from the uncompressed endpoint. The default compressed endpoint returns the same data, smaller.
|
|
136
|
+
* @returns {Promise<Array|Object>} An array of content type definitions, or an object keyed by type key when `hash` is set (by id prefix when `hex` is set).
|
|
137
|
+
* @example
|
|
138
|
+
* const { article, profile } = await sdk.content.glossary({ hash: true });
|
|
139
|
+
* const fieldKeys = profile.fields.map((field) => field.key);
|
|
140
|
+
*/
|
|
136
141
|
service.glossary = async function (options) {
|
|
137
142
|
options = options || {};
|
|
138
143
|
|
|
@@ -190,18 +195,17 @@ export default function (qik) {
|
|
|
190
195
|
let inflightScopeGlossaryRequest;
|
|
191
196
|
|
|
192
197
|
/**
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
*/
|
|
198
|
+
* Retrieves every scope the current user holds a permission in (GET /scope/glossary), handy for turning
|
|
199
|
+
* a scope id into a readable title. The result is kept in memory after the first call.
|
|
200
|
+
* @alias content.scopeGlossary
|
|
201
|
+
* @param {Object} [options] Options.
|
|
202
|
+
* @param {Boolean} [options.hash] Return an object keyed by scope _id instead of an array.
|
|
203
|
+
* @param {Boolean} [options.reload] Fetch again instead of returning the in-memory copy (`options.refresh` works too).
|
|
204
|
+
* @returns {Promise<Array|Object>} A flat array of scopes, each { _id, title, bgColor, color, meta: { definition } }, or an object keyed by _id when `hash` is set.
|
|
205
|
+
* @example
|
|
206
|
+
* const scopes = await sdk.content.scopeGlossary({ hash: true });
|
|
207
|
+
* const scopeTitle = scopes['61eca4746971e75c1fc670cf']?.title;
|
|
208
|
+
*/
|
|
205
209
|
|
|
206
210
|
service.scopeGlossary = async function (options) {
|
|
207
211
|
options = options || {};
|
|
@@ -234,15 +238,19 @@ export default function (qik) {
|
|
|
234
238
|
///////////////////////////////////////////////////
|
|
235
239
|
|
|
236
240
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
241
|
+
* Retrieves every filter comparator the server understands (GET /system/comparators), for building the
|
|
242
|
+
* `filter` of content.list. Each comparator has a `title`, an `operator` (the name to use as a filter's
|
|
243
|
+
* `comparator`), optional `aliases` (e.g. '>' for 'greater') and the data types it applies to.
|
|
244
|
+
* The result is kept in memory after the first call.
|
|
239
245
|
* @alias content.comparators
|
|
240
|
-
* @param
|
|
241
|
-
* @param
|
|
246
|
+
* @param {Object} [options] Options.
|
|
247
|
+
* @param {Boolean} [options.reload] Fetch again instead of returning the in-memory copy (`options.refresh` works too).
|
|
248
|
+
* @returns {Promise<Object>} { hash, types, available }: `hash` maps each operator name and alias to its comparator; `types` maps each data type (string, number, date, boolean, reference...) to the operator names that apply to it; `available` maps each data type to the comparator objects themselves.
|
|
242
249
|
* @example
|
|
243
|
-
* const {hash, available, types} = await sdk.content.comparators();
|
|
244
|
-
* console.log(
|
|
245
|
-
* console.log(
|
|
250
|
+
* const { hash, available, types } = await sdk.content.comparators();
|
|
251
|
+
* console.log(types.date); // includes 'datebefore', 'dateafter', 'datebetween' ...
|
|
252
|
+
* console.log(available.boolean[0]); // { title: 'Is equal to', operator: 'equal', ... }
|
|
253
|
+
* console.log(hash['>'].operator); // 'greater'
|
|
246
254
|
*/
|
|
247
255
|
|
|
248
256
|
const comparators = {};
|
|
@@ -295,6 +303,22 @@ export default function (qik) {
|
|
|
295
303
|
return { minimum, maximum };
|
|
296
304
|
}
|
|
297
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Checks a single value against a field's extra length and range rules. Used by validateField; it does
|
|
308
|
+
* not check the data type or how many values a field needs.
|
|
309
|
+
* @alias content.meetsValidationRequirements
|
|
310
|
+
* @param {*} input The value to check.
|
|
311
|
+
* @param {String} fieldType The field's data type (currently not used by the check).
|
|
312
|
+
* @param {Object} validationCriteria The rules to apply. Each is skipped when missing or 0.
|
|
313
|
+
* @param {Number} [validationCriteria.minLength] Minimum number of characters.
|
|
314
|
+
* @param {Number} [validationCriteria.maxLength] Maximum number of characters.
|
|
315
|
+
* @param {Number} [validationCriteria.minValue] Smallest number allowed.
|
|
316
|
+
* @param {Number} [validationCriteria.maxValue] Largest number allowed.
|
|
317
|
+
* @returns {String|undefined} A message describing the first rule the value breaks, or undefined when it passes.
|
|
318
|
+
* @example
|
|
319
|
+
* sdk.content.meetsValidationRequirements('Jo', 'string', { minLength: 3 });
|
|
320
|
+
* // 'Must be at least 3 characters'
|
|
321
|
+
*/
|
|
298
322
|
service.meetsValidationRequirements = function (
|
|
299
323
|
input,
|
|
300
324
|
fieldType,
|
|
@@ -334,19 +358,25 @@ export default function (qik) {
|
|
|
334
358
|
};
|
|
335
359
|
|
|
336
360
|
/**
|
|
337
|
-
* Checks
|
|
361
|
+
* Checks a value against a field definition the way forms do before submitting: required (minimum),
|
|
362
|
+
* how many values are allowed (maximum, 0 = unlimited; a field with maximum other than 1 expects an array),
|
|
363
|
+
* the data type, and minLength / maxLength / minValue / maxValue rules. Runs locally, no request is made.
|
|
364
|
+
* A required boolean field only passes when the value is true.
|
|
338
365
|
* @alias content.validateField
|
|
339
|
-
* @param
|
|
340
|
-
* @param
|
|
341
|
-
* @param
|
|
366
|
+
* @param {*} input The value to check (an array for multi-value fields).
|
|
367
|
+
* @param {Object} fieldDefinition The field from a definition or glossary entry: { title, key, type, minimum, maximum, minLength, maxLength, minValue, maxValue, validation, widget, asObject }.
|
|
368
|
+
* @param {Object} [options] Options.
|
|
369
|
+
* @param {Boolean} [options.strict] Require values to already be the right JavaScript type (e.g. a real number or Date) instead of accepting values that can be converted.
|
|
370
|
+
* @returns {Object} { valid: true } when the value passes, otherwise { valid: false, status: 400, message } with a readable message.
|
|
342
371
|
* @example
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* // Results in { valid:true }
|
|
372
|
+
* sdk.content.validateField('Johnny', { title: 'Name', key: 'firstName', type: 'string', minimum: 1, maximum: 1 });
|
|
373
|
+
* // { valid: true }
|
|
346
374
|
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
375
|
+
* sdk.content.validateField('Johnny', { title: 'Number', key: 'number', type: 'integer', minimum: 1, maximum: 1 });
|
|
376
|
+
* // { valid: false, status: 400, message: "Single value 'Johnny' is not a valid integer for Number", criteria: {...} }
|
|
377
|
+
*
|
|
378
|
+
* sdk.content.validateField(undefined, { title: 'Email', key: 'email', type: 'email', minimum: 1, maximum: 1 });
|
|
379
|
+
* // { valid: false, status: 400, message: 'Email is a required field' }
|
|
350
380
|
*/
|
|
351
381
|
|
|
352
382
|
service.validateField = function (input, fieldDefinition, options) {
|
|
@@ -615,7 +645,24 @@ export default function (qik) {
|
|
|
615
645
|
};
|
|
616
646
|
};
|
|
617
647
|
|
|
648
|
+
/**
|
|
649
|
+
* Converts a raw value to a data type the way validateField does before checking it: numbers are parsed,
|
|
650
|
+
* booleans read from 'yes' / 'true' / '1' style strings, emails lowercased, references reduced to an id,
|
|
651
|
+
* dates parsed. Runs locally.
|
|
652
|
+
* @alias content.getCleanedValue
|
|
653
|
+
* @param {*} input The raw value.
|
|
654
|
+
* @param {String} dataType The data type: 'number' | 'decimal' | 'float' | 'integer' | 'boolean' | 'email' | 'reference' | 'date' | 'url' | 'key' | 'string' | 'object' ...
|
|
655
|
+
* @param {Object} [options] Options.
|
|
656
|
+
* @param {Boolean} [options.strict] Return the value unchanged (numbers, integers and booleans are still converted).
|
|
657
|
+
* @returns {*} The converted value. Missing or unconvertible values come back as undefined (false for a url that can't be parsed).
|
|
658
|
+
* @example
|
|
659
|
+
* sdk.content.getCleanedValue('42', 'integer', {}); // 42
|
|
660
|
+
* sdk.content.getCleanedValue('Yes', 'boolean', {}); // true
|
|
661
|
+
* sdk.content.getCleanedValue('Hi@Example.COM', 'email', {}); // 'hi@example.com'
|
|
662
|
+
*/
|
|
618
663
|
service.getCleanedValue = function (input, dataType, options) {
|
|
664
|
+
options = options || {};
|
|
665
|
+
|
|
619
666
|
switch (dataType) {
|
|
620
667
|
case "number":
|
|
621
668
|
case "float":
|
|
@@ -658,43 +705,67 @@ export default function (qik) {
|
|
|
658
705
|
///////////////////////////////////////////////////
|
|
659
706
|
|
|
660
707
|
/**
|
|
708
|
+
* Lists the records of one content type that match a query (POST /content/:type/list). This is the way to
|
|
709
|
+
* read many records at once: filter, search, sort and page on the server, and ask for exactly the fields you need.
|
|
661
710
|
*
|
|
662
|
-
*
|
|
711
|
+
* ALWAYS pass `select` listing the fields you want. Without it each row contains only `_id` and `title`,
|
|
712
|
+
* and reading anything else would mean fetching every record again with content.get.
|
|
713
|
+
* Rows are filtered by the viewer's field permissions: a selected field the viewer isn't allowed to see is
|
|
714
|
+
* simply left out of the row (no error), and records they can't see aren't returned at all.
|
|
715
|
+
* By default only active, non-deleted records are listed.
|
|
663
716
|
* @alias content.list
|
|
664
|
-
* @param
|
|
665
|
-
* @param
|
|
666
|
-
* @param
|
|
667
|
-
* @param
|
|
668
|
-
* @param
|
|
669
|
-
* @param
|
|
670
|
-
* @param
|
|
671
|
-
* @param
|
|
672
|
-
* @param
|
|
673
|
-
* @param
|
|
674
|
-
* @param
|
|
717
|
+
* @param {String} type The type key or definition key to list, e.g. 'profile', 'event', or an organisation's own definition key. Look keys up with content.glossary(); they can't be inferred from titles.
|
|
718
|
+
* @param {Object} [options] The query. Every property is optional.
|
|
719
|
+
* @param {Array<String>} [options.select] Fields to include on each row, as dot paths: 'firstName', 'data.shirtSize', 'meta.created', 'meta.scopes'. Must be an array (a string is rejected with a 400). A parent key returns everything under it ('data' returns all custom fields). `_id` is always included. Reference fields named here come back populated with the referenced records. Computed keys are accepted too: '_age' and '_dob' on profiles, and 'join_...' keys for related data. Without select, rows contain only `_id` and `title`.
|
|
720
|
+
* @param {String} [options.search] Keywords. Matches whole words and the start of words (not the middle of words) in the record's indexed terms such as names, titles, emails and phone numbers. On profiles every word must match; on other types a record also matches when any one word matches a whole term. A 24-character record id finds that record.
|
|
721
|
+
* @param {Object} [options.filter] A filter group: { operator, filters }. `operator` is 'and' (default), 'or' or 'nor'. `filters` is an array of conditions or nested groups. A condition is { key, comparator, value, value2, values }: `key` is a field path ('data.shirtSize', 'meta.created', '_age'); `comparator` is a comparator name such as 'equal', 'notequal', 'in', 'notin', 'contains', 'startswith', 'greater', 'lesser', 'between', 'empty', 'notempty', 'datebefore', 'dateafter', 'datebetween', 'datenext', 'datepast' (content.comparators() lists them all, with aliases such as '>' and '=='); `value` is the value to compare with; `value2` the upper bound for between-style comparators; `values` the array for 'in' / 'notin'. A condition with no key, or no value for a comparator that needs one, is ignored. A key that isn't a real field matches nothing (see options.validateFilterKeys).
|
|
722
|
+
* @param {Object} [options.sort] How to order the results. When omitted: most recently updated first ('meta.updated', newest first); emails and SMS newest created first; events and assignments by start date, soonest first.
|
|
723
|
+
* @param {String} options.sort.key The field path to sort on, e.g. 'title', 'meta.created', 'data.rank'.
|
|
724
|
+
* @param {String} [options.sort.direction] 'asc' (the default when a sort is given) or 'desc' ('dsc' also works).
|
|
725
|
+
* @param {String} [options.sort.type] How values are compared: 'string' (default), 'number' ('integer', 'decimal' and 'float' also work), 'date', 'boolean', 'natural' (so 'Item 2' comes before 'Item 10') or 'dateproximity' (closest to now first).
|
|
726
|
+
* @param {Object} [options.page] Paging.
|
|
727
|
+
* @param {Number} [options.page.size] Rows per page. Default 50, maximum 5000.
|
|
728
|
+
* @param {Number} [options.page.index] Which page, starting at 1. Default 1.
|
|
729
|
+
* @param {Object} [options.date] A date range, { startDate, endDate } (dates or ISO strings; either may be left out). For most types it applies to meta.created. For events it returns events that overlap the range; for assignments and check-ins, those whose event overlaps it. Log queries default to the last 12 hours. (The property is `date`; `dates` is rejected with a 400.)
|
|
730
|
+
* @param {Array<String>} [options.ids] Only return records with these ids (inactive and deleted ones included). An empty array does NOT restrict the list: it is ignored and every matching record is returned.
|
|
731
|
+
* @param {Boolean} [options.includeInactive] Include records whose meta.status isn't 'active'. Events, runsheets, rosters, assignments and check-ins are never limited by status, and filtering on meta.status includes inactive records automatically.
|
|
732
|
+
* @param {Boolean} [options.includeTrash] Include deleted records alongside live ones.
|
|
733
|
+
* @param {Boolean} [options.trash] Return only deleted records (the trash).
|
|
734
|
+
* @param {Boolean} [options.includeAll] Also return `all`: the ids of every matching record across all pages, in sorted order.
|
|
735
|
+
* @param {Array<Object>} [options.math] Totals over every matching record, not just the page: [{ key: 'data.amount' }, ...]. Returned as `math`, keyed by field path: { total, count, mean, small, large, unique }.
|
|
736
|
+
* @param {Number} [options.sample] Return this many matching records picked at random.
|
|
737
|
+
* @param {Boolean} [options.validateFilterKeys] Fail with a 400 and "did you mean" suggestions when a filter key isn't a field of the type, instead of quietly matching nothing.
|
|
738
|
+
* @param {Boolean} [options.includeThreads] Attach each record's comment thread to its row.
|
|
739
|
+
* @param {String} [options.rolodexPrimary] Only records whose alphabetical index letter (meta.rolodexPrimary) is this letter.
|
|
740
|
+
* @param {String} [options.rolodexSecondary] Only records whose second index letter (meta.rolodexSecondary) is this letter.
|
|
741
|
+
* @param {Object} [advanced] SDK settings for the request itself; nothing here is sent as part of the query.
|
|
742
|
+
* @param {Boolean} [advanced.cancellable] Return { promise, cancel(message) } straight away instead of waiting. `promise` resolves to the full axios response, so read its `.data`; a cancelled request rejects with an error that sdk.api.wasCancelled(err) recognises.
|
|
743
|
+
* @param {Object} [advanced.config] Extra axios request config for the call (headers, timeout...).
|
|
744
|
+
* @param {String} [advanced.remoteURL] Send the query to this endpoint instead of /content/:type/list.
|
|
745
|
+
* @returns {Promise<Object>} { items, total, page: { index, total }, benchmarks } plus, when relevant, `all` (with includeAll), `math` (with math), `warnings` (problems found in the query) and `suggestions` (similar names, for a profile search that found nothing). `items` holds the rows of the requested page, `total` counts every matching record and `page.total` is the number of pages. With advanced.cancellable: { promise, cancel } instead.
|
|
675
746
|
* @example
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
* key:'age',
|
|
686
|
-
* direction:'asc',
|
|
687
|
-
* type:'integer',
|
|
747
|
+
* const { items, total, page } = await sdk.content.list('profile', {
|
|
748
|
+
* select: ['firstName', 'lastName', 'emails', 'data.shirtSize', 'meta.created'],
|
|
749
|
+
* search: 'jim',
|
|
750
|
+
* filter: {
|
|
751
|
+
* operator: 'and',
|
|
752
|
+
* filters: [
|
|
753
|
+
* { key: '_age', comparator: 'greater', value: 17 },
|
|
754
|
+
* { key: 'meta.created', comparator: 'datebetween', value: '2026-01-01', value2: '2026-06-30' },
|
|
755
|
+
* ],
|
|
688
756
|
* },
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
* }
|
|
697
|
-
*
|
|
757
|
+
* sort: { key: 'lastName', direction: 'asc', type: 'string' },
|
|
758
|
+
* page: { size: 100, index: 1 },
|
|
759
|
+
* });
|
|
760
|
+
*
|
|
761
|
+
* // Walk every page
|
|
762
|
+
* let index = 1, rows = [];
|
|
763
|
+
* while (true) {
|
|
764
|
+
* const result = await sdk.content.list('event', { select: ['title', 'startDate'], page: { size: 500, index } });
|
|
765
|
+
* rows.push(...result.items);
|
|
766
|
+
* if (index >= result.page.total) break;
|
|
767
|
+
* index++;
|
|
768
|
+
* }
|
|
698
769
|
*/
|
|
699
770
|
|
|
700
771
|
service.list = async function (type, options, advanced) {
|
|
@@ -730,30 +801,44 @@ export default function (qik) {
|
|
|
730
801
|
///////////////////////////////////////////////////
|
|
731
802
|
|
|
732
803
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
804
|
+
* Creates a record (POST /content/:type/create). The input is validated against the type's fields;
|
|
805
|
+
* a missing required field or a wrong value rejects with a 400 explaining which.
|
|
806
|
+
* Built-in fields go at the top level, an organisation definition's own fields go under `data`,
|
|
807
|
+
* and `meta.scopes` says which scopes (permission groups) the record belongs to — the user needs
|
|
808
|
+
* create permission in them. Objects in meta.scopes are reduced to their ids in the request; the input you pass in is left unchanged.
|
|
809
|
+
* To use the endpoint's query options (e.g. ?skipGetContent=true for a smaller response), call sdk.api.post directly.
|
|
735
810
|
* @alias content.create
|
|
736
|
-
* @param
|
|
737
|
-
* @param
|
|
811
|
+
* @param {String} type The type key or definition key of the new record, e.g. 'profile', 'article' or an organisation's definition key (see content.glossary()).
|
|
812
|
+
* @param {Object} input The new record's fields.
|
|
813
|
+
* @param {Object} [input.data] Values for the definition's own fields, keyed by field key.
|
|
814
|
+
* @param {Object} [input.meta] Record settings. The ones most often set: scopes (array of scope ids) and status ('active', the default, or 'inactive').
|
|
815
|
+
* @param {Array<String>} [input.tags] Tag names to attach; tags that don't exist yet are created.
|
|
816
|
+
* @returns {Promise<Object>} The created record, including its new `_id`, as the creator is allowed to see it.
|
|
738
817
|
* @example
|
|
818
|
+
* const profile = await sdk.content.create('profile', {
|
|
819
|
+
* firstName: 'Mickey',
|
|
820
|
+
* lastName: 'Mouse',
|
|
821
|
+
* emails: ['mickey@example.com'],
|
|
822
|
+
* meta: { scopes: ['61eca4746971e75c1fc670cf'] },
|
|
823
|
+
* });
|
|
739
824
|
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
* meta:{
|
|
746
|
-
* scopes:['61eca4746971e75c1fc670cf'],
|
|
747
|
-
* }
|
|
748
|
-
* })
|
|
825
|
+
* const application = await sdk.content.create('jobApplication', {
|
|
826
|
+
* title: 'Mickey Mouse',
|
|
827
|
+
* data: { role: 'Receptionist', startDate: '2026-10-01' },
|
|
828
|
+
* meta: { scopes: ['61eca4746971e75c1fc670cf'] },
|
|
829
|
+
* });
|
|
749
830
|
*/
|
|
750
831
|
|
|
751
832
|
service.create = async function (type, input) {
|
|
752
833
|
const dataModel = { ...input };
|
|
753
834
|
|
|
754
|
-
// Sanitize down to just ids before we send
|
|
835
|
+
// Sanitize down to just ids before we send. Copy meta rather than
|
|
836
|
+
// changing it in place, so the caller's own object keeps its scopes.
|
|
755
837
|
if (dataModel.meta?.scopes) {
|
|
756
|
-
dataModel.meta
|
|
838
|
+
dataModel.meta = {
|
|
839
|
+
...dataModel.meta,
|
|
840
|
+
scopes: qik.utils.ids(dataModel.meta.scopes),
|
|
841
|
+
};
|
|
757
842
|
}
|
|
758
843
|
|
|
759
844
|
const { data } = await qik.api.post(`/content/${type}/create`, dataModel);
|
|
@@ -761,30 +846,35 @@ export default function (qik) {
|
|
|
761
846
|
};
|
|
762
847
|
|
|
763
848
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
849
|
+
* Replaces a record with the input (PUT /content/:id). This is a full replacement, not a merge:
|
|
850
|
+
* top-level fields left out of the input are cleared and `data` is replaced as a whole, so send the
|
|
851
|
+
* complete record (for example what content.get returned, with your changes applied). Use content.patch
|
|
852
|
+
* to change only some fields. If the user isn't allowed to edit some of the record's fields, the server
|
|
853
|
+
* switches to a merge so those fields are kept. Objects in meta.scopes are reduced to their ids in the request; the input you pass in is left unchanged.
|
|
854
|
+
* To use the endpoint's query options (e.g. ?expectedUhash=... to reject the write with a 409 if someone else
|
|
855
|
+
* changed the record first), call sdk.api.put directly.
|
|
766
856
|
* @alias content.update
|
|
767
|
-
* @param
|
|
768
|
-
* @param
|
|
857
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
858
|
+
* @param {Object} input The complete record.
|
|
859
|
+
* @returns {Promise<Object>} The updated record, showing only the fields the user is allowed to see.
|
|
769
860
|
* @example
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
* gender:'female',
|
|
775
|
-
* meta:{
|
|
776
|
-
* scopes:['61eca4746971e75c1fc670cd'],
|
|
777
|
-
* }
|
|
778
|
-
* })
|
|
861
|
+
* const profile = await sdk.content.get('61eca4746971e75c1fc670cd');
|
|
862
|
+
* profile.firstName = 'Minnie';
|
|
863
|
+
* profile.data = { ...profile.data, shirtSize: 'M' };
|
|
864
|
+
* const updated = await sdk.content.update(profile._id, profile);
|
|
779
865
|
*/
|
|
780
866
|
service.update = async function (id, input) {
|
|
781
867
|
id = qik.utils.id(id);
|
|
782
868
|
|
|
783
869
|
const dataModel = { ...input };
|
|
784
870
|
|
|
785
|
-
// Sanitize down to just ids before we send
|
|
871
|
+
// Sanitize down to just ids before we send. Copy meta rather than
|
|
872
|
+
// changing it in place, so the caller's own object keeps its scopes.
|
|
786
873
|
if (dataModel.meta?.scopes) {
|
|
787
|
-
dataModel.meta
|
|
874
|
+
dataModel.meta = {
|
|
875
|
+
...dataModel.meta,
|
|
876
|
+
scopes: qik.utils.ids(dataModel.meta.scopes),
|
|
877
|
+
};
|
|
788
878
|
}
|
|
789
879
|
|
|
790
880
|
const { data } = await qik.api.put(`/content/${id}`, dataModel);
|
|
@@ -792,26 +882,35 @@ export default function (qik) {
|
|
|
792
882
|
};
|
|
793
883
|
|
|
794
884
|
/**
|
|
795
|
-
*
|
|
796
|
-
*
|
|
885
|
+
* Changes only the fields you send (PATCH /content/:id); everything else is kept. How values combine:
|
|
886
|
+
* a top-level field you send replaces the current value, arrays included (sending `emails` replaces the whole list);
|
|
887
|
+
* `data` is merged key by key, deeply; an array inside `data` is COMBINED with the existing one (new items
|
|
888
|
+
* are added, nothing is removed; an object with the same _id, id, title or name as an existing one replaces it). To remove items from a data array, either send the whole record with
|
|
889
|
+
* content.update, or call sdk.api.patch(`/content/${id}?mergeReplaceArrays=true`, input) so arrays are replaced.
|
|
890
|
+
* Within `meta` only some settings can be changed this way (scopes, status, security, slug, keywords, owners and a few more).
|
|
891
|
+
* Fields the user isn't allowed to edit are left unchanged. Objects in meta.scopes are reduced to their ids in the request; the input you pass in is left unchanged.
|
|
797
892
|
* @alias content.patch
|
|
798
|
-
* @param
|
|
799
|
-
* @param
|
|
893
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
894
|
+
* @param {Object} input The fields to change.
|
|
895
|
+
* @returns {Promise<Object>} The updated record, showing only the fields the user is allowed to see.
|
|
800
896
|
* @example
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
* })
|
|
897
|
+
* const updated = await sdk.content.patch('61eca4746971e75c1fc670cd', {
|
|
898
|
+
* firstName: 'Mickey',
|
|
899
|
+
* data: { shirtSize: 'L', tags: ['mentor'] }, // 'mentor' is added to data.tags, other data fields are kept
|
|
900
|
+
* });
|
|
806
901
|
*/
|
|
807
902
|
service.patch = async function (id, input) {
|
|
808
903
|
id = qik.utils.id(id);
|
|
809
904
|
|
|
810
905
|
const dataModel = { ...input };
|
|
811
906
|
|
|
812
|
-
// Sanitize down to just ids before we send
|
|
907
|
+
// Sanitize down to just ids before we send. Copy meta rather than
|
|
908
|
+
// changing it in place, so the caller's own object keeps its scopes.
|
|
813
909
|
if (dataModel.meta?.scopes) {
|
|
814
|
-
dataModel.meta
|
|
910
|
+
dataModel.meta = {
|
|
911
|
+
...dataModel.meta,
|
|
912
|
+
scopes: qik.utils.ids(dataModel.meta.scopes),
|
|
913
|
+
};
|
|
815
914
|
}
|
|
816
915
|
|
|
817
916
|
const { data } = await qik.api.patch(`/content/${id}`, dataModel);
|
|
@@ -819,13 +918,26 @@ export default function (qik) {
|
|
|
819
918
|
};
|
|
820
919
|
|
|
821
920
|
/**
|
|
822
|
-
*
|
|
823
|
-
*
|
|
921
|
+
* Fetches one record by id (GET /content/:id). The type is worked out from the id, so no type is needed.
|
|
922
|
+
* Returns every field the user is allowed to see (the rest are removed), with reference fields populated.
|
|
923
|
+
* Rejects with 403 when the user may not view the record, 404 when it doesn't exist and 400 for an invalid id.
|
|
924
|
+
* To fetch many records use content.list with `select` rather than calling this in a loop.
|
|
824
925
|
* @alias content.get
|
|
825
|
-
* @param
|
|
926
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
927
|
+
* @param {Object} [params] axios request config, passed unchanged to sdk.api.get. Query options for the server go inside its `params` property (see below).
|
|
928
|
+
* @param {Object} [params.params] Query string options for the server.
|
|
929
|
+
* @param {Array<String>} [params.params.select] Only return these fields (dot paths) plus _id. Give at least two keys: a single key is sent as a plain string, which the server doesn't read as a list.
|
|
930
|
+
* @param {String} [params.params.version] Id of an entry from the record's history (log) to return that earlier version instead of the current one. Needs edit access to the record (or the log.viewany permission).
|
|
931
|
+
* @param {String} [params.params.context] 'edit' to require edit permission instead of view permission.
|
|
932
|
+
* @param {Boolean} [params.params.unpopulated] Leave reference fields as ids instead of populating them.
|
|
933
|
+
* @returns {Promise<Object>} The record.
|
|
826
934
|
* @example
|
|
935
|
+
* const profile = await sdk.content.get('61eca4746971e75c1fc670cd');
|
|
827
936
|
*
|
|
828
|
-
*
|
|
937
|
+
* // Only some fields
|
|
938
|
+
* const partial = await sdk.content.get('61eca4746971e75c1fc670cd', {
|
|
939
|
+
* params: { select: ['firstName', 'lastName', 'data.shirtSize'] },
|
|
940
|
+
* });
|
|
829
941
|
*/
|
|
830
942
|
service.get = async function (id, params) {
|
|
831
943
|
id = qik.utils.id(id);
|
|
@@ -835,17 +947,33 @@ export default function (qik) {
|
|
|
835
947
|
return data;
|
|
836
948
|
};
|
|
837
949
|
|
|
950
|
+
/**
|
|
951
|
+
* Same as content.get(id) with no options.
|
|
952
|
+
* @alias content.getFromID
|
|
953
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
954
|
+
* @returns {Promise<Object>} The record.
|
|
955
|
+
* @example
|
|
956
|
+
* const record = await sdk.content.getFromID('61eca4746971e75c1fc670cd');
|
|
957
|
+
*/
|
|
838
958
|
service.getFromID = async function (id) {
|
|
839
959
|
return service.get(id);
|
|
840
960
|
};
|
|
841
961
|
|
|
842
962
|
/**
|
|
843
|
-
*
|
|
844
|
-
*
|
|
963
|
+
* Deletes a record (DELETE /content/:id). By default this moves it to the trash (meta.deleted = true),
|
|
964
|
+
* from where content.restore brings it back; trashed records are hidden from content.list unless
|
|
965
|
+
* `trash` or `includeTrash` is set. Needs delete permission on the record.
|
|
845
966
|
* @alias content.delete
|
|
846
|
-
* @param
|
|
967
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
968
|
+
* @param {Object} [input] axios request config, passed unchanged to sdk.api.delete. A request body goes in its `data` property.
|
|
969
|
+
* @param {Object} [input.data] Request body.
|
|
970
|
+
* @param {Boolean} [input.data.erase] Permanently erase the record instead of trashing it. Can't be undone; needs erase permission.
|
|
971
|
+
* @returns {Promise<Object>} For a normal delete: { _id, meta: { type, definition, deleted: true } }.
|
|
847
972
|
* @example
|
|
848
|
-
*
|
|
973
|
+
* await sdk.content.delete('61eca4746971e75c1fc670cd');
|
|
974
|
+
*
|
|
975
|
+
* // Permanently erase
|
|
976
|
+
* await sdk.content.delete('61eca4746971e75c1fc670cd', { data: { erase: true } });
|
|
849
977
|
*/
|
|
850
978
|
service.delete = async function (id, input) {
|
|
851
979
|
id = qik.utils.id(id);
|
|
@@ -854,12 +982,14 @@ export default function (qik) {
|
|
|
854
982
|
};
|
|
855
983
|
|
|
856
984
|
/**
|
|
857
|
-
*
|
|
858
|
-
*
|
|
985
|
+
* Brings a trashed record back (GET /content/:id/restore). Needs restore permission on the record.
|
|
986
|
+
* Erased records can't be restored.
|
|
859
987
|
* @alias content.restore
|
|
860
|
-
* @param
|
|
988
|
+
* @param {String|Object} id The record's id, or an object with an _id.
|
|
989
|
+
* @param {Object} [input] axios request config, passed unchanged to sdk.api.get.
|
|
990
|
+
* @returns {Promise<Object>} The restored record.
|
|
861
991
|
* @example
|
|
862
|
-
* const
|
|
992
|
+
* const restored = await sdk.content.restore('61eca4746971e75c1fc670cd');
|
|
863
993
|
*/
|
|
864
994
|
service.restore = async function (id, input) {
|
|
865
995
|
id = qik.utils.id(id);
|
|
@@ -868,16 +998,14 @@ export default function (qik) {
|
|
|
868
998
|
};
|
|
869
999
|
|
|
870
1000
|
/**
|
|
871
|
-
*
|
|
872
|
-
*
|
|
1001
|
+
* Fetches one record by its slug (GET /content/slug/:slug) instead of its id, with the same permission
|
|
1002
|
+
* rules as content.get. The slug is written as '<type or definition key>:<slug>', e.g. 'article:how-to-get-started';
|
|
1003
|
+
* a record's `meta.abs` holds this form.
|
|
873
1004
|
* @alias content.getFromSlug
|
|
874
|
-
* @param
|
|
875
|
-
*
|
|
876
|
-
* (if unsure use the `meta.abs` absolute slug property of the item you are wanting to retrieve).
|
|
1005
|
+
* @param {String} slug '<type or definition key>:<the record's meta.slug>'.
|
|
1006
|
+
* @returns {Promise<Object>} The record.
|
|
877
1007
|
* @example
|
|
878
|
-
* const
|
|
879
|
-
* const result = await sdk.content.getFromSlug('car:toyota-landcruiser')
|
|
880
|
-
* const result = await sdk.content.getFromSlug('article:toyota-landcruiser')
|
|
1008
|
+
* const article = await sdk.content.getFromSlug('article:how-to-get-started');
|
|
881
1009
|
*/
|
|
882
1010
|
service.getFromSlug = async function (slug) {
|
|
883
1011
|
const { data } = await qik.api.get(`/content/slug/${slug}`);
|