Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@flatfile/listener

Package Overview
Dependencies
Maintainers
26
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flatfile/listener - npm Package Compare versions

Comparing version 0.0.10 to 0.1.0

6

CHANGELOG.md
# @flatfile/listener
## 0.1.0
### Minor Changes
- 4d88069: Adds a major improvement to event filtering, allowing you to grep the key path and defaulting to matching any key in the object
## 0.0.10

@@ -4,0 +10,0 @@

47

dist/index.js

@@ -518,2 +518,3 @@ var __create = Object.create;

// src/events/glob.match.ts
import flat from "flat";
function glob(val, filter) {

@@ -525,21 +526,37 @@ if (!val || typeof val !== "string") {

}
function objectMatches(object, filter) {
function objectMatches(object, filterObject) {
const cleanFilter = !filterObject || typeof filterObject !== "object" ? { "**": filterObject } : filterObject;
if (typeof object !== "object") {
throw new Error("You cannot filter a non-object");
}
let denied = false;
for (const x in filter) {
const prop = object[x];
if (!object?.[x]) {
return false;
}
if (typeof prop === "string" || Array.isArray(prop) && typeof prop[0] === "string") {
if (Array.isArray(prop)) {
denied || (denied = !prop.some((p) => {
return glob(p, filter[x]);
}));
} else {
denied || (denied = !glob(prop, filter[x]));
}
}
const filter = flat(cleanFilter, { safe: true });
const flattened = flat(object, { safe: true });
for (const keyPattern in filter) {
const keys = filterKeys(flattened, keyPattern);
const valuePattern = Array.isArray(filter[keyPattern]) ? filter[keyPattern] : [filter[keyPattern]];
denied || (denied = !keys.some((key) => {
const value = flattened[key];
return valuePattern.some((match) => globOrMatch(value, match));
}));
}
return !denied;
}
function filterKeys(object, glob2) {
glob2 = glob2.includes("*") || glob2.includes(".") ? glob2 : `**.${glob2}`;
const matcher = index_es_default(glob2, ".");
return Object.keys(object).filter((key) => matcher(key));
}
function globOrMatch(val, filter) {
if (val === void 0 || val === null) {
return filter === null;
}
if (Array.isArray(val)) {
return val.some((v) => globOrMatch(v, filter));
}
if (typeof filter === "string") {
return glob(val.toString(), filter);
}
return val === filter;
}

@@ -546,0 +563,0 @@ // src/events/event.handler.ts

{
"name": "@flatfile/listener",
"version": "0.0.10",
"version": "0.1.0",
"description": "A PubSub Listener for configuring and using Flatfile",

@@ -20,2 +20,3 @@ "main": "dist/index.js",

"@flatfile/ts-config-platform-sdk": "*",
"@types/flat": "^5.0.2",
"@types/jest": "^29.5.1",

@@ -31,4 +32,5 @@ "eslint": "^8.19.0",

"@flatfile/api": "^0.0.19",
"flat": "^5.0.2",
"node-fetch": "^2.6.7"
}
}
import { objectMatches } from './glob.match'
describe('objectMatches', () => {
test('matches a primitive anywhere in the object', () => {
expect(objectMatches({ foo: 'bar' }, 'bar')).toBe(true)
expect(objectMatches({ foo: 11 }, 11)).toBe(true)
expect(objectMatches({ foo: 11 }, 13)).toBe(false)
expect(objectMatches({ foo: 11, bar: 13 }, 13)).toBe(true)
expect(objectMatches({ foo: { bar: 'blue' } }, 'blue')).toBe(true)
expect(objectMatches({ foo: ['bar', 'baz'] }, 'baz')).toBe(true)
expect(objectMatches({ foo: ['bar', 'baz'] }, 'qux')).toBe(false)
})
test('matches an object', () => {

@@ -77,2 +87,146 @@ expect(objectMatches({ foo: 'bar' }, { foo: 'bar' })).toBe(true)

})
test('allows user to provide a nested match object as well', () => {
expect(
objectMatches({ one: { two: 'bar' } }, { one: { two: 'bar' } })
).toBe(true)
})
test('matches a nested key', () => {
expect(objectMatches({ one: { two: 'bar' } }, { 'one.two': 'bar' })).toBe(
true
)
})
test('does not use end with logic when a nested pattern is provided', () => {
expect(
objectMatches({ zero: { one: { two: 'bar' } } }, { 'one.two': 'bar' })
).toBe(false)
})
test('does not use end with logic when a glob pattern is provided', () => {
expect(
objectMatches({ zero: { one: { two: 'bar' } } }, { '*two': 'bar' })
).toBe(false)
})
test('matches a glob pattern', () => {
expect(objectMatches({ one: { two: 'bar' } }, { '*.two': 'bar' })).toBe(
true
)
})
test('defaults to an `endsWith` pattern', () => {
expect(objectMatches({ one: { two: 'bar' } }, { two: 'bar' })).toBe(true)
})
test('false if non-existent key', () => {
expect(objectMatches({ one: { two: 'bar' } }, { three: 'bar' })).toBe(false)
})
test('matches a deep glob pattern', () => {
expect(
objectMatches({ one: { two: { three: 'bar' } } }, { '**.three': 'bar' })
).toBe(true)
expect(
objectMatches({ one: { two: { three: 'bar' } } }, { '*.three': 'bar' })
).toBe(false)
})
describe('additional edge cases', () => {
// Empty Objects
test('handles empty objects', () => {
expect(objectMatches({}, {})).toBe(true)
expect(objectMatches({ foo: 'bar' }, {})).toBe(true)
expect(objectMatches({}, { foo: 'bar' })).toBe(false)
})
// Non-Object Inputs
test('handles non-object inputs', () => {
// @ts-ignore
expect(() => objectMatches('foo', { foo: 'bar' })).toThrow()
// @ts-ignore
expect(() => objectMatches(123, { foo: 'bar' })).toThrow()
// @ts-ignore
expect(() => objectMatches(null, { foo: 'bar' })).toThrow()
})
// Special Characters in Keys
test('handles special characters in keys', () => {
expect(objectMatches({ 'foo*': 'bar' }, { 'foo*': 'bar' })).toBe(true)
expect(objectMatches({ 'foo.bar': 'baz' }, { 'foo.bar': 'baz' })).toBe(
true
)
expect(objectMatches({ foo$bar: 'baz' }, { foo$bar: 'baz' })).toBe(true)
})
// Matching Null Values
test('matches null values', () => {
expect(objectMatches({ foo: null }, { foo: null })).toBe(true)
expect(objectMatches({ foo: 'bar' }, { foo: null })).toBe(false)
})
// Matching Undefined Values
test('matches undefined values', () => {
expect(objectMatches({ foo: undefined }, { foo: null })).toBe(true)
// @ts-ignore
expect(objectMatches({ foo: 'bar' }, { foo: undefined })).toBe(false)
})
// Nested Array Matches
// test('handles nested array matches', () => {
// expect(
// objectMatches({ foo: [{ bar: 'baz' }] }, { foo: [{ bar: 'baz' }] })
// ).toBe(true)
// expect(
// objectMatches({ foo: [{ bar: 'qux' }] }, { foo: [{ bar: 'baz' }] })
// ).toBe(false)
// })
// Nested Wildcard Matches
test('handles nested wildcard matches', () => {
expect(
objectMatches({ foo: { bar: 'baz' } }, { foo: { bar: '*' } })
).toBe(true)
expect(
objectMatches({ foo: { bar: 'qux' } }, { foo: { bar: 'baz*' } })
).toBe(false)
})
// Nested Glob Pattern Matches
test('handles nested glob pattern matches', () => {
expect(objectMatches({ foo: { bar: 'baz' } }, { 'foo.*': 'baz' })).toBe(
true
)
expect(objectMatches({ foo: { bar: 'qux' } }, { 'foo.*': 'baz' })).toBe(
false
)
})
// Type Coercion
test('it attempts to perform type coercion when a string match is provided', () => {
expect(objectMatches({ foo: 1 }, { foo: '1' })).toBe(true)
})
// Type Coercion
test('it will not coerce types if a non-string matcher is provided', () => {
expect(objectMatches({ foo: '1' }, { foo: 1 })).toBe(false)
})
// Case Sensitivity
test('is case sensitive', () => {
expect(objectMatches({ foo: 'Bar' }, { foo: 'bar' })).toBe(false)
expect(objectMatches({ foo: 'bar' }, { foo: 'Bar' })).toBe(false)
})
// Order of Keys
test('does not care about order of keys', () => {
expect(
objectMatches({ foo: 'bar', baz: 'qux' }, { baz: 'qux', foo: 'bar' })
).toBe(true)
})
})
})
import { Arrayable } from './event.handler'
import wildMatch from 'wildcard-match'
import flat from 'flat'

@@ -21,30 +22,80 @@ /**

* @param object
* @param filter
* @param filterObject
*/
export function objectMatches(
object: Record<string, any>,
filter: Record<string, Arrayable<string>>
filterObject: JSONPrimitive | FilterObj
): boolean {
const cleanFilter: FilterObj =
!filterObject || typeof filterObject !== 'object'
? { '**': filterObject }
: filterObject
if (typeof object !== 'object') {
throw new Error('You cannot filter a non-object')
}
let denied = false
for (const x in filter) {
const prop: Arrayable<string> = object[x]
const filter: FilterObj = flat(cleanFilter, { safe: true })
const flattened = flat(object, { safe: true }) as Record<
string,
JSONPrimitive
>
if (!object?.[x]) {
return false
}
// all filters MUST resolve true
for (const keyPattern in filter) {
const keys = filterKeys(flattened, keyPattern)
if (
typeof prop === 'string' ||
(Array.isArray(prop) && typeof prop[0] === 'string')
) {
if (Array.isArray(prop)) {
denied ||= !prop.some((p) => {
return glob(p, filter[x])
})
} else {
denied ||= !glob(prop, filter[x])
}
}
const valuePattern = (
Array.isArray(filter[keyPattern])
? filter[keyPattern]
: [filter[keyPattern]]
) as JSONPrimitive[]
// only one filter must match
denied ||= !keys.some((key) => {
const value: JSONPrimitive = flattened[key]
return valuePattern.some((match) => globOrMatch(value, match))
})
}
return !denied
}
/**
* Glob keys of an object and return the narrowed set
*
* @param object
* @param glob
*/
function filterKeys<T extends Record<string, any>>(
object: Record<string, any>,
glob: string
): Array<keyof T> {
glob = glob.includes('*') || glob.includes('.') ? glob : `**.${glob}`
const matcher = wildMatch(glob, '.')
return Object.keys(object).filter((key) => matcher(key))
}
function globOrMatch(
val: Arrayable<JSONPrimitive>,
filter: JSONPrimitive
): boolean {
if (val === undefined || val === null) {
return filter === null
}
if (Array.isArray(val)) {
return val.some((v) => globOrMatch(v, filter))
}
if (typeof filter === 'string') {
return glob(val.toString(), filter)
}
// otherwise do a simple comparison
return val === filter
}
type JSONPrimitive = string | number | boolean | null
type FilterObj = Record<
string,
Arrayable<JSONPrimitive> | Record<string, Arrayable<JSONPrimitive>>
>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc