@schmock/openapi 1.1.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/crud-detector.d.ts +35 -0
- package/dist/crud-detector.d.ts.map +1 -0
- package/dist/crud-detector.js +153 -0
- package/dist/generators.d.ts +14 -0
- package/dist/generators.d.ts.map +1 -0
- package/dist/generators.js +158 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +221 -0
- package/dist/normalizer.d.ts +14 -0
- package/dist/normalizer.d.ts.map +1 -0
- package/dist/normalizer.js +194 -0
- package/dist/parser.d.ts +32 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +282 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +129 -0
- package/dist/seed.d.ts +15 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +41 -0
- package/package.json +45 -0
- package/src/__fixtures__/faker-stress-test.openapi.yaml +1030 -0
- package/src/__fixtures__/openapi31.json +34 -0
- package/src/__fixtures__/petstore-openapi3.json +168 -0
- package/src/__fixtures__/petstore-swagger2.json +141 -0
- package/src/__fixtures__/scalar-galaxy.yaml +1314 -0
- package/src/__fixtures__/stripe-fixtures3.json +6542 -0
- package/src/__fixtures__/stripe-spec3.yaml +161621 -0
- package/src/__fixtures__/train-travel.yaml +1264 -0
- package/src/crud-detector.test.ts +150 -0
- package/src/crud-detector.ts +194 -0
- package/src/generators.test.ts +214 -0
- package/src/generators.ts +212 -0
- package/src/index.ts +4 -0
- package/src/normalizer.test.ts +253 -0
- package/src/normalizer.ts +233 -0
- package/src/parser.test.ts +181 -0
- package/src/parser.ts +389 -0
- package/src/plugin.test.ts +205 -0
- package/src/plugin.ts +185 -0
- package/src/seed.ts +62 -0
- package/src/steps/openapi-crud.steps.ts +132 -0
- package/src/steps/openapi-parsing.steps.ts +111 -0
- package/src/steps/openapi-seed.steps.ts +94 -0
- package/src/stress.test.ts +2814 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
import type { ParsedPath } from "./parser.js";
|
|
3
|
+
export type CrudOperation = "list" | "create" | "read" | "update" | "delete";
|
|
4
|
+
export interface CrudResource {
|
|
5
|
+
/** Resource name e.g. "pets" */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Collection path e.g. "/pets" */
|
|
8
|
+
basePath: string;
|
|
9
|
+
/** Item path e.g. "/pets/:petId" */
|
|
10
|
+
itemPath: string;
|
|
11
|
+
/** ID parameter name e.g. "petId" */
|
|
12
|
+
idParam: string;
|
|
13
|
+
/** Detected CRUD operations */
|
|
14
|
+
operations: CrudOperation[];
|
|
15
|
+
/** Response schema for the resource item */
|
|
16
|
+
schema?: JSONSchema7;
|
|
17
|
+
}
|
|
18
|
+
interface DetectionResult {
|
|
19
|
+
resources: CrudResource[];
|
|
20
|
+
/** Paths that didn't match any CRUD pattern */
|
|
21
|
+
nonCrudPaths: ParsedPath[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detect CRUD resource patterns from parsed OpenAPI paths.
|
|
25
|
+
*
|
|
26
|
+
* Patterns:
|
|
27
|
+
* - GET /resources → list
|
|
28
|
+
* - POST /resources → create
|
|
29
|
+
* - GET /resources/:id → read
|
|
30
|
+
* - PUT/PATCH /resources/:id → update
|
|
31
|
+
* - DELETE /resources/:id → delete
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectCrudResources(paths: ParsedPath[]): DetectionResult;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=crud-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-detector.d.ts","sourceRoot":"","sources":["../src/crud-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,+CAA+C;IAC/C,YAAY,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,eAAe,CA6BxE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/// <reference path="../../../types/schmock.d.ts" />
|
|
2
|
+
/**
|
|
3
|
+
* Detect CRUD resource patterns from parsed OpenAPI paths.
|
|
4
|
+
*
|
|
5
|
+
* Patterns:
|
|
6
|
+
* - GET /resources → list
|
|
7
|
+
* - POST /resources → create
|
|
8
|
+
* - GET /resources/:id → read
|
|
9
|
+
* - PUT/PATCH /resources/:id → update
|
|
10
|
+
* - DELETE /resources/:id → delete
|
|
11
|
+
*/
|
|
12
|
+
export function detectCrudResources(paths) {
|
|
13
|
+
// Group paths by their base path (strip trailing /:param)
|
|
14
|
+
const groups = new Map();
|
|
15
|
+
const nonCrudPaths = [];
|
|
16
|
+
for (const p of paths) {
|
|
17
|
+
const basePath = getCollectionPath(p.path);
|
|
18
|
+
if (!basePath) {
|
|
19
|
+
nonCrudPaths.push(p);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const existing = groups.get(basePath) ?? [];
|
|
23
|
+
existing.push(p);
|
|
24
|
+
groups.set(basePath, existing);
|
|
25
|
+
}
|
|
26
|
+
const resources = [];
|
|
27
|
+
for (const [basePath, groupPaths] of groups) {
|
|
28
|
+
const resource = buildResource(basePath, groupPaths);
|
|
29
|
+
if (resource) {
|
|
30
|
+
resources.push(resource);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// If no CRUD pattern detected, treat as non-CRUD
|
|
34
|
+
nonCrudPaths.push(...groupPaths);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { resources, nonCrudPaths };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract the collection base path from a path.
|
|
41
|
+
* "/pets" → "/pets"
|
|
42
|
+
* "/pets/:petId" → "/pets"
|
|
43
|
+
* "/owners/:ownerId/pets" → "/owners/:ownerId/pets"
|
|
44
|
+
* "/owners/:ownerId/pets/:petId" → "/owners/:ownerId/pets"
|
|
45
|
+
*/
|
|
46
|
+
function getCollectionPath(path) {
|
|
47
|
+
const segments = path.split("/").filter(Boolean);
|
|
48
|
+
if (segments.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
// If last segment is a param (:xyz), remove it to get collection path
|
|
51
|
+
const last = segments[segments.length - 1];
|
|
52
|
+
if (last.startsWith(":")) {
|
|
53
|
+
return `/${segments.slice(0, -1).join("/")}`;
|
|
54
|
+
}
|
|
55
|
+
// Otherwise the path itself is a potential collection path
|
|
56
|
+
return `/${segments.join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function buildResource(basePath, paths) {
|
|
59
|
+
const operations = [];
|
|
60
|
+
let itemPath = "";
|
|
61
|
+
let idParam = "";
|
|
62
|
+
let schema;
|
|
63
|
+
for (const p of paths) {
|
|
64
|
+
const isCollection = p.path === basePath;
|
|
65
|
+
const isItem = !isCollection && p.path.startsWith(basePath);
|
|
66
|
+
if (isCollection) {
|
|
67
|
+
if (p.method === "GET") {
|
|
68
|
+
operations.push("list");
|
|
69
|
+
// Try to extract item schema from list response (array items)
|
|
70
|
+
const listSchema = getSuccessResponseSchema(p);
|
|
71
|
+
if (listSchema && listSchema.type === "array" && listSchema.items) {
|
|
72
|
+
const items = Array.isArray(listSchema.items)
|
|
73
|
+
? listSchema.items[0]
|
|
74
|
+
: listSchema.items;
|
|
75
|
+
if (typeof items === "object" && items !== null) {
|
|
76
|
+
schema = schema ?? items;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (p.method === "POST") {
|
|
81
|
+
operations.push("create");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (isItem) {
|
|
85
|
+
// Extract ID param from the item path
|
|
86
|
+
const paramSegments = p.path
|
|
87
|
+
.slice(basePath.length)
|
|
88
|
+
.split("/")
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
if (paramSegments.length === 1 && paramSegments[0].startsWith(":")) {
|
|
91
|
+
const param = paramSegments[0].slice(1);
|
|
92
|
+
if (!idParam) {
|
|
93
|
+
idParam = param;
|
|
94
|
+
itemPath = p.path;
|
|
95
|
+
}
|
|
96
|
+
if (p.method === "GET") {
|
|
97
|
+
operations.push("read");
|
|
98
|
+
schema = schema ?? getSuccessResponseSchema(p);
|
|
99
|
+
}
|
|
100
|
+
else if (p.method === "PUT" || p.method === "PATCH") {
|
|
101
|
+
if (!operations.includes("update")) {
|
|
102
|
+
operations.push("update");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (p.method === "DELETE") {
|
|
106
|
+
operations.push("delete");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (operations.length === 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
// Require evidence of a genuine CRUD collection:
|
|
114
|
+
// either item-level operations (read/update/delete) exist,
|
|
115
|
+
// or both list AND create exist on the collection path.
|
|
116
|
+
// Single GET /health or POST /login don't qualify.
|
|
117
|
+
const hasItemOps = operations.some((op) => op === "read" || op === "update" || op === "delete");
|
|
118
|
+
const hasList = operations.includes("list");
|
|
119
|
+
const hasCreate = operations.includes("create");
|
|
120
|
+
if (!hasItemOps && !(hasList && hasCreate))
|
|
121
|
+
return undefined;
|
|
122
|
+
// If we only have collection operations, infer item path
|
|
123
|
+
if (!itemPath) {
|
|
124
|
+
const resourceName = basePath.split("/").filter(Boolean).pop() ?? "";
|
|
125
|
+
const singular = resourceName.endsWith("s")
|
|
126
|
+
? resourceName.slice(0, -1)
|
|
127
|
+
: resourceName;
|
|
128
|
+
idParam = `${singular}Id`;
|
|
129
|
+
itemPath = `${basePath}/:${idParam}`;
|
|
130
|
+
}
|
|
131
|
+
const name = basePath.split("/").filter(Boolean).pop() ?? basePath;
|
|
132
|
+
return {
|
|
133
|
+
name,
|
|
134
|
+
basePath,
|
|
135
|
+
itemPath,
|
|
136
|
+
idParam,
|
|
137
|
+
operations,
|
|
138
|
+
schema,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function getSuccessResponseSchema(p) {
|
|
142
|
+
// Try 200, then 201, then first 2xx
|
|
143
|
+
for (const code of [200, 201]) {
|
|
144
|
+
const resp = p.responses.get(code);
|
|
145
|
+
if (resp?.schema)
|
|
146
|
+
return resp.schema;
|
|
147
|
+
}
|
|
148
|
+
for (const [code, resp] of p.responses) {
|
|
149
|
+
if (code >= 200 && code < 300 && resp.schema)
|
|
150
|
+
return resp.schema;
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
import type { CrudResource } from "./crud-detector.js";
|
|
3
|
+
import type { ParsedPath } from "./parser.js";
|
|
4
|
+
export declare function createListGenerator(resource: CrudResource): Schmock.GeneratorFunction;
|
|
5
|
+
export declare function createCreateGenerator(resource: CrudResource): Schmock.GeneratorFunction;
|
|
6
|
+
export declare function createReadGenerator(resource: CrudResource): Schmock.GeneratorFunction;
|
|
7
|
+
export declare function createUpdateGenerator(resource: CrudResource): Schmock.GeneratorFunction;
|
|
8
|
+
export declare function createDeleteGenerator(resource: CrudResource): Schmock.GeneratorFunction;
|
|
9
|
+
export declare function createStaticGenerator(parsedPath: ParsedPath): Schmock.GeneratorFunction;
|
|
10
|
+
/**
|
|
11
|
+
* Generate seed items for a resource using its schema.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateSeedItems(schema: JSONSchema7, count: number, idParam: string): unknown[];
|
|
14
|
+
//# sourceMappingURL=generators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../src/generators.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2C9C,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,iBAAiB,CAK3B;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,iBAAiB,CAkB3B;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,iBAAiB,CAY3B;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,iBAAiB,CAoB3B;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,iBAAiB,CAa3B;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,iBAAiB,CA+B3B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,EAAE,CAWX"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/// <reference path="../../../types/schmock.d.ts" />
|
|
2
|
+
import { generateFromSchema } from "@schmock/schema";
|
|
3
|
+
const COLLECTION_STATE_PREFIX = "openapi:collections:";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function toTuple(status, body) {
|
|
8
|
+
return [status, body];
|
|
9
|
+
}
|
|
10
|
+
function collectionKey(resourceName) {
|
|
11
|
+
return `${COLLECTION_STATE_PREFIX}${resourceName}`;
|
|
12
|
+
}
|
|
13
|
+
function getCollection(state, resourceName) {
|
|
14
|
+
const key = collectionKey(resourceName);
|
|
15
|
+
if (!Array.isArray(state[key])) {
|
|
16
|
+
state[key] = [];
|
|
17
|
+
}
|
|
18
|
+
const value = state[key];
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
function getNextId(state, resourceName) {
|
|
25
|
+
const counterKey = `openapi:counter:${resourceName}`;
|
|
26
|
+
const current = state[counterKey];
|
|
27
|
+
const base = typeof current === "number" ? current : 0;
|
|
28
|
+
const next = base + 1;
|
|
29
|
+
state[counterKey] = next;
|
|
30
|
+
return next;
|
|
31
|
+
}
|
|
32
|
+
export function createListGenerator(resource) {
|
|
33
|
+
return (ctx) => {
|
|
34
|
+
const collection = getCollection(ctx.state, resource.name);
|
|
35
|
+
return [...collection];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function createCreateGenerator(resource) {
|
|
39
|
+
return (ctx) => {
|
|
40
|
+
const collection = getCollection(ctx.state, resource.name);
|
|
41
|
+
const id = getNextId(ctx.state, resource.name);
|
|
42
|
+
let item;
|
|
43
|
+
if (isRecord(ctx.body)) {
|
|
44
|
+
item = {
|
|
45
|
+
...ctx.body,
|
|
46
|
+
[resource.idParam]: id,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
item = { [resource.idParam]: id };
|
|
51
|
+
}
|
|
52
|
+
collection.push(item);
|
|
53
|
+
return toTuple(201, item);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createReadGenerator(resource) {
|
|
57
|
+
return (ctx) => {
|
|
58
|
+
const collection = getCollection(ctx.state, resource.name);
|
|
59
|
+
const idValue = ctx.params[resource.idParam];
|
|
60
|
+
const item = findById(collection, resource.idParam, idValue);
|
|
61
|
+
if (!item) {
|
|
62
|
+
return toTuple(404, { error: "Not found", code: "NOT_FOUND" });
|
|
63
|
+
}
|
|
64
|
+
return item;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function createUpdateGenerator(resource) {
|
|
68
|
+
return (ctx) => {
|
|
69
|
+
const collection = getCollection(ctx.state, resource.name);
|
|
70
|
+
const idValue = ctx.params[resource.idParam];
|
|
71
|
+
const index = findIndexById(collection, resource.idParam, idValue);
|
|
72
|
+
if (index === -1) {
|
|
73
|
+
return toTuple(404, { error: "Not found", code: "NOT_FOUND" });
|
|
74
|
+
}
|
|
75
|
+
const existingRaw = collection[index];
|
|
76
|
+
const existing = isRecord(existingRaw) ? existingRaw : {};
|
|
77
|
+
const updated = {
|
|
78
|
+
...existing,
|
|
79
|
+
...(isRecord(ctx.body) ? ctx.body : {}),
|
|
80
|
+
[resource.idParam]: existing[resource.idParam], // Preserve ID
|
|
81
|
+
};
|
|
82
|
+
collection[index] = updated;
|
|
83
|
+
return updated;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function createDeleteGenerator(resource) {
|
|
87
|
+
return (ctx) => {
|
|
88
|
+
const collection = getCollection(ctx.state, resource.name);
|
|
89
|
+
const idValue = ctx.params[resource.idParam];
|
|
90
|
+
const index = findIndexById(collection, resource.idParam, idValue);
|
|
91
|
+
if (index === -1) {
|
|
92
|
+
return toTuple(404, { error: "Not found", code: "NOT_FOUND" });
|
|
93
|
+
}
|
|
94
|
+
collection.splice(index, 1);
|
|
95
|
+
return toTuple(204, undefined);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function createStaticGenerator(parsedPath) {
|
|
99
|
+
// Get the success response schema
|
|
100
|
+
let responseSchema;
|
|
101
|
+
for (const code of [200, 201]) {
|
|
102
|
+
const resp = parsedPath.responses.get(code);
|
|
103
|
+
if (resp?.schema) {
|
|
104
|
+
responseSchema = resp.schema;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!responseSchema) {
|
|
109
|
+
for (const [code, resp] of parsedPath.responses) {
|
|
110
|
+
if (code >= 200 && code < 300 && resp.schema) {
|
|
111
|
+
responseSchema = resp.schema;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return () => {
|
|
117
|
+
if (responseSchema) {
|
|
118
|
+
try {
|
|
119
|
+
return generateFromSchema({ schema: responseSchema });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Complex schemas (deep anyOf, circular refs) may fail generation.
|
|
123
|
+
// Fall back to an empty object rather than crashing.
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return toTuple(200, {});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate seed items for a resource using its schema.
|
|
132
|
+
*/
|
|
133
|
+
export function generateSeedItems(schema, count, idParam) {
|
|
134
|
+
const items = [];
|
|
135
|
+
for (let i = 0; i < count; i++) {
|
|
136
|
+
const generated = generateFromSchema({ schema });
|
|
137
|
+
const item = isRecord(generated)
|
|
138
|
+
? generated
|
|
139
|
+
: { value: generated };
|
|
140
|
+
item[idParam] = i + 1;
|
|
141
|
+
items.push(item);
|
|
142
|
+
}
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
function findById(collection, idParam, idValue) {
|
|
146
|
+
return collection.find((item) => {
|
|
147
|
+
if (!isRecord(item))
|
|
148
|
+
return false;
|
|
149
|
+
return String(item[idParam]) === String(idValue);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function findIndexById(collection, idParam, idValue) {
|
|
153
|
+
return collection.findIndex((item) => {
|
|
154
|
+
if (!isRecord(item))
|
|
155
|
+
return false;
|
|
156
|
+
return String(item[idParam]) === String(idValue);
|
|
157
|
+
});
|
|
158
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|